Compare commits
11 Commits
v0.12.6
...
master-120
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aba7a9e1c9 | ||
|
|
3db22a2479 | ||
|
|
884321cfce | ||
|
|
20c07a22e3 | ||
|
|
bf126d3a84 | ||
|
|
1e59f131e8 | ||
|
|
dfcd49bb5d | ||
|
|
a7ab26c81e | ||
|
|
35855daa12 | ||
|
|
3ce0b3d633 | ||
|
|
569d2390b9 |
@@ -42,6 +42,9 @@ type AdminInfoResponse struct {
|
||||
// objects
|
||||
Objects int64 `json:"objects,omitempty"`
|
||||
|
||||
// prometheus not ready
|
||||
PrometheusNotReady bool `json:"prometheusNotReady,omitempty"`
|
||||
|
||||
// servers
|
||||
Servers []*ServerProperties `json:"servers"`
|
||||
|
||||
|
||||
@@ -122,6 +122,9 @@ export const IAM_SCOPES = {
|
||||
ADMIN_DELETE_POLICY: "admin:DeletePolicy",
|
||||
ADMIN_ATTACH_USER_OR_GROUP_POLICY: "admin:AttachUserOrGroupPolicy",
|
||||
ADMIN_HEAL_ACTION: "admin:Heal",
|
||||
ADMIN_HEALTH_ACTION: "admin:OBDInfo",
|
||||
ADMIN_CONSOLE_LOG_ACTION: "admin:ConsoleLog",
|
||||
ADMIN_TRACE_ACTION: "admin:ServerTrace",
|
||||
S3_ALL_ACTIONS: "s3:*",
|
||||
ADMIN_ALL_ACTIONS: "admin:*",
|
||||
};
|
||||
|
||||
@@ -472,7 +472,12 @@ export const getTimeFromTimestamp = (
|
||||
timestamp: string,
|
||||
fullDate: boolean = false
|
||||
) => {
|
||||
const dateObject = new Date(parseInt(timestamp) * 1000);
|
||||
const timestampToInt = parseInt(timestamp);
|
||||
|
||||
if (isNaN(timestampToInt)) {
|
||||
return "";
|
||||
}
|
||||
const dateObject = new Date(timestampToInt * 1000);
|
||||
|
||||
if (fullDate) {
|
||||
return `${dateObject.getFullYear()}-${String(
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
// 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 * as React from "react";
|
||||
import { SVGProps } from "react";
|
||||
|
||||
const DiagnosticIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`min-icon`}
|
||||
fill={"currentcolor"}
|
||||
viewBox="0 0 256 256"
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<clipPath id="prefix__a">
|
||||
<path d="M0 0h256v256H0z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clipPath="url(#prefix__a)">
|
||||
<path fill="none" d="M0 0h256v256H0z" />
|
||||
<path
|
||||
data-name="Uni\xF3n 17"
|
||||
d="M.449 128.494A128.188 128.188 0 0 1 128.494.45h10.6v52.857a76.1 76.1 0 0 1 46.531 25.151 75.572 75.572 0 0 1 13.854 22.845 75.251 75.251 0 0 1 5.039 27.189 76.11 76.11 0 0 1-76.023 76.022 76.1 76.1 0 0 1-76.012-76.022 75.291 75.291 0 0 1 5.037-27.189 75.678 75.678 0 0 1 13.85-22.845 76.135 76.135 0 0 1 46.555-25.151v-31.18a106.369 106.369 0 0 0-19.6 3.814 106.378 106.378 0 0 0-18.193 7.25 107.579 107.579 0 0 0-16.385 10.312A108.253 108.253 0 0 0 49.54 56.524a108.229 108.229 0 0 0-11.676 15.37 107.348 107.348 0 0 0-8.787 17.356 106.17 106.17 0 0 0-7.459 39.244 107.008 107.008 0 0 0 106.877 106.892 107.017 107.017 0 0 0 106.9-106.892 10.5 10.5 0 0 1 3.1-7.479 10.49 10.49 0 0 1 7.475-3.1 10.593 10.593 0 0 1 10.584 10.58 128.2 128.2 0 0 1-128.057 128.057A128.2 128.2 0 0 1 .449 128.494Zm99.967-47.048a55.106 55.106 0 0 0-14.062 12.016 54.643 54.643 0 0 0-9.336 16.083 54.492 54.492 0 0 0-3.379 18.95 54.464 54.464 0 0 0 4.316 21.333 54.924 54.924 0 0 0 5.068 9.317 55.648 55.648 0 0 0 6.7 8.12 55.546 55.546 0 0 0 8.125 6.7 54.955 54.955 0 0 0 9.316 5.068 54.353 54.353 0 0 0 21.328 4.316 54.917 54.917 0 0 0 54.854-54.857 54.492 54.492 0 0 0-3.379-18.95 54.614 54.614 0 0 0-9.326-16.083 55.144 55.144 0 0 0-14.049-12.016 54.571 54.571 0 0 0-17.5-6.723v30.482a25.816 25.816 0 0 1 10.824 9.254 25.366 25.366 0 0 1 4.211 14.035 25.433 25.433 0 0 1-2.014 9.982 25.524 25.524 0 0 1-5.494 8.145 25.5 25.5 0 0 1-8.145 5.493 25.518 25.518 0 0 1-9.982 2.015 25.477 25.477 0 0 1-9.973-2.015 25.621 25.621 0 0 1-8.148-5.493 25.538 25.538 0 0 1-5.488-8.145 25.522 25.522 0 0 1-2.016-9.982 25.393 25.393 0 0 1 4.207-14.035 25.82 25.82 0 0 1 10.848-9.254V74.72a54.537 54.537 0 0 0-17.508 6.73Z"
|
||||
/>
|
||||
<path data-name="Rect\xE1ngulo 878" fill="none" d="M0 0h256v256H0z" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default DiagnosticIcon;
|
||||
@@ -19,17 +19,25 @@ import React, { SVGProps } from "react";
|
||||
const DiagnosticsIcon = (props: SVGProps<SVGSVGElement>) => {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`min-icon`}
|
||||
fill={"currentcolor"}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 10.405 10.405"
|
||||
viewBox="0 0 256 256"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M9.932 4.778a.424.424 0 00-.424.424 4.31 4.31 0 01-4.305 4.3 4.309 4.309 0 01-4.3-4.3A4.311 4.311 0 014.784.918v1.259A3.06 3.06 0 002.15 5.202 3.062 3.062 0 005.208 8.26a3.062 3.062 0 003.058-3.058 3.06 3.06 0 00-2.634-3.025V.049h-.424A5.158 5.158 0 00.055 5.201a5.158 5.158 0 005.153 5.153 5.158 5.158 0 005.153-5.153.424.424 0 00-.429-.423zm-2.519.424a2.213 2.213 0 01-2.21 2.21 2.213 2.213 0 01-2.21-2.21A2.213 2.213 0 014.78 3.035v1.231a1.028 1.028 0 00-.606.936 1.03 1.03 0 001.03 1.03 1.03 1.03 0 001.03-1.03 1.028 1.028 0 00-.605-.936V3.035a2.212 2.212 0 011.784 2.167z"
|
||||
stroke="#000"
|
||||
strokeWidth={0.1}
|
||||
/>
|
||||
<defs>
|
||||
<clipPath id="prefix__a">
|
||||
<path d="M0 0h256v256H0z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clipPath="url(#prefix__a)">
|
||||
<path fill="none" d="M0 0h256v256H0z" />
|
||||
<path
|
||||
data-name="Uni\xF3n 17"
|
||||
d="M.449 128.494A128.188 128.188 0 0 1 128.494.45h10.6v52.857a76.1 76.1 0 0 1 46.531 25.151 75.572 75.572 0 0 1 13.854 22.845 75.251 75.251 0 0 1 5.039 27.189 76.11 76.11 0 0 1-76.023 76.022 76.1 76.1 0 0 1-76.012-76.022 75.291 75.291 0 0 1 5.037-27.189 75.678 75.678 0 0 1 13.85-22.845 76.135 76.135 0 0 1 46.555-25.151v-31.18a106.369 106.369 0 0 0-19.6 3.814 106.378 106.378 0 0 0-18.193 7.25 107.579 107.579 0 0 0-16.385 10.312A108.253 108.253 0 0 0 49.54 56.524a108.229 108.229 0 0 0-11.676 15.37 107.348 107.348 0 0 0-8.787 17.356 106.17 106.17 0 0 0-7.459 39.244 107.008 107.008 0 0 0 106.877 106.892 107.017 107.017 0 0 0 106.9-106.892 10.5 10.5 0 0 1 3.1-7.479 10.49 10.49 0 0 1 7.475-3.1 10.593 10.593 0 0 1 10.584 10.58 128.2 128.2 0 0 1-128.057 128.057A128.2 128.2 0 0 1 .449 128.494Zm99.967-47.048a55.106 55.106 0 0 0-14.062 12.016 54.643 54.643 0 0 0-9.336 16.083 54.492 54.492 0 0 0-3.379 18.95 54.464 54.464 0 0 0 4.316 21.333 54.924 54.924 0 0 0 5.068 9.317 55.648 55.648 0 0 0 6.7 8.12 55.546 55.546 0 0 0 8.125 6.7 54.955 54.955 0 0 0 9.316 5.068 54.353 54.353 0 0 0 21.328 4.316 54.917 54.917 0 0 0 54.854-54.857 54.492 54.492 0 0 0-3.379-18.95 54.614 54.614 0 0 0-9.326-16.083 55.144 55.144 0 0 0-14.049-12.016 54.571 54.571 0 0 0-17.5-6.723v30.482a25.816 25.816 0 0 1 10.824 9.254 25.366 25.366 0 0 1 4.211 14.035 25.433 25.433 0 0 1-2.014 9.982 25.524 25.524 0 0 1-5.494 8.145 25.5 25.5 0 0 1-8.145 5.493 25.518 25.518 0 0 1-9.982 2.015 25.477 25.477 0 0 1-9.973-2.015 25.621 25.621 0 0 1-8.148-5.493 25.538 25.538 0 0 1-5.488-8.145 25.522 25.522 0 0 1-2.016-9.982 25.393 25.393 0 0 1 4.207-14.035 25.82 25.82 0 0 1 10.848-9.254V74.72a54.537 54.537 0 0 0-17.508 6.73Z"
|
||||
/>
|
||||
<path data-name="Rect\xE1ngulo 878" fill="none" d="M0 0h256v256H0z" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -42,11 +42,11 @@ const UploadFile = (props: SVGProps<SVGSVGElement>) => {
|
||||
<g transform="translate(-0.036 -24.789)">
|
||||
<path d="M239.185,72.637A29.456,29.456,0,0,0,209.767,43.6H128.581l-1.119-1.512c-5.078-6.886-12.756-17.3-26.1-17.3H49.394A29.455,29.455,0,0,0,19.972,54.21a19.778,19.778,0,0,0,.236,3.081V70.763A29.818,29.818,0,0,0,.036,98.947c0,.6.023,1.205.076,1.806L9.8,207.577A29.8,29.8,0,0,0,39.545,236.2h175.73A29.8,29.8,0,0,0,245.021,207.6L254.947,100.8q.088-.928.09-1.852A29.792,29.792,0,0,0,239.185,72.637ZM49.394,44.808h51.963c6.586,0,13.645,18.813,20.7,18.813h87.709a9.429,9.429,0,0,1,9.4,9.4v4.7H40.213V54.206h-.229A9.431,9.431,0,0,1,49.394,44.808ZM225.031,206.43a9.781,9.781,0,0,1-9.754,9.748H39.547a9.779,9.779,0,0,1-9.75-9.748L20.051,98.947A9.782,9.782,0,0,1,29.8,89.192H225.268a9.788,9.788,0,0,1,9.758,9.755Z" />
|
||||
<g transform="translate(-351.512 467)">
|
||||
<g transform="translate(352 -469)" clip-path="url(#a)">
|
||||
<g transform="translate(352 -469)" clipPath="url(#a)">
|
||||
<path d="M118.046,203.4c0,12.123,18.976,12.123,18.976,0V126.379l10.748,10.443c8.823,8.569,22.236-4.465,13.415-13.034L134.3,97.665a9.685,9.685,0,0,0-13.526,0L93.89,123.788c-8.82,8.568,4.592,21.6,13.415,13.034l10.745-10.443V203.4Z" />
|
||||
</g>
|
||||
</g>
|
||||
<g clip-path="url(#b)">
|
||||
<g clipPath="url(#b)">
|
||||
<path d="M56.052,158.235c0-12.121,18.978-12.121,18.978,0v66.218H185.056V158.235c0-12.121,18.973-12.121,18.973,0v75.436a9.357,9.357,0,0,1-9.486,9.217h-129a9.357,9.357,0,0,1-9.486-9.217V158.235Zm64.5,45.162c0,12.123,18.976,12.123,18.976,0V126.379l10.748,10.443c8.823,8.569,22.236-4.465,13.415-13.034L136.8,97.665a9.685,9.685,0,0,0-13.526,0L96.394,123.788c-8.82,8.568,4.593,21.6,13.415,13.034l10.745-10.443V203.4Z" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
@@ -34,7 +34,6 @@ export { default as CopyIcon } from "./CopyIcon";
|
||||
export { default as CreateIcon } from "./CreateIcon";
|
||||
export { default as DashboardIcon } from "./DashboardIcon";
|
||||
export { default as DeleteIcon } from "./DeleteIcon";
|
||||
export { default as DiagnosticIcon } from "./DiagnosticIcon";
|
||||
export { default as DiagnosticsIcon } from "./DiagnosticsIcon";
|
||||
export { default as DocumentationIcon } from "./DocumentationIcon";
|
||||
export { default as DownloadIcon } from "./DownloadIcon";
|
||||
|
||||
@@ -14,30 +14,19 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { setErrorSnackMessage } from "../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import api from "../../../common/api";
|
||||
import { deleteDialogStyles } from "../Common/FormComponents/common/styleLibrary";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...deleteDialogStyles,
|
||||
wrapText: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
@@ -60,95 +49,38 @@ const DeleteServiceAccount = ({
|
||||
selectedServiceAccount,
|
||||
setErrorSnackMessage,
|
||||
}: IDeleteServiceAccountProps) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (deleteLoading) {
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/service-accounts/${selectedServiceAccount}`)
|
||||
.then(() => {
|
||||
setDeleteLoading(false);
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
}
|
||||
}, [
|
||||
deleteLoading,
|
||||
closeDeleteModalAndRefresh,
|
||||
selectedServiceAccount,
|
||||
setErrorSnackMessage,
|
||||
]);
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
const removeRecord = () => {
|
||||
if (selectedServiceAccount === null) {
|
||||
return;
|
||||
}
|
||||
if (!selectedServiceAccount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
setDeleteLoading(true);
|
||||
const onConfirmDelete = () => {
|
||||
invokeDeleteApi(
|
||||
"DELETE",
|
||||
`/api/v1/service-accounts/${selectedServiceAccount}`
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
classes={classes}
|
||||
className={classes.root}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title" className={classes.title}>
|
||||
<div className={classes.titleText}>Delete ServiceAccount</div>
|
||||
<div className={classes.closeContainer}>
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
className={classes.closeButton}
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(true);
|
||||
}}
|
||||
disableRipple
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<ConfirmDialog
|
||||
title={`Delete Service Account`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete service account{" "}
|
||||
<b className={classes.wrapText}>{selectedServiceAccount}</b>?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
onClick={removeRecord}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -15,23 +15,17 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
} from "@mui/material";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { connect } from "react-redux";
|
||||
import api from "../../../../common/api";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { AppState } from "../../../../store";
|
||||
import useApi from "../../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
session: state.console.session,
|
||||
@@ -64,46 +58,31 @@ const DeleteAccessRule = ({
|
||||
bucket,
|
||||
toDelete,
|
||||
}: IDeleteAccessRule) => {
|
||||
const deleteProcess = () => {
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/bucket/${bucket}/access-rules`, {
|
||||
prefix: toDelete,
|
||||
})
|
||||
.then((res: any) => {})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
const onDelSuccess = () => onClose();
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
invokeDeleteApi("DELETE", `/api/v1/bucket/${bucket}/access-rules`, {
|
||||
prefix: toDelete,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={modalOpen}
|
||||
<ConfirmDialog
|
||||
title={`Delete Access Rule`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={modalOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Access Rule</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete this access rule?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
deleteProcess();
|
||||
onClose();
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,18 +14,10 @@
|
||||
// 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 } from "react";
|
||||
import React from "react";
|
||||
import get from "lodash/get";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
@@ -33,7 +25,8 @@ import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { AppState } from "../../../../store";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import api from "../../../../common/api";
|
||||
import useApi from "../../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
interface IDeleteBucketTagModal {
|
||||
deleteOpen: boolean;
|
||||
@@ -66,62 +59,45 @@ const DeleteBucketTagModal = ({
|
||||
setErrorSnackMessage,
|
||||
classes,
|
||||
}: IDeleteBucketTagModal) => {
|
||||
const [deleteLoading, setDeleteSending] = useState<boolean>(false);
|
||||
const [tagKey, tagLabel] = selectedTag;
|
||||
|
||||
const removeTagProcess = () => {
|
||||
setDeleteSending(true);
|
||||
const onDelSuccess = () => onCloseAndUpdate(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => onCloseAndUpdate(false);
|
||||
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
if (!selectedTag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
const cleanObject = { ...currentTags };
|
||||
delete cleanObject[tagKey];
|
||||
|
||||
api
|
||||
.invoke("PUT", `/api/v1/buckets/${bucketName}/tags`, {
|
||||
tags: cleanObject,
|
||||
})
|
||||
.then((res: any) => {
|
||||
setDeleteSending(false);
|
||||
onCloseAndUpdate(true);
|
||||
})
|
||||
.catch((error: ErrorResponseHandler) => {
|
||||
setErrorSnackMessage(error);
|
||||
setDeleteSending(false);
|
||||
});
|
||||
invokeDeleteApi("PUT", `/api/v1/buckets/${bucketName}/tags`, {
|
||||
tags: cleanObject,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
onCloseAndUpdate(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Tag</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<ConfirmDialog
|
||||
title={`Delete Tag`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete the tag{" "}
|
||||
<b className={classes.wrapText}>
|
||||
{tagKey} : {tagLabel}
|
||||
</b>
|
||||
</b>{" "}
|
||||
?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onCloseAndUpdate(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={removeTagProcess} color="secondary" autoFocus>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,22 +14,15 @@
|
||||
// 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 } from "react";
|
||||
import React from "react";
|
||||
import get from "lodash/get";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import api from "../../../../common/api";
|
||||
import { BucketEvent, BucketList } from "../types";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { BucketEvent } from "../types";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import useApi from "../../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
interface IDeleteEventProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
@@ -46,78 +39,50 @@ const DeleteEvent = ({
|
||||
bucketEvent,
|
||||
setErrorSnackMessage,
|
||||
}: IDeleteEventProps) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
const removeRecord = () => {
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
if (!selectedBucket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
if (bucketEvent === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDeleteLoading(true);
|
||||
|
||||
const events = get(bucketEvent, "events", []);
|
||||
const prefix = get(bucketEvent, "prefix", "");
|
||||
const suffix = get(bucketEvent, "suffix", "");
|
||||
api
|
||||
.invoke(
|
||||
"DELETE",
|
||||
`/api/v1/buckets/${selectedBucket}/events/${bucketEvent.arn}`,
|
||||
{
|
||||
events,
|
||||
prefix,
|
||||
suffix,
|
||||
}
|
||||
)
|
||||
.then((res: BucketList) => {
|
||||
setDeleteLoading(false);
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
|
||||
invokeDeleteApi(
|
||||
"DELETE",
|
||||
`/api/v1/buckets/${selectedBucket}/events/${bucketEvent.arn}`,
|
||||
{
|
||||
events,
|
||||
prefix,
|
||||
suffix,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Event</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<ConfirmDialog
|
||||
title={`Delete Event`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete this event?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
removeRecord();
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,20 +14,13 @@
|
||||
// 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 } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import api from "../../../../common/api";
|
||||
import useApi from "../../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
interface IDeleteReplicationProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
@@ -44,68 +37,40 @@ const DeleteReplicationRule = ({
|
||||
ruleToDelete,
|
||||
setErrorSnackMessage,
|
||||
}: IDeleteReplicationProps) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
const removeRecord = () => {
|
||||
if (!deleteLoading) {
|
||||
setDeleteLoading(true);
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"DELETE",
|
||||
`/api/v1/buckets/${selectedBucket}/replication/${ruleToDelete}`
|
||||
)
|
||||
.then(() => {
|
||||
setDeleteLoading(false);
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
}
|
||||
if (!selectedBucket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
invokeDeleteApi(
|
||||
"DELETE",
|
||||
`/api/v1/buckets/${selectedBucket}/replication/${ruleToDelete}`
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Replication Rule</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<ConfirmDialog
|
||||
title={`Delete Replication Rule`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete replication rule <b>{ruleToDelete}</b>
|
||||
? <br />
|
||||
Remember, at lease one rule must be present once replication has been
|
||||
enabled
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
removeRecord();
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,21 +14,13 @@
|
||||
// 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 } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import { BucketList } from "../types";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import api from "../../../../common/api";
|
||||
import useApi from "../../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
interface IDeleteBucketProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
@@ -43,65 +35,37 @@ const DeleteBucket = ({
|
||||
selectedBucket,
|
||||
setErrorSnackMessage,
|
||||
}: IDeleteBucketProps) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
const removeRecord = () => {
|
||||
if (!deleteLoading) {
|
||||
setDeleteLoading(true);
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/buckets/${selectedBucket}`, {
|
||||
name: selectedBucket,
|
||||
})
|
||||
.then((res: BucketList) => {
|
||||
setDeleteLoading(false);
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
}
|
||||
if (!selectedBucket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
invokeDeleteApi("DELETE", `/api/v1/buckets/${selectedBucket}`, {
|
||||
name: selectedBucket,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Bucket</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<ConfirmDialog
|
||||
title={`Delete Bucket`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete bucket <b>{selectedBucket}</b>? <br />
|
||||
A bucket can only be deleted if it's empty.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
removeRecord();
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import { connect } from "react-redux";
|
||||
import { setFileModeEnabled } from "../../../../ObjectBrowser/actions";
|
||||
import history from "../../../../../../history";
|
||||
import { decodeFileName, encodeFileName } from "../../../../../../common/utils";
|
||||
import { setModalErrorSnackMessage } from "../../../../../../actions";
|
||||
import { BucketObject } from "./types";
|
||||
|
||||
interface ICreateFolder {
|
||||
classes: any;
|
||||
@@ -33,7 +35,9 @@ interface ICreateFolder {
|
||||
bucketName: string;
|
||||
folderName: string;
|
||||
setFileModeEnabled: typeof setFileModeEnabled;
|
||||
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
|
||||
onClose: () => any;
|
||||
existingFiles: BucketObject[];
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -54,7 +58,9 @@ const CreateFolderModal = ({
|
||||
bucketName,
|
||||
onClose,
|
||||
setFileModeEnabled,
|
||||
setModalErrorSnackMessage,
|
||||
classes,
|
||||
existingFiles,
|
||||
}: ICreateFolder) => {
|
||||
const [pathUrl, setPathUrl] = useState("");
|
||||
const [isFormValid, setIsFormValid] = useState<boolean>(false);
|
||||
@@ -73,6 +79,15 @@ const CreateFolderModal = ({
|
||||
? decodedFolderName
|
||||
: `${decodedFolderName}/`;
|
||||
}
|
||||
const sharesName = (record: BucketObject) =>
|
||||
record.name === folderPath + pathUrl;
|
||||
if (existingFiles.findIndex(sharesName) !== -1) {
|
||||
setModalErrorSnackMessage({
|
||||
errorMessage: "Folder cannot have the same name as an existing file",
|
||||
detailedError: "",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const newPath = `/buckets/${bucketName}/browse/${encodeFileName(
|
||||
`${folderPath}${pathUrl}`
|
||||
)}/`;
|
||||
@@ -138,6 +153,7 @@ const CreateFolderModal = ({
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setFileModeEnabled,
|
||||
setModalErrorSnackMessage,
|
||||
};
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
|
||||
@@ -14,20 +14,13 @@
|
||||
// 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 } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { setErrorSnackMessage } from "../../../../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
import api from "../../../../../../common/api";
|
||||
import useApi from "../../../../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../../../../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
interface IDeleteObjectProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
@@ -44,12 +37,16 @@ const DeleteObject = ({
|
||||
selectedObjects,
|
||||
setErrorSnackMessage,
|
||||
}: IDeleteObjectProps) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
const removeRecord = () => {
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
if (!selectedObjects) {
|
||||
return null;
|
||||
}
|
||||
const onConfirmDelete = () => {
|
||||
let toSend = [];
|
||||
for (let i = 0; i < selectedObjects.length; i++) {
|
||||
if (selectedObjects[i].endsWith("/")) {
|
||||
@@ -66,60 +63,31 @@ const DeleteObject = ({
|
||||
});
|
||||
}
|
||||
}
|
||||
setDeleteLoading(true);
|
||||
api
|
||||
.invoke(
|
||||
|
||||
if (toSend) {
|
||||
invokeDeleteApi(
|
||||
"POST",
|
||||
`/api/v1/buckets/${selectedBucket}/delete-objects`,
|
||||
toSend
|
||||
)
|
||||
.then(() => {
|
||||
setDeleteLoading(false);
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete the selected objects?{" "}
|
||||
<ConfirmDialog
|
||||
title={`Delete Objects`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete the selected {selectedObjects.length}{" "}
|
||||
objects?{" "}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
removeRecord();
|
||||
}}
|
||||
color="secondary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,21 +14,14 @@
|
||||
// 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 } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { setErrorSnackMessage } from "../../../../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
import api from "../../../../../../common/api";
|
||||
import { decodeFileName } from "../../../../../../common/utils";
|
||||
import ConfirmDialog from "../../../../Common/ModalWrapper/ConfirmDialog";
|
||||
import useApi from "../../../../Common/Hooks/useApi";
|
||||
|
||||
interface IDeleteObjectProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
@@ -45,67 +38,39 @@ const DeleteObject = ({
|
||||
selectedObject,
|
||||
setErrorSnackMessage,
|
||||
}: IDeleteObjectProps) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
const removeRecord = () => {
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
if (!selectedObject) {
|
||||
return null;
|
||||
}
|
||||
const onConfirmDelete = () => {
|
||||
const decodedSelectedObject = decodeFileName(selectedObject);
|
||||
const recursive = decodedSelectedObject.endsWith("/");
|
||||
api
|
||||
.invoke(
|
||||
"DELETE",
|
||||
`/api/v1/buckets/${selectedBucket}/objects?path=${selectedObject}&recursive=${recursive}`
|
||||
)
|
||||
.then(() => {
|
||||
setDeleteLoading(false);
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
invokeDeleteApi(
|
||||
"DELETE",
|
||||
`/api/v1/buckets/${selectedBucket}/objects?path=${selectedObject}&recursive=${recursive}`
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<ConfirmDialog
|
||||
title={`Delete Object`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete:{" "}
|
||||
<b>{decodeFileName(selectedObject)}</b>?{" "}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
removeRecord();
|
||||
}}
|
||||
color="secondary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ import SearchBox from "../../../../Common/SearchBox";
|
||||
|
||||
import withSuspense from "../../../../Common/Components/withSuspense";
|
||||
import { displayName } from "./utils";
|
||||
import UploadFolderIcon from "../../../../../../icons/UploadFolderIcon";
|
||||
import { UploadFolderIcon, DownloadIcon } from "../../../../../../icons";
|
||||
|
||||
const AddFolderIcon = React.lazy(
|
||||
() => import("../../../../../../icons/AddFolderIcon")
|
||||
@@ -490,7 +490,6 @@ const ListObjects = ({
|
||||
? decodedPath
|
||||
: decodedPath + "/";
|
||||
}
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
@@ -531,10 +530,23 @@ const ListObjects = ({
|
||||
setFileModeEnabled(false);
|
||||
setLoading(false);
|
||||
} else {
|
||||
// This is an empty folder.
|
||||
// This code prevents the program from opening a file when a substring of that file is entered as a new folder.
|
||||
// Previously, if there was a file test1.txt and the folder test was created with the same prefix, the program
|
||||
// would open test1.txt instead
|
||||
let found = false;
|
||||
let pathPrefixChopped = pathPrefix.slice(
|
||||
0,
|
||||
pathPrefix.length - 1
|
||||
);
|
||||
for (let i = 0; i < res.objects.length; i++) {
|
||||
if (res.objects[i].name === pathPrefixChopped) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (
|
||||
res.objects.length === 1 &&
|
||||
res.objects[0].name.endsWith("/")
|
||||
(res.objects.length === 1 &&
|
||||
res.objects[0].name.endsWith("/")) ||
|
||||
!found
|
||||
) {
|
||||
setFileModeEnabled(false);
|
||||
} else {
|
||||
@@ -661,7 +673,7 @@ const ListObjects = ({
|
||||
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
|
||||
}
|
||||
|
||||
const identity = btoa(
|
||||
const identity = encodeFileName(
|
||||
`${bucketName}-${encodedPath}-${new Date().getTime()}-${Math.random()}`
|
||||
);
|
||||
|
||||
@@ -753,8 +765,8 @@ const ListObjects = ({
|
||||
return state ? "Yes" : "No";
|
||||
};
|
||||
|
||||
const downloadObject = (object: BucketObject) => {
|
||||
const identityDownload = btoa(
|
||||
const downloadObject = (object: BucketObject | RewindObject) => {
|
||||
const identityDownload = encodeFileName(
|
||||
`${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}`
|
||||
);
|
||||
|
||||
@@ -990,6 +1002,25 @@ const ListObjects = ({
|
||||
setSelectedObjects(elements);
|
||||
};
|
||||
|
||||
const downloadSelected = () => {
|
||||
if (selectedObjects.length !== 0) {
|
||||
let itemsToDownload: BucketObject[] | RewindObject[] = [];
|
||||
|
||||
const filterFunction = (currValue: BucketObject | RewindObject) =>
|
||||
selectedObjects.includes(currValue.name);
|
||||
|
||||
if (rewindEnabled) {
|
||||
itemsToDownload = rewind.filter(filterFunction);
|
||||
} else {
|
||||
itemsToDownload = filteredRecords.filter(filterFunction);
|
||||
}
|
||||
|
||||
itemsToDownload.forEach((filteredItem) => {
|
||||
downloadObject(filteredItem);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{shareFileModalOpen && selectedPreview && (
|
||||
@@ -1026,6 +1057,7 @@ const ListObjects = ({
|
||||
bucketName={bucketName}
|
||||
folderName={internalPaths}
|
||||
onClose={closeAddFolderModal}
|
||||
existingFiles={records}
|
||||
/>
|
||||
)}
|
||||
{rewindSelect && (
|
||||
@@ -1175,22 +1207,33 @@ const ListObjects = ({
|
||||
placeholder="Search Objects"
|
||||
/>
|
||||
</SecureComponent>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_DELETE_OBJECT]}
|
||||
resource={bucketName}
|
||||
>
|
||||
<div>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
endIcon={<DeleteIcon />}
|
||||
onClick={() => {
|
||||
setDeleteMultipleOpen(true);
|
||||
}}
|
||||
endIcon={<DownloadIcon />}
|
||||
onClick={downloadSelected}
|
||||
disabled={selectedObjects.length === 0}
|
||||
>
|
||||
Delete Selected
|
||||
Download Selected
|
||||
</Button>
|
||||
</SecureComponent>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_DELETE_OBJECT]}
|
||||
resource={bucketName}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
endIcon={<DeleteIcon />}
|
||||
onClick={() => {
|
||||
setDeleteMultipleOpen(true);
|
||||
}}
|
||||
disabled={selectedObjects.length === 0}
|
||||
>
|
||||
Delete Selected
|
||||
</Button>
|
||||
</SecureComponent>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
|
||||
@@ -14,27 +14,19 @@
|
||||
// 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 } from "react";
|
||||
import React from "react";
|
||||
import get from "lodash/get";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { setErrorSnackMessage } from "../../../../../../actions";
|
||||
import { AppState } from "../../../../../../store";
|
||||
import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
import api from "../../../../../../common/api";
|
||||
import { encodeFileName } from "../../../../../../common/utils";
|
||||
import useApi from "../../../../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../../../../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
interface IDeleteTagModal {
|
||||
deleteOpen: boolean;
|
||||
@@ -58,7 +50,6 @@ const styles = (theme: Theme) =>
|
||||
marginTop: 0,
|
||||
marginBottom: 32,
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const DeleteTagModal = ({
|
||||
@@ -73,69 +64,51 @@ const DeleteTagModal = ({
|
||||
setErrorSnackMessage,
|
||||
classes,
|
||||
}: IDeleteTagModal) => {
|
||||
const [deleteLoading, setDeleteSending] = useState<boolean>(false);
|
||||
const [tagKey, tagLabel] = selectedTag;
|
||||
|
||||
const removeTagProcess = () => {
|
||||
setDeleteSending(true);
|
||||
const onDelSuccess = () => onCloseAndUpdate(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => onCloseAndUpdate(false);
|
||||
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
if (!selectedTag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
const cleanObject = { ...currentTags };
|
||||
delete cleanObject[tagKey];
|
||||
|
||||
const verID = distributedSetup ? versionId : "null";
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"PUT",
|
||||
`/api/v1/buckets/${bucketName}/objects/tags?prefix=${encodeFileName(
|
||||
selectedObject
|
||||
)}&version_id=${verID}`,
|
||||
{ tags: cleanObject }
|
||||
)
|
||||
.then((res: any) => {
|
||||
setDeleteSending(false);
|
||||
onCloseAndUpdate(true);
|
||||
})
|
||||
.catch((error: ErrorResponseHandler) => {
|
||||
setErrorSnackMessage(error);
|
||||
setDeleteSending(false);
|
||||
});
|
||||
invokeDeleteApi(
|
||||
"PUT",
|
||||
`/api/v1/buckets/${bucketName}/objects/tags?prefix=${encodeFileName(
|
||||
selectedObject
|
||||
)}&version_id=${verID}`,
|
||||
{ tags: cleanObject }
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
onCloseAndUpdate(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Tag</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<ConfirmDialog
|
||||
title={`Delete Tag`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete the tag{" "}
|
||||
<b className={classes.wrapText}>
|
||||
{tagKey} : {tagLabel}
|
||||
</b>{" "}
|
||||
from {selectedObject}?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onCloseAndUpdate(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={removeTagProcess} color="secondary" autoFocus>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -391,7 +391,7 @@ const ObjectDetails = ({
|
||||
};
|
||||
|
||||
const downloadObject = (object: IFileInfo) => {
|
||||
const identityDownload = btoa(
|
||||
const identityDownload = encodeFileName(
|
||||
`${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}`
|
||||
);
|
||||
|
||||
|
||||
@@ -94,19 +94,55 @@ const PreviewFile = ({
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
<div className={`${loading ? classes.iframeHidden : ""} iframeBase`}>
|
||||
<iframe
|
||||
src={path}
|
||||
title="File Preview"
|
||||
allowTransparency
|
||||
className={`${classes.iframeContainer} ${
|
||||
isFullscreen ? "fullHeight" : objectType
|
||||
}`}
|
||||
onLoad={iframeLoaded}
|
||||
{objectType === "video" && (
|
||||
<video
|
||||
style={{ width: "100%", height: "auto" }}
|
||||
autoPlay={true}
|
||||
controls={true}
|
||||
muted={false}
|
||||
playsInline={true}
|
||||
onPlay={iframeLoaded}
|
||||
>
|
||||
File couldn't be loaded. Please try Download instead
|
||||
</iframe>
|
||||
</div>
|
||||
<source src={path} type="video/mp4" />
|
||||
</video>
|
||||
)}
|
||||
{objectType === "audio" && (
|
||||
<audio
|
||||
style={{ width: "100%", height: "auto" }}
|
||||
autoPlay={true}
|
||||
controls={true}
|
||||
muted={false}
|
||||
playsInline={true}
|
||||
onPlay={iframeLoaded}
|
||||
>
|
||||
<source src={path} type="audio/mpeg" />
|
||||
</audio>
|
||||
)}
|
||||
{objectType === "image" && (
|
||||
<img
|
||||
style={{ width: "100%", height: "auto" }}
|
||||
src={path}
|
||||
alt={"preview"}
|
||||
onLoad={iframeLoaded}
|
||||
/>
|
||||
)}
|
||||
{objectType !== "video" &&
|
||||
objectType !== "audio" &&
|
||||
objectType !== "image" && (
|
||||
<div className={`${loading ? classes.iframeHidden : ""} iframeBase`}>
|
||||
<iframe
|
||||
src={path}
|
||||
title="File Preview"
|
||||
allowTransparency
|
||||
className={`${classes.iframeContainer} ${
|
||||
isFullscreen ? "fullHeight" : objectType
|
||||
}`}
|
||||
onLoad={iframeLoaded}
|
||||
>
|
||||
File couldn't be loaded. Please try Download instead
|
||||
</iframe>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
32
portal-ui/src/screens/Console/Common/Hooks/useApi.tsx
Normal file
32
portal-ui/src/screens/Console/Common/Hooks/useApi.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useState } from "react";
|
||||
import api from "../../../../common/api";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
|
||||
type NoReturnFunction = (param?: any) => void;
|
||||
type ApiMethodToInvoke = (method: string, url: string, data?: any) => void;
|
||||
type IsApiInProgress = boolean;
|
||||
|
||||
const useApi = (
|
||||
onSuccess: NoReturnFunction,
|
||||
onError: NoReturnFunction
|
||||
): [IsApiInProgress, ApiMethodToInvoke] => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const callApi = (method: string, url: string, data?: any) => {
|
||||
setIsLoading(true);
|
||||
api
|
||||
.invoke(method, url, data)
|
||||
.then((res: any) => {
|
||||
setIsLoading(false);
|
||||
onSuccess(res);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setIsLoading(false);
|
||||
onError(err);
|
||||
});
|
||||
};
|
||||
|
||||
return [isLoading, callApi];
|
||||
};
|
||||
|
||||
export default useApi;
|
||||
@@ -41,7 +41,6 @@ import {
|
||||
CreateIcon,
|
||||
DashboardIcon,
|
||||
DeleteIcon,
|
||||
DiagnosticIcon,
|
||||
DiagnosticsIcon,
|
||||
DocumentationIcon,
|
||||
DownloadIcon,
|
||||
@@ -225,10 +224,6 @@ const IconsScreen = ({ classes }: IIconsScreenSimple) => {
|
||||
<DeleteIcon /> <br />
|
||||
DeleteIcon
|
||||
</Grid>
|
||||
<Grid item xs={3} sm={2} md={1}>
|
||||
<DiagnosticIcon /> <br />
|
||||
DiagnosticIcon
|
||||
</Grid>
|
||||
<Grid item xs={3} sm={2} md={1}>
|
||||
<DiagnosticsIcon /> <br />
|
||||
DiagnosticsIcon
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Button,
|
||||
ButtonProps,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
} from "@mui/material";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { deleteDialogStyles } from "../FormComponents/common/styleLibrary";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...deleteDialogStyles,
|
||||
});
|
||||
|
||||
type ConfirmDialogProps = {
|
||||
isOpen?: boolean;
|
||||
onClose: () => void;
|
||||
onCancel?: () => void;
|
||||
onConfirm: () => void;
|
||||
classes?: any;
|
||||
title: string;
|
||||
isLoading?: boolean;
|
||||
confirmationContent: React.ReactNode | React.ReactNode[];
|
||||
cancelText?: string;
|
||||
confirmText?: string;
|
||||
confirmButtonProps?: Partial<ButtonProps>;
|
||||
cancelButtonProps?: Partial<ButtonProps>;
|
||||
};
|
||||
|
||||
const ConfirmDialog = ({
|
||||
isOpen = false,
|
||||
onClose,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
classes = {},
|
||||
title = "",
|
||||
isLoading,
|
||||
confirmationContent,
|
||||
cancelText = "Cancel",
|
||||
confirmText = "Confirm",
|
||||
confirmButtonProps = {},
|
||||
cancelButtonProps = {},
|
||||
}: ConfirmDialogProps) => {
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
classes={classes}
|
||||
onClose={(event, reason) => {
|
||||
if (reason !== "backdropClick") {
|
||||
onClose(); // close on Esc but not on click outside
|
||||
}
|
||||
}}
|
||||
className={classes.root}
|
||||
onBackdropClick={() => {
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<DialogTitle className={classes.title}>
|
||||
<div className={classes.titleText}>{title}</div>
|
||||
<div className={classes.closeContainer}>
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
className={classes.closeButton}
|
||||
onClick={onClose}
|
||||
disableRipple
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent className={classes.content}>
|
||||
{confirmationContent}
|
||||
</DialogContent>
|
||||
<DialogActions className={classes.actions}>
|
||||
<Button
|
||||
className={classes.cancelButton}
|
||||
onClick={onCancel || onClose}
|
||||
disabled={isLoading}
|
||||
type="button"
|
||||
{...cancelButtonProps}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
className={classes.confirmButton}
|
||||
type="button"
|
||||
onClick={onConfirm}
|
||||
loading={isLoading}
|
||||
disabled={isLoading}
|
||||
{...confirmButtonProps}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
loadingPosition="start"
|
||||
startIcon={null}
|
||||
autoFocus
|
||||
>
|
||||
{confirmText}
|
||||
</LoadingButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ConfirmDialog);
|
||||
@@ -270,6 +270,9 @@ const Console = ({
|
||||
const allowedPages = !session
|
||||
? []
|
||||
: session.pages.reduce((result: any, item: any, index: any) => {
|
||||
if (item.startsWith("/tools")) {
|
||||
result["/tools"] = true;
|
||||
}
|
||||
result[item] = true;
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
@@ -133,6 +133,31 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.dashboardBG} />
|
||||
{usage?.prometheusNotReady && (
|
||||
<Grid
|
||||
container
|
||||
justifyContent={"center"}
|
||||
alignContent={"center"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Grid item xs={8}>
|
||||
<HelpBox
|
||||
iconComponent={<PrometheusIcon />}
|
||||
title={"We can't retrieve advanced metrics at this time"}
|
||||
help={
|
||||
<Fragment>
|
||||
MinIO Dashboard will display basic metrics as we couldn't
|
||||
connect to Prometheus successfully.
|
||||
<br /> <br />
|
||||
Please try again in a few minutes. If the problem persists,
|
||||
you can review your configuration and confirm that Prometheus
|
||||
server is up and running.
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} className={classes.generalStatusTitle}>
|
||||
General Status
|
||||
@@ -241,45 +266,47 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => {
|
||||
</TabPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
justifyContent={"center"}
|
||||
alignContent={"center"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Grid item xs={8}>
|
||||
<HelpBox
|
||||
iconComponent={<PrometheusIcon />}
|
||||
title={"Monitoring"}
|
||||
help={
|
||||
<Fragment>
|
||||
The MinIO Dashboard is displaying basic metrics only due to
|
||||
missing the{" "}
|
||||
<a
|
||||
href="https://docs.min.io/minio/baremetal/console/minio-console.html?ref=con#configuration"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
necessary settings
|
||||
</a>{" "}
|
||||
for displaying extended metrics.
|
||||
<br />
|
||||
<br />
|
||||
See{" "}
|
||||
<a
|
||||
href="https://docs.min.io/minio/baremetal/monitoring/metrics-alerts/collect-minio-metrics-using-prometheus.html?ref=con#minio-metrics-collect-using-prometheus"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Collect MinIO Metrics Using Prometheus
|
||||
</a>{" "}
|
||||
for a complete tutorial on scraping and visualizing MinIO
|
||||
metrics with Prometheus.
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
{!usage?.prometheusNotReady && (
|
||||
<Grid
|
||||
container
|
||||
justifyContent={"center"}
|
||||
alignContent={"center"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Grid item xs={8}>
|
||||
<HelpBox
|
||||
iconComponent={<PrometheusIcon />}
|
||||
title={"Monitoring"}
|
||||
help={
|
||||
<Fragment>
|
||||
The MinIO Dashboard is displaying basic metrics only due to
|
||||
missing the{" "}
|
||||
<a
|
||||
href="https://docs.min.io/minio/baremetal/console/minio-console.html?ref=con#configuration"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
necessary settings
|
||||
</a>{" "}
|
||||
for displaying extended metrics.
|
||||
<br />
|
||||
<br />
|
||||
See{" "}
|
||||
<a
|
||||
href="https://docs.min.io/minio/baremetal/monitoring/metrics-alerts/collect-minio-metrics-using-prometheus.html?ref=con#minio-metrics-collect-using-prometheus"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Collect MinIO Metrics Using Prometheus
|
||||
</a>{" "}
|
||||
for a complete tutorial on scraping and visualizing MinIO
|
||||
metrics with Prometheus.
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -154,7 +154,12 @@ const LinearGraphWidget = ({
|
||||
if (key === "name") {
|
||||
continue;
|
||||
}
|
||||
const val = parseInt(dp[key]);
|
||||
let val = parseInt(dp[key]);
|
||||
|
||||
if (isNaN(val)) {
|
||||
val = 0;
|
||||
}
|
||||
|
||||
if (maxVal < val) {
|
||||
maxVal = val;
|
||||
}
|
||||
|
||||
@@ -111,6 +111,18 @@ const SingleRepWidget = ({
|
||||
}, [loading, panelItem, timeEnd, timeStart, displayErrorMessage, apiPrefix]);
|
||||
const gradientID = `colorGradient-${title.split(" ").join("-")}`;
|
||||
|
||||
let repNumber = "";
|
||||
|
||||
if (result) {
|
||||
const resultRep = parseInt(result.innerLabel || "0");
|
||||
|
||||
if (!isNaN(resultRep)) {
|
||||
repNumber = representationNumber(resultRep);
|
||||
} else {
|
||||
repNumber = "0";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.singleValueContainer}>
|
||||
<div className={classes.titleContainer}>{title}</div>
|
||||
@@ -150,7 +162,7 @@ const SingleRepWidget = ({
|
||||
fill={"#07193E"}
|
||||
>
|
||||
{result
|
||||
? representationNumber(parseInt(result.innerLabel || "0"))
|
||||
? repNumber
|
||||
: ""}
|
||||
</text>
|
||||
</AreaChart>
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface Usage {
|
||||
usage: number;
|
||||
buckets: number;
|
||||
objects: number;
|
||||
prometheusNotReady?: boolean;
|
||||
widgets?: any;
|
||||
servers: ServerInfo[];
|
||||
}
|
||||
|
||||
@@ -14,38 +14,19 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import api from "../../../common/api";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { setErrorSnackMessage } from "../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { deleteDialogStyles } from "../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...deleteDialogStyles,
|
||||
});
|
||||
import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
|
||||
interface IDeleteGroup {
|
||||
selectedGroup: string;
|
||||
deleteOpen: boolean;
|
||||
closeDeleteModalAndRefresh: any;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const DeleteGroup = ({
|
||||
@@ -53,94 +34,36 @@ const DeleteGroup = ({
|
||||
deleteOpen,
|
||||
closeDeleteModalAndRefresh,
|
||||
setErrorSnackMessage,
|
||||
classes,
|
||||
}: IDeleteGroup) => {
|
||||
const [isDeleting, setDeleteLoading] = useState<boolean>(false);
|
||||
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDeleting) {
|
||||
const removeRecord = () => {
|
||||
if (!selectedGroup) {
|
||||
return;
|
||||
}
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/group?name=${encodeURI(selectedGroup)}`)
|
||||
.then(() => {
|
||||
setDeleteLoading(false);
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
};
|
||||
removeRecord();
|
||||
}
|
||||
}, [
|
||||
isDeleting,
|
||||
selectedGroup,
|
||||
closeDeleteModalAndRefresh,
|
||||
setErrorSnackMessage,
|
||||
]);
|
||||
|
||||
const closeNoAction = () => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
if (!selectedGroup) {
|
||||
return null;
|
||||
}
|
||||
const onDeleteGroup = () => {
|
||||
invokeDeleteApi("DELETE", `/api/v1/group?name=${encodeURI(selectedGroup)}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={closeNoAction}
|
||||
classes={classes}
|
||||
className={classes.root}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title" className={classes.title}>
|
||||
<div className={classes.titleText}>Delete Group</div>
|
||||
<div className={classes.closeContainer}>
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
className={classes.closeButton}
|
||||
onClick={closeNoAction}
|
||||
disableRipple
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{isDeleting && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete group <br />
|
||||
<ConfirmDialog
|
||||
title={`Delete Group`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onDeleteGroup}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete group
|
||||
<br />
|
||||
<b>{selectedGroup}</b>?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
onClick={closeNoAction}
|
||||
color="primary"
|
||||
disabled={isDeleting}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
setDeleteLoading(true);
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -150,4 +73,4 @@ const mapDispatchToProps = {
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
|
||||
export default withStyles(styles)(connector(DeleteGroup));
|
||||
export default connector(DeleteGroup);
|
||||
|
||||
@@ -42,14 +42,12 @@ import api from "../../../common/api";
|
||||
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import LogoutIcon from "../../../icons/LogoutIcon";
|
||||
import { resetSession } from "../actions";
|
||||
|
||||
const drawerWidth = 245;
|
||||
|
||||
const BucketsIcon = React.lazy(() => import("../../../icons/BucketsIcon"));
|
||||
const DashboardIcon = React.lazy(() => import("../../../icons/DashboardIcon"));
|
||||
const DiagnosticsIcon = React.lazy(
|
||||
() => import("../../../icons/DiagnosticsIcon")
|
||||
);
|
||||
const GroupsIcon = React.lazy(() => import("../../../icons/GroupsIcon"));
|
||||
const IAMPoliciesIcon = React.lazy(
|
||||
() => import("../../../icons/IAMPoliciesIcon")
|
||||
@@ -61,7 +59,6 @@ const UsersIcon = React.lazy(() => import("../../../icons/UsersIcon"));
|
||||
const VersionIcon = React.lazy(() => import("../../../icons/VersionIcon"));
|
||||
const LicenseIcon = React.lazy(() => import("../../../icons/LicenseIcon"));
|
||||
|
||||
const HealIcon = React.lazy(() => import("../../../icons/HealIcon"));
|
||||
const AccountIcon = React.lazy(() => import("../../../icons/AccountIcon"));
|
||||
const DocumentationIcon = React.lazy(
|
||||
() => import("../../../icons/DocumentationIcon")
|
||||
@@ -290,6 +287,7 @@ interface IMenuProps {
|
||||
distributedSetup: boolean;
|
||||
sidebarOpen: boolean;
|
||||
setMenuOpen: typeof setMenuOpen;
|
||||
resetSession: typeof resetSession;
|
||||
}
|
||||
|
||||
const Menu = ({
|
||||
@@ -300,13 +298,14 @@ const Menu = ({
|
||||
distributedSetup,
|
||||
sidebarOpen,
|
||||
setMenuOpen,
|
||||
resetSession,
|
||||
}: IMenuProps) => {
|
||||
const logout = () => {
|
||||
const deleteSession = () => {
|
||||
clearSession();
|
||||
userLoggedIn(false);
|
||||
localStorage.setItem("userLoggedIn", "");
|
||||
|
||||
resetSession();
|
||||
history.push("/login");
|
||||
};
|
||||
api
|
||||
@@ -402,23 +401,6 @@ const Menu = ({
|
||||
name: "Tools",
|
||||
icon: ToolsIcon,
|
||||
},
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/heal",
|
||||
name: "Heal",
|
||||
icon: HealIcon,
|
||||
fsHidden: distributedSetup,
|
||||
},
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/health-info",
|
||||
name: "Diagnostic",
|
||||
icon: DiagnosticsIcon,
|
||||
},
|
||||
{
|
||||
group: "Operator",
|
||||
type: "item",
|
||||
@@ -438,6 +420,9 @@ const Menu = ({
|
||||
];
|
||||
|
||||
const allowedPages = pages.reduce((result: any, item: any) => {
|
||||
if (item.startsWith("/tools")) {
|
||||
result["/tools"] = true;
|
||||
}
|
||||
result[item] = true;
|
||||
return result;
|
||||
}, {});
|
||||
@@ -643,6 +628,10 @@ const mapState = (state: AppState) => ({
|
||||
distributedSetup: state.system.distributedSetup,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, { userLoggedIn, setMenuOpen });
|
||||
const connector = connect(mapState, {
|
||||
userLoggedIn,
|
||||
setMenuOpen,
|
||||
resetSession,
|
||||
});
|
||||
|
||||
export default connector(withStyles(styles)(Menu));
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
IElementValue,
|
||||
} from "../../Configurations/types";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import ResetConfigurationModal from "./ResetConfigurationModal";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -84,24 +85,31 @@ const EditConfiguration = ({
|
||||
const [saving, setSaving] = useState<boolean>(false);
|
||||
const [loadingConfig, setLoadingConfig] = useState<boolean>(true);
|
||||
const [configValues, setConfigValues] = useState<IElementValue[]>([]);
|
||||
const [resetConfigurationOpen, setResetConfigurationOpen] =
|
||||
useState<boolean>(false);
|
||||
//Effects
|
||||
useEffect(() => {
|
||||
const configId = get(selectedConfiguration, "configuration_id", false);
|
||||
if (loadingConfig) {
|
||||
const configId = get(selectedConfiguration, "configuration_id", false);
|
||||
|
||||
if (configId) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/configs/${configId}`)
|
||||
.then((res) => {
|
||||
const keyVals = get(res, "key_values", []);
|
||||
setConfigValues(keyVals);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoadingConfig(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
if (configId) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/configs/${configId}`)
|
||||
.then((res) => {
|
||||
const keyVals = get(res, "key_values", []);
|
||||
setConfigValues(keyVals);
|
||||
setLoadingConfig(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoadingConfig(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
setLoadingConfig(false);
|
||||
}
|
||||
setLoadingConfig(false);
|
||||
}, [selectedConfiguration, setErrorSnackMessage]);
|
||||
}, [loadingConfig, selectedConfiguration, setErrorSnackMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (saving) {
|
||||
@@ -147,8 +155,24 @@ const EditConfiguration = ({
|
||||
[setValueObj]
|
||||
);
|
||||
|
||||
const continueReset = (restart: boolean) => {
|
||||
setResetConfigurationOpen(false);
|
||||
serverNeedsRestart(restart);
|
||||
if (restart) {
|
||||
setLoadingConfig(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{resetConfigurationOpen && (
|
||||
<ResetConfigurationModal
|
||||
configurationName={selectedConfiguration.configuration_id}
|
||||
closeResetModalAndRefresh={continueReset}
|
||||
resetOpen={resetConfigurationOpen}
|
||||
/>
|
||||
)}
|
||||
|
||||
<form noValidate onSubmit={submitForm} className={className}>
|
||||
<Grid item xs={12} className={classes.settingsFormContainer}>
|
||||
{loadingConfig && (
|
||||
@@ -165,6 +189,17 @@ const EditConfiguration = ({
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.settingsButtonContainer}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
setResetConfigurationOpen(true);
|
||||
}}
|
||||
>
|
||||
Restore Defaults
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
// 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 {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import {
|
||||
deleteDialogStyles,
|
||||
modalBasic,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import api from "../../../../common/api";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
wrapText: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
...modalBasic,
|
||||
...deleteDialogStyles,
|
||||
});
|
||||
|
||||
interface IResetConfiguration {
|
||||
classes: any;
|
||||
configurationName: string;
|
||||
closeResetModalAndRefresh: (reloadConfiguration: boolean) => void;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
resetOpen: boolean;
|
||||
}
|
||||
|
||||
const ResetConfigurationModal = ({
|
||||
classes,
|
||||
configurationName,
|
||||
closeResetModalAndRefresh,
|
||||
setErrorSnackMessage,
|
||||
resetOpen,
|
||||
}: IResetConfiguration) => {
|
||||
const [resetLoading, setResetLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (resetLoading) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/configs/${configurationName}/reset`)
|
||||
.then((res) => {
|
||||
setResetLoading(false);
|
||||
closeResetModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setResetLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
}
|
||||
}, [
|
||||
closeResetModalAndRefresh,
|
||||
configurationName,
|
||||
resetLoading,
|
||||
setErrorSnackMessage,
|
||||
]);
|
||||
|
||||
const resetConfiguration = () => {
|
||||
setResetLoading(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={resetOpen}
|
||||
classes={classes}
|
||||
onClose={() => {
|
||||
closeResetModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title" className={classes.title}>
|
||||
<div className={classes.titleText}>Restore Defaults</div>
|
||||
<div className={classes.closeContainer}>
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
className={classes.closeButton}
|
||||
onClick={() => {
|
||||
closeResetModalAndRefresh(false);
|
||||
}}
|
||||
disableRipple
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
{resetLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to restore these configurations to default
|
||||
values?
|
||||
<br />
|
||||
<b className={classes.wrapText}>
|
||||
Please note that this may cause your system to not be accessible
|
||||
</b>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
closeResetModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={resetLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={resetConfiguration}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
autoFocus
|
||||
disabled={resetLoading}
|
||||
>
|
||||
Yes, Reset Configuration
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setErrorSnackMessage,
|
||||
};
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
|
||||
export default withStyles(styles)(connector(ResetConfigurationModal));
|
||||
@@ -14,123 +14,56 @@
|
||||
// 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 } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import api from "../../../common/api";
|
||||
import { PolicyList } from "./types";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { setErrorSnackMessage } from "../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import { deleteDialogStyles } from "../Common/FormComponents/common/styleLibrary";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
interface IDeletePolicyProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
selectedPolicy: string;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...deleteDialogStyles,
|
||||
});
|
||||
|
||||
const DeletePolicy = ({
|
||||
classes,
|
||||
closeDeleteModalAndRefresh,
|
||||
deleteOpen,
|
||||
selectedPolicy,
|
||||
setErrorSnackMessage,
|
||||
}: IDeletePolicyProps) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||
const removeRecord = () => {
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
setDeleteLoading(true);
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/policy?name=${selectedPolicy}`)
|
||||
.then((res: PolicyList) => {
|
||||
setDeleteLoading(false);
|
||||
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
if (!selectedPolicy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
invokeDeleteApi("DELETE", `/api/v1/policy?name=${selectedPolicy}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
classes={classes}
|
||||
className={classes.root}
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title" className={classes.title}>
|
||||
<div className={classes.titleText}>Delete Policy</div>
|
||||
<div className={classes.closeContainer}>
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
className={classes.closeButton}
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
disableRipple
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<ConfirmDialog
|
||||
title={`Delete Policy`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete policy <br />
|
||||
<b>{selectedPolicy}</b>?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
removeRecord();
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -139,4 +72,4 @@ const mapDispatchToProps = {
|
||||
};
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
export default withStyles(styles)(connector(DeletePolicy));
|
||||
export default connector(DeletePolicy);
|
||||
|
||||
@@ -14,23 +14,16 @@
|
||||
// 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 {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import api from "../../../../common/api";
|
||||
import React, { useState } from "react";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { ITenant } from "./types";
|
||||
import { connect } from "react-redux";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import useApi from "../../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
interface IDeleteTenant {
|
||||
deleteOpen: boolean;
|
||||
@@ -45,29 +38,15 @@ const DeleteTenant = ({
|
||||
closeDeleteModalAndRefresh,
|
||||
setErrorSnackMessage,
|
||||
}: IDeleteTenant) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||
const [retypeTenant, setRetypeTenant] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (deleteLoading) {
|
||||
api
|
||||
.invoke(
|
||||
"DELETE",
|
||||
`/api/v1/namespaces/${selectedTenant.namespace}/tenants/${selectedTenant.name}`
|
||||
)
|
||||
.then(() => {
|
||||
setDeleteLoading(false);
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [deleteLoading]);
|
||||
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
const removeRecord = () => {
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
if (retypeTenant !== selectedTenant.name) {
|
||||
setErrorSnackMessage({
|
||||
errorMessage: "Tenant name is incorrect",
|
||||
@@ -75,22 +54,25 @@ const DeleteTenant = ({
|
||||
});
|
||||
return;
|
||||
}
|
||||
setDeleteLoading(true);
|
||||
invokeDeleteApi(
|
||||
"DELETE",
|
||||
`/api/v1/namespaces/${selectedTenant.namespace}/tenants/${selectedTenant.name}`
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
<ConfirmDialog
|
||||
title={`Delete Tenant`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmButtonProps={{
|
||||
disabled: retypeTenant !== selectedTenant.name || deleteLoading,
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Tenant</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
To continue please type <b>{selectedTenant.name}</b> in the box.
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
@@ -104,27 +86,8 @@ const DeleteTenant = ({
|
||||
/>
|
||||
</Grid>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={removeRecord}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
disabled={retypeTenant !== selectedTenant.name}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import {
|
||||
containerForHeader,
|
||||
tenantDetailsStyles,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
|
||||
interface IConfirmationDialog {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
cancelLabel: string;
|
||||
okLabel: string;
|
||||
onClose: any;
|
||||
cancelOnClick: any;
|
||||
okOnClick: any;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...tenantDetailsStyles,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const ConfirmationDialog = ({
|
||||
classes,
|
||||
open,
|
||||
cancelLabel,
|
||||
okLabel,
|
||||
onClose,
|
||||
cancelOnClick,
|
||||
okOnClick,
|
||||
title,
|
||||
description,
|
||||
}: IConfirmationDialog) => {
|
||||
const [isSending, setIsSending] = useState<boolean>(false);
|
||||
const onClick = () => {
|
||||
setIsSending(true);
|
||||
if (okOnClick !== null) {
|
||||
okOnClick();
|
||||
}
|
||||
setIsSending(false);
|
||||
};
|
||||
if (!open) return null;
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">{title}</DialogTitle>
|
||||
<DialogContent>
|
||||
{isSending && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{description}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={cancelOnClick} color="primary" disabled={isSending}>
|
||||
{cancelLabel || "Cancel"}
|
||||
</Button>
|
||||
<Button onClick={onClick} color="secondary" autoFocus>
|
||||
{okLabel || "Ok"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ConfirmationDialog);
|
||||
@@ -14,23 +14,16 @@
|
||||
// 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 {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import api from "../../../../common/api";
|
||||
import React, { useState } from "react";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { IPodListElement } from "../ListTenants/types";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { connect } from "react-redux";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import useApi from "../../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
interface IDeletePod {
|
||||
deleteOpen: boolean;
|
||||
@@ -45,29 +38,15 @@ const DeletePod = ({
|
||||
closeDeleteModalAndRefresh,
|
||||
setErrorSnackMessage,
|
||||
}: IDeletePod) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||
const [retypePod, setRetypePod] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (deleteLoading) {
|
||||
api
|
||||
.invoke(
|
||||
"DELETE",
|
||||
`/api/v1/namespaces/${selectedPod.namespace}/tenants/${selectedPod.tenant}/pods/${selectedPod.name}`
|
||||
)
|
||||
.then(() => {
|
||||
setDeleteLoading(false);
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [deleteLoading]);
|
||||
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
const removeRecord = () => {
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
if (retypePod !== selectedPod.name) {
|
||||
setErrorSnackMessage({
|
||||
errorMessage: "Tenant name is incorrect",
|
||||
@@ -75,22 +54,25 @@ const DeletePod = ({
|
||||
});
|
||||
return;
|
||||
}
|
||||
setDeleteLoading(true);
|
||||
invokeDeleteApi(
|
||||
"DELETE",
|
||||
`/api/v1/namespaces/${selectedPod.namespace}/tenants/${selectedPod.tenant}/pods/${selectedPod.name}`
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
<ConfirmDialog
|
||||
title={`Delete Pod`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmButtonProps={{
|
||||
disabled: retypePod !== selectedPod.name || deleteLoading,
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Pod</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
To continue please type <b>{selectedPod.name}</b> in the box.
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
@@ -104,27 +86,8 @@ const DeletePod = ({
|
||||
/>
|
||||
</Grid>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={removeRecord}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
disabled={retypePod !== selectedPod.name}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -29,7 +29,12 @@ import Chip from "@mui/material/Chip";
|
||||
import React, { Fragment, useCallback, useEffect, useState } from "react";
|
||||
import Moment from "react-moment";
|
||||
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
import { Button, CircularProgress, Typography } from "@mui/material";
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
DialogContentText,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { KeyPair } from "../ListTenants/utils";
|
||||
import FileSelector from "../../Common/FormComponents/FileSelector/FileSelector";
|
||||
import api from "../../../../common/api";
|
||||
@@ -38,7 +43,7 @@ import { connect } from "react-redux";
|
||||
import { AppState } from "../../../../store";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import { setTenantDetailsLoad } from "../actions";
|
||||
import ConfirmationDialog from "./ConfirmationDialog";
|
||||
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
interface ITenantSecurity {
|
||||
classes: any;
|
||||
@@ -311,15 +316,19 @@ const TenantSecurity = ({
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ConfirmationDialog
|
||||
open={dialogOpen}
|
||||
title="Save and Restart"
|
||||
description="Are you sure you want to save the changes and restart the service?"
|
||||
<ConfirmDialog
|
||||
title={"Save and Restart"}
|
||||
confirmText={"Restart"}
|
||||
cancelText="Cancel"
|
||||
isLoading={isSending}
|
||||
onClose={() => setDialogOpen(false)}
|
||||
cancelOnClick={() => setDialogOpen(false)}
|
||||
okOnClick={updateTenantSecurity}
|
||||
cancelLabel="Cancel"
|
||||
okLabel={"Restart"}
|
||||
isOpen={dialogOpen}
|
||||
onConfirm={updateTenantSecurity}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to save the changes and restart the service?
|
||||
</DialogContentText>
|
||||
}
|
||||
/>
|
||||
{loadingTenant ? (
|
||||
<Paper className={classes.paperContainer}>
|
||||
|
||||
@@ -20,7 +20,6 @@ import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import Grid from "@mui/material/Grid";
|
||||
|
||||
import { configurationElements } from "../utils";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
@@ -29,6 +28,21 @@ import {
|
||||
import PageHeader from "../../Common/PageHeader/PageHeader";
|
||||
import SettingsCard from "../../Common/SettingsCard/SettingsCard";
|
||||
import PageLayout from "../../Common/Layout/PageLayout";
|
||||
import { IElement } from "../types";
|
||||
import {
|
||||
DiagnosticsIcon,
|
||||
HealIcon,
|
||||
LogsIcon,
|
||||
SearchIcon,
|
||||
TraceIcon,
|
||||
WatchIcon,
|
||||
} from "../../../../icons";
|
||||
import { hasPermission } from "../../../../common/SecureComponent/SecureComponent";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_SCOPES,
|
||||
} from "../../../../common/SecureComponent/permissions";
|
||||
import SpeedtestIcon from "../../../../icons/SpeedtestIcon";
|
||||
|
||||
interface IConfigurationOptions {
|
||||
classes: any;
|
||||
@@ -76,6 +90,59 @@ const styles = (theme: Theme) =>
|
||||
});
|
||||
|
||||
const ToolsList = ({ classes }: IConfigurationOptions) => {
|
||||
const configurationElements: IElement[] = [
|
||||
{
|
||||
icon: <LogsIcon />,
|
||||
configuration_id: "logs",
|
||||
configuration_label: "Logs",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_CONSOLE_LOG_ACTION,
|
||||
]),
|
||||
},
|
||||
{
|
||||
icon: <SearchIcon />,
|
||||
configuration_id: "audit-logs",
|
||||
configuration_label: "Audit Logs",
|
||||
},
|
||||
{
|
||||
icon: <WatchIcon />,
|
||||
configuration_id: "watch",
|
||||
configuration_label: "Watch",
|
||||
},
|
||||
{
|
||||
icon: <TraceIcon />,
|
||||
configuration_id: "trace",
|
||||
configuration_label: "Trace",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_TRACE_ACTION,
|
||||
]),
|
||||
},
|
||||
{
|
||||
icon: <HealIcon />,
|
||||
configuration_id: "heal",
|
||||
configuration_label: "Heal",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_HEAL_ACTION,
|
||||
]),
|
||||
},
|
||||
{
|
||||
icon: <DiagnosticsIcon />,
|
||||
configuration_id: "diagnostics",
|
||||
configuration_label: "Diagnostics",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_HEALTH_ACTION,
|
||||
]),
|
||||
},
|
||||
{
|
||||
icon: <SpeedtestIcon />,
|
||||
configuration_id: "speedtest",
|
||||
configuration_label: "Speedtest",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_HEAL_ACTION,
|
||||
]),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageHeader label={"Tools"} />
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// 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 from "react";
|
||||
import { IElement } from "./types";
|
||||
import {
|
||||
DiagnosticsIcon,
|
||||
HealIcon,
|
||||
LogsIcon,
|
||||
SearchIcon,
|
||||
TraceIcon,
|
||||
WatchIcon,
|
||||
} from "../../../icons";
|
||||
import SpeedtestIcon from "../../../icons/SpeedtestIcon";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_SCOPES,
|
||||
} from "../../../common/SecureComponent/permissions";
|
||||
import { hasPermission } from "../../../common/SecureComponent/SecureComponent";
|
||||
|
||||
export const configurationElements: IElement[] = [
|
||||
{
|
||||
icon: <LogsIcon />,
|
||||
configuration_id: "logs",
|
||||
configuration_label: "Logs",
|
||||
},
|
||||
{
|
||||
icon: <SearchIcon />,
|
||||
configuration_id: "audit-logs",
|
||||
configuration_label: "Audit Logs",
|
||||
},
|
||||
{
|
||||
icon: <WatchIcon />,
|
||||
configuration_id: "watch",
|
||||
configuration_label: "Watch",
|
||||
},
|
||||
{
|
||||
icon: <TraceIcon />,
|
||||
configuration_id: "trace",
|
||||
configuration_label: "trace",
|
||||
},
|
||||
{
|
||||
icon: <HealIcon />,
|
||||
configuration_id: "heal",
|
||||
configuration_label: "heal",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_HEAL_ACTION,
|
||||
]),
|
||||
},
|
||||
{
|
||||
icon: <DiagnosticsIcon />,
|
||||
configuration_id: "diagnostics",
|
||||
configuration_label: "Diagnostics",
|
||||
},
|
||||
{
|
||||
icon: <SpeedtestIcon />,
|
||||
configuration_id: "speedtest",
|
||||
configuration_label: "Speedtest",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_HEAL_ACTION,
|
||||
]),
|
||||
},
|
||||
];
|
||||
@@ -14,141 +14,63 @@
|
||||
// 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 } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import api from "../../../common/api";
|
||||
import { User, UsersList } from "./types";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { User } from "./types";
|
||||
import { setErrorSnackMessage } from "../../../actions";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import { deleteDialogStyles } from "../Common/FormComponents/common/styleLibrary";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...deleteDialogStyles,
|
||||
});
|
||||
|
||||
interface IDeleteUserProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
selectedUser: User | null;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const DeleteUser = ({
|
||||
classes,
|
||||
closeDeleteModalAndRefresh,
|
||||
deleteOpen,
|
||||
selectedUser,
|
||||
setErrorSnackMessage,
|
||||
}: IDeleteUserProps) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
const removeRecord = () => {
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
if (selectedUser === null) {
|
||||
return;
|
||||
}
|
||||
setDeleteLoading(true);
|
||||
api
|
||||
.invoke(
|
||||
"DELETE",
|
||||
`/api/v1/user?name=${encodeURI(selectedUser.accessKey)}`,
|
||||
{
|
||||
id: selectedUser.id,
|
||||
}
|
||||
)
|
||||
.then((res: UsersList) => {
|
||||
setDeleteLoading(false);
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
};
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
if (selectedUser === null) {
|
||||
return <div />;
|
||||
if (!selectedUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
classes={classes}
|
||||
className={classes.root}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title" className={classes.title}>
|
||||
<div className={classes.titleText}>Delete User</div>
|
||||
<div className={classes.closeContainer}>
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
className={classes.closeButton}
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(true);
|
||||
}}
|
||||
disableRipple
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
const onConfirmDelete = () => {
|
||||
invokeDeleteApi(
|
||||
"DELETE",
|
||||
`/api/v1/user?name=${encodeURI(selectedUser.accessKey)}`,
|
||||
{
|
||||
id: selectedUser.id,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
return (
|
||||
<ConfirmDialog
|
||||
title={`Delete User`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete user <br />
|
||||
<b>{selectedUser.accessKey}</b>?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
removeRecord();
|
||||
}}
|
||||
type="button"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -158,4 +80,4 @@ const mapDispatchToProps = {
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
|
||||
export default withStyles(styles)(connector(DeleteUser));
|
||||
export default connector(DeleteUser);
|
||||
|
||||
@@ -14,22 +14,14 @@
|
||||
// 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 } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { setErrorSnackMessage } from "../../../actions";
|
||||
import { UsersList } from "./types";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import history from "../../../history";
|
||||
import api from "../../../common/api";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
|
||||
|
||||
interface IDeleteUserProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
@@ -44,73 +36,39 @@ const DeleteUserString = ({
|
||||
userName,
|
||||
setErrorSnackMessage,
|
||||
}: IDeleteUserProps) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||
|
||||
const removeRecord = () => {
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
if (userName === null) {
|
||||
return;
|
||||
}
|
||||
setDeleteLoading(true);
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/user?name=${encodeURI(userName)}`, {
|
||||
id: userName,
|
||||
})
|
||||
.then((res: UsersList) => {
|
||||
setDeleteLoading(false);
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
const onDelSuccess = () => {
|
||||
history.push(`/users/`);
|
||||
};
|
||||
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
if (userName === null) {
|
||||
return <div />;
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
if (!userName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
invokeDeleteApi("DELETE", `/api/v1/user?name=${encodeURI(userName)}`, {
|
||||
id: userName,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete User</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete user <b>{userName}</b>?
|
||||
<ConfirmDialog
|
||||
title={`Delete User`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete user <br />
|
||||
<b>{userName}</b>?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
removeRecord();
|
||||
closeDeleteModalAndRefresh(true);
|
||||
history.push(`/users/`);
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,12 +17,18 @@
|
||||
import { ISessionResponse } from "./types";
|
||||
|
||||
export const SESSION_RESPONSE = "SESSION_RESPONSE";
|
||||
export const RESET_SESSION = "RESET_SESSION";
|
||||
|
||||
interface SessionAction {
|
||||
type: typeof SESSION_RESPONSE;
|
||||
message: ISessionResponse;
|
||||
}
|
||||
export type SessionActionTypes = SessionAction;
|
||||
|
||||
interface ResetSessionAction {
|
||||
type: typeof RESET_SESSION;
|
||||
}
|
||||
|
||||
export type SessionActionTypes = SessionAction | ResetSessionAction;
|
||||
|
||||
export function saveSessionResponse(message: ISessionResponse) {
|
||||
return {
|
||||
@@ -30,3 +36,9 @@ export function saveSessionResponse(message: ISessionResponse) {
|
||||
message: message,
|
||||
};
|
||||
}
|
||||
|
||||
export function resetSession() {
|
||||
return {
|
||||
type: RESET_SESSION,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { ISessionResponse } from "./types";
|
||||
import { SessionActionTypes, SESSION_RESPONSE } from "./actions";
|
||||
import { RESET_SESSION, SESSION_RESPONSE, SessionActionTypes } from "./actions";
|
||||
|
||||
export interface ConsoleState {
|
||||
session: ISessionResponse;
|
||||
@@ -42,6 +42,11 @@ export function consoleReducer(
|
||||
...state,
|
||||
session: action.message,
|
||||
};
|
||||
case RESET_SESSION:
|
||||
return {
|
||||
...state,
|
||||
session: initialState.session,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,15 @@ func registerConfigHandlers(api *operations.ConsoleAPI) {
|
||||
}
|
||||
return admin_api.NewSetConfigOK().WithPayload(resp)
|
||||
})
|
||||
// Reset Configuration
|
||||
api.AdminAPIResetConfigHandler = admin_api.ResetConfigHandlerFunc(func(params admin_api.ResetConfigParams, session *models.Principal) middleware.Responder {
|
||||
resp, err := resetConfigResponse(session, params.Name)
|
||||
if err != nil {
|
||||
return admin_api.NewResetConfigDefault(int(err.Code)).WithPayload(err)
|
||||
}
|
||||
return admin_api.NewResetConfigOK().WithPayload(resp)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// listConfig gets all configurations' names and their descriptions
|
||||
@@ -198,3 +207,29 @@ func setConfigResponse(session *models.Principal, name string, configRequest *mo
|
||||
}
|
||||
return &models.SetConfigResponse{Restart: needsRestart}, nil
|
||||
}
|
||||
|
||||
func resetConfig(ctx context.Context, client MinioAdmin, configName *string) (err error) {
|
||||
err = client.delConfigKV(ctx, *configName)
|
||||
return err
|
||||
}
|
||||
|
||||
// resetConfigResponse implements resetConfig() to be used by handler
|
||||
func resetConfigResponse(session *models.Principal, configName string) (*models.SetConfigResponse, *models.Error) {
|
||||
mAdmin, err := NewMinioAdminClient(session)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
// create a MinIO Admin Client interface implementation
|
||||
// defining the client to be used
|
||||
adminClient := AdminClient{Client: mAdmin}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err = resetConfig(ctx, adminClient, &configName)
|
||||
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
|
||||
return &models.SetConfigResponse{Restart: true}, nil
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ const (
|
||||
var minioHelpConfigKVMock func(subSys, key string, envOnly bool) (madmin.Help, error)
|
||||
var minioGetConfigKVMock func(key string) ([]byte, error)
|
||||
var minioSetConfigKVMock func(kv string) (restart bool, err error)
|
||||
var minioDelConfigKVMock func(name string) (err error)
|
||||
|
||||
// mock function helpConfigKV()
|
||||
func (ac adminClientMock) helpConfigKV(ctx context.Context, subSys, key string, envOnly bool) (madmin.Help, error) {
|
||||
@@ -66,6 +67,10 @@ func (ac adminClientMock) setConfigKV(ctx context.Context, kv string) (restart b
|
||||
return minioSetConfigKVMock(kv)
|
||||
}
|
||||
|
||||
func (ac adminClientMock) delConfigKV(ctx context.Context, name string) (err error) {
|
||||
return minioDelConfigKVMock(name)
|
||||
}
|
||||
|
||||
func TestListConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
adminClient := adminClientMock{}
|
||||
@@ -165,6 +170,34 @@ func TestSetConfig(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestDelConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
adminClient := adminClientMock{}
|
||||
function := "resetConfig()"
|
||||
// mock function response from setConfig()
|
||||
minioDelConfigKVMock = func(name string) (err error) {
|
||||
return nil
|
||||
}
|
||||
configName := "region"
|
||||
|
||||
ctx := context.Background()
|
||||
// Test-1 : resetConfig() resets a config with the config name
|
||||
err := resetConfig(ctx, adminClient, &configName)
|
||||
if err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
|
||||
}
|
||||
|
||||
// Test-2 : resetConfig() returns error, handle properly
|
||||
minioDelConfigKVMock = func(name string) (err error) {
|
||||
return errors.New("error")
|
||||
}
|
||||
|
||||
err = resetConfig(ctx, adminClient, &configName)
|
||||
if assert.Error(err) {
|
||||
assert.Equal("error", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildConfig(t *testing.T) {
|
||||
type args struct {
|
||||
configName *string
|
||||
|
||||
@@ -58,11 +58,12 @@ func registerAdminInfoHandlers(api *operations.ConsoleAPI) {
|
||||
}
|
||||
|
||||
type UsageInfo struct {
|
||||
Buckets int64
|
||||
Objects int64
|
||||
Usage int64
|
||||
DisksUsage int64
|
||||
Servers []*models.ServerProperties
|
||||
Buckets int64
|
||||
Objects int64
|
||||
Usage int64
|
||||
DisksUsage int64
|
||||
Servers []*models.ServerProperties
|
||||
EndpointNotReady bool
|
||||
}
|
||||
|
||||
// GetAdminInfo invokes admin info and returns a parsed `UsageInfo` structure
|
||||
@@ -845,7 +846,12 @@ func getAdminInfoResponse(session *models.Principal, params admin_api.AdminInfoP
|
||||
}
|
||||
|
||||
func getUsageWidgetsForDeployment(prometheusURL string, mAdmin *madmin.AdminClient) (*models.AdminInfoResponse, *models.Error) {
|
||||
if prometheusURL == "" {
|
||||
prometheusNotReady := false
|
||||
|
||||
if prometheusURL != "" && !testPrometheusURL(prometheusURL) {
|
||||
prometheusNotReady = true
|
||||
}
|
||||
if prometheusURL == "" || prometheusNotReady {
|
||||
// create a minioClient interface implementation
|
||||
// defining the client to be used
|
||||
adminClient := AdminClient{Client: mAdmin}
|
||||
@@ -858,10 +864,11 @@ func getUsageWidgetsForDeployment(prometheusURL string, mAdmin *madmin.AdminClie
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
sessionResp := &models.AdminInfoResponse{
|
||||
Buckets: usage.Buckets,
|
||||
Objects: usage.Objects,
|
||||
Usage: usage.Usage,
|
||||
Servers: usage.Servers,
|
||||
Buckets: usage.Buckets,
|
||||
Objects: usage.Objects,
|
||||
Usage: usage.Usage,
|
||||
Servers: usage.Servers,
|
||||
PrometheusNotReady: prometheusNotReady,
|
||||
}
|
||||
return sessionResp, nil
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ type MinioAdmin interface {
|
||||
getConfigKV(ctx context.Context, key string) ([]byte, error)
|
||||
helpConfigKV(ctx context.Context, subSys, key string, envOnly bool) (madmin.Help, error)
|
||||
setConfigKV(ctx context.Context, kv string) (restart bool, err error)
|
||||
delConfigKV(ctx context.Context, kv string) (err error)
|
||||
serviceRestart(ctx context.Context) error
|
||||
serverInfo(ctx context.Context) (madmin.InfoMessage, error)
|
||||
startProfiling(ctx context.Context, profiler madmin.ProfilerType) ([]madmin.StartProfilingResult, error)
|
||||
@@ -233,6 +234,11 @@ func (ac AdminClient) setConfigKV(ctx context.Context, kv string) (restart bool,
|
||||
return ac.Client.SetConfigKV(ctx, kv)
|
||||
}
|
||||
|
||||
// implements madmin.DelConfigKV()
|
||||
func (ac AdminClient) delConfigKV(ctx context.Context, kv string) (err error) {
|
||||
return ac.Client.DelConfigKV(ctx, kv)
|
||||
}
|
||||
|
||||
// implements madmin.ServiceRestart()
|
||||
func (ac AdminClient) serviceRestart(ctx context.Context) (err error) {
|
||||
return ac.Client.ServiceRestart(ctx)
|
||||
|
||||
@@ -377,23 +377,36 @@ func newMinioClient(claims *models.Principal) (*minio.Client, error) {
|
||||
return minioClient, nil
|
||||
}
|
||||
|
||||
// computeObjectURLWithoutEncode returns a MinIO url containing the object filename without encoding
|
||||
func computeObjectURLWithoutEncode(bucketName, prefix string) (string, error) {
|
||||
endpoint := getMinIOServer()
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("the provided endpoint is invalid")
|
||||
}
|
||||
objectURL := fmt.Sprintf("%s:%s", u.Hostname(), u.Port())
|
||||
if strings.TrimSpace(bucketName) != "" {
|
||||
objectURL = path.Join(objectURL, bucketName)
|
||||
}
|
||||
if strings.TrimSpace(prefix) != "" {
|
||||
objectURL = pathJoinFinalSlash(objectURL, prefix)
|
||||
}
|
||||
|
||||
objectURL = fmt.Sprintf("%s://%s", u.Scheme, objectURL)
|
||||
return objectURL, nil
|
||||
}
|
||||
|
||||
// newS3BucketClient creates a new mc S3Client to talk to the server based on a bucket
|
||||
func newS3BucketClient(claims *models.Principal, bucketName string, prefix string) (*mc.S3Client, error) {
|
||||
if claims == nil {
|
||||
return nil, fmt.Errorf("the provided credentials are invalid")
|
||||
}
|
||||
endpoint := getMinIOServer()
|
||||
u, err := url.Parse(endpoint)
|
||||
// It's very important to avoid encoding the prefix since the minio client will encode the path itself
|
||||
objectURL, err := computeObjectURLWithoutEncode(bucketName, prefix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the provided endpoint is invalid")
|
||||
}
|
||||
if strings.TrimSpace(bucketName) != "" {
|
||||
u.Path = path.Join(u.Path, bucketName)
|
||||
}
|
||||
if strings.TrimSpace(prefix) != "" {
|
||||
u.Path = path.Join(u.Path, prefix)
|
||||
}
|
||||
s3Config := newS3Config(u.String(), claims.STSAccessKeyID, claims.STSSecretAccessKey, claims.STSSessionToken, false)
|
||||
s3Config := newS3Config(objectURL, claims.STSAccessKeyID, claims.STSSecretAccessKey, claims.STSSessionToken, false)
|
||||
client, pErr := mc.S3New(s3Config)
|
||||
if pErr != nil {
|
||||
return nil, pErr.Cause
|
||||
@@ -405,6 +418,16 @@ func newS3BucketClient(claims *models.Principal, bucketName string, prefix strin
|
||||
return s3Client, nil
|
||||
}
|
||||
|
||||
// pathJoinFinalSlash - like path.Join() but retains trailing slashSeparator of the last element
|
||||
func pathJoinFinalSlash(elem ...string) string {
|
||||
if len(elem) > 0 {
|
||||
if strings.HasSuffix(elem[len(elem)-1], SlashSeparator) {
|
||||
return path.Join(elem...) + SlashSeparator
|
||||
}
|
||||
}
|
||||
return path.Join(elem...)
|
||||
}
|
||||
|
||||
// newS3Config simply creates a new Config struct using the passed
|
||||
// parameters.
|
||||
func newS3Config(endpoint, accessKey, secretKey, sessionToken string, insecure bool) *mc.Config {
|
||||
|
||||
90
restapi/client_test.go
Normal file
90
restapi/client_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// This file is part of MinIO Orchestrator
|
||||
// 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 restapi
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_computeObjectURLWithoutEncode(t *testing.T) {
|
||||
type args struct {
|
||||
bucketName string
|
||||
prefix string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "http://localhost:9000/bucket-1/小飼弾小飼弾小飼弾.jp",
|
||||
args: args{
|
||||
bucketName: "bucket-1",
|
||||
prefix: "小飼弾小飼弾小飼弾.jpg",
|
||||
},
|
||||
want: "http://localhost:9000/bucket-1/小飼弾小飼弾小飼弾.jpg",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "http://localhost:9000/bucket-1/a a - a a & a a - a a a.jpg",
|
||||
args: args{
|
||||
bucketName: "bucket-1",
|
||||
prefix: "a a - a a & a a - a a a.jpg",
|
||||
},
|
||||
want: "http://localhost:9000/bucket-1/a a - a a & a a - a a a.jpg",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "http://localhost:9000/bucket-1/02%20-%20FLY%20ME%20TO%20THE%20MOON%20.jpg",
|
||||
args: args{
|
||||
bucketName: "bucket-1",
|
||||
prefix: "02%20-%20FLY%20ME%20TO%20THE%20MOON%20.jpg",
|
||||
},
|
||||
want: "http://localhost:9000/bucket-1/02%20-%20FLY%20ME%20TO%20THE%20MOON%20.jpg",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "http://localhost:9000/bucket-1/!@#$%^&*()_+.jpg",
|
||||
args: args{
|
||||
bucketName: "bucket-1",
|
||||
prefix: "!@#$%^&*()_+.jpg",
|
||||
},
|
||||
want: "http://localhost:9000/bucket-1/!@#$%^&*()_+.jpg",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "http://localhost:9000/bucket-1/test/test2/小飼弾小飼弾小飼弾.jpg",
|
||||
args: args{
|
||||
bucketName: "bucket-1",
|
||||
prefix: "test/test2/小飼弾小飼弾小飼弾.jpg",
|
||||
},
|
||||
want: "http://localhost:9000/bucket-1/test/test2/小飼弾小飼弾小飼弾.jpg",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := computeObjectURLWithoutEncode(tt.args.bucketName, tt.args.prefix)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("computeObjectURLWithoutEncode() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("computeObjectURLWithoutEncode() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -54,4 +54,5 @@ const (
|
||||
ConsoleLogQueryURL = "CONSOLE_LOG_QUERY_URL"
|
||||
ConsoleLogQueryAuthToken = "CONSOLE_LOG_QUERY_AUTH_TOKEN"
|
||||
LogSearchQueryAuthToken = "LOGSEARCH_QUERY_AUTH_TOKEN"
|
||||
SlashSeparator = "/"
|
||||
)
|
||||
|
||||
@@ -2099,6 +2099,37 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"/configs/{name}/reset": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"AdminAPI"
|
||||
],
|
||||
"summary": "Configuration reset",
|
||||
"operationId": "ResetConfig",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/setConfigResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Generic error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/group": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -3624,6 +3655,9 @@ func init() {
|
||||
"objects": {
|
||||
"type": "integer"
|
||||
},
|
||||
"prometheusNotReady": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"servers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -7796,6 +7830,37 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"/configs/{name}/reset": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"AdminAPI"
|
||||
],
|
||||
"summary": "Configuration reset",
|
||||
"operationId": "ResetConfig",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/setConfigResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Generic error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/group": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -9441,6 +9506,9 @@ func init() {
|
||||
"objects": {
|
||||
"type": "integer"
|
||||
},
|
||||
"prometheusNotReady": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"servers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
88
restapi/operations/admin_api/reset_config.go
Normal file
88
restapi/operations/admin_api/reset_config.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 admin_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"
|
||||
)
|
||||
|
||||
// ResetConfigHandlerFunc turns a function with the right signature into a reset config handler
|
||||
type ResetConfigHandlerFunc func(ResetConfigParams, *models.Principal) middleware.Responder
|
||||
|
||||
// Handle executing the request and returning a response
|
||||
func (fn ResetConfigHandlerFunc) Handle(params ResetConfigParams, principal *models.Principal) middleware.Responder {
|
||||
return fn(params, principal)
|
||||
}
|
||||
|
||||
// ResetConfigHandler interface for that can handle valid reset config params
|
||||
type ResetConfigHandler interface {
|
||||
Handle(ResetConfigParams, *models.Principal) middleware.Responder
|
||||
}
|
||||
|
||||
// NewResetConfig creates a new http.Handler for the reset config operation
|
||||
func NewResetConfig(ctx *middleware.Context, handler ResetConfigHandler) *ResetConfig {
|
||||
return &ResetConfig{Context: ctx, Handler: handler}
|
||||
}
|
||||
|
||||
/* ResetConfig swagger:route GET /configs/{name}/reset AdminAPI resetConfig
|
||||
|
||||
Configuration reset
|
||||
|
||||
*/
|
||||
type ResetConfig struct {
|
||||
Context *middleware.Context
|
||||
Handler ResetConfigHandler
|
||||
}
|
||||
|
||||
func (o *ResetConfig) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
route, rCtx, _ := o.Context.RouteInfo(r)
|
||||
if rCtx != nil {
|
||||
*r = *rCtx
|
||||
}
|
||||
var Params = NewResetConfigParams()
|
||||
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)
|
||||
|
||||
}
|
||||
88
restapi/operations/admin_api/reset_config_parameters.go
Normal file
88
restapi/operations/admin_api/reset_config_parameters.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 admin_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/middleware"
|
||||
"github.com/go-openapi/strfmt"
|
||||
)
|
||||
|
||||
// NewResetConfigParams creates a new ResetConfigParams object
|
||||
//
|
||||
// There are no default values defined in the spec.
|
||||
func NewResetConfigParams() ResetConfigParams {
|
||||
|
||||
return ResetConfigParams{}
|
||||
}
|
||||
|
||||
// ResetConfigParams contains all the bound params for the reset config operation
|
||||
// typically these are obtained from a http.Request
|
||||
//
|
||||
// swagger:parameters ResetConfig
|
||||
type ResetConfigParams struct {
|
||||
|
||||
// HTTP Request Object
|
||||
HTTPRequest *http.Request `json:"-"`
|
||||
|
||||
/*
|
||||
Required: true
|
||||
In: path
|
||||
*/
|
||||
Name 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 NewResetConfigParams() beforehand.
|
||||
func (o *ResetConfigParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
|
||||
var res []error
|
||||
|
||||
o.HTTPRequest = r
|
||||
|
||||
rName, rhkName, _ := route.Params.GetOK("name")
|
||||
if err := o.bindName(rName, rhkName, route.Formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindName binds and validates parameter Name from path.
|
||||
func (o *ResetConfigParams) bindName(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.Name = raw
|
||||
|
||||
return nil
|
||||
}
|
||||
133
restapi/operations/admin_api/reset_config_responses.go
Normal file
133
restapi/operations/admin_api/reset_config_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 admin_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"
|
||||
)
|
||||
|
||||
// ResetConfigOKCode is the HTTP code returned for type ResetConfigOK
|
||||
const ResetConfigOKCode int = 200
|
||||
|
||||
/*ResetConfigOK A successful response.
|
||||
|
||||
swagger:response resetConfigOK
|
||||
*/
|
||||
type ResetConfigOK struct {
|
||||
|
||||
/*
|
||||
In: Body
|
||||
*/
|
||||
Payload *models.SetConfigResponse `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
// NewResetConfigOK creates ResetConfigOK with default headers values
|
||||
func NewResetConfigOK() *ResetConfigOK {
|
||||
|
||||
return &ResetConfigOK{}
|
||||
}
|
||||
|
||||
// WithPayload adds the payload to the reset config o k response
|
||||
func (o *ResetConfigOK) WithPayload(payload *models.SetConfigResponse) *ResetConfigOK {
|
||||
o.Payload = payload
|
||||
return o
|
||||
}
|
||||
|
||||
// SetPayload sets the payload to the reset config o k response
|
||||
func (o *ResetConfigOK) SetPayload(payload *models.SetConfigResponse) {
|
||||
o.Payload = payload
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *ResetConfigOK) 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*ResetConfigDefault Generic error response.
|
||||
|
||||
swagger:response resetConfigDefault
|
||||
*/
|
||||
type ResetConfigDefault struct {
|
||||
_statusCode int
|
||||
|
||||
/*
|
||||
In: Body
|
||||
*/
|
||||
Payload *models.Error `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
// NewResetConfigDefault creates ResetConfigDefault with default headers values
|
||||
func NewResetConfigDefault(code int) *ResetConfigDefault {
|
||||
if code <= 0 {
|
||||
code = 500
|
||||
}
|
||||
|
||||
return &ResetConfigDefault{
|
||||
_statusCode: code,
|
||||
}
|
||||
}
|
||||
|
||||
// WithStatusCode adds the status to the reset config default response
|
||||
func (o *ResetConfigDefault) WithStatusCode(code int) *ResetConfigDefault {
|
||||
o._statusCode = code
|
||||
return o
|
||||
}
|
||||
|
||||
// SetStatusCode sets the status to the reset config default response
|
||||
func (o *ResetConfigDefault) SetStatusCode(code int) {
|
||||
o._statusCode = code
|
||||
}
|
||||
|
||||
// WithPayload adds the payload to the reset config default response
|
||||
func (o *ResetConfigDefault) WithPayload(payload *models.Error) *ResetConfigDefault {
|
||||
o.Payload = payload
|
||||
return o
|
||||
}
|
||||
|
||||
// SetPayload sets the payload to the reset config default response
|
||||
func (o *ResetConfigDefault) SetPayload(payload *models.Error) {
|
||||
o.Payload = payload
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *ResetConfigDefault) 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
|
||||
}
|
||||
}
|
||||
}
|
||||
116
restapi/operations/admin_api/reset_config_urlbuilder.go
Normal file
116
restapi/operations/admin_api/reset_config_urlbuilder.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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 admin_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"
|
||||
)
|
||||
|
||||
// ResetConfigURL generates an URL for the reset config operation
|
||||
type ResetConfigURL struct {
|
||||
Name 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 *ResetConfigURL) WithBasePath(bp string) *ResetConfigURL {
|
||||
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 *ResetConfigURL) SetBasePath(bp string) {
|
||||
o._basePath = bp
|
||||
}
|
||||
|
||||
// Build a url path and query string
|
||||
func (o *ResetConfigURL) Build() (*url.URL, error) {
|
||||
var _result url.URL
|
||||
|
||||
var _path = "/configs/{name}/reset"
|
||||
|
||||
name := o.Name
|
||||
if name != "" {
|
||||
_path = strings.Replace(_path, "{name}", name, -1)
|
||||
} else {
|
||||
return nil, errors.New("name is required on ResetConfigURL")
|
||||
}
|
||||
|
||||
_basePath := o._basePath
|
||||
if _basePath == "" {
|
||||
_basePath = "/api/v1"
|
||||
}
|
||||
_result.Path = golangswaggerpaths.Join(_basePath, _path)
|
||||
|
||||
return &_result, nil
|
||||
}
|
||||
|
||||
// Must is a helper function to panic when the url builder returns an error
|
||||
func (o *ResetConfigURL) 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 *ResetConfigURL) String() string {
|
||||
return o.Must(o.Build()).String()
|
||||
}
|
||||
|
||||
// BuildFull builds a full url with scheme, host, path and query string
|
||||
func (o *ResetConfigURL) BuildFull(scheme, host string) (*url.URL, error) {
|
||||
if scheme == "" {
|
||||
return nil, errors.New("scheme is required for a full url on ResetConfigURL")
|
||||
}
|
||||
if host == "" {
|
||||
return nil, errors.New("host is required for a full url on ResetConfigURL")
|
||||
}
|
||||
|
||||
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 *ResetConfigURL) StringFull(scheme, host string) string {
|
||||
return o.Must(o.BuildFull(scheme, host)).String()
|
||||
}
|
||||
@@ -311,6 +311,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
|
||||
AdminAPIRemoveUserHandler: admin_api.RemoveUserHandlerFunc(func(params admin_api.RemoveUserParams, principal *models.Principal) middleware.Responder {
|
||||
return middleware.NotImplemented("operation admin_api.RemoveUser has not yet been implemented")
|
||||
}),
|
||||
AdminAPIResetConfigHandler: admin_api.ResetConfigHandlerFunc(func(params admin_api.ResetConfigParams, principal *models.Principal) middleware.Responder {
|
||||
return middleware.NotImplemented("operation admin_api.ResetConfig has not yet been implemented")
|
||||
}),
|
||||
AdminAPIRestartServiceHandler: admin_api.RestartServiceHandlerFunc(func(params admin_api.RestartServiceParams, principal *models.Principal) middleware.Responder {
|
||||
return middleware.NotImplemented("operation admin_api.RestartService has not yet been implemented")
|
||||
}),
|
||||
@@ -581,6 +584,8 @@ type ConsoleAPI struct {
|
||||
AdminAPIRemovePolicyHandler admin_api.RemovePolicyHandler
|
||||
// AdminAPIRemoveUserHandler sets the operation handler for the remove user operation
|
||||
AdminAPIRemoveUserHandler admin_api.RemoveUserHandler
|
||||
// AdminAPIResetConfigHandler sets the operation handler for the reset config operation
|
||||
AdminAPIResetConfigHandler admin_api.ResetConfigHandler
|
||||
// AdminAPIRestartServiceHandler sets the operation handler for the restart service operation
|
||||
AdminAPIRestartServiceHandler admin_api.RestartServiceHandler
|
||||
// UserAPISessionCheckHandler sets the operation handler for the session check operation
|
||||
@@ -948,6 +953,9 @@ func (o *ConsoleAPI) Validate() error {
|
||||
if o.AdminAPIRemoveUserHandler == nil {
|
||||
unregistered = append(unregistered, "admin_api.RemoveUserHandler")
|
||||
}
|
||||
if o.AdminAPIResetConfigHandler == nil {
|
||||
unregistered = append(unregistered, "admin_api.ResetConfigHandler")
|
||||
}
|
||||
if o.AdminAPIRestartServiceHandler == nil {
|
||||
unregistered = append(unregistered, "admin_api.RestartServiceHandler")
|
||||
}
|
||||
@@ -1429,6 +1437,10 @@ func (o *ConsoleAPI) initHandlerCache() {
|
||||
o.handlers["DELETE"] = make(map[string]http.Handler)
|
||||
}
|
||||
o.handlers["DELETE"]["/user"] = admin_api.NewRemoveUser(o.context, o.AdminAPIRemoveUserHandler)
|
||||
if o.handlers["GET"] == nil {
|
||||
o.handlers["GET"] = make(map[string]http.Handler)
|
||||
}
|
||||
o.handlers["GET"]["/configs/{name}/reset"] = admin_api.NewResetConfig(o.context, o.AdminAPIResetConfigHandler)
|
||||
if o.handlers["POST"] == nil {
|
||||
o.handlers["POST"] = make(map[string]http.Handler)
|
||||
}
|
||||
|
||||
@@ -898,9 +898,6 @@ func getBucketRewindResponse(session *models.Principal, params user_api.GetBucke
|
||||
Name: name,
|
||||
}
|
||||
|
||||
cont, _ := json.Marshal(content)
|
||||
fmt.Println(string(cont))
|
||||
|
||||
rewindItems = append(rewindItems, listElement)
|
||||
}
|
||||
|
||||
|
||||
@@ -77,76 +77,35 @@ func registerObjectsHandlers(api *operations.ConsoleAPI) {
|
||||
})
|
||||
// download object
|
||||
api.UserAPIDownloadObjectHandler = user_api.DownloadObjectHandlerFunc(func(params user_api.DownloadObjectParams, session *models.Principal) middleware.Responder {
|
||||
isPreview := *params.Preview
|
||||
resp, err := getDownloadObjectResponse(session, params)
|
||||
isFolder := false
|
||||
|
||||
var prefix string
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
return user_api.NewDownloadObjectDefault(int(400)).WithPayload(prepareError(err))
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
|
||||
folders := strings.Split(prefix, "/")
|
||||
if folders[len(folders)-1] == "" {
|
||||
isFolder = true
|
||||
}
|
||||
var resp middleware.Responder
|
||||
var err *models.Error
|
||||
|
||||
if isFolder {
|
||||
resp, err = getDownloadFolderResponse(session, params)
|
||||
} else {
|
||||
resp, err = getDownloadObjectResponse(session, params)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return user_api.NewDownloadObjectDefault(int(err.Code)).WithPayload(err)
|
||||
}
|
||||
return middleware.ResponderFunc(func(rw http.ResponseWriter, _ runtime.Producer) {
|
||||
defer resp.Close()
|
||||
|
||||
// indicate it's a download / inline content to the browser, and the size of the object
|
||||
var prefixPath string
|
||||
var filename string
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
prefixPath = string(decodedPrefix)
|
||||
}
|
||||
prefixElements := strings.Split(prefixPath, "/")
|
||||
isFolder := false
|
||||
if len(prefixElements) > 0 {
|
||||
if prefixElements[len(prefixElements)-1] == "" {
|
||||
filename = prefixElements[len(prefixElements)-2]
|
||||
isFolder = true
|
||||
} else {
|
||||
filename = prefixElements[len(prefixElements)-1]
|
||||
}
|
||||
}
|
||||
if isPreview {
|
||||
rw.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filename))
|
||||
rw.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||
rw.Header().Set("X-XSS-Protection", "1")
|
||||
|
||||
} else if isFolder {
|
||||
rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", filename))
|
||||
rw.Header().Set("Content-Type", "application/zip")
|
||||
} else {
|
||||
rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
||||
rw.Header().Set("Content-Type", "application/octet-stream")
|
||||
}
|
||||
|
||||
// indicate object size & content type
|
||||
if !isFolder {
|
||||
stat, err := resp.(*minio.Object).Stat()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
rw.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size))
|
||||
|
||||
contentType := stat.ContentType
|
||||
|
||||
if isPreview {
|
||||
// In case content type was uploaded as octet-stream, we double verify content type
|
||||
if stat.ContentType == "application/octet-stream" {
|
||||
contentType = mimedb.TypeByExtension(filepath.Ext(filename))
|
||||
}
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", contentType)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the stream
|
||||
_, err := io.Copy(rw, resp)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
})
|
||||
return resp
|
||||
})
|
||||
// upload object
|
||||
api.UserAPIPostBucketsBucketNameObjectsUploadHandler = user_api.PostBucketsBucketNameObjectsUploadHandlerFunc(func(params user_api.PostBucketsBucketNameObjectsUploadParams, session *models.Principal) middleware.Responder {
|
||||
@@ -315,7 +274,128 @@ func listBucketObjects(ctx context.Context, client MinioClient, bucketName strin
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func getDownloadObjectResponse(session *models.Principal, params user_api.DownloadObjectParams) (io.ReadCloser, *models.Error) {
|
||||
func getDownloadObjectResponse(session *models.Principal, params user_api.DownloadObjectParams) (middleware.Responder, *models.Error) {
|
||||
ctx := context.Background()
|
||||
var prefix string
|
||||
mClient, err := newMinioClient(session)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
|
||||
resp, err := mClient.GetObject(ctx, params.BucketName, prefix, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
|
||||
return middleware.ResponderFunc(func(rw http.ResponseWriter, _ runtime.Producer) {
|
||||
defer resp.Close()
|
||||
|
||||
// indicate object size & content type
|
||||
stat, err := resp.Stat()
|
||||
statOk := false
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
statOk = true
|
||||
}
|
||||
|
||||
isPreview := params.Preview != nil && *params.Preview
|
||||
|
||||
// indicate it's a download / inline content to the browser, and the size of the object
|
||||
var prefixPath string
|
||||
var filename string
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
prefixPath = string(decodedPrefix)
|
||||
}
|
||||
prefixElements := strings.Split(prefixPath, "/")
|
||||
if len(prefixElements) > 0 {
|
||||
if prefixElements[len(prefixElements)-1] == "" {
|
||||
filename = prefixElements[len(prefixElements)-2]
|
||||
} else {
|
||||
filename = prefixElements[len(prefixElements)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// if we are getting a Range Request (video) handle that specially
|
||||
isRange := params.HTTPRequest.Header.Get("Range")
|
||||
if isRange != "" {
|
||||
|
||||
rangeFrom := -1
|
||||
rangeTo := -1
|
||||
|
||||
parts := strings.Split(isRange, "=")
|
||||
if len(parts) > 1 {
|
||||
rangeParts := strings.Split(parts[1], "-")
|
||||
var err error
|
||||
rangeFrom, err = strconv.Atoi(rangeParts[0])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
if rangeParts[1] != "" {
|
||||
rangeTo, err = strconv.Atoi(rangeParts[1])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if handleRangeRequest(rw, isRange, stat, isPreview, filename, resp, params, rangeTo, rangeFrom) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if isPreview {
|
||||
rw.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filename))
|
||||
rw.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||
rw.Header().Set("X-XSS-Protection", "1")
|
||||
|
||||
} else {
|
||||
rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
||||
rw.Header().Set("Content-Type", "application/octet-stream")
|
||||
}
|
||||
|
||||
// indicate object size & content type
|
||||
|
||||
if statOk {
|
||||
rw.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size))
|
||||
|
||||
contentType := stat.ContentType
|
||||
|
||||
if isPreview {
|
||||
// In case content type was uploaded as octet-stream, we double verify content type
|
||||
if stat.ContentType == "application/octet-stream" {
|
||||
contentType = mimedb.TypeByExtension(filepath.Ext(filename))
|
||||
}
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", contentType)
|
||||
}
|
||||
|
||||
// Copy the stream
|
||||
_, err = io.Copy(rw, resp)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
func getDownloadFolderResponse(session *models.Principal, params user_api.DownloadObjectParams) (middleware.Responder, *models.Error) {
|
||||
ctx := context.Background()
|
||||
var prefix string
|
||||
mClient, err := newMinioClient(session)
|
||||
@@ -327,49 +407,73 @@ func getDownloadObjectResponse(session *models.Principal, params user_api.Downlo
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
isFolder := false
|
||||
|
||||
folders := strings.Split(prefix, "/")
|
||||
if folders[len(folders)-1] == "" {
|
||||
isFolder = true
|
||||
}
|
||||
if isFolder {
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
minioClient := minioClient{client: mClient}
|
||||
objects, err := listBucketObjects(ctx, minioClient, params.BucketName, prefix, true, false, false)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
w := new(bytes.Buffer)
|
||||
zipw := zip.NewWriter(w)
|
||||
var folder string
|
||||
if len(folders) > 1 {
|
||||
folder = folders[len(folders)-2]
|
||||
}
|
||||
for i := 0; i < len(objects); i++ {
|
||||
name := folder + objects[i].Name[len(prefix)-1:]
|
||||
object, err := mClient.GetObject(ctx, params.BucketName, objects[i].Name, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
f, err := zipw.Create(name)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(object)
|
||||
f.Write(buf.Bytes())
|
||||
}
|
||||
zipw.Close()
|
||||
zipfile := io.NopCloser(bytes.NewReader(w.Bytes()))
|
||||
return zipfile, nil
|
||||
}
|
||||
object, err := mClient.GetObject(ctx, params.BucketName, prefix, minio.GetObjectOptions{})
|
||||
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
return object, nil
|
||||
minioClient := minioClient{client: mClient}
|
||||
objects, err := listBucketObjects(ctx, minioClient, params.BucketName, prefix, true, false, false)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
w := new(bytes.Buffer)
|
||||
zipw := zip.NewWriter(w)
|
||||
var folder string
|
||||
if len(folders) > 1 {
|
||||
folder = folders[len(folders)-2]
|
||||
}
|
||||
for i := 0; i < len(objects); i++ {
|
||||
name := folder + objects[i].Name[len(prefix)-1:]
|
||||
object, err := mClient.GetObject(ctx, params.BucketName, objects[i].Name, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
f, err := zipw.Create(name)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(object)
|
||||
f.Write(buf.Bytes())
|
||||
}
|
||||
zipw.Close()
|
||||
resp := io.NopCloser(bytes.NewReader(w.Bytes()))
|
||||
|
||||
return middleware.ResponderFunc(func(rw http.ResponseWriter, _ runtime.Producer) {
|
||||
defer resp.Close()
|
||||
|
||||
// indicate it's a download / inline content to the browser, and the size of the object
|
||||
var prefixPath string
|
||||
var filename string
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
prefixPath = string(decodedPrefix)
|
||||
}
|
||||
prefixElements := strings.Split(prefixPath, "/")
|
||||
if len(prefixElements) > 0 {
|
||||
if prefixElements[len(prefixElements)-1] == "" {
|
||||
filename = prefixElements[len(prefixElements)-2]
|
||||
} else {
|
||||
filename = prefixElements[len(prefixElements)-1]
|
||||
}
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", filename))
|
||||
rw.Header().Set("Content-Type", "application/zip")
|
||||
|
||||
// Copy the stream
|
||||
_, err := io.Copy(rw, resp)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
// getDeleteObjectResponse returns whether there was an error on deletion of object
|
||||
@@ -929,3 +1033,84 @@ func getHost(authority string) (host string) {
|
||||
}
|
||||
return authority
|
||||
}
|
||||
|
||||
func handleRangeRequest(rw http.ResponseWriter, isRange string, stat minio.ObjectInfo, isPreview bool, filename string, resp *minio.Object, params user_api.DownloadObjectParams, rangeTo int, rangeFrom int) bool {
|
||||
parts := strings.Split(isRange, "=")
|
||||
if len(parts) > 1 {
|
||||
if parts[1] == "0-1" {
|
||||
contentType := stat.ContentType
|
||||
|
||||
if isPreview {
|
||||
// In case content type was uploaded as octet-stream, we double verify content type
|
||||
if stat.ContentType == "application/octet-stream" {
|
||||
contentType = mimedb.TypeByExtension(filepath.Ext(filename))
|
||||
}
|
||||
}
|
||||
rw.Header().Set("Content-Type", contentType)
|
||||
rw.Header().Set("Content-Length", "2")
|
||||
rw.Header().Set("Content-Range", fmt.Sprintf("bytes 0-1/%d", stat.Size))
|
||||
rw.Header().Set("Accept-Ranges", "bytes")
|
||||
rw.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
rw.WriteHeader(206)
|
||||
byts := make([]byte, 2)
|
||||
t, err := resp.Read(byts)
|
||||
log.Println("read", t, "bytes")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
rw.Write(byts)
|
||||
return true
|
||||
}
|
||||
|
||||
contentType := stat.ContentType
|
||||
|
||||
if isPreview {
|
||||
// In case content type was uploaded as octet-stream, we double verify content type
|
||||
if stat.ContentType == "application/octet-stream" {
|
||||
contentType = mimedb.TypeByExtension(filepath.Ext(filename))
|
||||
}
|
||||
}
|
||||
rw.Header().Set("Content-Type", contentType)
|
||||
isFirefox := false
|
||||
if strings.Contains(params.HTTPRequest.UserAgent(), "Firefox") {
|
||||
isFirefox = true
|
||||
}
|
||||
if !isFirefox {
|
||||
rw.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size))
|
||||
}
|
||||
|
||||
if rangeTo > -1 {
|
||||
rw.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", rangeFrom, rangeTo, stat.Size))
|
||||
if isFirefox {
|
||||
rw.Header().Set("Content-Length", fmt.Sprintf("%d", rangeTo-rangeFrom+1))
|
||||
}
|
||||
} else {
|
||||
rw.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", rangeFrom, stat.Size-1, stat.Size))
|
||||
if isFirefox {
|
||||
rw.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size-int64(rangeFrom)))
|
||||
}
|
||||
}
|
||||
rw.Header().Set("Accept-Ranges", "bytes")
|
||||
rw.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
rw.WriteHeader(206)
|
||||
if rangeTo > -1 {
|
||||
byts := make([]byte, rangeTo+1)
|
||||
t, err := resp.ReadAt(byts, int64(rangeFrom))
|
||||
log.Println("0 read", t, "bytes")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
rw.Write(byts)
|
||||
} else {
|
||||
byts := make([]byte, stat.Size-int64(rangeFrom))
|
||||
t, err := resp.ReadAt(byts, int64(rangeFrom))
|
||||
log.Println("1 read", t, "bytes")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
rw.Write(byts)
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1932,6 +1932,27 @@ paths:
|
||||
tags:
|
||||
- AdminAPI
|
||||
|
||||
/configs/{name}/reset:
|
||||
get:
|
||||
summary: Configuration reset
|
||||
operationId: ResetConfig
|
||||
parameters:
|
||||
- name: name
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: A successful response.
|
||||
schema:
|
||||
$ref: "#/definitions/setConfigResponse"
|
||||
default:
|
||||
description: Generic error response.
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
tags:
|
||||
- AdminAPI
|
||||
|
||||
/service/restart:
|
||||
post:
|
||||
summary: Restart Service
|
||||
@@ -2643,7 +2664,7 @@ definitions:
|
||||
name:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
type: string
|
||||
entityType:
|
||||
$ref: "#/definitions/policyEntity"
|
||||
entityName:
|
||||
@@ -3207,6 +3228,8 @@ definitions:
|
||||
type: integer
|
||||
usage:
|
||||
type: integer
|
||||
prometheusNotReady:
|
||||
type: boolean
|
||||
widgets:
|
||||
type: array
|
||||
items:
|
||||
@@ -3893,7 +3916,6 @@ definitions:
|
||||
items:
|
||||
$ref: "#/definitions/iamPolicyStatement"
|
||||
|
||||
|
||||
iamPolicyStatement:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user