Added lifecycle rule edit capability (#1539)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net> Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -42,6 +42,9 @@ type ExpirationResponse struct {
|
||||
|
||||
// delete marker
|
||||
DeleteMarker bool `json:"delete_marker,omitempty"`
|
||||
|
||||
// noncurrent expiration days
|
||||
NoncurrentExpirationDays int64 `json:"noncurrent_expiration_days,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this expiration response
|
||||
|
||||
95
models/lifecycle_rule_type.go
Normal file
95
models/lifecycle_rule_type.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// LifecycleRuleType lifecycle rule type
|
||||
//
|
||||
// swagger:model lifecycleRuleType
|
||||
type LifecycleRuleType string
|
||||
|
||||
func NewLifecycleRuleType(value LifecycleRuleType) *LifecycleRuleType {
|
||||
return &value
|
||||
}
|
||||
|
||||
// Pointer returns a pointer to a freshly-allocated LifecycleRuleType.
|
||||
func (m LifecycleRuleType) Pointer() *LifecycleRuleType {
|
||||
return &m
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
// LifecycleRuleTypeExpiry captures enum value "expiry"
|
||||
LifecycleRuleTypeExpiry LifecycleRuleType = "expiry"
|
||||
|
||||
// LifecycleRuleTypeTransition captures enum value "transition"
|
||||
LifecycleRuleTypeTransition LifecycleRuleType = "transition"
|
||||
)
|
||||
|
||||
// for schema
|
||||
var lifecycleRuleTypeEnum []interface{}
|
||||
|
||||
func init() {
|
||||
var res []LifecycleRuleType
|
||||
if err := json.Unmarshal([]byte(`["expiry","transition"]`), &res); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range res {
|
||||
lifecycleRuleTypeEnum = append(lifecycleRuleTypeEnum, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (m LifecycleRuleType) validateLifecycleRuleTypeEnum(path, location string, value LifecycleRuleType) error {
|
||||
if err := validate.EnumCase(path, location, value, lifecycleRuleTypeEnum, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates this lifecycle rule type
|
||||
func (m LifecycleRuleType) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
// value enum
|
||||
if err := m.validateLifecycleRuleTypeEnum("", "body", m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this lifecycle rule type based on context it is used
|
||||
func (m LifecycleRuleType) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
@@ -40,6 +40,12 @@ type TransitionResponse struct {
|
||||
// days
|
||||
Days int64 `json:"days,omitempty"`
|
||||
|
||||
// noncurrent storage class
|
||||
NoncurrentStorageClass string `json:"noncurrent_storage_class,omitempty"`
|
||||
|
||||
// noncurrent transition days
|
||||
NoncurrentTransitionDays int64 `json:"noncurrent_transition_days,omitempty"`
|
||||
|
||||
// storage class
|
||||
StorageClass string `json:"storage_class,omitempty"`
|
||||
}
|
||||
|
||||
@@ -24,9 +24,12 @@ package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// UpdateBucketLifecycle update bucket lifecycle
|
||||
@@ -63,10 +66,67 @@ type UpdateBucketLifecycle struct {
|
||||
|
||||
// Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM
|
||||
TransitionDays int32 `json:"transition_days,omitempty"`
|
||||
|
||||
// ILM Rule type (Expiry or transition)
|
||||
// Required: true
|
||||
// Enum: [expiry transition]
|
||||
Type *string `json:"type"`
|
||||
}
|
||||
|
||||
// Validate validates this update bucket lifecycle
|
||||
func (m *UpdateBucketLifecycle) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateType(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var updateBucketLifecycleTypeTypePropEnum []interface{}
|
||||
|
||||
func init() {
|
||||
var res []string
|
||||
if err := json.Unmarshal([]byte(`["expiry","transition"]`), &res); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range res {
|
||||
updateBucketLifecycleTypeTypePropEnum = append(updateBucketLifecycleTypeTypePropEnum, v)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
// UpdateBucketLifecycleTypeExpiry captures enum value "expiry"
|
||||
UpdateBucketLifecycleTypeExpiry string = "expiry"
|
||||
|
||||
// UpdateBucketLifecycleTypeTransition captures enum value "transition"
|
||||
UpdateBucketLifecycleTypeTransition string = "transition"
|
||||
)
|
||||
|
||||
// prop value enum
|
||||
func (m *UpdateBucketLifecycle) validateTypeEnum(path, location string, value string) error {
|
||||
if err := validate.EnumCase(path, location, value, updateBucketLifecycleTypeTypePropEnum, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UpdateBucketLifecycle) validateType(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("type", "body", m.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// value enum
|
||||
if err := m.validateTypeEnum("type", "body", *m.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ interface IReplicationModal {
|
||||
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
|
||||
}
|
||||
|
||||
interface ITiersDropDown {
|
||||
export interface ITiersDropDown {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ const BucketLifecyclePanel = ({
|
||||
const [lifecycleRecords, setLifecycleRecords] = useState<LifeCycleItem[]>([]);
|
||||
const [addLifecycleOpen, setAddLifecycleOpen] = useState<boolean>(false);
|
||||
const [editLifecycleOpen, setEditLifecycleOpen] = useState<boolean>(false);
|
||||
const [selectedLifecycleRule, setSelectedLifecycleRule] =
|
||||
useState<LifeCycleItem | null>(null);
|
||||
|
||||
const bucketName = match.params["bucketName"];
|
||||
|
||||
@@ -112,6 +114,7 @@ const BucketLifecyclePanel = ({
|
||||
|
||||
const closeEditLCAndRefresh = (refresh: boolean) => {
|
||||
setEditLifecycleOpen(false);
|
||||
setSelectedLifecycleRule(null);
|
||||
if (refresh) {
|
||||
setLoadingLifecycle(true);
|
||||
}
|
||||
@@ -182,16 +185,25 @@ const BucketLifecyclePanel = ({
|
||||
},
|
||||
];
|
||||
|
||||
const lifecycleActions = [
|
||||
{
|
||||
type: "view",
|
||||
|
||||
onClick(valueToSend: any): any {
|
||||
setSelectedLifecycleRule(valueToSend);
|
||||
setEditLifecycleOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{editLifecycleOpen && (
|
||||
{editLifecycleOpen && selectedLifecycleRule && (
|
||||
<EditLifecycleConfiguration
|
||||
open={editLifecycleOpen}
|
||||
closeModalAndRefresh={closeEditLCAndRefresh}
|
||||
selectedBucket={bucketName}
|
||||
lifecycle={{
|
||||
id: "",
|
||||
}}
|
||||
lifecycle={selectedLifecycleRule}
|
||||
/>
|
||||
)}
|
||||
{addLifecycleOpen && (
|
||||
@@ -232,7 +244,7 @@ const BucketLifecyclePanel = ({
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
itemActions={[]}
|
||||
itemActions={lifecycleActions}
|
||||
columns={lifecycleColumns}
|
||||
isLoading={loadingLifecycle}
|
||||
records={lifecycleRecords}
|
||||
|
||||
@@ -14,30 +14,58 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, Fragment } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { Button, LinearProgress } from "@mui/material";
|
||||
import { Button, LinearProgress, SelectChangeEvent } from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import get from "lodash/get";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
createTenantCommon,
|
||||
formFieldStyles,
|
||||
modalStyleUtils,
|
||||
spacingUtils,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { setModalErrorSnackMessage } from "../../../../actions";
|
||||
import { LifeCycleItem } from "../types";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import { LifecycleConfigIcon } from "../../../../icons";
|
||||
import { ITiersDropDown } from "./AddLifecycleModal";
|
||||
import {
|
||||
ITierElement,
|
||||
ITierResponse,
|
||||
} from "../../Configurations/TiersConfiguration/types";
|
||||
import api from "../../../../common/api";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
import QueryMultiSelector from "../../Common/FormComponents/QueryMultiSelector/QueryMultiSelector";
|
||||
import { LifecycleConfigIcon } from "../../../../icons";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
dateSelector: {
|
||||
"& div": {
|
||||
borderBottom: 0,
|
||||
marginBottom: 0,
|
||||
|
||||
"& div:nth-child(2)": {
|
||||
border: "1px solid #EAEAEA",
|
||||
paddingLeft: 5,
|
||||
|
||||
"& div": {
|
||||
border: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...modalBasic,
|
||||
...spacingUtils,
|
||||
...modalStyleUtils,
|
||||
...formFieldStyles,
|
||||
...createTenantCommon,
|
||||
});
|
||||
|
||||
interface IAddUserContentProps {
|
||||
@@ -57,15 +85,120 @@ const EditLifecycleConfiguration = ({
|
||||
open,
|
||||
setModalErrorSnackMessage,
|
||||
}: IAddUserContentProps) => {
|
||||
const [loadingTiers, setLoadingTiers] = useState<boolean>(true);
|
||||
const [addLoading, setAddLoading] = useState<boolean>(false);
|
||||
const [tags, setTags] = useState<string>("");
|
||||
const [enabled, setEnabled] = useState<boolean>(false);
|
||||
const [tiersList, setTiersList] = useState<ITiersDropDown[]>([]);
|
||||
const [prefix, setPrefix] = useState("");
|
||||
const [storageClass, setStorageClass] = useState("");
|
||||
const [NCTransitionSC, setNCTransitionSC] = useState("");
|
||||
const [expiredObjectDM, setExpiredObjectDM] = useState<boolean>(false);
|
||||
const [NCExpirationDays, setNCExpirationDays] = useState<string>("0");
|
||||
const [NCTransitionDays, setNCTransitionDays] = useState<string>("0");
|
||||
const [ilmType, setIlmType] = useState<string>("expiry");
|
||||
const [expiryDays, setExpiryDays] = useState<string>("0");
|
||||
const [transitionDays, setTransitionDays] = useState<string>("0");
|
||||
const [isFormValid, setIsFormValid] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingTiers) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/admin/tiers`)
|
||||
.then((res: ITierResponse) => {
|
||||
const tiersList: ITierElement[] | null = get(res, "items", []);
|
||||
|
||||
if (tiersList !== null && tiersList.length >= 1) {
|
||||
const objList = tiersList.map((tier: ITierElement) => {
|
||||
const tierType = tier.type;
|
||||
const value = get(tier, `${tierType}.name`, "");
|
||||
|
||||
return { label: value, value: value };
|
||||
});
|
||||
|
||||
setTiersList(objList);
|
||||
if (objList.length > 0) {
|
||||
setStorageClass(objList[0].value);
|
||||
}
|
||||
}
|
||||
setLoadingTiers(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoadingTiers(false);
|
||||
});
|
||||
}
|
||||
}, [loadingTiers]);
|
||||
|
||||
useEffect(() => {
|
||||
let valid = true;
|
||||
|
||||
if (ilmType !== "expiry") {
|
||||
if (storageClass === "") {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
setIsFormValid(valid);
|
||||
}, [ilmType, expiryDays, transitionDays, storageClass]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("lifecycle::", lifecycle);
|
||||
if (lifecycle.status === "Enabled") {
|
||||
setEnabled(true);
|
||||
}
|
||||
|
||||
let transitionMode = false;
|
||||
|
||||
if (lifecycle.transition) {
|
||||
if (lifecycle.transition.days && lifecycle.transition.days !== 0) {
|
||||
setTransitionDays(lifecycle.transition.days.toString());
|
||||
setIlmType("transition");
|
||||
transitionMode = true;
|
||||
}
|
||||
|
||||
// Fallback to old rules by date
|
||||
if (
|
||||
lifecycle.transition.date &&
|
||||
lifecycle.transition.date !== "0001-01-01T00:00:00Z"
|
||||
) {
|
||||
setIlmType("transition");
|
||||
transitionMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (lifecycle.expiration) {
|
||||
if (lifecycle.expiration.days && lifecycle.expiration.days !== 0) {
|
||||
setExpiryDays(lifecycle.expiration.days.toString());
|
||||
setIlmType("expiry");
|
||||
transitionMode = false;
|
||||
}
|
||||
|
||||
// Fallback to old rules by date
|
||||
if (
|
||||
lifecycle.expiration.date &&
|
||||
lifecycle.expiration.date !== "0001-01-01T00:00:00Z"
|
||||
) {
|
||||
setIlmType("expiry");
|
||||
transitionMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Transition fields
|
||||
if (transitionMode) {
|
||||
setStorageClass(lifecycle.transition?.storage_class || "");
|
||||
setNCTransitionDays(
|
||||
lifecycle.transition?.noncurrent_transition_days?.toString() || "0"
|
||||
);
|
||||
setNCTransitionSC(lifecycle.transition?.noncurrent_storage_class || "");
|
||||
} else {
|
||||
// Expiry fields
|
||||
setNCExpirationDays(
|
||||
lifecycle.expiration?.noncurrent_expiration_days?.toString() || "0"
|
||||
);
|
||||
}
|
||||
|
||||
setExpiredObjectDM(!!lifecycle.expiration?.delete_marker);
|
||||
setPrefix(lifecycle.prefix || "");
|
||||
|
||||
if (lifecycle.tags) {
|
||||
const tgs = lifecycle.tags.reduce(
|
||||
(stringLab: string, currItem: any, index: number) => {
|
||||
@@ -88,14 +221,44 @@ const EditLifecycleConfiguration = ({
|
||||
}
|
||||
setAddLoading(true);
|
||||
if (selectedBucket !== null && lifecycle !== null) {
|
||||
let rules = {};
|
||||
|
||||
if (ilmType === "expiry") {
|
||||
let expiry = {
|
||||
expiry_days: parseInt(expiryDays),
|
||||
};
|
||||
|
||||
rules = {
|
||||
...expiry,
|
||||
noncurrentversion_expiration_days: parseInt(NCExpirationDays),
|
||||
};
|
||||
} else {
|
||||
let transition = {
|
||||
transition_days: parseInt(transitionDays),
|
||||
};
|
||||
|
||||
rules = {
|
||||
...transition,
|
||||
noncurrentversion_transition_days: parseInt(NCTransitionDays),
|
||||
noncurrentversion_transition_storage_class: NCTransitionSC,
|
||||
storage_class: storageClass,
|
||||
};
|
||||
}
|
||||
|
||||
const lifecycleUpdate = {
|
||||
type: ilmType,
|
||||
disable: !enabled,
|
||||
prefix,
|
||||
tags,
|
||||
expired_object_delete_marker: expiredObjectDM,
|
||||
...rules,
|
||||
};
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"PUT",
|
||||
`/api/v1/buckets/${selectedBucket}/lifecycle/${lifecycle.id}`,
|
||||
{
|
||||
disable: !enabled,
|
||||
tags: tags,
|
||||
}
|
||||
lifecycleUpdate
|
||||
)
|
||||
.then((res) => {
|
||||
setAddLoading(false);
|
||||
@@ -117,72 +280,217 @@ const EditLifecycleConfiguration = ({
|
||||
title={"Edit Lifecycle Configuration"}
|
||||
titleIcon={<LifecycleConfigIcon />}
|
||||
>
|
||||
<div className={classes.floatingEnabled}>
|
||||
<FormSwitchWrapper
|
||||
indicatorLabels={["Enabled", "Disabled"]}
|
||||
checked={enabled}
|
||||
value={"user_enabled"}
|
||||
id="user-status"
|
||||
name="user-status"
|
||||
onChange={(e) => {
|
||||
setEnabled(e.target.checked);
|
||||
}}
|
||||
switchOnly
|
||||
/>
|
||||
</div>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
saveRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<InputBoxWrapper
|
||||
id="id"
|
||||
name="id"
|
||||
label="Id"
|
||||
value={lifecycle.id}
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<FormSwitchWrapper
|
||||
label="Rule State"
|
||||
indicatorLabels={["Enabled", "Disabled"]}
|
||||
checked={enabled}
|
||||
value={"user_enabled"}
|
||||
id="user-status"
|
||||
name="user-status"
|
||||
onChange={(e) => {
|
||||
setEnabled(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<fieldset className={classes.fieldGroup}>
|
||||
<legend className={classes.descriptionText}>
|
||||
Lifecycle Configuration
|
||||
</legend>
|
||||
|
||||
<React.Fragment>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
saveRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="id"
|
||||
name="id"
|
||||
label="Id"
|
||||
value={lifecycle.id}
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<QueryMultiSelector
|
||||
name="tags"
|
||||
label="Tags"
|
||||
elements={tags}
|
||||
onChange={(vl: string) => {
|
||||
setTags(vl);
|
||||
}}
|
||||
keyPlaceholder="Tag Key"
|
||||
valuePlaceholder="Tag Value"
|
||||
withBorder
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroupSelector
|
||||
currentSelection={ilmType}
|
||||
id="quota_type"
|
||||
name="quota_type"
|
||||
label="ILM Rule"
|
||||
selectorOptions={[
|
||||
{ value: "expiry", label: "Expiry" },
|
||||
{ value: "transition", label: "Transition" },
|
||||
]}
|
||||
onChange={() => {}}
|
||||
disableOptions
|
||||
/>
|
||||
</Grid>
|
||||
{ilmType === "expiry" ? (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
id="expiry_days"
|
||||
name="expiry_days"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setExpiryDays(e.target.value);
|
||||
}}
|
||||
label="Expiry Days"
|
||||
value={expiryDays}
|
||||
min="0"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
id="noncurrentversion_expiration_days"
|
||||
name="noncurrentversion_expiration_days"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNCExpirationDays(e.target.value);
|
||||
}}
|
||||
label="Non-current Expiration Days"
|
||||
value={NCExpirationDays}
|
||||
min="0"
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
id="transition_days"
|
||||
name="transition_days"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTransitionDays(e.target.value);
|
||||
}}
|
||||
label="Transition Days"
|
||||
value={transitionDays}
|
||||
min="0"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
id="noncurrentversion_transition_days"
|
||||
name="noncurrentversion_transition_days"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNCTransitionDays(e.target.value);
|
||||
}}
|
||||
label="Non-current Transition Days"
|
||||
value={NCTransitionDays}
|
||||
min="0"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<InputBoxWrapper
|
||||
id="noncurrentversion_t_SC"
|
||||
name="noncurrentversion_t_SC"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNCTransitionSC(e.target.value);
|
||||
}}
|
||||
placeholder="Set Non-current Version Transition Storage Class"
|
||||
label="Non-current Version Transition Storage Class"
|
||||
value={NCTransitionSC}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<SelectWrapper
|
||||
label="Storage Class"
|
||||
id="storage_class"
|
||||
name="storage_class"
|
||||
value={storageClass}
|
||||
onChange={(e: SelectChangeEvent<string>) => {
|
||||
setStorageClass(e.target.value as string);
|
||||
}}
|
||||
options={tiersList}
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</fieldset>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addLoading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<fieldset className={classes.fieldGroup}>
|
||||
<legend className={classes.descriptionText}>
|
||||
File Configuration
|
||||
</legend>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="prefix"
|
||||
name="prefix"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPrefix(e.target.value);
|
||||
}}
|
||||
label="Prefix"
|
||||
value={prefix}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<QueryMultiSelector
|
||||
name="tags"
|
||||
label="Tags"
|
||||
elements={tags}
|
||||
onChange={(vl: string) => {
|
||||
setTags(vl);
|
||||
}}
|
||||
keyPlaceholder="Tag Key"
|
||||
valuePlaceholder="Tag Value"
|
||||
withBorder
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="expired_delete_marker"
|
||||
id="expired_delete_marker"
|
||||
name="expired_delete_marker"
|
||||
checked={expiredObjectDM}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setExpiredObjectDM(event.target.checked);
|
||||
}}
|
||||
label={"Expired Object Delete Marker"}
|
||||
/>
|
||||
</Grid>
|
||||
</fieldset>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</React.Fragment>
|
||||
<Grid item xs={12} className={classes.modalButtonBar}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
disabled={addLoading}
|
||||
onClick={() => {
|
||||
closeModalAndRefresh(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addLoading || !isFormValid}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -181,12 +181,16 @@ export interface BulkReplicationItem {
|
||||
interface IExpirationLifecycle {
|
||||
days: number;
|
||||
date: string;
|
||||
delete_marker?: boolean;
|
||||
noncurrent_expiration_days?: number;
|
||||
}
|
||||
|
||||
interface ITransitionLifecycle {
|
||||
days: number;
|
||||
date: string;
|
||||
storage_class?: string;
|
||||
noncurrent_transition_days?: number;
|
||||
noncurrent_storage_class?: string;
|
||||
}
|
||||
|
||||
export interface LifeCycleItem {
|
||||
|
||||
@@ -146,6 +146,7 @@ export const RadioGroupSelector = ({
|
||||
[classes.checkedOption]:
|
||||
selectorOption.value === currentSelection,
|
||||
})}
|
||||
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -4425,6 +4425,10 @@ func init() {
|
||||
},
|
||||
"delete_marker": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"noncurrent_expiration_days": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5947,6 +5951,13 @@ func init() {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"noncurrent_storage_class": {
|
||||
"type": "string"
|
||||
},
|
||||
"noncurrent_transition_days": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"storage_class": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -5954,6 +5965,9 @@ func init() {
|
||||
},
|
||||
"updateBucketLifecycle": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"disable": {
|
||||
"description": "Non required, toggle to disable or enable rule",
|
||||
@@ -6002,6 +6016,14 @@ func init() {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"default": 0
|
||||
},
|
||||
"type": {
|
||||
"description": "ILM Rule type (Expiry or transition)",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"expiry",
|
||||
"transition"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -10694,6 +10716,10 @@ func init() {
|
||||
},
|
||||
"delete_marker": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"noncurrent_expiration_days": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -12216,6 +12242,13 @@ func init() {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"noncurrent_storage_class": {
|
||||
"type": "string"
|
||||
},
|
||||
"noncurrent_transition_days": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"storage_class": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -12223,6 +12256,9 @@ func init() {
|
||||
},
|
||||
"updateBucketLifecycle": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"disable": {
|
||||
"description": "Non required, toggle to disable or enable rule",
|
||||
@@ -12271,6 +12307,14 @@ func init() {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"default": 0
|
||||
},
|
||||
"type": {
|
||||
"description": "ILM Rule type (Expiry or transition)",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"expiry",
|
||||
"transition"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -56,7 +56,7 @@ func registerBucketsLifecycleHandlers(api *operations.ConsoleAPI) {
|
||||
api.UserAPIUpdateBucketLifecycleHandler = user_api.UpdateBucketLifecycleHandlerFunc(func(params user_api.UpdateBucketLifecycleParams, session *models.Principal) middleware.Responder {
|
||||
err := getEditBucketLifecycleRule(session, params)
|
||||
if err != nil {
|
||||
user_api.NewUpdateBucketLifecycleDefault(int(err.Code)).WithPayload(err)
|
||||
return user_api.NewUpdateBucketLifecycleDefault(int(err.Code)).WithPayload(err)
|
||||
}
|
||||
|
||||
return user_api.NewUpdateBucketLifecycleOK()
|
||||
@@ -83,13 +83,30 @@ func getBucketLifecycle(ctx context.Context, client MinioClient, bucketName stri
|
||||
})
|
||||
}
|
||||
|
||||
rulePrefix := rule.RuleFilter.And.Prefix
|
||||
|
||||
if rulePrefix == "" {
|
||||
rulePrefix = rule.RuleFilter.Prefix
|
||||
}
|
||||
|
||||
rules = append(rules, &models.ObjectBucketLifecycle{
|
||||
ID: rule.ID,
|
||||
Status: rule.Status,
|
||||
Prefix: rule.RuleFilter.And.Prefix,
|
||||
Expiration: &models.ExpirationResponse{Date: rule.Expiration.Date.Format(time.RFC3339), Days: int64(rule.Expiration.Days), DeleteMarker: rule.Expiration.DeleteMarker.IsEnabled()},
|
||||
Transition: &models.TransitionResponse{Date: rule.Transition.Date.Format(time.RFC3339), Days: int64(rule.Transition.Days), StorageClass: rule.Transition.StorageClass},
|
||||
Tags: tags,
|
||||
ID: rule.ID,
|
||||
Status: rule.Status,
|
||||
Prefix: rulePrefix,
|
||||
Expiration: &models.ExpirationResponse{
|
||||
Date: rule.Expiration.Date.Format(time.RFC3339),
|
||||
Days: int64(rule.Expiration.Days),
|
||||
DeleteMarker: rule.Expiration.DeleteMarker.IsEnabled(),
|
||||
NoncurrentExpirationDays: int64(rule.NoncurrentVersionExpiration.NoncurrentDays),
|
||||
},
|
||||
Transition: &models.TransitionResponse{
|
||||
Date: rule.Transition.Date.Format(time.RFC3339),
|
||||
Days: int64(rule.Transition.Days),
|
||||
StorageClass: rule.Transition.StorageClass,
|
||||
NoncurrentStorageClass: rule.NoncurrentVersionTransition.StorageClass,
|
||||
NoncurrentTransitionDays: int64(rule.NoncurrentVersionTransition.NoncurrentDays),
|
||||
},
|
||||
Tags: tags,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -251,7 +268,7 @@ func getAddBucketLifecycleResponse(session *models.Principal, params user_api.Ad
|
||||
return nil
|
||||
}
|
||||
|
||||
// addBucketLifecycle gets lifecycle lists for a bucket from MinIO API and returns their implementations
|
||||
// editBucketLifecycle gets lifecycle lists for a bucket from MinIO API and updates the selected lifecycle rule
|
||||
func editBucketLifecycle(ctx context.Context, client MinioClient, params user_api.UpdateBucketLifecycleParams) error {
|
||||
// Configuration that is already set.
|
||||
lfcCfg, err := client.getLifecycleRules(ctx, params.BucketName)
|
||||
@@ -268,10 +285,9 @@ func editBucketLifecycle(ctx context.Context, client MinioClient, params user_ap
|
||||
opts := ilm.LifecycleOptions{}
|
||||
|
||||
// Verify if transition items are set
|
||||
if params.Body.ExpiryDays == 0 && params.Body.TransitionDays != 0 {
|
||||
|
||||
if params.Body.NoncurrentversionExpirationDays != 0 {
|
||||
return errors.New("non current version expiration days cannot be set when transition is being configured")
|
||||
if *params.Body.Type == models.UpdateBucketLifecycleTypeTransition {
|
||||
if params.Body.TransitionDays == 0 && params.Body.NoncurrentversionTransitionDays == 0 {
|
||||
return errors.New("you must select transition days or non-current transition days configuration")
|
||||
}
|
||||
|
||||
opts = ilm.LifecycleOptions{
|
||||
@@ -285,8 +301,10 @@ func editBucketLifecycle(ctx context.Context, client MinioClient, params user_ap
|
||||
ExpiredObjectDeleteMarker: params.Body.ExpiredObjectDeleteMarker,
|
||||
NoncurrentVersionTransitionDays: int(params.Body.NoncurrentversionTransitionDays),
|
||||
NoncurrentVersionTransitionStorageClass: strings.ToUpper(params.Body.NoncurrentversionTransitionStorageClass),
|
||||
IsTransitionDaysSet: params.Body.TransitionDays != 0,
|
||||
IsNoncurrentVersionTransitionDaysSet: params.Body.NoncurrentversionTransitionDays != 0,
|
||||
}
|
||||
} else if params.Body.TransitionDays == 0 && params.Body.ExpiryDays != 0 { // Verify if expiry configuration is set
|
||||
} else if *params.Body.Type == models.UpdateBucketLifecycleTypeExpiry { // Verify if expiry configuration is set
|
||||
if params.Body.NoncurrentversionTransitionDays != 0 {
|
||||
return errors.New("non current version Transition Days cannot be set when expiry is being configured")
|
||||
}
|
||||
|
||||
@@ -226,9 +226,12 @@ func TestUpdateLifecycleRule(t *testing.T) {
|
||||
|
||||
// Test-2 : editBucketLifecycle() Update lifecycle rule
|
||||
|
||||
expiryRule := "expiry"
|
||||
|
||||
editMock := user_api.UpdateBucketLifecycleParams{
|
||||
BucketName: "testBucket",
|
||||
Body: &models.UpdateBucketLifecycle{
|
||||
Type: &expiryRule,
|
||||
Disable: false,
|
||||
ExpiredObjectDeleteMarker: false,
|
||||
ExpiryDays: int32(16),
|
||||
@@ -250,6 +253,33 @@ func TestUpdateLifecycleRule(t *testing.T) {
|
||||
|
||||
assert.Equal(nil, err, fmt.Sprintf("Failed on %s: Error returned", function))
|
||||
|
||||
// Test-2a : editBucketLifecycle() Update lifecycle rule
|
||||
|
||||
transitionRule := "transition"
|
||||
|
||||
editMock = user_api.UpdateBucketLifecycleParams{
|
||||
BucketName: "testBucket",
|
||||
Body: &models.UpdateBucketLifecycle{
|
||||
Type: &transitionRule,
|
||||
Disable: false,
|
||||
ExpiredObjectDeleteMarker: false,
|
||||
NoncurrentversionTransitionDays: 5,
|
||||
Prefix: "pref1",
|
||||
StorageClass: "TEST",
|
||||
NoncurrentversionTransitionStorageClass: "TESTNC",
|
||||
Tags: "",
|
||||
TransitionDays: int32(16),
|
||||
},
|
||||
}
|
||||
|
||||
minioSetBucketLifecycleMock = func(ctx context.Context, bucketName string, config *lifecycle.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = editBucketLifecycle(ctx, minClient, editMock)
|
||||
|
||||
assert.Equal(nil, err, fmt.Sprintf("Failed on %s: Error returned", function))
|
||||
|
||||
// Test-3 : editBucketLifecycle() returns error
|
||||
|
||||
minioSetBucketLifecycleMock = func(ctx context.Context, bucketName string, config *lifecycle.Configuration) error {
|
||||
|
||||
@@ -3817,6 +3817,9 @@ definitions:
|
||||
format: int64
|
||||
delete_marker:
|
||||
type: boolean
|
||||
noncurrent_expiration_days:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
transitionResponse:
|
||||
type: object
|
||||
@@ -3828,6 +3831,11 @@ definitions:
|
||||
days:
|
||||
type: integer
|
||||
format: int64
|
||||
noncurrent_transition_days:
|
||||
type: integer
|
||||
format: int64
|
||||
noncurrent_storage_class:
|
||||
type: string
|
||||
|
||||
lifecycleTag:
|
||||
type: object
|
||||
@@ -3905,7 +3913,15 @@ definitions:
|
||||
|
||||
updateBucketLifecycle:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
properties:
|
||||
type:
|
||||
description: ILM Rule type (Expiry or transition)
|
||||
type: string
|
||||
enum:
|
||||
- expiry
|
||||
- transition
|
||||
prefix:
|
||||
description: Non required field, it matches a prefix to perform ILM operations on it
|
||||
type: string
|
||||
|
||||
Reference in New Issue
Block a user