Added versioning edit in console (#645)

This commit is contained in:
Alex
2021-03-19 18:48:58 -06:00
committed by GitHub
parent 26f7982323
commit 03dc83af3a
11 changed files with 305 additions and 17 deletions

View File

@@ -1,8 +1,8 @@
{
"files": {
"main.css": "/static/css/main.a19f3d53.chunk.css",
"main.js": "/static/js/main.cc2e1a78.chunk.js",
"main.js.map": "/static/js/main.cc2e1a78.chunk.js.map",
"main.js": "/static/js/main.5dcc63ee.chunk.js",
"main.js.map": "/static/js/main.5dcc63ee.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.f48e99e5.js",
"runtime-main.js.map": "/static/js/runtime-main.f48e99e5.js.map",
"static/css/2.76b14b73.chunk.css": "/static/css/2.76b14b73.chunk.css",
@@ -20,6 +20,6 @@
"static/css/2.76b14b73.chunk.css",
"static/js/2.1da96d0d.chunk.js",
"static/css/main.a19f3d53.chunk.css",
"static/js/main.cc2e1a78.chunk.js"
"static/js/main.5dcc63ee.chunk.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.76b14b73.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.1da96d0d.chunk.js"></script><script src="/static/js/main.cc2e1a78.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.76b14b73.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.1da96d0d.chunk.js"></script><script src="/static/js/main.5dcc63ee.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,121 @@
// 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/>.
import React, { useState, Fragment } from "react";
import { connect } from "react-redux";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
LinearProgress,
} from "@material-ui/core";
import api from "../../../../common/api";
import { setErrorSnackMessage } from "../../../../actions";
interface IVersioningEventProps {
closeVersioningModalAndRefresh: (refresh: boolean) => void;
modalOpen: boolean;
selectedBucket: string;
versioningCurrentState: boolean;
setErrorSnackMessage: typeof setErrorSnackMessage;
}
const EnableVersioningModal = ({
closeVersioningModalAndRefresh,
modalOpen,
selectedBucket,
versioningCurrentState,
setErrorSnackMessage,
}: IVersioningEventProps) => {
const [versioningLoading, setVersioningLoading] = useState<boolean>(false);
const enableVersioning = () => {
if (versioningLoading) {
return;
}
setVersioningLoading(true);
api
.invoke("PUT", `/api/v1/buckets/${selectedBucket}/versioning`, {
versioning: !versioningCurrentState,
})
.then(() => {
setVersioningLoading(false);
closeVersioningModalAndRefresh(true);
})
.catch((err) => {
setVersioningLoading(false);
setErrorSnackMessage(err);
});
};
return (
<Dialog
open={modalOpen}
onClose={() => {
closeVersioningModalAndRefresh(false);
}}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Edit Versioning</DialogTitle>
<DialogContent>
{versioningLoading && <LinearProgress />}
<DialogContentText id="alert-dialog-description">
Are you sure you want to{" "}
<strong>{versioningCurrentState ? "disable" : "enable"}</strong>{" "}
versioning for this bucket?
{versioningCurrentState && (
<Fragment>
<br />
<br />
<strong>File versions won't be automatically deleted</strong>
</Fragment>
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
onClick={() => {
closeVersioningModalAndRefresh(false);
}}
color="primary"
disabled={versioningLoading}
>
Cancel
</Button>
<Button
onClick={() => {
enableVersioning();
}}
color="secondary"
autoFocus
>
{versioningCurrentState ? "Disable" : "Enable"}
</Button>
</DialogActions>
</Dialog>
);
};
const connector = connect(null, {
setErrorSnackMessage,
});
export default connector(EnableVersioningModal);

View File

@@ -15,13 +15,16 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import get from "lodash/get";
import { connect } from "react-redux";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button, IconButton } from "@material-ui/core";
import get from "lodash/get";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import CircularProgress from "@material-ui/core/CircularProgress";
import Checkbox from "@material-ui/core/Checkbox";
import api from "../../../../common/api";
import {
BucketEvent,
@@ -35,21 +38,20 @@ import {
BucketReplicationRuleDeleteMarker,
BucketVersioning,
} from "../types";
import { Button } from "@material-ui/core";
import { CreateIcon } from "../../../../icons";
import { niceBytes } from "../../../../common/utils";
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
import { setErrorSnackMessage } from "../../../../actions";
import SetAccessPolicy from "./SetAccessPolicy";
import SetRetentionConfig from "./SetRetentionConfig";
import { CreateIcon } from "../../../../icons";
import AddEvent from "./AddEvent";
import DeleteEvent from "./DeleteEvent";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import { niceBytes } from "../../../../common/utils";
import AddReplicationModal from "./AddReplicationModal";
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
import PageHeader from "../../Common/PageHeader/PageHeader";
import Checkbox from "@material-ui/core/Checkbox";
import EnableBucketEncryption from "./EnableBucketEncryption";
import { connect } from "react-redux";
import { setErrorSnackMessage } from "../../../../actions";
import PencilIcon from "../../Common/TableWrapper/TableActionIcons/PencilIcon";
import EnableVersioningModal from "./EnableVersioningModal";
const styles = (theme: Theme) =>
createStyles({
@@ -211,6 +213,9 @@ const ViewBucket = ({
const [retentionConfigOpen, setRetentionConfigOpen] = useState<boolean>(
false
);
const [enableVersioningOpen, setEnableVersioningOpen] = useState<boolean>(
false
);
const bucketName = match.params["bucketName"];
@@ -315,6 +320,10 @@ const ViewBucket = ({
}
}, [loadingEncryption, bucketName]);
const setBucketVersioning = () => {
setEnableVersioningOpen(true);
};
const loadAllBucketData = () => {
setLoadingBucket(true);
setLoadingSize(true);
@@ -361,6 +370,13 @@ const ViewBucket = ({
setSelectedEvent(evnt);
};
const closeEnableVersioning = (refresh: boolean) => {
setEnableVersioningOpen(false);
if (refresh) {
loadAllBucketData();
}
};
let accessPolicy = "n/a";
if (info !== null) {
@@ -452,6 +468,14 @@ const ViewBucket = ({
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
/>
)}
{enableVersioningOpen && (
<EnableVersioningModal
closeVersioningModalAndRefresh={closeEnableVersioning}
modalOpen={enableVersioningOpen}
selectedBucket={bucketName}
versioningCurrentState={isVersioned}
/>
)}
<PageHeader label={`Bucket > ${match.params["bucketName"]}`} />
<Grid container>
<Grid item xs={12} className={classes.container}>
@@ -489,7 +513,29 @@ const ViewBucket = ({
<span>{replicationRules.length ? "Yes" : "No"}</span>
</div>
<div>Versioning:</div>
<div>{isVersioned ? "Yes" : "No"}&nbsp;</div>
<div>
{loadingVersioning ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
<React.Fragment>
{isVersioned && !loadingVersioning ? "Yes" : "No"}
&nbsp;
<IconButton
color="primary"
aria-label="retention"
size="small"
className={classes.propertiesIcon}
onClick={setBucketVersioning}
>
<PencilIcon active={true} />
</IconButton>
</React.Fragment>
)}
</div>
<div>Encryption:</div>
<div>
<Checkbox

View File

@@ -191,6 +191,7 @@ type MCClient interface {
list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent
get(ctx context.Context, opts mc.GetOptions) (io.ReadCloser, *probe.Error)
shareDownload(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error)
setVersioning(ctx context.Context, status string) *probe.Error
}
// Interface implementation
@@ -219,6 +220,10 @@ func (c mcClient) setReplication(ctx context.Context, cfg *replication.Config, o
return c.client.SetReplication(ctx, cfg, opts)
}
func (c mcClient) setVersioning(ctx context.Context, status string) *probe.Error {
return c.client.SetVersion(ctx, status)
}
func (c mcClient) remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass bool, contentCh <-chan *mc.ClientContent) <-chan *probe.Error {
return c.client.Remove(ctx, isIncomplete, isRemoveBucket, isBypass, contentCh)
}

View File

@@ -89,7 +89,14 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
}
return user_api.NewGetBucketVersioningOK().WithPayload(getBucketVersioning)
})
// update bucket versioning
api.UserAPISetBucketVersioningHandler = user_api.SetBucketVersioningHandlerFunc(func(params user_api.SetBucketVersioningParams, session *models.Principal) middleware.Responder {
err := setBucketVersioningResponse(session, params.BucketName, &params)
if err != nil {
return user_api.NewSetBucketVersioningDefault(500).WithPayload(err)
}
return user_api.NewSetBucketVersioningCreated()
})
// get bucket replication
api.UserAPIGetBucketReplicationHandler = user_api.GetBucketReplicationHandlerFunc(func(params user_api.GetBucketReplicationParams, session *models.Principal) middleware.Responder {
getBucketReplication, err := getBucketReplicationdResponse(session, params.BucketName)
@@ -200,6 +207,47 @@ func getAddBucketReplicationdResponse(session *models.Principal, bucketName stri
return nil
}
type VersionState string
const (
VersionEnable VersionState = "enable"
VersionSuspend = "suspend"
)
// removeBucket deletes a bucket
func doSetVersioning(client MCClient, state VersionState) error {
err := client.setVersioning(context.Background(), string(state))
if err != nil {
log.Println("error setting versioning for bucket:", err.Cause)
return err.Cause
}
return nil
}
func setBucketVersioningResponse(session *models.Principal, bucketName string, params *user_api.SetBucketVersioningParams) *models.Error {
s3Client, err := newS3BucketClient(session, bucketName, "")
if err != nil {
return prepareError(err)
}
// create a mc S3Client interface implementation
// defining the client to be used
amcClient := mcClient{client: s3Client}
var versioningState VersionState = VersionSuspend
if params.Body.Versioning {
versioningState = VersionEnable
}
err2 := doSetVersioning(amcClient, versioningState)
if err2 != nil {
return prepareError(err2)
}
return nil
}
func getBucketReplicationdResponse(session *models.Principal, bucketName string) (*models.BucketReplicationResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()

View File

@@ -27,6 +27,7 @@ import (
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/sse"
"github.com/minio/minio/pkg/madmin"
@@ -44,6 +45,7 @@ var minioRemoveBucketEncryptionMock func(ctx context.Context, bucketName string)
var minioGetBucketEncryptionMock func(ctx context.Context, bucketName string) (*sse.Configuration, error)
var minioSetObjectLockConfigMock func(ctx context.Context, bucketName string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit) error
var minioGetObjectLockConfigMock func(ctx context.Context, bucketName string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error)
var minioSetVersioningMock func(ctx context.Context, state string) *probe.Error
// Define a mock struct of minio Client interface implementation
type minioClientMock struct {
@@ -94,6 +96,10 @@ func (mc minioClientMock) getBucketObjectLockConfig(ctx context.Context, bucketN
return minioGetObjectLockConfigMock(ctx, bucketName)
}
func (c s3ClientMock) setVersioning(ctx context.Context, state string) *probe.Error {
return minioSetVersioningMock(ctx, state)
}
var minioAccountInfoMock func(ctx context.Context) (madmin.AccountInfo, error)
// mock function of dataUsageInfo() needed for list bucket's usage
@@ -765,3 +771,65 @@ func Test_GetBucketRetentionConfig(t *testing.T) {
})
}
}
func Test_SetBucketVersioning(t *testing.T) {
assert := assert.New(t)
ctx := context.Background()
errorMsg := "Error Message"
minClient := s3ClientMock{}
type args struct {
ctx context.Context
state VersionState
bucketName string
client s3ClientMock
setVersioningFunc func(ctx context.Context, state string) *probe.Error
}
tests := []struct {
name string
args args
expectedError error
}{
{
name: "Set Bucket Version Success",
args: args{
ctx: ctx,
state: VersionEnable,
bucketName: "test",
client: minClient,
setVersioningFunc: func(ctx context.Context, state string) *probe.Error {
return nil
},
},
expectedError: nil,
},
{
name: "Set Bucket Version Error",
args: args{
ctx: ctx,
state: VersionEnable,
bucketName: "test",
client: minClient,
setVersioningFunc: func(ctx context.Context, state string) *probe.Error {
return probe.NewError(errors.New(errorMsg))
},
},
expectedError: errors.New(errorMsg),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
minioSetVersioningMock = tt.args.setVersioningFunc
err := doSetVersioning(tt.args.client, tt.args.state)
fmt.Println(t.Name())
fmt.Println("Expected:", tt.expectedError, "Error:", err)
if tt.expectedError != nil {
fmt.Println(t.Name())
assert.Equal(tt.expectedError.Error(), err.Error(), fmt.Sprintf("getBucketRetentionConfig() error: `%s`, wantErr: `%s`", err, tt.expectedError))
}
})
}
}