Added rewind functionality to console (#828)
* Added rewind functionality to console Signed-off-by: Benjamin Perez <benjamin@bexsoft.net> * Fix for object details Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> Co-authored-by: Benjamin Perez <benjamin@bexsoft.net> Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
@@ -31,7 +31,7 @@ ENV CGO_ENABLED=0
|
||||
COPY --from=uilayer /app/build /go/src/github.com/minio/console/portal-ui/build
|
||||
RUN go build -ldflags "-w -s" -a -o console ./cmd/console
|
||||
|
||||
FROM scratch
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
|
||||
MAINTAINER MinIO Development "dev@min.io"
|
||||
EXPOSE 9090
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -20,7 +20,7 @@ require (
|
||||
github.com/minio/kes v0.11.0
|
||||
github.com/minio/madmin-go v1.0.12
|
||||
github.com/minio/mc v0.0.0-20210531030240-fbbae711bdb4
|
||||
github.com/minio/minio-go/v7 v7.0.11-0.20210517200026-f0518ca447d6
|
||||
github.com/minio/minio-go/v7 v7.0.12-0.20210617160455-b7103728fb87
|
||||
github.com/minio/operator v0.0.0-20210616045941-65f31f5f78ae
|
||||
github.com/minio/operator/logsearchapi v0.0.0-20210604224119-7e256f98cf90
|
||||
github.com/minio/pkg v1.0.6
|
||||
|
||||
82
models/rewind_item.go
Normal file
82
models/rewind_item.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// 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"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// RewindItem rewind item
|
||||
//
|
||||
// swagger:model rewindItem
|
||||
type RewindItem struct {
|
||||
|
||||
// action
|
||||
Action string `json:"action,omitempty"`
|
||||
|
||||
// delete flag
|
||||
DeleteFlag bool `json:"delete_flag,omitempty"`
|
||||
|
||||
// last modified
|
||||
LastModified string `json:"last_modified,omitempty"`
|
||||
|
||||
// name
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// size
|
||||
Size int64 `json:"size,omitempty"`
|
||||
|
||||
// version id
|
||||
VersionID string `json:"version_id,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this rewind item
|
||||
func (m *RewindItem) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this rewind item based on context it is used
|
||||
func (m *RewindItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *RewindItem) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *RewindItem) UnmarshalBinary(b []byte) error {
|
||||
var res RewindItem
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
129
models/rewind_response.go
Normal file
129
models/rewind_response.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// RewindResponse rewind response
|
||||
//
|
||||
// swagger:model rewindResponse
|
||||
type RewindResponse struct {
|
||||
|
||||
// objects
|
||||
Objects []*RewindItem `json:"objects"`
|
||||
}
|
||||
|
||||
// Validate validates this rewind response
|
||||
func (m *RewindResponse) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateObjects(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RewindResponse) validateObjects(formats strfmt.Registry) error {
|
||||
if swag.IsZero(m.Objects) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.Objects); i++ {
|
||||
if swag.IsZero(m.Objects[i]) { // not required
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Objects[i] != nil {
|
||||
if err := m.Objects[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("objects" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validate this rewind response based on the context it is used
|
||||
func (m *RewindResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.contextValidateObjects(ctx, formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RewindResponse) contextValidateObjects(ctx context.Context, formats strfmt.Registry) error {
|
||||
|
||||
for i := 0; i < len(m.Objects); i++ {
|
||||
|
||||
if m.Objects[i] != nil {
|
||||
if err := m.Objects[i].ContextValidate(ctx, formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("objects" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *RewindResponse) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *RewindResponse) UnmarshalBinary(b []byte) error {
|
||||
var res RewindResponse
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
@@ -26,14 +26,12 @@ const CreateIcon = () => {
|
||||
width="2"
|
||||
height="12"
|
||||
transform="translate(-997 2555)"
|
||||
fill="#fff"
|
||||
/>
|
||||
<rect
|
||||
id="Rectangle_30"
|
||||
width="2"
|
||||
height="12"
|
||||
transform="translate(-990 2560) rotate(90)"
|
||||
fill="#fff"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
@@ -15,12 +15,22 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import get from "lodash/get";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import { BucketObject, BucketObjectsList } from "./types";
|
||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||
import RestoreIcon from "@material-ui/icons/Restore";
|
||||
import {
|
||||
BucketObject,
|
||||
BucketObjectsList,
|
||||
RewindObject,
|
||||
RewindObjectList,
|
||||
} from "./types";
|
||||
import api from "../../../../../../common/api";
|
||||
import TableWrapper from "../../../../Common/TableWrapper/TableWrapper";
|
||||
import { niceBytes } from "../../../../../../common/utils";
|
||||
@@ -33,20 +43,24 @@ import {
|
||||
searchField,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../../../../Common/PageHeader/PageHeader";
|
||||
import { Button, IconButton, Input, Typography } from "@material-ui/core";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
IconButton,
|
||||
Input,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import * as reactMoment from "react-moment";
|
||||
import { CreateIcon } from "../../../../../../icons";
|
||||
import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
|
||||
import get from "lodash/get";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import {
|
||||
addRoute,
|
||||
setAllRoutes,
|
||||
setLastAsFile,
|
||||
fileIsBeingPrepared,
|
||||
fileDownloadStarted,
|
||||
resetRewind,
|
||||
} from "../../../../ObjectBrowser/actions";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
ObjectBrowserReducer,
|
||||
Route,
|
||||
@@ -59,7 +73,8 @@ import {
|
||||
setSnackBarMessage,
|
||||
setErrorSnackMessage,
|
||||
} from "../../../../../../actions";
|
||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||
import { BucketVersioning } from "../../../types";
|
||||
import RewindEnable from "./RewindEnable";
|
||||
|
||||
const commonIcon = {
|
||||
backgroundRepeat: "no-repeat",
|
||||
@@ -133,6 +148,12 @@ const styles = (theme: Theme) =>
|
||||
listButton: {
|
||||
marginLeft: "10px",
|
||||
},
|
||||
badgeOverlap: {
|
||||
"& .MuiBadge-badge": {
|
||||
top: 35,
|
||||
right: 10,
|
||||
},
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...objectBrowserCommon,
|
||||
@@ -147,11 +168,15 @@ interface IListObjectsProps {
|
||||
routesList: Route[];
|
||||
downloadingFiles: string[];
|
||||
setLastAsFile: () => any;
|
||||
rewindEnabled: boolean;
|
||||
rewindDate: any;
|
||||
bucketToRewind: string;
|
||||
setLoadingProgress: typeof setLoadingProgress;
|
||||
setSnackBarMessage: typeof setSnackBarMessage;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
fileIsBeingPrepared: typeof fileIsBeingPrepared;
|
||||
fileDownloadStarted: typeof fileDownloadStarted;
|
||||
resetRewind: typeof resetRewind;
|
||||
}
|
||||
|
||||
function useInterval(callback: any, delay: number) {
|
||||
@@ -185,15 +210,21 @@ const ListObjects = ({
|
||||
setAllRoutes,
|
||||
routesList,
|
||||
downloadingFiles,
|
||||
rewindEnabled,
|
||||
rewindDate,
|
||||
bucketToRewind,
|
||||
setLastAsFile,
|
||||
setLoadingProgress,
|
||||
setSnackBarMessage,
|
||||
setErrorSnackMessage,
|
||||
fileIsBeingPrepared,
|
||||
fileDownloadStarted,
|
||||
resetRewind,
|
||||
}: IListObjectsProps) => {
|
||||
const [records, setRecords] = useState<BucketObject[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [rewind, setRewind] = useState<RewindObject[]>([]);
|
||||
const [loadingRewind, setLoadingRewind] = useState<boolean>(true);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
|
||||
const [selectedObject, setSelectedObject] = useState<string>("");
|
||||
@@ -202,6 +233,11 @@ const ListObjects = ({
|
||||
const [loadingStartTime, setLoadingStartTime] = useState<number>(0);
|
||||
const [loadingMessage, setLoadingMessage] =
|
||||
useState<React.ReactNode>(defLoading);
|
||||
const [loadingVersioning, setLoadingVersioning] = useState<boolean>(true);
|
||||
const [isVersioned, setIsVersioned] = useState<boolean>(false);
|
||||
const [rewindSelect, setRewindSelect] = useState<boolean>(false);
|
||||
|
||||
const bucketName = match.params["bucket"];
|
||||
|
||||
const updateMessage = () => {
|
||||
let timeDelta = Date.now() - loadingStartTime;
|
||||
@@ -232,31 +268,113 @@ const ListObjects = ({
|
||||
}, 1000);
|
||||
|
||||
useEffect(() => {
|
||||
const bucketName = match.params["bucket"];
|
||||
if (loadingVersioning) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/versioning`)
|
||||
.then((res: BucketVersioning) => {
|
||||
setIsVersioned(res.is_versioned);
|
||||
setLoadingVersioning(false);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
setErrorSnackMessage(err);
|
||||
setLoadingVersioning(false);
|
||||
});
|
||||
}
|
||||
}, [bucketName, loadingVersioning, setErrorSnackMessage]);
|
||||
|
||||
// Rewind
|
||||
useEffect(() => {
|
||||
const internalPaths = match.params[0];
|
||||
|
||||
if (rewindEnabled) {
|
||||
if (bucketToRewind !== bucketName) {
|
||||
resetRewind();
|
||||
return;
|
||||
}
|
||||
|
||||
if (rewindDate) {
|
||||
setLoadingRewind(true);
|
||||
const rewindParsed = rewindDate.toISOString();
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/rewind/${rewindParsed}?prefix=${
|
||||
internalPaths ? `${internalPaths}/` : ""
|
||||
}`
|
||||
)
|
||||
.then((res: RewindObjectList) => {
|
||||
setLoadingRewind(false);
|
||||
if (res.objects) {
|
||||
setRewind(res.objects);
|
||||
} else {
|
||||
setRewind([]);
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
setLoadingRewind(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [
|
||||
rewindEnabled,
|
||||
rewindDate,
|
||||
bucketToRewind,
|
||||
bucketName,
|
||||
match,
|
||||
setErrorSnackMessage,
|
||||
resetRewind,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const internalPaths = match.params[0];
|
||||
|
||||
const verifyIfIsFile = () => {
|
||||
const bucketName = match.params["bucket"];
|
||||
const internalPaths = match.params[0];
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}`
|
||||
)
|
||||
.then((res: BucketObjectsList) => {
|
||||
//It is a file since it has elements in the object, setting file flag and waiting for component mount
|
||||
if (res.objects !== null) {
|
||||
setLastAsFile();
|
||||
} else {
|
||||
// It is a folder, we remove loader
|
||||
if (rewindEnabled) {
|
||||
const rewindParsed = rewindDate.toISOString();
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/rewind/${rewindParsed}?prefix=${
|
||||
internalPaths ? `${internalPaths}/` : ""
|
||||
}`
|
||||
)
|
||||
.then((res: RewindObjectList) => {
|
||||
//It is a file since it has elements in the object, setting file flag and waiting for component mount
|
||||
if (res.objects === null) {
|
||||
setLastAsFile();
|
||||
} else {
|
||||
// It is a folder, we remove loader
|
||||
setLoadingRewind(false);
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
setLoadingRewind(false);
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
setLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
} else {
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}`
|
||||
)
|
||||
.then((res: BucketObjectsList) => {
|
||||
//It is a file since it has elements in the object, setting file flag and waiting for component mount
|
||||
if (res.objects !== null) {
|
||||
setLastAsFile();
|
||||
} else {
|
||||
// It is a folder, we remove loader
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
setLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
@@ -303,7 +421,15 @@ const ListObjects = ({
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
}
|
||||
}, [loading, match, setLastAsFile, setErrorSnackMessage]);
|
||||
}, [
|
||||
loading,
|
||||
match,
|
||||
setLastAsFile,
|
||||
setErrorSnackMessage,
|
||||
bucketName,
|
||||
rewindEnabled,
|
||||
rewindDate,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const url = get(match, "url", "/object-browser");
|
||||
@@ -421,6 +547,10 @@ const ListObjects = ({
|
||||
fileDownloadStarted(path);
|
||||
};
|
||||
|
||||
const displayDeleteFlag = (state: boolean) => {
|
||||
return state ? "Yes" : "No";
|
||||
};
|
||||
|
||||
const downloadObject = (object: BucketObject) => {
|
||||
fileIsBeingPrepared(`${selectedBucket}/${object.name}`);
|
||||
if (object.size > 104857600) {
|
||||
@@ -484,8 +614,26 @@ const ListObjects = ({
|
||||
onClick: downloadObject,
|
||||
showLoaderFunction: (item: string) =>
|
||||
downloadingFiles.includes(`${match.params["bucket"]}/${item}`),
|
||||
disableButtonFunction: (item: string) => {
|
||||
if (rewindEnabled) {
|
||||
const element = rewind.find((elm) => elm.name === item);
|
||||
|
||||
if (element && element.delete_flag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
sendOnlyId: false,
|
||||
},
|
||||
{
|
||||
type: "delete",
|
||||
onClick: confirmDeleteObject,
|
||||
sendOnlyId: true,
|
||||
disableButtonFunction: () => {
|
||||
return rewindEnabled;
|
||||
},
|
||||
},
|
||||
{ type: "delete", onClick: confirmDeleteObject, sendOnlyId: true },
|
||||
];
|
||||
|
||||
const displayName = (element: string) => {
|
||||
@@ -521,6 +669,64 @@ const ListObjects = ({
|
||||
}
|
||||
});
|
||||
|
||||
const rewindCloseModal = (refresh: boolean) => {
|
||||
setRewindSelect(false);
|
||||
|
||||
if (refresh) {
|
||||
}
|
||||
};
|
||||
|
||||
const listModeColumns = [
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: displayName,
|
||||
},
|
||||
{
|
||||
label: "Last Modified",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: displayNiceBytes,
|
||||
renderFullObject: true,
|
||||
width: 60,
|
||||
contentTextAlign: "right",
|
||||
},
|
||||
];
|
||||
|
||||
const rewindModeColumns = [
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: displayName,
|
||||
},
|
||||
{
|
||||
label: "Object Date",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: displayNiceBytes,
|
||||
renderFullObject: true,
|
||||
width: 60,
|
||||
contentTextAlign: "right",
|
||||
},
|
||||
{
|
||||
label: "Deleted",
|
||||
elementKey: "delete_flag",
|
||||
renderFunction: displayDeleteFlag,
|
||||
width: 60,
|
||||
contentTextAlign: "center",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{deleteOpen && (
|
||||
@@ -538,6 +744,13 @@ const ListObjects = ({
|
||||
onClose={closeAddFolderModal}
|
||||
/>
|
||||
)}
|
||||
{rewindSelect && (
|
||||
<RewindEnable
|
||||
open={rewindSelect}
|
||||
closeModalAndRefresh={rewindCloseModal}
|
||||
bucketName={bucketName}
|
||||
/>
|
||||
)}
|
||||
<PageHeader label="Object Browser" />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
@@ -571,9 +784,29 @@ const ListObjects = ({
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
}}
|
||||
disabled={rewindEnabled}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
<Badge
|
||||
badgeContent=" "
|
||||
color="secondary"
|
||||
variant="dot"
|
||||
invisible={!rewindEnabled}
|
||||
className={classes.badgeOverlap}
|
||||
>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Rewind"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
setRewindSelect(true);
|
||||
}}
|
||||
disabled={!isVersioned}
|
||||
>
|
||||
<RestoreIcon />
|
||||
</IconButton>
|
||||
</Badge>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
@@ -583,6 +816,7 @@ const ListObjects = ({
|
||||
setCreateFolderOpen(true);
|
||||
}}
|
||||
className={classes.listButton}
|
||||
disabled={rewindEnabled}
|
||||
>
|
||||
Create Folder
|
||||
</Button>
|
||||
@@ -592,6 +826,7 @@ const ListObjects = ({
|
||||
startIcon={<UploadFile />}
|
||||
component="label"
|
||||
className={classes.listButton}
|
||||
disabled={rewindEnabled}
|
||||
>
|
||||
File
|
||||
<Input
|
||||
@@ -609,32 +844,12 @@ const ListObjects = ({
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: displayName,
|
||||
},
|
||||
{
|
||||
label: "Last Modified",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: displayNiceBytes,
|
||||
renderFullObject: true,
|
||||
width: 60,
|
||||
contentTextAlign: "right",
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
columns={rewindEnabled ? rewindModeColumns : listModeColumns}
|
||||
isLoading={rewindEnabled ? loadingRewind : loading}
|
||||
loadingMessage={loadingMessage}
|
||||
entityName="Objects"
|
||||
entityName="Rewind Objects"
|
||||
idField="name"
|
||||
records={filteredRecords}
|
||||
records={rewindEnabled ? rewind : filteredRecords}
|
||||
customPaperHeight={classes.browsePaper}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -647,6 +862,9 @@ const ListObjects = ({
|
||||
const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({
|
||||
routesList: get(objectBrowser, "routesList", []),
|
||||
downloadingFiles: get(objectBrowser, "downloadingFiles", []),
|
||||
rewindEnabled: get(objectBrowser, "rewind.rewindEnabled", false),
|
||||
rewindDate: get(objectBrowser, "rewind.dateToRewind", null),
|
||||
bucketToRewind: get(objectBrowser, "rewind.bucketToRewind", ""),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
@@ -658,6 +876,7 @@ const mapDispatchToProps = {
|
||||
setErrorSnackMessage,
|
||||
fileIsBeingPrepared,
|
||||
fileDownloadStarted,
|
||||
resetRewind,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
// 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, { useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { ObjectBrowserReducer } from "../../../../ObjectBrowser/reducers";
|
||||
import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
resetRewind,
|
||||
setRewindEnable,
|
||||
} from "../../../../ObjectBrowser/actions";
|
||||
import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper";
|
||||
import DateTimePickerWrapper from "../../../../Common/FormComponents/DateTimePickerWrapper/DateTimePickerWrapper";
|
||||
import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
interface IRewindEnable {
|
||||
closeModalAndRefresh: (reload: boolean) => void;
|
||||
classes: any;
|
||||
open: boolean;
|
||||
bucketName: string;
|
||||
bucketToRewind: string;
|
||||
rewindEnabled: boolean;
|
||||
dateRewind: any;
|
||||
resetRewind: typeof resetRewind;
|
||||
setRewindEnable: typeof setRewindEnable;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const RewindEnable = ({
|
||||
closeModalAndRefresh,
|
||||
classes,
|
||||
open,
|
||||
bucketName,
|
||||
bucketToRewind,
|
||||
rewindEnabled,
|
||||
dateRewind,
|
||||
resetRewind,
|
||||
setRewindEnable,
|
||||
}: IRewindEnable) => {
|
||||
const [rewindEnabling, setRewindEnabling] = useState<boolean>(false);
|
||||
const [rewindEnableButton, setRewindEnableButton] = useState<boolean>(true);
|
||||
const [dateSelected, setDateSelected] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (rewindEnabled) {
|
||||
setRewindEnableButton(true);
|
||||
setDateSelected(new Date(dateRewind));
|
||||
}
|
||||
}, [rewindEnabled, dateRewind]);
|
||||
|
||||
const rewindApply = () => {
|
||||
if (!rewindEnableButton && rewindEnabled) {
|
||||
resetRewind();
|
||||
} else {
|
||||
setRewindEnabling(true);
|
||||
setRewindEnable(true, bucketName, dateSelected);
|
||||
}
|
||||
closeModalAndRefresh(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
closeModalAndRefresh(false);
|
||||
}}
|
||||
title={`Rewind - ${bucketName}`}
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<DateTimePickerWrapper
|
||||
value={dateSelected}
|
||||
onChange={setDateSelected}
|
||||
id="rewind-selector"
|
||||
label="Rewind to"
|
||||
disabled={!rewindEnableButton}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid container>
|
||||
{rewindEnabled && (
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="status"
|
||||
id="status"
|
||||
name="status"
|
||||
checked={rewindEnableButton}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRewindEnableButton(false);
|
||||
}}
|
||||
label={"Current Status"}
|
||||
indicatorLabels={["Enabled", "Disabled"]}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={rewindEnabling || (!dateSelected && rewindEnableButton)}
|
||||
onClick={rewindApply}
|
||||
>
|
||||
{!rewindEnableButton && rewindEnabled
|
||||
? "Show Current Data"
|
||||
: "Show Rewind Data"}
|
||||
</Button>
|
||||
</Grid>
|
||||
{rewindEnabling && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({
|
||||
bucketToRewind: objectBrowser.rewind.bucketToRewind,
|
||||
rewindEnabled: objectBrowser.rewind.rewindEnabled,
|
||||
dateRewind: objectBrowser.rewind.dateToRewind,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
resetRewind,
|
||||
setRewindEnable,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export default withStyles(styles)(connector(RewindEnable));
|
||||
@@ -26,3 +26,15 @@ export interface BucketObjectsList {
|
||||
objects: BucketObject[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface RewindObject {
|
||||
last_modified: string;
|
||||
delete_flag: boolean;
|
||||
name: string;
|
||||
version_id: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface RewindObjectList {
|
||||
objects: RewindObject[];
|
||||
}
|
||||
|
||||
@@ -51,7 +51,9 @@ import PageHeader from "../../../../Common/PageHeader/PageHeader";
|
||||
import ShareIcon from "../../../../../../icons/ShareIcon";
|
||||
import DownloadIcon from "../../../../../../icons/DownloadIcon";
|
||||
import DeleteIcon from "../../../../../../icons/DeleteIcon";
|
||||
import TableWrapper from "../../../../Common/TableWrapper/TableWrapper";
|
||||
import TableWrapper, {
|
||||
ItemActions,
|
||||
} from "../../../../Common/TableWrapper/TableWrapper";
|
||||
import PencilIcon from "../../../../Common/TableWrapper/TableActionIcons/PencilIcon";
|
||||
import SetRetention from "./SetRetention";
|
||||
import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
|
||||
@@ -162,6 +164,9 @@ interface IObjectDetailsProps {
|
||||
classes: any;
|
||||
routesList: Route[];
|
||||
downloadingFiles: string[];
|
||||
rewindEnabled: boolean;
|
||||
rewindDate: any;
|
||||
bucketToRewind: string;
|
||||
removeRouteLevel: (newRoute: string) => any;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
setSnackBarMessage: typeof setSnackBarMessage;
|
||||
@@ -185,6 +190,9 @@ const ObjectDetails = ({
|
||||
classes,
|
||||
routesList,
|
||||
downloadingFiles,
|
||||
rewindEnabled,
|
||||
rewindDate,
|
||||
bucketToRewind,
|
||||
removeRouteLevel,
|
||||
setErrorSnackMessage,
|
||||
setSnackBarMessage,
|
||||
@@ -221,7 +229,7 @@ const ObjectDetails = ({
|
||||
setActualInfo(
|
||||
result.find((el: IFileInfo) => el.is_latest) || emptyFile
|
||||
);
|
||||
setVersions(result.filter((el: IFileInfo) => !el.is_latest));
|
||||
setVersions(result);
|
||||
setLoadObjectData(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -286,8 +294,19 @@ const ObjectDetails = ({
|
||||
);
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "share", onClick: shareObject, sendOnlyId: true },
|
||||
const tableActions: ItemActions[] = [
|
||||
{
|
||||
type: "share",
|
||||
onClick: shareObject,
|
||||
sendOnlyId: true,
|
||||
disableButtonFunction: (item: string) => {
|
||||
const element = versions.find((elm) => elm.version_id === item);
|
||||
if (element && element.is_delete_marker) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "download",
|
||||
onClick: (item: IFileInfo) => {
|
||||
@@ -298,6 +317,13 @@ const ObjectDetails = ({
|
||||
`${bucketName}/${objectName}-${version}`
|
||||
);
|
||||
},
|
||||
disableButtonFunction: (item: string) => {
|
||||
const element = versions.find((elm) => elm.version_id === item);
|
||||
if (element && element.is_delete_marker) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -479,6 +505,7 @@ const ObjectDetails = ({
|
||||
onClick={() => {
|
||||
shareObject();
|
||||
}}
|
||||
disabled={actualInfo.is_delete_marker}
|
||||
>
|
||||
<ShareIcon />
|
||||
</IconButton>
|
||||
@@ -503,6 +530,7 @@ const ObjectDetails = ({
|
||||
onClick={() => {
|
||||
downloadObject(actualInfo);
|
||||
}}
|
||||
disabled={actualInfo.is_delete_marker}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
@@ -517,6 +545,7 @@ const ObjectDetails = ({
|
||||
onClick={() => {
|
||||
setDeleteOpen(true);
|
||||
}}
|
||||
disabled={actualInfo.is_delete_marker}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
@@ -585,12 +614,31 @@ const ObjectDetails = ({
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{
|
||||
label: "",
|
||||
width: 20,
|
||||
renderFullObject: true,
|
||||
renderFunction: (r) => {
|
||||
const versOrd = versions.length - versions.indexOf(r);
|
||||
return `v${versOrd}`;
|
||||
},
|
||||
},
|
||||
{ label: "Version ID", elementKey: "version_id" },
|
||||
{
|
||||
label: "Last Modified",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
},
|
||||
{
|
||||
label: "Deleted",
|
||||
width: 60,
|
||||
contentTextAlign: "center",
|
||||
renderFullObject: true,
|
||||
renderFunction: (r) => {
|
||||
const versOrd = r.is_delete_marker ? "Yes" : "No";
|
||||
return `${versOrd}`;
|
||||
},
|
||||
},
|
||||
]}
|
||||
isLoading={false}
|
||||
entityName="Versions"
|
||||
@@ -607,6 +655,9 @@ const ObjectDetails = ({
|
||||
|
||||
const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({
|
||||
downloadingFiles: get(objectBrowser, "downloadingFiles", []),
|
||||
rewindEnabled: get(objectBrowser, "rewind.rewindEnabled", false),
|
||||
rewindDate: get(objectBrowser, "rewind.dateToRewind", null),
|
||||
bucketToRewind: get(objectBrowser, "rewind.bucketToRewind", ""),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
|
||||
@@ -24,4 +24,5 @@ export interface IFileInfo {
|
||||
size?: string;
|
||||
tags?: object;
|
||||
version_id: string | null;
|
||||
is_delete_marker?: boolean;
|
||||
}
|
||||
|
||||
@@ -14,17 +14,26 @@
|
||||
// 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 from "react";
|
||||
import React, { Fragment } from "react";
|
||||
import MomentUtils from "@date-io/moment";
|
||||
import { Grid, InputLabel, Tooltip } from "@material-ui/core";
|
||||
import { DateTimePicker, MuiPickersUtilsProvider } from "@material-ui/pickers";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import ScheduleIcon from "@material-ui/icons/Schedule";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import HelpIcon from "../../../../../icons/HelpIcon";
|
||||
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
||||
|
||||
interface IDateTimePicker {
|
||||
value: any;
|
||||
onChange: (value: any) => any;
|
||||
classes: any;
|
||||
forSearchBlock?: boolean;
|
||||
label?: string;
|
||||
required?: boolean;
|
||||
tooltip?: string;
|
||||
id: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -56,17 +65,36 @@ const styles = (theme: Theme) =>
|
||||
color: "#393939",
|
||||
},
|
||||
},
|
||||
dateSelectorFormOverride: {
|
||||
width: "100%",
|
||||
maxWidth: 840,
|
||||
},
|
||||
parentDateOverride: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
textBoxContainer: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
textBoxWithIcon: {
|
||||
position: "relative",
|
||||
paddingRight: 25,
|
||||
},
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
});
|
||||
|
||||
const DateTimePickerWrapper = ({
|
||||
value,
|
||||
onChange,
|
||||
classes,
|
||||
forSearchBlock = false,
|
||||
label,
|
||||
tooltip = "",
|
||||
required,
|
||||
id,
|
||||
disabled = false,
|
||||
}: IDateTimePicker) => {
|
||||
return (
|
||||
const inputItem = (
|
||||
<MuiPickersUtilsProvider utils={MomentUtils}>
|
||||
<DateTimePicker
|
||||
value={value}
|
||||
@@ -77,16 +105,52 @@ const DateTimePickerWrapper = ({
|
||||
<ScheduleIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
className: classes.dateSelectorOverride,
|
||||
className: forSearchBlock ? classes.dateSelectorOverride : "",
|
||||
}}
|
||||
label=""
|
||||
ampm={false}
|
||||
variant={"inline"}
|
||||
className={classes.parentDateOverride}
|
||||
className={
|
||||
forSearchBlock
|
||||
? classes.parentDateOverride
|
||||
: classes.dateSelectorFormOverride
|
||||
}
|
||||
format="MMMM Do YYYY, h:mm a"
|
||||
id={id}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</MuiPickersUtilsProvider>
|
||||
);
|
||||
|
||||
if (forSearchBlock) {
|
||||
return inputItem;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={`${classes.fieldContainer}`}>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>
|
||||
{label}
|
||||
{required ? "*" : ""}
|
||||
</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<div>
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
)}
|
||||
|
||||
<div className={classes.textBoxContainer}>{inputItem}</div>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(DateTimePickerWrapper);
|
||||
|
||||
@@ -259,6 +259,10 @@ export const objectBrowserCommon = {
|
||||
},
|
||||
},
|
||||
},
|
||||
smallLabel: {
|
||||
color: "#9C9C9C",
|
||||
fontSize: 15,
|
||||
},
|
||||
};
|
||||
|
||||
export const selectorsCommon = {
|
||||
|
||||
@@ -41,7 +41,7 @@ import CheckboxWrapper from "../FormComponents/CheckboxWrapper/CheckboxWrapper";
|
||||
|
||||
//Interfaces for table Items
|
||||
|
||||
interface ItemActions {
|
||||
export interface ItemActions {
|
||||
type: string;
|
||||
to?: string;
|
||||
sendOnlyId?: boolean;
|
||||
|
||||
@@ -259,9 +259,19 @@ const PrDashboard = ({
|
||||
className={`${classes.actionsTray} ${classes.timeContainers}`}
|
||||
>
|
||||
<span className={classes.label}>Start Time</span>
|
||||
<DateTimePickerWrapper value={timeStart} onChange={setTimeStart} />
|
||||
<DateTimePickerWrapper
|
||||
value={timeStart}
|
||||
onChange={setTimeStart}
|
||||
forSearchBlock
|
||||
id="stTime"
|
||||
/>
|
||||
<span className={classes.label}>End Time</span>
|
||||
<DateTimePickerWrapper value={timeEnd} onChange={setTimeEnd} />
|
||||
<DateTimePickerWrapper
|
||||
value={timeEnd}
|
||||
onChange={setTimeEnd}
|
||||
forSearchBlock
|
||||
id="endTime"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="contained"
|
||||
|
||||
@@ -260,9 +260,19 @@ const LogsSearchMain = ({ classes, setErrorSnackMessage }: ILogSearchProps) => {
|
||||
className={`${classes.actionsTray} ${classes.timeContainers}`}
|
||||
>
|
||||
<span className={classes.label}>Start Time</span>
|
||||
<DateTimePickerWrapper value={timeStart} onChange={setTimeStart} />
|
||||
<DateTimePickerWrapper
|
||||
value={timeStart}
|
||||
onChange={setTimeStart}
|
||||
forSearchBlock
|
||||
id="stTime"
|
||||
/>
|
||||
<span className={classes.label}>End Time</span>
|
||||
<DateTimePickerWrapper value={timeEnd} onChange={setTimeEnd} />
|
||||
<DateTimePickerWrapper
|
||||
value={timeEnd}
|
||||
onChange={setTimeEnd}
|
||||
forSearchBlock
|
||||
id="endTime"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={`${classes.advancedLabelContainer}`}>
|
||||
<div
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import React from "react";
|
||||
import get from "lodash/get";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Moment from "react-moment";
|
||||
import { connect } from "react-redux";
|
||||
import { withStyles } from "@material-ui/core";
|
||||
import { createStyles, Theme } from "@material-ui/core/styles";
|
||||
@@ -32,6 +33,8 @@ interface ObjectBrowserReducer {
|
||||
interface IObjectBrowser {
|
||||
classes: any;
|
||||
objectsList: Route[];
|
||||
rewindEnabled: boolean;
|
||||
rewindDate: any;
|
||||
removeRouteLevel: (path: string) => any;
|
||||
}
|
||||
|
||||
@@ -43,6 +46,8 @@ const styles = (theme: Theme) =>
|
||||
const BrowserBreadcrumbs = ({
|
||||
classes,
|
||||
objectsList,
|
||||
rewindEnabled,
|
||||
rewindDate,
|
||||
removeRouteLevel,
|
||||
}: IObjectBrowser) => {
|
||||
const listBreadcrumbs = objectsList.map((objectItem, index) => {
|
||||
@@ -60,7 +65,6 @@ const BrowserBreadcrumbs = ({
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
@@ -68,6 +72,12 @@ const BrowserBreadcrumbs = ({
|
||||
{objectsList && objectsList.length > 0
|
||||
? objectsList.slice(-1)[0].label
|
||||
: ""}
|
||||
{rewindEnabled && objectsList.length > 1 && (
|
||||
<small className={classes.smallLabel}>
|
||||
(Rewind:{" "}
|
||||
<Moment date={rewindDate} format="MMMM Do YYYY, h:mm a" /> )
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.breadcrumbs}>
|
||||
@@ -79,6 +89,8 @@ const BrowserBreadcrumbs = ({
|
||||
|
||||
const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({
|
||||
objectsList: get(objectBrowser, "routesList", []),
|
||||
rewindEnabled: get(objectBrowser, "rewind.rewindEnabled", false),
|
||||
rewindDate: get(objectBrowser, "rewind.dateToRewind", null),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
|
||||
@@ -26,6 +26,8 @@ export const OBJECT_BROWSER_SET_LAST_AS_FILE =
|
||||
export const OBJECT_BROWSER_DOWNLOAD_FILE_LOADER =
|
||||
"OBJECT_BROWSER/DOWNLOAD_FILE_LOADER";
|
||||
export const OBJECT_BROWSER_DOWNLOADED_FILE = "OBJECT_BROWSER/DOWNLOADED_FILE";
|
||||
export const REWIND_SET_ENABLE = "REWIND/SET_ENABLE";
|
||||
export const REWIND_RESET_REWIND = "REWIND/RESET_REWIND";
|
||||
|
||||
interface AddRouteAction {
|
||||
type: typeof OBJECT_BROWSER_ADD_ROUTE;
|
||||
@@ -68,6 +70,17 @@ interface FileDownloaded {
|
||||
path: string;
|
||||
}
|
||||
|
||||
interface RewindSetEnabled {
|
||||
type: typeof REWIND_SET_ENABLE;
|
||||
bucket: string;
|
||||
state: boolean;
|
||||
dateRewind: any;
|
||||
}
|
||||
|
||||
interface RewindReset {
|
||||
type: typeof REWIND_RESET_REWIND;
|
||||
}
|
||||
|
||||
export type ObjectBrowserActionTypes =
|
||||
| AddRouteAction
|
||||
| ResetRoutesList
|
||||
@@ -76,7 +89,9 @@ export type ObjectBrowserActionTypes =
|
||||
| CreateFolder
|
||||
| SetLastAsFile
|
||||
| SetFileDownload
|
||||
| FileDownloaded;
|
||||
| FileDownloaded
|
||||
| RewindSetEnabled
|
||||
| RewindReset;
|
||||
|
||||
export const addRoute = (route: string, label: string, routeType: string) => {
|
||||
return {
|
||||
@@ -134,3 +149,22 @@ export const fileDownloadStarted = (path: string) => {
|
||||
path,
|
||||
};
|
||||
};
|
||||
|
||||
export const setRewindEnable = (
|
||||
state: boolean,
|
||||
bucket: string,
|
||||
dateRewind: any
|
||||
) => {
|
||||
return {
|
||||
type: REWIND_SET_ENABLE,
|
||||
state,
|
||||
bucket,
|
||||
dateRewind,
|
||||
};
|
||||
};
|
||||
|
||||
export const resetRewind = () => {
|
||||
return {
|
||||
type: REWIND_RESET_REWIND,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
OBJECT_BROWSER_SET_LAST_AS_FILE,
|
||||
OBJECT_BROWSER_DOWNLOAD_FILE_LOADER,
|
||||
OBJECT_BROWSER_DOWNLOADED_FILE,
|
||||
REWIND_SET_ENABLE,
|
||||
REWIND_RESET_REWIND,
|
||||
ObjectBrowserActionTypes,
|
||||
} from "./actions";
|
||||
|
||||
@@ -33,9 +35,16 @@ export interface Route {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface RewindItem {
|
||||
rewindEnabled: boolean;
|
||||
bucketToRewind: string;
|
||||
dateToRewind: any;
|
||||
}
|
||||
|
||||
export interface ObjectBrowserState {
|
||||
routesList: Route[];
|
||||
downloadingFiles: string[];
|
||||
rewind: RewindItem;
|
||||
}
|
||||
|
||||
export interface ObjectBrowserReducer {
|
||||
@@ -46,9 +55,18 @@ const initialRoute = [
|
||||
{ route: "/object-browser", label: "All Buckets", type: "path" },
|
||||
];
|
||||
|
||||
const defaultRewind = {
|
||||
rewindEnabled: false,
|
||||
bucketToRewind: "",
|
||||
dateToRewind: null,
|
||||
};
|
||||
|
||||
const initialState: ObjectBrowserState = {
|
||||
routesList: initialRoute,
|
||||
downloadingFiles: [],
|
||||
rewind: {
|
||||
...defaultRewind,
|
||||
},
|
||||
};
|
||||
|
||||
export function objectBrowserReducer(
|
||||
@@ -157,6 +175,21 @@ export function objectBrowserReducer(
|
||||
...state,
|
||||
downloadingFiles: [...downloadingFiles],
|
||||
};
|
||||
case REWIND_SET_ENABLE:
|
||||
const rewindSetEnabled = {
|
||||
...state.rewind,
|
||||
rewindEnabled: action.state,
|
||||
bucketToRewind: action.bucket,
|
||||
dateToRewind: action.dateRewind,
|
||||
};
|
||||
return { ...state, rewind: rewindSetEnabled };
|
||||
case REWIND_RESET_REWIND:
|
||||
const resetItem = {
|
||||
rewindEnabled: false,
|
||||
bucketToRewind: "",
|
||||
dateToRewind: null,
|
||||
};
|
||||
return { ...state, rewind: resetItem };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ import UserServiceAccountsPanel from "./UserServiceAccountsPanel";
|
||||
import MoreVertIcon from "@material-ui/icons/MoreVert";
|
||||
import ChangeUserPasswordModal from "../Account/ChangeUserPasswordModal";
|
||||
import DeleteUserString from "./DeleteUserString";
|
||||
import DeleteUser from "./DeleteUser";
|
||||
import { usersSort } from "../../../utils/sortFunctions";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -113,6 +112,7 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
actionsTray: { ...actionsTray.actionsTray, justifyContent: "flex-end" },
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
|
||||
@@ -1475,6 +1475,48 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"/buckets/{bucket_name}/rewind/{date}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"UserAPI"
|
||||
],
|
||||
"summary": "Get objects in a bucket for a rewind date",
|
||||
"operationId": "GetBucketRewind",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "bucket_name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "date",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "prefix",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rewindResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Generic error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/buckets/{bucket_name}/versioning": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -2440,6 +2482,36 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"/namespace": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"AdminAPI"
|
||||
],
|
||||
"summary": "Creates a new Namespace with given information",
|
||||
"operationId": "CreateNamespace",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/namespace"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "A successful response."
|
||||
},
|
||||
"default": {
|
||||
"description": "Generic error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/namespaces/{namespace}/resourcequotas/{resource-quota-name}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -5912,6 +5984,17 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"namespace": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nodeLabels": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@@ -6774,6 +6857,41 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"rewindItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string"
|
||||
},
|
||||
"delete_flag": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"last_modified": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"version_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rewindResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"objects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/rewindItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"serviceAccountCreds": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -9155,6 +9273,48 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"/buckets/{bucket_name}/rewind/{date}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"UserAPI"
|
||||
],
|
||||
"summary": "Get objects in a bucket for a rewind date",
|
||||
"operationId": "GetBucketRewind",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "bucket_name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "date",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "prefix",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rewindResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Generic error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/buckets/{bucket_name}/versioning": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -10120,6 +10280,36 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"/namespace": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"AdminAPI"
|
||||
],
|
||||
"summary": "Creates a new Namespace with given information",
|
||||
"operationId": "CreateNamespace",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/namespace"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "A successful response."
|
||||
},
|
||||
"default": {
|
||||
"description": "Generic error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/namespaces/{namespace}/resourcequotas/{resource-quota-name}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -14284,6 +14474,17 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"namespace": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nodeLabels": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@@ -15011,6 +15212,41 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"rewindItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string"
|
||||
},
|
||||
"delete_flag": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"last_modified": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"version_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rewindResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"objects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/rewindItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"serviceAccountCreds": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -183,6 +183,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
|
||||
UserAPIGetBucketRetentionConfigHandler: user_api.GetBucketRetentionConfigHandlerFunc(func(params user_api.GetBucketRetentionConfigParams, principal *models.Principal) middleware.Responder {
|
||||
return middleware.NotImplemented("operation user_api.GetBucketRetentionConfig has not yet been implemented")
|
||||
}),
|
||||
UserAPIGetBucketRewindHandler: user_api.GetBucketRewindHandlerFunc(func(params user_api.GetBucketRewindParams, principal *models.Principal) middleware.Responder {
|
||||
return middleware.NotImplemented("operation user_api.GetBucketRewind has not yet been implemented")
|
||||
}),
|
||||
UserAPIGetBucketVersioningHandler: user_api.GetBucketVersioningHandlerFunc(func(params user_api.GetBucketVersioningParams, principal *models.Principal) middleware.Responder {
|
||||
return middleware.NotImplemented("operation user_api.GetBucketVersioning has not yet been implemented")
|
||||
}),
|
||||
@@ -562,6 +565,8 @@ type ConsoleAPI struct {
|
||||
UserAPIGetBucketReplicationHandler user_api.GetBucketReplicationHandler
|
||||
// UserAPIGetBucketRetentionConfigHandler sets the operation handler for the get bucket retention config operation
|
||||
UserAPIGetBucketRetentionConfigHandler user_api.GetBucketRetentionConfigHandler
|
||||
// UserAPIGetBucketRewindHandler sets the operation handler for the get bucket rewind operation
|
||||
UserAPIGetBucketRewindHandler user_api.GetBucketRewindHandler
|
||||
// UserAPIGetBucketVersioningHandler sets the operation handler for the get bucket versioning operation
|
||||
UserAPIGetBucketVersioningHandler user_api.GetBucketVersioningHandler
|
||||
// AdminAPIGetDirectCSIDriveListHandler sets the operation handler for the get direct c s i drive list operation
|
||||
@@ -930,6 +935,9 @@ func (o *ConsoleAPI) Validate() error {
|
||||
if o.UserAPIGetBucketRetentionConfigHandler == nil {
|
||||
unregistered = append(unregistered, "user_api.GetBucketRetentionConfigHandler")
|
||||
}
|
||||
if o.UserAPIGetBucketRewindHandler == nil {
|
||||
unregistered = append(unregistered, "user_api.GetBucketRewindHandler")
|
||||
}
|
||||
if o.UserAPIGetBucketVersioningHandler == nil {
|
||||
unregistered = append(unregistered, "user_api.GetBucketVersioningHandler")
|
||||
}
|
||||
@@ -1437,6 +1445,10 @@ func (o *ConsoleAPI) initHandlerCache() {
|
||||
if o.handlers["GET"] == nil {
|
||||
o.handlers["GET"] = make(map[string]http.Handler)
|
||||
}
|
||||
o.handlers["GET"]["/buckets/{bucket_name}/rewind/{date}"] = user_api.NewGetBucketRewind(o.context, o.UserAPIGetBucketRewindHandler)
|
||||
if o.handlers["GET"] == nil {
|
||||
o.handlers["GET"] = make(map[string]http.Handler)
|
||||
}
|
||||
o.handlers["GET"]["/buckets/{bucket_name}/versioning"] = user_api.NewGetBucketVersioning(o.context, o.UserAPIGetBucketVersioningHandler)
|
||||
if o.handlers["GET"] == nil {
|
||||
o.handlers["GET"] = make(map[string]http.Handler)
|
||||
|
||||
88
restapi/operations/user_api/get_bucket_rewind.go
Normal file
88
restapi/operations/user_api/get_bucket_rewind.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// 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 user_api
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the generate command
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
|
||||
"github.com/minio/console/models"
|
||||
)
|
||||
|
||||
// GetBucketRewindHandlerFunc turns a function with the right signature into a get bucket rewind handler
|
||||
type GetBucketRewindHandlerFunc func(GetBucketRewindParams, *models.Principal) middleware.Responder
|
||||
|
||||
// Handle executing the request and returning a response
|
||||
func (fn GetBucketRewindHandlerFunc) Handle(params GetBucketRewindParams, principal *models.Principal) middleware.Responder {
|
||||
return fn(params, principal)
|
||||
}
|
||||
|
||||
// GetBucketRewindHandler interface for that can handle valid get bucket rewind params
|
||||
type GetBucketRewindHandler interface {
|
||||
Handle(GetBucketRewindParams, *models.Principal) middleware.Responder
|
||||
}
|
||||
|
||||
// NewGetBucketRewind creates a new http.Handler for the get bucket rewind operation
|
||||
func NewGetBucketRewind(ctx *middleware.Context, handler GetBucketRewindHandler) *GetBucketRewind {
|
||||
return &GetBucketRewind{Context: ctx, Handler: handler}
|
||||
}
|
||||
|
||||
/* GetBucketRewind swagger:route GET /buckets/{bucket_name}/rewind/{date} UserAPI getBucketRewind
|
||||
|
||||
Get objects in a bucket for a rewind date
|
||||
|
||||
*/
|
||||
type GetBucketRewind struct {
|
||||
Context *middleware.Context
|
||||
Handler GetBucketRewindHandler
|
||||
}
|
||||
|
||||
func (o *GetBucketRewind) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
route, rCtx, _ := o.Context.RouteInfo(r)
|
||||
if rCtx != nil {
|
||||
*r = *rCtx
|
||||
}
|
||||
var Params = NewGetBucketRewindParams()
|
||||
uprinc, aCtx, err := o.Context.Authorize(r, route)
|
||||
if err != nil {
|
||||
o.Context.Respond(rw, r, route.Produces, route, err)
|
||||
return
|
||||
}
|
||||
if aCtx != nil {
|
||||
*r = *aCtx
|
||||
}
|
||||
var principal *models.Principal
|
||||
if uprinc != nil {
|
||||
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
|
||||
}
|
||||
|
||||
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
|
||||
o.Context.Respond(rw, r, route.Produces, route, err)
|
||||
return
|
||||
}
|
||||
|
||||
res := o.Handler.Handle(Params, principal) // actually handle the request
|
||||
o.Context.Respond(rw, r, route.Produces, route, res)
|
||||
|
||||
}
|
||||
142
restapi/operations/user_api/get_bucket_rewind_parameters.go
Normal file
142
restapi/operations/user_api/get_bucket_rewind_parameters.go
Normal file
@@ -0,0 +1,142 @@
|
||||
// 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 user_api
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/strfmt"
|
||||
)
|
||||
|
||||
// NewGetBucketRewindParams creates a new GetBucketRewindParams object
|
||||
//
|
||||
// There are no default values defined in the spec.
|
||||
func NewGetBucketRewindParams() GetBucketRewindParams {
|
||||
|
||||
return GetBucketRewindParams{}
|
||||
}
|
||||
|
||||
// GetBucketRewindParams contains all the bound params for the get bucket rewind operation
|
||||
// typically these are obtained from a http.Request
|
||||
//
|
||||
// swagger:parameters GetBucketRewind
|
||||
type GetBucketRewindParams struct {
|
||||
|
||||
// HTTP Request Object
|
||||
HTTPRequest *http.Request `json:"-"`
|
||||
|
||||
/*
|
||||
Required: true
|
||||
In: path
|
||||
*/
|
||||
BucketName string
|
||||
/*
|
||||
Required: true
|
||||
In: path
|
||||
*/
|
||||
Date string
|
||||
/*
|
||||
In: query
|
||||
*/
|
||||
Prefix *string
|
||||
}
|
||||
|
||||
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
|
||||
// for simple values it will use straight method calls.
|
||||
//
|
||||
// To ensure default values, the struct must have been initialized with NewGetBucketRewindParams() beforehand.
|
||||
func (o *GetBucketRewindParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
|
||||
var res []error
|
||||
|
||||
o.HTTPRequest = r
|
||||
|
||||
qs := runtime.Values(r.URL.Query())
|
||||
|
||||
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
|
||||
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
rDate, rhkDate, _ := route.Params.GetOK("date")
|
||||
if err := o.bindDate(rDate, rhkDate, route.Formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
qPrefix, qhkPrefix, _ := qs.GetOK("prefix")
|
||||
if err := o.bindPrefix(qPrefix, qhkPrefix, route.Formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindBucketName binds and validates parameter BucketName from path.
|
||||
func (o *GetBucketRewindParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
|
||||
var raw string
|
||||
if len(rawData) > 0 {
|
||||
raw = rawData[len(rawData)-1]
|
||||
}
|
||||
|
||||
// Required: true
|
||||
// Parameter is provided by construction from the route
|
||||
o.BucketName = raw
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindDate binds and validates parameter Date from path.
|
||||
func (o *GetBucketRewindParams) bindDate(rawData []string, hasKey bool, formats strfmt.Registry) error {
|
||||
var raw string
|
||||
if len(rawData) > 0 {
|
||||
raw = rawData[len(rawData)-1]
|
||||
}
|
||||
|
||||
// Required: true
|
||||
// Parameter is provided by construction from the route
|
||||
o.Date = raw
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindPrefix binds and validates parameter Prefix from query.
|
||||
func (o *GetBucketRewindParams) bindPrefix(rawData []string, hasKey bool, formats strfmt.Registry) error {
|
||||
var raw string
|
||||
if len(rawData) > 0 {
|
||||
raw = rawData[len(rawData)-1]
|
||||
}
|
||||
|
||||
// Required: false
|
||||
// AllowEmptyValue: false
|
||||
|
||||
if raw == "" { // empty values pass all other validations
|
||||
return nil
|
||||
}
|
||||
o.Prefix = &raw
|
||||
|
||||
return nil
|
||||
}
|
||||
133
restapi/operations/user_api/get_bucket_rewind_responses.go
Normal file
133
restapi/operations/user_api/get_bucket_rewind_responses.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// 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 user_api
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
|
||||
"github.com/minio/console/models"
|
||||
)
|
||||
|
||||
// GetBucketRewindOKCode is the HTTP code returned for type GetBucketRewindOK
|
||||
const GetBucketRewindOKCode int = 200
|
||||
|
||||
/*GetBucketRewindOK A successful response.
|
||||
|
||||
swagger:response getBucketRewindOK
|
||||
*/
|
||||
type GetBucketRewindOK struct {
|
||||
|
||||
/*
|
||||
In: Body
|
||||
*/
|
||||
Payload *models.RewindResponse `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
// NewGetBucketRewindOK creates GetBucketRewindOK with default headers values
|
||||
func NewGetBucketRewindOK() *GetBucketRewindOK {
|
||||
|
||||
return &GetBucketRewindOK{}
|
||||
}
|
||||
|
||||
// WithPayload adds the payload to the get bucket rewind o k response
|
||||
func (o *GetBucketRewindOK) WithPayload(payload *models.RewindResponse) *GetBucketRewindOK {
|
||||
o.Payload = payload
|
||||
return o
|
||||
}
|
||||
|
||||
// SetPayload sets the payload to the get bucket rewind o k response
|
||||
func (o *GetBucketRewindOK) SetPayload(payload *models.RewindResponse) {
|
||||
o.Payload = payload
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *GetBucketRewindOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
|
||||
|
||||
rw.WriteHeader(200)
|
||||
if o.Payload != nil {
|
||||
payload := o.Payload
|
||||
if err := producer.Produce(rw, payload); err != nil {
|
||||
panic(err) // let the recovery middleware deal with this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*GetBucketRewindDefault Generic error response.
|
||||
|
||||
swagger:response getBucketRewindDefault
|
||||
*/
|
||||
type GetBucketRewindDefault struct {
|
||||
_statusCode int
|
||||
|
||||
/*
|
||||
In: Body
|
||||
*/
|
||||
Payload *models.Error `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
// NewGetBucketRewindDefault creates GetBucketRewindDefault with default headers values
|
||||
func NewGetBucketRewindDefault(code int) *GetBucketRewindDefault {
|
||||
if code <= 0 {
|
||||
code = 500
|
||||
}
|
||||
|
||||
return &GetBucketRewindDefault{
|
||||
_statusCode: code,
|
||||
}
|
||||
}
|
||||
|
||||
// WithStatusCode adds the status to the get bucket rewind default response
|
||||
func (o *GetBucketRewindDefault) WithStatusCode(code int) *GetBucketRewindDefault {
|
||||
o._statusCode = code
|
||||
return o
|
||||
}
|
||||
|
||||
// SetStatusCode sets the status to the get bucket rewind default response
|
||||
func (o *GetBucketRewindDefault) SetStatusCode(code int) {
|
||||
o._statusCode = code
|
||||
}
|
||||
|
||||
// WithPayload adds the payload to the get bucket rewind default response
|
||||
func (o *GetBucketRewindDefault) WithPayload(payload *models.Error) *GetBucketRewindDefault {
|
||||
o.Payload = payload
|
||||
return o
|
||||
}
|
||||
|
||||
// SetPayload sets the payload to the get bucket rewind default response
|
||||
func (o *GetBucketRewindDefault) SetPayload(payload *models.Error) {
|
||||
o.Payload = payload
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *GetBucketRewindDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
|
||||
|
||||
rw.WriteHeader(o._statusCode)
|
||||
if o.Payload != nil {
|
||||
payload := o.Payload
|
||||
if err := producer.Produce(rw, payload); err != nil {
|
||||
panic(err) // let the recovery middleware deal with this
|
||||
}
|
||||
}
|
||||
}
|
||||
138
restapi/operations/user_api/get_bucket_rewind_urlbuilder.go
Normal file
138
restapi/operations/user_api/get_bucket_rewind_urlbuilder.go
Normal file
@@ -0,0 +1,138 @@
|
||||
// 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 user_api
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the generate command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
golangswaggerpaths "path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetBucketRewindURL generates an URL for the get bucket rewind operation
|
||||
type GetBucketRewindURL struct {
|
||||
BucketName string
|
||||
Date string
|
||||
|
||||
Prefix *string
|
||||
|
||||
_basePath string
|
||||
// avoid unkeyed usage
|
||||
_ struct{}
|
||||
}
|
||||
|
||||
// WithBasePath sets the base path for this url builder, only required when it's different from the
|
||||
// base path specified in the swagger spec.
|
||||
// When the value of the base path is an empty string
|
||||
func (o *GetBucketRewindURL) WithBasePath(bp string) *GetBucketRewindURL {
|
||||
o.SetBasePath(bp)
|
||||
return o
|
||||
}
|
||||
|
||||
// SetBasePath sets the base path for this url builder, only required when it's different from the
|
||||
// base path specified in the swagger spec.
|
||||
// When the value of the base path is an empty string
|
||||
func (o *GetBucketRewindURL) SetBasePath(bp string) {
|
||||
o._basePath = bp
|
||||
}
|
||||
|
||||
// Build a url path and query string
|
||||
func (o *GetBucketRewindURL) Build() (*url.URL, error) {
|
||||
var _result url.URL
|
||||
|
||||
var _path = "/buckets/{bucket_name}/rewind/{date}"
|
||||
|
||||
bucketName := o.BucketName
|
||||
if bucketName != "" {
|
||||
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
|
||||
} else {
|
||||
return nil, errors.New("bucketName is required on GetBucketRewindURL")
|
||||
}
|
||||
|
||||
date := o.Date
|
||||
if date != "" {
|
||||
_path = strings.Replace(_path, "{date}", date, -1)
|
||||
} else {
|
||||
return nil, errors.New("date is required on GetBucketRewindURL")
|
||||
}
|
||||
|
||||
_basePath := o._basePath
|
||||
if _basePath == "" {
|
||||
_basePath = "/api/v1"
|
||||
}
|
||||
_result.Path = golangswaggerpaths.Join(_basePath, _path)
|
||||
|
||||
qs := make(url.Values)
|
||||
|
||||
var prefixQ string
|
||||
if o.Prefix != nil {
|
||||
prefixQ = *o.Prefix
|
||||
}
|
||||
if prefixQ != "" {
|
||||
qs.Set("prefix", prefixQ)
|
||||
}
|
||||
|
||||
_result.RawQuery = qs.Encode()
|
||||
|
||||
return &_result, nil
|
||||
}
|
||||
|
||||
// Must is a helper function to panic when the url builder returns an error
|
||||
func (o *GetBucketRewindURL) Must(u *url.URL, err error) *url.URL {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if u == nil {
|
||||
panic("url can't be nil")
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// String returns the string representation of the path with query string
|
||||
func (o *GetBucketRewindURL) String() string {
|
||||
return o.Must(o.Build()).String()
|
||||
}
|
||||
|
||||
// BuildFull builds a full url with scheme, host, path and query string
|
||||
func (o *GetBucketRewindURL) BuildFull(scheme, host string) (*url.URL, error) {
|
||||
if scheme == "" {
|
||||
return nil, errors.New("scheme is required for a full url on GetBucketRewindURL")
|
||||
}
|
||||
if host == "" {
|
||||
return nil, errors.New("host is required for a full url on GetBucketRewindURL")
|
||||
}
|
||||
|
||||
base, err := o.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base.Scheme = scheme
|
||||
base.Host = host
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// StringFull returns the string representation of a complete url
|
||||
func (o *GetBucketRewindURL) StringFull(scheme, host string) string {
|
||||
return o.Must(o.BuildFull(scheme, host)).String()
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/mc/cmd"
|
||||
"github.com/minio/mc/pkg/probe"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/sse"
|
||||
@@ -148,6 +149,14 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
|
||||
}
|
||||
return user_api.NewGetBucketObjectLockingStatusOK().WithPayload(getBucketObjectLockingStatus)
|
||||
})
|
||||
// get objects rewind for a bucket
|
||||
api.UserAPIGetBucketRewindHandler = user_api.GetBucketRewindHandlerFunc(func(params user_api.GetBucketRewindParams, session *models.Principal) middleware.Responder {
|
||||
getBucketRewind, err := getBucketRewindResponse(session, params)
|
||||
if err != nil {
|
||||
return user_api.NewGetBucketRewindDefault(500).WithPayload(err)
|
||||
}
|
||||
return user_api.NewGetBucketRewindOK().WithPayload(getBucketRewind)
|
||||
})
|
||||
}
|
||||
|
||||
type VersionState string
|
||||
@@ -749,3 +758,55 @@ func getBucketObLockingResponse(session *models.Principal, bucketName string) (*
|
||||
}
|
||||
return listBucketsResponse, nil
|
||||
}
|
||||
|
||||
func getBucketRewindResponse(session *models.Principal, params user_api.GetBucketRewindParams) (*models.RewindResponse, *models.Error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
|
||||
var prefix = ""
|
||||
|
||||
if params.Prefix != nil {
|
||||
prefix = *params.Prefix
|
||||
}
|
||||
|
||||
s3Client, err := newS3BucketClient(session, params.BucketName, prefix)
|
||||
if err != nil {
|
||||
LogError("error creating S3Client: %v", err)
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
|
||||
// create a mc S3Client interface implementation
|
||||
// defining the client to be used
|
||||
mcClient := mcClient{client: s3Client}
|
||||
|
||||
parsedDate, errDate := time.Parse(time.RFC3339, params.Date)
|
||||
|
||||
if errDate != nil {
|
||||
return nil, prepareError(errDate)
|
||||
}
|
||||
|
||||
var rewindItems []*models.RewindItem
|
||||
|
||||
for content := range mcClient.client.List(ctx, cmd.ListOptions{TimeRef: parsedDate, WithDeleteMarkers: true}) {
|
||||
// build object name
|
||||
name := strings.Replace(content.URL.Path, fmt.Sprintf("/%s/", params.BucketName), "", -1)
|
||||
|
||||
listElement := &models.RewindItem{
|
||||
LastModified: content.Time.Format(time.RFC3339),
|
||||
Size: content.Size,
|
||||
VersionID: content.VersionID,
|
||||
DeleteFlag: content.IsDeleteMarker,
|
||||
Action: "",
|
||||
Name: name,
|
||||
}
|
||||
|
||||
cont, _ := json.Marshal(content)
|
||||
fmt.Println(string(cont))
|
||||
|
||||
rewindItems = append(rewindItems, listElement)
|
||||
}
|
||||
|
||||
return &models.RewindResponse{
|
||||
Objects: rewindItems,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -68,7 +68,11 @@ func registerObjectsHandlers(api *operations.ConsoleAPI) {
|
||||
return user_api.NewDownloadObjectDefault(int(err.Code)).WithPayload(err)
|
||||
}
|
||||
return middleware.ResponderFunc(func(rw http.ResponseWriter, _ runtime.Producer) {
|
||||
io.Copy(rw, resp)
|
||||
x, err := io.Copy(rw, resp)
|
||||
|
||||
fmt.Println(x)
|
||||
fmt.Println(err)
|
||||
|
||||
resp.Close()
|
||||
})
|
||||
})
|
||||
@@ -229,7 +233,7 @@ func getDownloadObjectResponse(session *models.Principal, params user_api.Downlo
|
||||
}
|
||||
|
||||
func downloadObject(ctx context.Context, client MCClient, versionID *string) (io.ReadCloser, error) {
|
||||
// TODO: handle encripted files
|
||||
// TODO: handle encrypted files
|
||||
var reader io.ReadCloser
|
||||
var version string
|
||||
if versionID != nil {
|
||||
|
||||
55
swagger.yml
55
swagger.yml
@@ -988,6 +988,7 @@ paths:
|
||||
$ref: "#/definitions/error"
|
||||
tags:
|
||||
- UserAPI
|
||||
|
||||
/buckets/{bucket_name}/lifecycle/{lifecycle_id}:
|
||||
put:
|
||||
summary: Update Lifecycle rule
|
||||
@@ -1015,6 +1016,35 @@ paths:
|
||||
$ref: "#/definitions/error"
|
||||
tags:
|
||||
- UserAPI
|
||||
|
||||
/buckets/{bucket_name}/rewind/{date}:
|
||||
get:
|
||||
summary: Get objects in a bucket for a rewind date
|
||||
operationId: GetBucketRewind
|
||||
parameters:
|
||||
- name: bucket_name
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- name: date
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- name: prefix
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: A successful response.
|
||||
schema:
|
||||
$ref: "#/definitions/rewindResponse"
|
||||
default:
|
||||
description: Generic error response.
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
tags:
|
||||
- UserAPI
|
||||
|
||||
/service-accounts:
|
||||
get:
|
||||
@@ -5485,3 +5515,28 @@ definitions:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
|
||||
rewindItem:
|
||||
type: object
|
||||
properties:
|
||||
last_modified:
|
||||
type: string
|
||||
size:
|
||||
type: integer
|
||||
format: int64
|
||||
version_id:
|
||||
type: string
|
||||
delete_flag:
|
||||
type: boolean
|
||||
action:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
|
||||
rewindResponse:
|
||||
type: object
|
||||
properties:
|
||||
objects:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/rewindItem"
|
||||
|
||||
Reference in New Issue
Block a user