Compare commits

...

15 Commits

Author SHA1 Message Date
Minio Trusted
fce361e5bd update to v0.4.3 2020-10-23 02:15:25 -07:00
Alex
ed6d6e8b9d Fixed audit issues (#342)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-23 02:03:49 -07:00
Alex
406709f66b Updated Watch view to have console consistent styles (#341)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-22 17:15:40 -07:00
Cesar N
3ac45a2211 Add Set object's legalhold status api (#339)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-22 16:23:29 -07:00
Minio Trusted
716f886780 update to v0.4.2 2020-10-22 15:35:17 -07:00
Alex
4ef498f0c3 Updated Logs page to be more consistent with current styles (#338)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-22 12:08:36 -07:00
Alex
5e764e61ba Changed trace view to be a table (#337)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-22 11:27:24 -07:00
Cesar N
1466632fd6 Add share object api (#335) 2020-10-22 11:18:27 -07:00
Lenin Alevski
0c43e5c3f4 React Router fixes for Console (#336)
- Adding protectedRoute component
- Removed unnecessary redirect login
2020-10-21 13:13:40 -07:00
Alex
7e9d581277 Updated styles & behavior for settings page (#334)
Updated styles & behavior for settings page, also implemented a couple of performance improvements on some fields

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-20 16:31:08 -07:00
Cesar N
c928972137 Change Users label to Tenants on Tenants Page (#330) 2020-10-20 11:24:52 -07:00
Daniel Valdivia
78884e3806 Make logs, trace and watch have fixed height (#333) 2020-10-20 09:06:23 -07:00
Lenin Alevski
f6ac7e047e Invalidate console session when minio user doesn't exists (#332) 2020-10-19 15:32:21 -07:00
Alex
e1fdf3fb28 Modals UI style changes (#331)
Implements new input styles & adjusts information on modal boxes for console.

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-19 11:27:54 -07:00
Cesar N
e4510cbc18 Add upload api and integrate it with object browser on UI (#327) 2020-10-14 23:09:33 -07:00
85 changed files with 6561 additions and 4227 deletions

View File

@@ -101,8 +101,6 @@ Additionally, you can create policies to limit the privileges for `console` user
To run the server:
```
export CONSOLE_HMAC_JWT_SECRET=YOURJWTSIGNINGSECRET
#required to encrypt jwet payload
export CONSOLE_PBKDF_PASSPHRASE=SECRET

View File

@@ -2,7 +2,7 @@
`Console` will authenticate against `Kubernetes`using bearer tokens via HTTP `Authorization` header. The user will provide this token once
in the login form, Console will validate it against Kubernetes (list apis) and if valid will generate and return a new Console sessions
with encrypted claims (the user Service account token will be inside the JWT in the data field)
with encrypted claims (the user Service account token will be inside the session encrypted token
# Kubernetes

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:v0.4.1
image: minio/console:v0.4.3
imagePullPolicy: "IfNotPresent"
args:
- server

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:v0.4.1
image: minio/console:v0.4.3
imagePullPolicy: "IfNotPresent"
env:
- name: CONSOLE_OPERATOR_MODE

View File

@@ -0,0 +1,80 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// ObjectLegalHoldStatus object legal hold status
//
// swagger:model objectLegalHoldStatus
type ObjectLegalHoldStatus string
const (
// ObjectLegalHoldStatusEnabled captures enum value "enabled"
ObjectLegalHoldStatusEnabled ObjectLegalHoldStatus = "enabled"
// ObjectLegalHoldStatusDisabled captures enum value "disabled"
ObjectLegalHoldStatusDisabled ObjectLegalHoldStatus = "disabled"
)
// for schema
var objectLegalHoldStatusEnum []interface{}
func init() {
var res []ObjectLegalHoldStatus
if err := json.Unmarshal([]byte(`["enabled","disabled"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
objectLegalHoldStatusEnum = append(objectLegalHoldStatusEnum, v)
}
}
func (m ObjectLegalHoldStatus) validateObjectLegalHoldStatusEnum(path, location string, value ObjectLegalHoldStatus) error {
if err := validate.EnumCase(path, location, value, objectLegalHoldStatusEnum, true); err != nil {
return err
}
return nil
}
// Validate validates this object legal hold status
func (m ObjectLegalHoldStatus) Validate(formats strfmt.Registry) error {
var res []error
// value enum
if err := m.validateObjectLegalHoldStatusEnum("", "body", m); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,83 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// PutObjectLegalHoldRequest put object legal hold request
//
// swagger:model putObjectLegalHoldRequest
type PutObjectLegalHoldRequest struct {
// status
// Required: true
Status ObjectLegalHoldStatus `json:"status"`
}
// Validate validates this put object legal hold request
func (m *PutObjectLegalHoldRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateStatus(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *PutObjectLegalHoldRequest) validateStatus(formats strfmt.Registry) error {
if err := m.Status.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("status")
}
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *PutObjectLegalHoldRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *PutObjectLegalHoldRequest) UnmarshalBinary(b []byte) error {
var res PutObjectLegalHoldRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -23,10 +23,9 @@ import (
"github.com/minio/minio/pkg/env"
)
// ConsoleSTSAndJWTDurationSeconds returns the default session duration for the STS requested tokens and the generated JWTs.
// Ideally both values should match so jwt and Minio sts sessions expires at the same time.
func GetConsoleSTSAndJWTDurationInSeconds() int {
duration, err := strconv.Atoi(env.Get(ConsoleSTSAndJWTDurationSeconds, "3600"))
// ConsoleSTSDurationSeconds returns the default session duration for the STS requested tokens.
func GetConsoleSTSDurationInSeconds() int {
duration, err := strconv.Atoi(env.Get(ConsoleSTSDurationSeconds, "3600"))
if err != nil {
duration = 3600
}

View File

@@ -17,7 +17,7 @@
package token
const (
ConsoleSTSAndJWTDurationSeconds = "CONSOLE_STS_AND_JWT_DURATION_SECONDS"
ConsolePBKDFPassphrase = "CONSOLE_PBKDF_PASSPHRASE"
ConsolePBKDFSalt = "CONSOLE_PBKDF_SALT"
ConsoleSTSDurationSeconds = "CONSOLE_STS_DURATION_SECONDS"
ConsolePBKDFPassphrase = "CONSOLE_PBKDF_PASSPHRASE"
ConsolePBKDFSalt = "CONSOLE_PBKDF_SALT"
)

View File

@@ -60,17 +60,17 @@ func TestJWTAuthenticate(t *testing.T) {
funcAssert.Equal(claims.SecretAccessKey, creds.SecretAccessKey)
funcAssert.Equal(claims.SessionToken, creds.SessionToken)
}
// Test-2 : SessionTokenAuthenticate() return an error because of a tampered jwt
// Test-2 : SessionTokenAuthenticate() return an error because of a tampered token
if _, err := SessionTokenAuthenticate(badToken); err != nil {
funcAssert.Equal("session token internal data is malformed", err.Error())
}
// Test-3 : SessionTokenAuthenticate() return an error because of an empty jwt
// Test-3 : SessionTokenAuthenticate() return an error because of an empty token
if _, err := SessionTokenAuthenticate(""); err != nil {
funcAssert.Equal("session token missing", err.Error())
}
}
func TestIsJWTValid(t *testing.T) {
func TestSessionTokenValid(t *testing.T) {
funcAssert := assert.New(t)
// Test-1 : SessionTokenAuthenticate() provided token is valid
funcAssert.Equal(true, IsSessionTokenValid(goodToken))

File diff suppressed because one or more lines are too long

View File

@@ -28,7 +28,6 @@
"local-storage-fallback": "^4.1.1",
"lodash": "^4.17.19",
"moment": "^2.24.0",
"npm": "^6.14.4",
"react": "^16.13.1",
"react-app-rewire-hot-loader": "^2.0.1",
"react-app-rewired": "^2.1.6",
@@ -40,7 +39,7 @@
"react-moment": "^0.9.7",
"react-redux": "^7.1.3",
"react-router-dom": "^5.1.2",
"react-scripts": "3.4.1",
"react-scripts": "3.4.4",
"recharts": "^1.8.5",
"redux": "^4.0.4",
"redux-thunk": "^2.3.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -15,7 +15,13 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { Redirect, Route, Router, Switch } from "react-router-dom";
import {
Redirect,
Route,
Router,
Switch,
BrowserRouter,
} from "react-router-dom";
import history from "./history";
import Login from "./screens/LoginPage/LoginPage";
import Console from "./screens/Console/Console";
@@ -27,6 +33,22 @@ import { userLoggedIn } from "./actions";
import LoginCallback from "./screens/LoginPage/LoginCallback";
import { hot } from "react-hot-loader/root";
interface ProtectedRouteProps {
loggedIn: boolean;
component: any;
}
export class ProtectedRoute extends React.Component<ProtectedRouteProps> {
render() {
const Component = this.props.component;
return this.props.loggedIn ? (
<Component />
) : (
<Redirect to={{ pathname: "/login" }} />
);
}
}
const isLoggedIn = () => {
return (
storage.getItem("token") !== undefined &&
@@ -47,29 +69,14 @@ interface RoutesProps {
}
class Routes extends React.Component<RoutesProps> {
componentDidMount(): void {
if (isLoggedIn()) {
this.props.userLoggedIn(true);
}
}
render() {
const loggedIn = isLoggedIn();
return (
<Router history={history}>
<Switch>
<Route exact path="/oauth_callback" component={LoginCallback} />
<Route exact path="/login" component={Login} />
{this.props.loggedIn ? (
<Switch>
<Route path="/*" component={Console} />
<Route component={NotFoundPage} />
</Switch>
) : (
<Switch>
<Route exact path="/" component={Login} />
<Redirect to="/" />
</Switch>
)}
<ProtectedRoute component={Console} loggedIn={loggedIn} />
</Switch>
</Router>
);

View File

@@ -0,0 +1,34 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 { SvgIcon } from "@material-ui/core";
class AddIcon extends React.Component {
render() {
return (
<SvgIcon viewBox="0 0 12 12">
<path
fill="#081c42"
className="a"
d="M-13160.269,1885.114h-3.235v-4.381h-4.382V1877.5h4.382v-4.381h3.235v4.381h4.383v3.238h-4.383v4.38Z"
transform="translate(13167.886 -1873.114)"
/>
</SvgIcon>
);
}
}
export default AddIcon;

View File

@@ -0,0 +1,33 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 { SvgIcon } from "@material-ui/core";
class RemoveIcon extends React.Component {
render() {
return (
<SvgIcon viewBox="0 0 11.656 3.101">
<path
fill="#081c42"
d="M-13157.172,1879.551h-11.656v-3.1h11.656v3.1Z"
transform="translate(13168.828 -1876.449)"
/>
</SvgIcon>
);
}
}
export default RemoveIcon;

View File

@@ -103,6 +103,7 @@ const AddBucket = ({
const [bName, setBName] = useState<string>(bucketName);
const [addLoading, setAddLoading] = useState<boolean>(false);
const [addError, setAddError] = useState<string>("");
const [sendEnabled, setSendEnabled] = useState<boolean>(false);
const addRecord = (event: React.FormEvent) => {
event.preventDefault();
@@ -141,10 +142,34 @@ const AddBucket = ({
const [value] = useDebounce(bName, 1000);
useEffect(() => {
console.log("called");
addBucketName(value);
}, [value]);
const resetForm = () => {
setBName("");
addBucketVersioned(false);
addBucketQuota(false);
addBucketQuotaType("hard");
addBucketQuotaSize("1");
addBucketQuotaUnit("TiB");
};
useEffect(() => {
let valid = false;
if (bName.trim() !== "") {
valid = true;
}
if (enableQuota && valid) {
if (quotaSize.trim() === "" || parseInt(quotaSize) === 0) {
valid = false;
}
}
setSendEnabled(valid);
}, [bName, versioned, quotaType, quotaSize, quotaUnit, enableQuota]);
return (
<ModalWrapper
title="Create Bucket"
@@ -196,7 +221,8 @@ const AddBucket = ({
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
addBucketVersioned(event.target.checked);
}}
label={"Turn On Versioning"}
label={"Versioning"}
indicatorLabel={"On"}
/>
</Grid>
<Grid item xs={12}>
@@ -209,6 +235,7 @@ const AddBucket = ({
addBucketQuota(event.target.checked);
}}
label={"Enable Bucket Quota"}
indicatorLabel={"On"}
/>
</Grid>
{enableQuota && (
@@ -264,11 +291,19 @@ const AddBucket = ({
)}
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetForm}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
disabled={addLoading || !sendEnabled}
>
Save
</Button>

View File

@@ -16,20 +16,16 @@
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import { Button } from "@material-ui/core";
import Typography from "@material-ui/core/Typography";
import TextField from "@material-ui/core/TextField";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import { BucketObject, BucketObjectsList } from "./types";
import api from "../../../../../../common/api";
import React, { useEffect, useState } from "react";
import React from "react";
import TableWrapper from "../../../../Common/TableWrapper/TableWrapper";
import { MinTablePaginationActions } from "../../../../../../common/MinTablePaginationActions";
import { CreateIcon } from "../../.././../../../icons";
import { niceBytes } from "../../../../../../common/utils";
import Moment from "react-moment";
import DeleteObject from "./DeleteObject";
import {
actionsTray,
containerForHeader,
@@ -37,6 +33,11 @@ import {
} from "../../../../Common/FormComponents/common/styleLibrary";
import PageHeader from "../../../../Common/PageHeader/PageHeader";
import storage from "local-storage-fallback";
import { isNullOrUndefined } from "util";
import { Button, Input } from "@material-ui/core";
import * as reactMoment from "react-moment";
import { CreateIcon } from "../../../../../../icons";
import Snackbar from "@material-ui/core/Snackbar";
const styles = (theme: Theme) =>
createStyles({
@@ -88,6 +89,8 @@ interface IListObjectsState {
selectedObject: string;
selectedBucket: string;
filterObjects: string;
openSnackbar: boolean;
snackBarMessage: string;
}
class ListObjects extends React.Component<
@@ -104,6 +107,8 @@ class ListObjects extends React.Component<
selectedObject: "",
selectedBucket: "",
filterObjects: "",
openSnackbar: false,
snackBarMessage: "",
};
fetchRecords = () => {
@@ -141,6 +146,73 @@ class ListObjects extends React.Component<
});
}
showSnackBarMessage(text: string) {
this.setState({ openSnackbar: true, snackBarMessage: text });
}
closeSnackBar() {
this.setState({ openSnackbar: false, snackBarMessage: `` });
}
upload(e: any, bucketName: string, path: string) {
let listObjects = this;
if (isNullOrUndefined(e) || isNullOrUndefined(e.target)) {
return;
}
const token: string = storage.getItem("token")!;
e.preventDefault();
let file = e.target.files[0];
const fileName = file.name;
const objectName = `${path}${fileName}`;
let uploadUrl = `/api/v1/buckets/${bucketName}/objects/upload?prefix=${objectName}`;
let xhr = new XMLHttpRequest();
xhr.open("POST", uploadUrl, true);
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
xhr.withCredentials = false;
xhr.onload = function (event) {
// TODO: handle status
if (xhr.status == 401 || xhr.status == 403) {
listObjects.showSnackBarMessage(
"An error occurred while uploading the file."
);
}
if (xhr.status == 500) {
listObjects.showSnackBarMessage(
"An error occurred while uploading the file."
);
}
if (xhr.status == 200) {
listObjects.showSnackBarMessage("Object uploaded successfully.");
listObjects.fetchRecords();
}
};
xhr.upload.addEventListener("error", (event) => {
// TODO: handle error
this.showSnackBarMessage("An error occurred while uploading the file.");
});
xhr.upload.addEventListener("progress", (event) => {
// TODO: handle progress with event.loaded, event.total
});
xhr.onerror = () => {
listObjects.showSnackBarMessage(
"An error occurred while uploading the file."
);
};
var formData = new FormData();
var blobFile = new Blob([file]);
formData.append("upfile", blobFile);
xhr.send(formData);
e.target.value = null;
}
download(bucketName: string, objectName: string) {
var anchor = document.createElement("a");
document.body.appendChild(anchor);
@@ -155,7 +227,7 @@ class ListObjects extends React.Component<
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
xhr.responseType = "blob";
xhr.onload = function(e) {
xhr.onload = function (e) {
if (this.status == 200) {
var blob = new Blob([this.response], {
type: "octet/stream",
@@ -184,9 +256,11 @@ class ListObjects extends React.Component<
selectedBucket,
deleteOpen,
filterObjects,
snackBarMessage,
openSnackbar,
} = this.state;
const displayParsedDate = (date: string) => {
return <Moment>{date}</Moment>;
return <reactMoment.default>{date}</reactMoment.default>;
};
const confirmDeleteObject = (object: string) => {
@@ -197,6 +271,25 @@ class ListObjects extends React.Component<
this.download(selectedBucket, object);
};
const uploadObject = (e: any): void => {
// TODO: handle deeper paths/folders
let file = e.target.files[0];
this.showSnackBarMessage(`Uploading: ${file.name}`);
this.upload(e, selectedBucket, "");
};
const snackBarAction = (
<Button
color="secondary"
size="small"
onClick={() => {
this.closeSnackBar();
}}
>
Dismiss
</Button>
);
const tableActions = [
{ type: "download", onClick: downloadObject, sendOnlyId: true },
{ type: "delete", onClick: confirmDeleteObject, sendOnlyId: true },
@@ -226,6 +319,11 @@ class ListObjects extends React.Component<
}}
/>
)}
<Snackbar
open={openSnackbar}
message={snackBarMessage}
action={snackBarAction}
/>
<PageHeader label="Objects" />
<Grid container>
<Grid item xs={12} className={classes.container}>
@@ -249,6 +347,23 @@ class ListObjects extends React.Component<
),
}}
/>
<>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
component="label"
>
Upload Object
<Input
type="file"
onChange={(e) => uploadObject(e)}
id="file-input"
style={{ display: "none" }}
/>
</Button>
</>
</Grid>
<Grid item xs={12}>
<br />

View File

@@ -1,5 +1,5 @@
// This file is part of MinIO Console Server
// Copyright (c) 2019 MinIO, Inc.
// Copyright (c) 2020 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
@@ -13,21 +13,32 @@
//
// 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, useEffect, createRef, ChangeEvent } from "react";
import React, {
useState,
useEffect,
createRef,
useLayoutEffect,
ChangeEvent,
useRef,
} from "react";
import get from "lodash/get";
import debounce from "lodash/debounce";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import get from "lodash/get";
import InputBoxWrapper from "../InputBoxWrapper/InputBoxWrapper";
import HelpIcon from "@material-ui/icons/Help";
import { InputLabel, Tooltip } from "@material-ui/core";
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
import HelpIcon from "@material-ui/icons/Help";
import InputBoxWrapper from "../InputBoxWrapper/InputBoxWrapper";
import AddIcon from "../../../../../icons/AddIcon";
interface ICSVMultiSelector {
elements: string;
name: string;
label: string;
tooltip?: string;
commonPlaceholder?: string;
classes: any;
withBorder?: boolean;
onChange: (elements: string) => void;
}
@@ -35,16 +46,13 @@ const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
...tooltipHelper,
inputLabel: {
...fieldBasic.inputLabel,
width: 116,
},
inputContainer: {
inputWithBorder: {
border: "1px solid #EAEAEA",
padding: 15,
height: 150,
overflowY: "auto",
padding: 15,
position: "relative",
border: "1px solid #c4c4c4",
marginTop: 15,
},
labelContainer: {
display: "flex",
@@ -56,7 +64,9 @@ const CSVMultiSelector = ({
name,
label,
tooltip = "",
commonPlaceholder = "",
onChange,
withBorder = false,
classes,
}: ICSVMultiSelector) => {
const [currentElements, setCurrentElements] = useState<string[]>([""]);
@@ -75,29 +85,37 @@ const CSVMultiSelector = ({
setCurrentElements(elementsSplit);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [elements, currentElements]);
// Use effect to send new values to onChange
useEffect(() => {
const elementsString = currentElements
.filter((element) => element.trim() !== "")
.join(",");
onChange(elementsString);
const refScroll = bottomList.current;
if (refScroll) {
refScroll.scrollIntoView(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentElements]);
// We avoid multiple re-renders / hang issue typing too fast
const firstUpdate = useRef(true);
useLayoutEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
return;
}
debouncedOnChange();
}, [currentElements]);
// If the last input is not empty, we add a new one
const addEmptyLine = (elementsUp: string[]) => {
if (elementsUp[elementsUp.length - 1].trim() !== "") {
elementsUp.push("");
const refScroll = bottomList.current;
if (refScroll) {
refScroll.scrollIntoView(false);
}
const cpList = [...elementsUp];
cpList.push("");
setCurrentElements(cpList);
}
return elementsUp;
};
// Onchange function for input box, we get the dataset-index & only update that value in the array
@@ -108,10 +126,18 @@ const CSVMultiSelector = ({
const index = get(e.target, "dataset.index", 0);
updatedElement[index] = e.target.value;
updatedElement = addEmptyLine(updatedElement);
setCurrentElements(updatedElement);
};
// Debounce for On Change
const debouncedOnChange = debounce(() => {
const elementsString = currentElements
.filter((element) => element.trim() !== "")
.join(",");
onChange(elementsString);
}, 500);
const inputs = currentElements.map((element, index) => {
return (
<InputBoxWrapper
@@ -122,6 +148,11 @@ const CSVMultiSelector = ({
onChange={onChangeElement}
index={index}
key={`csv-${name}-${index.toString()}`}
placeholder={commonPlaceholder}
overlayIcon={index === currentElements.length - 1 ? <AddIcon /> : null}
overlayAction={() => {
addEmptyLine(currentElements);
}}
/>
);
});
@@ -139,7 +170,11 @@ const CSVMultiSelector = ({
</div>
)}
</InputLabel>
<Grid item xs={12} className={classes.inputContainer}>
<Grid
item
xs={12}
className={`${withBorder ? classes.inputWithBorder : ""}`}
>
{inputs}
<div ref={bottomList} />
</Grid>
@@ -147,5 +182,4 @@ const CSVMultiSelector = ({
</React.Fragment>
);
};
export default withStyles(styles)(CSVMultiSelector);

View File

@@ -0,0 +1,83 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 HelpIcon from "@material-ui/icons/Help";
import Grid from "@material-ui/core/Grid";
import { Controlled as CodeMirror } from "react-codemirror2";
import { InputLabel, Tooltip } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { fieldBasic } from "../common/styleLibrary";
import "./ConsoleCodeMirror.css";
require("codemirror/mode/javascript/javascript");
interface ICodeWrapper {
value: string;
label?: string;
tooltip?: string;
classes: any;
onChange?: (editor: any, data: any, value: string) => any;
onBeforeChange: (editor: any, data: any, value: string) => any;
readOnly?: boolean;
}
const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
});
const CodeMirrorWrapper = ({
value,
label = "",
tooltip = "",
classes,
onChange = () => {},
onBeforeChange,
readOnly = false,
}: ICodeWrapper) => {
return (
<React.Fragment>
<InputLabel className={classes.inputLabel}>
<span>{label}</span>
{tooltip !== "" && (
<div className={classes.tooltipContainer}>
<Tooltip title={tooltip} placement="top-start">
<HelpIcon className={classes.tooltip} />
</Tooltip>
</div>
)}
</InputLabel>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<CodeMirror
value={value}
options={{
mode: "javascript",
lineNumbers: true,
readOnly,
}}
onBeforeChange={onBeforeChange}
onChange={onChange}
/>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(CodeMirrorWrapper);

View File

@@ -0,0 +1,353 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: #fff;
background: #081C42;
direction: ltr;
font-size: 13px;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: rgba(255,255,255,0.8); /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #ffffff80;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
white-space: nowrap;
color: #000;
font-size: 10px;
height: 18px;
line-height: 18px;
text-align: center;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid white;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, 0.5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: 0;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: #fff;}
.cm-s-default .cm-quote {color: #fff;}
.cm-negative {color: #fff;}
.cm-positive {color: #fff;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #fff;}
.cm-s-default .cm-atom {color: #fff;}
.cm-s-default .cm-number {color: #fff;}
.cm-s-default .cm-def {color: #fff;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #fff;}
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #fff;}
.cm-s-default .cm-comment {color: #fff;}
.cm-s-default .cm-string {color: #fff;}
.cm-s-default .cm-string-2 {color: #fff;}
.cm-s-default .cm-meta {color: #fff;}
.cm-s-default .cm-qualifier {color: #fff;}
.cm-s-default .cm-builtin {color: #fff;}
.cm-s-default .cm-bracket {color: #fff;}
.cm-s-default .cm-tag {color: #fff;}
.cm-s-default .cm-attribute {color: #fff;}
.cm-s-default .cm-hr {color: #fff;}
.cm-s-default .cm-link {color: #fff;}
.cm-s-default .cm-error {color: #fff;}
.cm-invalidchar {color: #fff;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #fff;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #fff;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 50px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -50px; margin-right: -50px;
padding-bottom: 50px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 50px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -50px;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre.CodeMirror-line,
.CodeMirror-wrap pre.CodeMirror-line-like {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-widget {}
.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background-color: #ffa;
background-color: rgba(255, 255, 0, .4);
}
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

View File

@@ -0,0 +1,158 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 {
Grid,
InputLabel,
TextField,
TextFieldProps,
Tooltip,
} from "@material-ui/core";
import { OutlinedInputProps } from "@material-ui/core/OutlinedInput";
import {
createStyles,
makeStyles,
Theme,
withStyles,
} from "@material-ui/core/styles";
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
import HelpIcon from "@material-ui/icons/Help";
interface CommentBoxProps {
label: string;
classes: any;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
value: string | boolean;
id: string;
name: string;
disabled?: boolean;
tooltip?: string;
index?: number;
error?: string;
required?: boolean;
placeholder?: string;
}
const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
...tooltipHelper,
inputLabel: {
...fieldBasic.inputLabel,
marginBottom: 16,
fontSize: 14,
},
textBoxContainer: {
flexGrow: 1,
position: "relative",
},
errorState: {
color: "#b53b4b",
fontSize: 14,
position: "absolute",
top: 7,
right: 7,
},
cssOutlinedInput: {
borderColor: "#9C9C9C",
padding: 16,
},
rootContainer: {
"& .MuiOutlinedInput-inputMultiline": {
...fieldBasic.inputLabel,
fontSize: 13,
minHeight: 150,
},
},
});
const CommentBoxWrapper = ({
label,
onChange,
value,
id,
name,
disabled = false,
tooltip = "",
index = 0,
error = "",
required = false,
placeholder = "",
classes,
}: CommentBoxProps) => {
let inputProps: any = { "data-index": index };
return (
<React.Fragment>
<Grid
item
xs={12}
className={`${classes.fieldContainer} ${
error !== "" ? classes.errorInField : ""
}`}
>
{label !== "" && (
<InputLabel
htmlFor={id}
className={`${error !== "" ? classes.fieldLabelError : ""} ${
classes.inputLabel
}`}
>
<span>
{label}
{required ? "*" : ""}
</span>
{tooltip !== "" && (
<div className={classes.tooltipContainer}>
<Tooltip title={tooltip} placement="top-start">
<HelpIcon className={classes.tooltip} />
</Tooltip>
</div>
)}
</InputLabel>
)}
<div className={classes.textBoxContainer}>
<TextField
id={id}
name={name}
fullWidth
value={value}
disabled={disabled}
onChange={onChange}
multiline
inputProps={inputProps}
error={error !== ""}
helperText={error}
placeholder={placeholder}
InputLabelProps={{
shrink: true,
}}
InputProps={{
classes: {
notchedOutline: classes.cssOutlinedInput,
root: classes.rootContainer,
},
}}
variant="outlined"
/>
</div>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(CommentBoxWrapper);

View File

@@ -22,7 +22,7 @@ import { actionsTray, fieldBasic } from "../common/styleLibrary";
import HelpIcon from "@material-ui/icons/Help";
interface IFormSwitch {
label: string;
label?: string;
classes: any;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
value: string | boolean;
@@ -31,7 +31,9 @@ interface IFormSwitch {
disabled?: boolean;
tooltip?: string;
index?: number;
indicatorLabel?: string;
checked: boolean;
switchOnly?: boolean;
}
const styles = (theme: Theme) =>
@@ -82,7 +84,7 @@ const styles = (theme: Theme) =>
},
actionsTitle: {
fontWeight: 600,
color: "#000",
color: "#081C42",
fontSize: 16,
alignSelf: "center",
},
@@ -95,7 +97,7 @@ const styles = (theme: Theme) =>
"& .input": {
"&::placeholder": {
fontWeight: 600,
color: "#000",
color: "#081C42",
},
},
},
@@ -107,6 +109,15 @@ const styles = (theme: Theme) =>
paddingBottom: 14,
marginBottom: 20,
},
indicatorLabel: {
fontSize: 12,
fontWeight: 600,
color: "#081C42",
margin: "0 8px 0 10px",
},
switchContainer: {
display: "flex",
},
...actionsTray,
...fieldBasic,
});
@@ -127,7 +138,7 @@ const StyledSwitch = withStyles({
color: "#fff",
},
"&$checked + $track": {
backgroundColor: "#000",
backgroundColor: "#081C42",
opacity: 1,
height: 15,
},
@@ -135,14 +146,14 @@ const StyledSwitch = withStyles({
checked: {},
track: {
height: 15,
backgroundColor: "#000",
backgroundColor: "#081C42",
opacity: 1,
padding: 0,
marginTop: 1.5,
},
thumb: {
backgroundColor: "#fff",
border: "#000 1px solid",
border: "#081C42 1px solid",
boxShadow: "none",
width: 18,
height: 18,
@@ -152,16 +163,44 @@ const StyledSwitch = withStyles({
})(Switch);
const FormSwitchWrapper = ({
label,
label = "",
onChange,
value,
id,
name,
checked = false,
disabled = false,
switchOnly = false,
tooltip = "",
indicatorLabel = "",
classes,
}: IFormSwitch) => {
const switchComponent = (
<React.Fragment>
<div className={classes.switchContainer}>
<StyledSwitch
checked={checked}
onChange={onChange}
color="primary"
name={name}
inputProps={{ "aria-label": "primary checkbox" }}
disabled={disabled}
disableRipple
disableFocusRipple
disableTouchRipple
value={value}
/>
{indicatorLabel !== "" && (
<span className={classes.indicatorLabel}>{indicatorLabel}</span>
)}
</div>
</React.Fragment>
);
if (switchOnly) {
return switchComponent;
}
return (
<React.Fragment>
<Grid item xs={12} className={`${classes.wrapperContainer}`}>
@@ -177,21 +216,7 @@ const FormSwitchWrapper = ({
)}
</InputLabel>
)}
<div className={classes.textBoxContainer}>
<StyledSwitch
checked={checked}
onChange={onChange}
color="primary"
name={name}
inputProps={{ "aria-label": "primary checkbox" }}
disabled={disabled}
disableRipple
disableFocusRipple
disableTouchRipple
value={value}
/>
</div>
{switchComponent}
</Grid>
</React.Fragment>
);

View File

@@ -16,6 +16,7 @@
import React from "react";
import {
Grid,
IconButton,
InputLabel,
TextField,
TextFieldProps,
@@ -49,6 +50,8 @@ interface InputBoxProps {
placeholder?: string;
min?: string;
max?: string;
overlayIcon?: any;
overlayAction?: () => void;
}
const styles = (theme: Theme) =>
@@ -57,7 +60,10 @@ const styles = (theme: Theme) =>
...tooltipHelper,
textBoxContainer: {
flexGrow: 1,
},
textBoxWithIcon: {
position: "relative",
paddingRight: 25,
},
errorState: {
color: "#b53b4b",
@@ -66,6 +72,15 @@ const styles = (theme: Theme) =>
top: 7,
right: 7,
},
overlayAction: {
position: "absolute",
right: 0,
top: 15,
"& svg": {
maxWidth: 15,
maxHeight: 15,
},
},
});
const inputStyles = makeStyles((theme: Theme) =>
@@ -76,11 +91,21 @@ const inputStyles = makeStyles((theme: Theme) =>
borderColor: "#9c9c9c",
},
},
disabled: {
"&.MuiInput-underline::before": {
borderColor: "#eaeaea",
borderBottomStyle: "solid",
},
},
input: {
padding: "15px 5px 10px",
padding: "15px 30px 10px 5px",
color: "#393939",
fontSize: 13,
fontWeight: 600,
"&:placeholder": {
color: "#393939",
opacity: 1,
},
},
error: {
color: "#b53b4b",
@@ -117,6 +142,8 @@ const InputBoxWrapper = ({
placeholder = "",
min,
max,
overlayIcon = null,
overlayAction,
classes,
}: InputBoxProps) => {
let inputProps: any = { "data-index": index };
@@ -174,12 +201,28 @@ const InputBoxWrapper = ({
error={error !== ""}
helperText={error}
placeholder={placeholder}
InputLabelProps={{
shrink: true,
}}
className={classes.inputRebase}
/>
</div>
{overlayIcon && (
<div className={classes.overlayAction}>
<IconButton
onClick={
overlayAction
? () => {
overlayAction();
}
: () => null
}
size={"small"}
disableFocusRipple={false}
disableRipple={false}
disableTouchRipple={false}
>
{overlayIcon}
</IconButton>
</div>
)}
</Grid>
</React.Fragment>
);

View File

@@ -25,7 +25,7 @@ import {
withStyles,
makeStyles,
} from "@material-ui/core/styles";
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
import { fieldBasic, radioIcons, tooltipHelper } from "../common/styleLibrary";
import HelpIcon from "@material-ui/icons/Help";
export interface SelectorTypes {
@@ -49,8 +49,20 @@ const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
...tooltipHelper,
radioBoxContainer: {
flexGrow: 1,
radioBoxContainer: {},
fieldContainer: {
...fieldBasic.fieldContainer,
display: "flex",
justifyContent: "space-between",
borderBottom: "#9c9c9c 1px solid",
paddingBottom: 10,
marginTop: 11,
},
checkedOption: {
"& .MuiFormControlLabel-label": {
color: "#000",
fontWeight: 700,
},
},
});
@@ -60,31 +72,7 @@ const radioStyles = makeStyles({
backgroundColor: "transparent",
},
},
icon: {
borderRadius: "100%",
width: 14,
height: 14,
border: "1px solid #000",
},
checkedIcon: {
borderRadius: "100%",
width: 14,
height: 14,
border: "1px solid #000",
padding: 4,
position: "relative",
"&::after": {
content: '" "',
width: 8,
height: 8,
borderRadius: "100%",
display: "block",
position: "absolute",
backgroundColor: "#000",
top: 2,
left: 2,
},
},
...radioIcons,
});
const RadioButton = (props: RadioProps) => {
@@ -95,8 +83,8 @@ const RadioButton = (props: RadioProps) => {
className={classes.root}
disableRipple
color="default"
checkedIcon={<span className={classes.checkedIcon} />}
icon={<span className={classes.icon} />}
checkedIcon={<span className={classes.radioSelectedIcon} />}
icon={<span className={classes.radioUnselectedIcon} />}
{...props}
/>
);
@@ -143,6 +131,11 @@ export const RadioGroupSelector = ({
value={selectorOption.value}
control={<RadioButton />}
label={selectorOption.label}
className={
selectorOption.value === currentSelection
? classes.checkedOption
: ""
}
/>
);
})}

View File

@@ -20,7 +20,6 @@ export const fieldBasic = {
inputLabel: {
fontWeight: 600,
marginRight: 10,
width: 160,
fontSize: 15,
color: "#000",
textAlign: "left" as const,
@@ -36,6 +35,7 @@ export const fieldBasic = {
},
fieldContainer: {
marginBottom: 20,
position: "relative" as const,
},
tooltipContainer: {
marginLeft: 5,
@@ -53,6 +53,30 @@ export const modalBasic = {
formSlider: {
marginLeft: 0,
},
clearButton: {
border: "0",
backgroundColor: "transparent",
color: "#393939",
fontWeight: 600,
fontSize: 14,
marginRight: 10,
outline: "0",
padding: "16px 25px 16px 25px",
cursor: "pointer",
},
floatingEnabled: {
position: "absolute" as const,
right: 58,
zIndex: 1000,
marginTop: -38,
},
configureString: {
border: "#EAEAEA 1px solid",
borderRadius: 4,
padding: "24px 50px",
overflowY: "auto" as const,
height: 170,
},
};
export const tooltipHelper = {
@@ -76,6 +100,21 @@ export const checkboxIcons = {
},
};
const radioBasic = {
width: 12,
height: 12,
borderRadius: "100%",
};
export const radioIcons = {
radioUnselectedIcon: { ...radioBasic, border: "1px solid #000" },
radioSelectedIcon: {
...radioBasic,
border: "1px solid #000",
backgroundColor: "#000",
},
};
export const containerForHeader = (bottomSpacing: any) => ({
container: {
padding: "110px 33px 30px",
@@ -106,7 +145,7 @@ export const actionsTray = {
export const searchField = {
searchField: {
flexGrow: 1,
marginRight: 30,
height: 40,
background: "#FFFFFF",
borderRadius: 5,
border: "#EAEDEE 1px solid",
@@ -137,5 +176,6 @@ export const predefinedList = {
color: "#393939",
fontSize: 12,
fontWeight: 600,
minHeight: 41,
},
};

View File

@@ -29,11 +29,12 @@ interface IModalProps {
modalOpen: boolean;
title: string;
children: any;
wideLimit?: boolean;
}
const baseCloseLine = {
content: '" "',
borderLeft: "2px solid #707070",
borderLeft: "2px solid #9C9C9C",
height: 33,
width: 1,
position: "absolute",
@@ -61,10 +62,10 @@ const styles = (theme: Theme) =>
},
modalCloseIcon: {
fontSize: 35,
color: "#707070",
color: "#9C9C9C",
fontWeight: 300,
"&:hover": {
color: "#000",
color: "#9C9C9C",
},
},
closeIcon: {
@@ -77,7 +78,7 @@ const styles = (theme: Theme) =>
transform: "rotate(-45deg)",
},
"&:hover::before, &:hover::after": {
borderColor: "#000",
borderColor: "#9C9C9C",
},
width: 24,
height: 24,
@@ -95,6 +96,10 @@ const styles = (theme: Theme) =>
modalContent: {
padding: "0 50px",
},
customDialogSize: {
width: "100%",
maxWidth: 765,
},
});
const ModalWrapper = ({
@@ -103,15 +108,22 @@ const ModalWrapper = ({
title,
children,
classes,
wideLimit = true,
}: IModalProps) => {
const customSize = wideLimit
? {
classes: {
paper: classes.customDialogSize,
},
}
: { maxWidth: "md" as const, fullWidth: true };
return (
<Dialog
open={modalOpen}
onClose={onClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
maxWidth={"md"}
fullWidth
{...customSize}
>
<div className={classes.dialogContainer}>
<div className={classes.closeContainer}>

View File

@@ -33,7 +33,10 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { TablePaginationActionsProps } from "@material-ui/core/TablePagination/TablePaginationActions";
import TableActionButton from "./TableActionButton";
import history from "../../../../history";
import { checkboxIcons } from "../FormComponents/common/styleLibrary";
import {
checkboxIcons,
radioIcons,
} from "../FormComponents/common/styleLibrary";
//Interfaces for table Items
@@ -49,6 +52,7 @@ interface IColumns {
elementKey: string;
sortable?: boolean;
renderFunction?: (input: any) => any;
renderFullObject?: boolean;
globalClass?: any;
}
@@ -80,6 +84,8 @@ interface TableWrapperProps {
entityName: string;
selectedItems?: string[];
stickyHeader?: boolean;
radioSelection?: boolean;
customEmptyMessage?: string;
paginatorConfig?: IPaginatorConfig;
}
@@ -103,7 +109,8 @@ const styles = (theme: Theme) =>
padding: "19px 38px",
minHeight: "200px",
boxShadow: "none",
border: "#e7e7e7 1px solid",
border: "#EAEDEE 1px solid",
borderRadius: 3,
},
minTableHeader: {
color: "#393939",
@@ -159,6 +166,7 @@ const styles = (theme: Theme) =>
cursor: "pointer",
},
...checkboxIcons,
...radioIcons,
});
// Function that renders Title Columns
@@ -186,9 +194,11 @@ const rowColumnsMap = (
const itemElement = isString(itemData)
? itemData
: get(itemData, column.elementKey, null); // If the element is just a string, we render it as it is
const renderConst = column.renderFullObject ? itemData : itemElement;
const renderElement = column.renderFunction
? column.renderFunction(itemElement)
: itemElement; // If render function is set, we send the value to the function.
? column.renderFunction(renderConst)
: renderConst; // If render function is set, we send the value to the function.
return (
<TableCell
key={`tbRE-${column.elementKey}-${index}`}
@@ -239,6 +249,8 @@ const TableWrapper = ({
idField,
classes,
stickyHeader = false,
radioSelection = false,
customEmptyMessage = "",
paginatorConfig,
}: TableWrapperProps) => {
const findView = itemActions
@@ -333,8 +345,24 @@ const TableWrapper = ({
e.stopPropagation();
e.preventDefault();
}}
checkedIcon={<span className={classes.checkedIcon} />}
icon={<span className={classes.unCheckedIcon} />}
checkedIcon={
<span
className={
radioSelection
? classes.radioSelectedIcon
: classes.checkedIcon
}
/>
}
icon={
<span
className={
radioSelection
? classes.radioUnselectedIcon
: classes.unCheckedIcon
}
/>
}
/>
</TableCell>
)}
@@ -362,7 +390,13 @@ const TableWrapper = ({
</Table>
) : (
<React.Fragment>
{!isLoading && <div>{`There are no ${entityName} yet.`}</div>}
{!isLoading && (
<div>
{customEmptyMessage !== ""
? customEmptyMessage
: `There are no ${entityName} yet.`}
</div>
)}
</React.Fragment>
)}
</Paper>

View File

@@ -22,6 +22,8 @@ import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import CSVMultiSelector from "../Common/FormComponents/CSVMultiSelector/CSVMultiSelector";
import CommentBoxWrapper from "../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
interface IConfGenericProps {
onChange: (newValue: IElementValue[]) => void;
@@ -94,20 +96,21 @@ const ConfTargetGeneric = ({
const fieldDefinition = (field: KVField, item: number) => {
switch (field.type) {
case "on|off":
const value = valueHolder[item] ? valueHolder[item].value : "false";
return (
<RadioGroupSelector
currentSelection={valueHolder[item] ? valueHolder[item].value : ""}
<FormSwitchWrapper
indicatorLabel="On"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.checked ? "true" : "false";
setValueElement(field.name, value, item);
}}
id={field.name}
name={field.name}
label={field.label}
value={"switch_on"}
tooltip={field.tooltip}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValueElement(field.name, e.target.value, item)
}
selectorOptions={[
{ label: "On", value: "true" },
{ label: "Off", value: "false" },
]}
checked={value === "true"}
/>
);
case "csv":
@@ -120,6 +123,22 @@ const ConfTargetGeneric = ({
setValueElement(field.name, value, item)
}
tooltip={field.tooltip}
commonPlaceholder={field.placeholder}
withBorder={!!field.withBorder}
/>
);
case "comment":
return (
<CommentBoxWrapper
id={field.name}
name={field.name}
label={field.label}
tooltip={field.tooltip}
value={valueHolder[item] ? valueHolder[item].value : ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValueElement(field.name, e.target.value, item)
}
placeholder={field.placeholder}
/>
);
default:
@@ -134,6 +153,7 @@ const ConfTargetGeneric = ({
setValueElement(field.name, e.target.value, item)
}
multiline={!!field.multiline}
placeholder={field.placeholder}
/>
);
}

View File

@@ -21,7 +21,12 @@ import Grid from "@material-ui/core/Grid";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import { IElementValue } from "../types";
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
import {
modalBasic,
predefinedList,
} from "../../Common/FormComponents/common/styleLibrary";
import CommentBoxWrapper from "../../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
interface IConfMySqlProps {
onChange: (newValue: IElementValue[]) => void;
@@ -31,6 +36,7 @@ interface IConfMySqlProps {
const styles = (theme: Theme) =>
createStyles({
...modalBasic,
...predefinedList,
});
const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
@@ -104,44 +110,41 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
setDsnString(cs);
}, [user, dbName, password, port, host, setDsnString, configToDsnString]);
const switcherChangeEvt = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.checked) {
// build dsn_string
const cs = configToDsnString();
setDsnString(cs);
} else {
// parse dsn_string
const kv = parseDsnString(dsnString, [
"host",
"port",
"dbname",
"user",
"password",
]);
setHostname(kv.get("host") ? kv.get("host") + "" : "");
setPort(kv.get("port") ? kv.get("port") + "" : "");
setDbName(kv.get("dbname") ? kv.get("dbname") + "" : "");
setUser(kv.get("user") ? kv.get("user") + "" : "");
setPassword(kv.get("password") ? kv.get("password") + "" : "");
}
setUseDsnString(event.target.checked);
};
return (
<Grid container className={classes.formScrollable}>
<Grid item xs={12}>
<FormControlLabel
control={
<Switch
checked={useDsnString}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.checked) {
// build dsn_string
const cs = configToDsnString();
setDsnString(cs);
} else {
// parse dsn_string
const kv = parseDsnString(dsnString, [
"host",
"port",
"dbname",
"user",
"password",
]);
setHostname(kv.get("host") ? kv.get("host") + "" : "");
setPort(kv.get("port") ? kv.get("port") + "" : "");
setDbName(kv.get("dbname") ? kv.get("dbname") + "" : "");
setUser(kv.get("user") ? kv.get("user") + "" : "");
setPassword(
kv.get("password") ? kv.get("password") + "" : ""
);
}
setUseDsnString(event.target.checked);
}}
name="checkedB"
color="primary"
/>
}
label="Enter DSN String"
className={classes.formSlider}
<FormSwitchWrapper
label={"Enter DNS String"}
checked={useDsnString}
id="checkedB"
name="checkedB"
onChange={switcherChangeEvt}
value={"dnsString"}
indicatorLabel={"On"}
/>
</Grid>
{useDsnString ? (
@@ -160,62 +163,78 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
</React.Fragment>
) : (
<React.Fragment>
<Grid item xs={12}>
<InputBoxWrapper
id="host"
name="host"
label="Host"
value={host}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setHostname(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="db-name"
name="db-name"
label="DB Name"
value={dbName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setDbName(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="port"
name="port"
label="Port"
value={port}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPort(e.target.value);
}}
/>
</Grid>
<Grid item xs={12} className={classes.configureString}>
<Grid item xs={12}>
<InputBoxWrapper
id="host"
name="host"
label=""
placeholder="Enter Host"
value={host}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setHostname(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="db-name"
name="db-name"
label=""
placeholder="Enter DB Name"
value={dbName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setDbName(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="port"
name="port"
label=""
placeholder="Enter Port"
value={port}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPort(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="user"
name="user"
label="User"
value={user}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUser(e.target.value);
}}
/>
<Grid item xs={12}>
<InputBoxWrapper
id="user"
name="user"
label=""
placeholder="Enter User"
value={user}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUser(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="password"
name="password"
label=""
placeholder="Enter Password"
type="password"
value={password}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
}}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.predefinedTitle}>
Connection String
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{dsnString}
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="password"
name="password"
label="Password"
type="password"
value={password}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
}}
/>
<br />
</Grid>
</React.Fragment>
)}
@@ -224,6 +243,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
id="table"
name="table"
label="Table"
placeholder="Enter Table Name"
value={table}
tooltip="DB table name to store/update events, table is auto-created"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -252,6 +272,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
id="queue-dir"
name="queue_dir"
label="Queue Dir"
placeholder="Enter Queue Dir"
value={queueDir}
tooltip="staging dir for undelivered messages e.g. '/home/events'"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -264,6 +285,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
id="queue-limit"
name="queue_limit"
label="Queue Limit"
placeholder="Enter Queue Limit"
type="number"
value={queueLimit}
tooltip="maximum limit for undelivered messages, defaults to '10000'"
@@ -273,11 +295,11 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
<CommentBoxWrapper
id="comment"
name="comment"
label="Comment"
multiline={true}
placeholder="Enter Comment"
value={comment}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setComment(e.target.value);

View File

@@ -22,7 +22,12 @@ import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBo
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import { IElementValue } from "../types";
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
import {
modalBasic,
predefinedList,
} from "../../Common/FormComponents/common/styleLibrary";
import CommentBoxWrapper from "../../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
interface IConfPostgresProps {
onChange: (newValue: IElementValue[]) => void;
@@ -32,6 +37,7 @@ interface IConfPostgresProps {
const styles = (theme: Theme) =>
createStyles({
...modalBasic,
...predefinedList,
});
const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
@@ -45,7 +51,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
const [port, setPort] = useState<string>("");
const [user, setUser] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [sslMode, setSslMode] = useState<string>("require");
const [sslMode, setSslMode] = useState<string>(" ");
const [table, setTable] = useState<string>("");
const [format, setFormat] = useState<string>("namespace");
@@ -126,8 +132,11 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
if (port !== "") {
strValue = `${strValue} port=${port}`;
}
if (sslMode !== " ") {
strValue = `${strValue} sslmode=${sslMode}`;
}
strValue = `${strValue} sslmode=${sslMode}`;
strValue = `${strValue} `;
return strValue.trim();
}, [host, dbName, user, password, port, sslMode]);
@@ -169,48 +178,44 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
configToString,
]);
useEffect(() => {
if (useConnectionString) {
// build connection_string
const cs = configToString();
setConnectionString(cs);
return;
}
// parse connection_string
const kv = parseConnectionString(connectionString, [
"host",
"port",
"dbname",
"user",
"password",
"sslmode",
]);
setHostname(kv.get("host") ? kv.get("host") + "" : "");
setPort(kv.get("port") ? kv.get("port") + "" : "");
setDbName(kv.get("dbname") ? kv.get("dbname") + "" : "");
setUser(kv.get("user") ? kv.get("user") + "" : "");
setPassword(kv.get("password") ? kv.get("password") + "" : "");
setSslMode(kv.get("sslmode") ? kv.get("sslmode") + "" : " ");
}, [useConnectionString]);
return (
<Grid container className={classes.formScrollable}>
<Grid item xs={12}>
<FormControlLabel
control={
<Switch
checked={useConnectionString}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.checked) {
// build connection_string
const cs = configToString();
setConnectionString(cs);
} else {
// parse connection_string
const kv = parseConnectionString(connectionString, [
"host",
"port",
"dbname",
"user",
"password",
"sslmode",
]);
setHostname(kv.get("host") ? kv.get("host") + "" : "");
setPort(kv.get("port") ? kv.get("port") + "" : "");
setDbName(kv.get("dbname") ? kv.get("dbname") + "" : "");
setUser(kv.get("user") ? kv.get("user") + "" : "");
setPassword(
kv.get("password") ? kv.get("password") + "" : ""
);
setSslMode(
kv.get("sslmode") ? kv.get("sslmode") + "" : "require"
);
}
setUseConnectionString(event.target.checked);
}}
name="checkedB"
color="primary"
/>
}
label="Enter Connection String"
className={classes.formSlider}
<FormSwitchWrapper
label={"Manually Configure String"}
checked={useConnectionString}
id="manualString"
name="manualString"
onChange={(e) => {
setUseConnectionString(e.target.checked);
}}
value={"manualString"}
indicatorLabel={"On"}
/>
</Grid>
{useConnectionString ? (
@@ -229,81 +234,97 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
</React.Fragment>
) : (
<React.Fragment>
<Grid item xs={12}>
<InputBoxWrapper
id="host"
name="host"
label="Host"
value={host}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setHostname(e.target.value);
}}
/>
<Grid item xs={12} className={classes.configureString}>
<Grid item xs={12}>
<InputBoxWrapper
id="host"
name="host"
label=""
placeholder="Enter Host"
value={host}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setHostname(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="db-name"
name="db-name"
label=""
placeholder="Enter DB Name"
value={dbName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setDbName(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="port"
name="port"
label=""
placeholder="Enter Port"
value={port}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPort(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<SelectWrapper
value={sslMode}
label=""
id="sslmode"
name="sslmode"
onChange={(e): void => {
if (e.target.value !== undefined) {
setSslMode(e.target.value + "");
}
}}
options={[
{ label: "Enter SSL Mode", value: " " },
{ label: "Require", value: "require" },
{ label: "Disable", value: "disable" },
{ label: "Verify CA", value: "verify-ca" },
{ label: "Verify Full", value: "verify-full" },
]}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="user"
name="user"
label=""
placeholder="Enter User"
value={user}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUser(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="password"
name="password"
label=""
type="password"
placeholder="Enter Password"
value={password}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
}}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.predefinedTitle}>
Connection String
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{connectionString}
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="db-name"
name="db-name"
label="DB Name"
value={dbName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setDbName(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="port"
name="port"
label="Port"
value={port}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPort(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<SelectWrapper
value={sslMode}
label="SSL Mode"
id="sslmode"
name="sslmode"
onChange={(e): void => {
if (e.target.value !== undefined) {
setSslMode(e.target.value + "");
}
}}
options={[
{ label: "Require", value: "require" },
{ label: "Disable", value: "disable" },
{ label: "Verify CA", value: "verify-ca" },
{ label: "Verify Full", value: "verify-full" },
]}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="user"
name="user"
label="User"
value={user}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUser(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="password"
name="password"
label="Password"
type="password"
value={password}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
}}
/>
<br />
</Grid>
</React.Fragment>
)}
@@ -312,6 +333,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
id="table"
name="table"
label="Table"
placeholder={"Enter Table Name"}
value={table}
tooltip="DB table name to store/update events, table is auto-created"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -340,6 +362,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
id="queue-dir"
name="queue_dir"
label="Queue Dir"
placeholder="Enter Queue Directory"
value={queueDir}
tooltip="staging dir for undelivered messages e.g. '/home/events'"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -352,6 +375,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
id="queue-limit"
name="queue_limit"
label="Queue Limit"
placeholder="Enter Queue Limit"
type="number"
value={queueLimit}
tooltip="maximum limit for undelivered messages, defaults to '10000'"
@@ -361,11 +385,11 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
<CommentBoxWrapper
id="comment"
name="comment"
label="Comment"
multiline={true}
placeholder="Enter Comment"
value={comment}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setComment(e.target.value);

View File

@@ -27,7 +27,9 @@ export type KVFieldType =
| "duration"
| "uri"
| "sentence"
| "csv";
| "csv"
| "comment"
| "switch";
export interface KVField {
name: string;
@@ -37,6 +39,8 @@ export interface KVField {
type: KVFieldType;
options?: SelectorTypes[];
multiline?: boolean;
placeholder?: string;
withBorder?: boolean;
}
export interface IConfigurationElement {

File diff suppressed because it is too large Load Diff

View File

@@ -71,19 +71,6 @@ import ObjectBrowser from "./ObjectBrowser/ObjectBrowser";
import ListObjects from "./Buckets/ListBuckets/Objects/ListObjects/ListObjects";
import License from "./License/License";
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{"Copyright © "}
<Link color="inherit" href="https://material-ui.com/">
MinIO
</Link>{" "}
{new Date().getFullYear()}
{"."}
</Typography>
);
}
const drawerWidth = 245;
const styles = (theme: Theme) =>
@@ -385,9 +372,7 @@ const Console = ({
/>
))}
{allowedRoutes.length > 0 ? (
<Route exact path="/">
<Redirect to={allowedRoutes[0].path} />
</Route>
<Redirect to={allowedRoutes[0].path} />
) : null}
</Switch>
</Router>

View File

@@ -19,12 +19,16 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button, LinearProgress } from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import {
modalBasic,
predefinedList,
} from "../Common/FormComponents/common/styleLibrary";
import api from "../../../common/api";
import UsersSelectors from "./UsersSelectors";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
interface IGroupProps {
open: boolean;
@@ -54,6 +58,7 @@ const styles = (theme: Theme) =>
textAlign: "right",
},
...modalBasic,
...predefinedList,
});
const AddGroup = ({
@@ -64,11 +69,12 @@ const AddGroup = ({
}: IGroupProps) => {
//Local States
const [groupName, setGroupName] = useState<string>("");
const [groupEnabled, setGroupEnabled] = useState<string>("");
const [groupEnabled, setGroupEnabled] = useState<boolean>(false);
const [saving, isSaving] = useState<boolean>(false);
const [addError, setError] = useState<string>("");
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
const [loadingGroup, isLoadingGroup] = useState<boolean>(false);
const [validGroup, setValidGroup] = useState<boolean>(false);
//Effects
useEffect(() => {
@@ -80,6 +86,10 @@ const AddGroup = ({
}
}, [selectedGroup]);
useEffect(() => {
setValidGroup(groupName.trim() !== "");
}, [groupName, selectedUsers]);
useEffect(() => {
if (saving) {
const saveRecord = () => {
@@ -88,7 +98,7 @@ const AddGroup = ({
.invoke("PUT", `/api/v1/groups/${groupName}`, {
group: groupName,
members: selectedUsers,
status: groupEnabled,
status: groupEnabled ? "enabled" : "disabled",
})
.then((res) => {
isSaving(false);
@@ -133,7 +143,7 @@ const AddGroup = ({
api
.invoke("GET", `/api/v1/groups/${selectedGroup}`)
.then((res: MainGroupProps) => {
setGroupEnabled(res.status);
setGroupEnabled(res.status === "enabled");
setGroupName(res.name);
setSelectedUsers(res.members);
})
@@ -153,12 +163,35 @@ const AddGroup = ({
isSaving(true);
};
const resetForm = () => {
if (selectedGroup === null) {
setGroupName("");
}
setSelectedUsers([]);
};
return (
<ModalWrapper
modalOpen={open}
onClose={closeModalAndRefresh}
title={selectedGroup !== null ? `Group Edit - ${groupName}` : "Add Group"}
title={selectedGroup !== null ? `Edit Group` : "Create Group"}
>
{selectedGroup !== null && (
<div className={classes.floatingEnabled}>
<FormSwitchWrapper
indicatorLabel={"Enabled"}
checked={groupEnabled}
value={"group_enabled"}
id="group-status"
name="group-status"
onChange={(e) => {
setGroupEnabled(e.target.checked);
}}
switchOnly
/>
</div>
)}
<form noValidate autoComplete="off" onSubmit={setSaving}>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
@@ -174,31 +207,13 @@ const AddGroup = ({
</Grid>
)}
{selectedGroup !== null ? (
<React.Fragment>
<Grid item xs={12}>
<RadioGroupSelector
currentSelection={groupEnabled}
id="group-status"
name="group-status"
label="Status"
onChange={(e) => {
setGroupEnabled(e.target.value);
}}
selectorOptions={[
{ label: "Enabled", value: "enabled" },
{ label: "Disabled", value: "disabled" },
]}
/>
</Grid>
</React.Fragment>
) : (
{selectedGroup === null ? (
<React.Fragment>
<Grid item xs={12}>
<InputBoxWrapper
id="group-name"
name="group-name"
label="Name"
label="Group Name"
value={groupName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setGroupName(e.target.value);
@@ -206,23 +221,38 @@ const AddGroup = ({
/>
</Grid>
</React.Fragment>
) : (
<React.Fragment>
<Grid item xs={12} className={classes.predefinedTitle}>
Group Name
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{selectedGroup}
</Grid>
</React.Fragment>
)}
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<UsersSelectors
selectedUsers={selectedUsers}
setSelectedUsers={setSelectedUsers}
editMode={selectedGroup !== null}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetForm}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={saving}
disabled={saving || !validGroup}
>
Save
</Button>

View File

@@ -28,11 +28,16 @@ import TextField from "@material-ui/core/TextField";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import {
actionsTray,
predefinedList,
} from "../Common/FormComponents/common/styleLibrary";
interface IGroupsProps {
classes: any;
selectedUsers: string[];
setSelectedUsers: any;
editMode?: boolean;
}
const styles = (theme: Theme) =>
@@ -41,10 +46,11 @@ const styles = (theme: Theme) =>
marginTop: theme.spacing(3),
},
paper: {
// padding: theme.spacing(2),
display: "flex",
overflow: "auto",
flexDirection: "column",
paddingTop: 15,
boxShadow: "none",
},
addSideBar: {
width: "320px",
@@ -70,36 +76,43 @@ const styles = (theme: Theme) =>
},
},
},
actionsTray: {
textAlign: "left",
"& button": {
marginLeft: 10,
},
},
filterField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
width: "100%",
zIndex: 500,
},
noFound: {
textAlign: "center",
padding: "10px 0",
},
tableContainer: {
maxHeight: 250,
maxHeight: 200,
},
stickyHeader: {
backgroundColor: "#fff",
},
actionsTitle: {
fontWeight: 600,
color: "#000",
fontSize: 16,
alignSelf: "center",
},
tableBlock: {
marginTop: 15,
},
filterField: {
width: 375,
fontWeight: 600,
"& .input": {
"&::placeholder": {
fontWeight: 600,
color: "#000",
},
},
},
...actionsTray,
});
const UsersSelectors = ({
classes,
selectedUsers,
setSelectedUsers,
editMode = false,
}: IGroupsProps) => {
//Local States
const [records, setRecords] = useState<any[]>([]);
@@ -166,21 +179,22 @@ const UsersSelectors = ({
return (
<React.Fragment>
<Title>Assign Users</Title>
{error !== "" ? <div>{error}</div> : <React.Fragment />}
<Grid item xs={12}>
<Paper className={classes.paper}>
{loading && <LinearProgress />}
{error !== "" ? <div>{error}</div> : <React.Fragment />}
{records != null && records.length > 0 ? (
<React.Fragment>
<Grid item xs={12} className={classes.actionsTray}>
<span className={classes.actionsTitle}>
{editMode ? "Edit" : "Assign"} Members
</span>
<TextField
placeholder="Filter Groups"
className={classes.filterField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
@@ -192,7 +206,7 @@ const UsersSelectors = ({
}}
/>
</Grid>
<Grid item xs={12}>
<Grid item xs={12} className={classes.tableBlock}>
<TableWrapper
columns={[{ label: "Access Key", elementKey: "accessKey" }]}
onSelect={selectionChanged}

View File

@@ -13,7 +13,7 @@
//
// 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 } from "react";
import React, { useState, useEffect } from "react";
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
import { AppState } from "../../../store";
import { connect } from "react-redux";
@@ -21,42 +21,51 @@ import { logMessageReceived, logResetMessages } from "./actions";
import { LogMessage } from "./types";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { timeFromDate } from "../../../common/utils";
import { isNullOrUndefined } from "util";
import { wsProtocol } from "../../../utils/wsUtils";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import { Grid } from "@material-ui/core";
import {
actionsTray,
containerForHeader,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import { Button, Grid } from "@material-ui/core";
import PageHeader from "../Common/PageHeader/PageHeader";
import TextField from "@material-ui/core/TextField";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import { CreateIcon } from "../../../icons";
const styles = (theme: Theme) =>
createStyles({
logList: {
background: "white",
maxHeight: "400px",
background: "#fff",
minHeight: 400,
height: "calc(100vh - 270px)",
overflow: "auto",
"& ul": {
margin: "4px",
padding: "0px",
},
"& ul li": {
listStyle: "none",
margin: "0px",
padding: "0px",
borderBottom: "1px solid #dedede",
},
fontSize: 13,
padding: "25px 45px",
border: "1px solid #EAEDEE",
borderRadius: 4,
},
tab: {
padding: "25px",
paddingLeft: 25,
},
logerror: {
color: "#A52A2A",
},
logerror_tab: {
color: "#A52A2A",
padding: "25px",
paddingLeft: 25,
},
ansidefault: {
color: "black",
color: "#000",
},
highlight: {
"& span": {
backgroundColor: "#082F5238",
},
},
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
});
@@ -73,6 +82,8 @@ const Logs = ({
logResetMessages,
messages,
}: ILogs) => {
const [highlight, setHighlight] = useState("");
useEffect(() => {
logResetMessages();
const url = new URL(window.location.toString());
@@ -128,84 +139,145 @@ const Logs = ({
const renderError = (logElement: LogMessage) => {
let errorElems = [];
if (!isNullOrUndefined(logElement.error)) {
if (logElement.error !== null && logElement.error !== undefined) {
if (logElement.api && logElement.api.name) {
const errorText = `API: ${logElement.api.name}`;
const highlightedLine =
highlight !== ""
? errorText.toLowerCase().includes(highlight.toLowerCase())
: false;
errorElems.push(
<li key={`api-${logElement.key}`}>
<span className={classes.logerror}>API: {logElement.api.name}</span>
</li>
<div
key={`api-${logElement.key}`}
className={`${highlightedLine ? classes.highlight : ""}`}
>
<br />
<span className={classes.logerror}>{errorText}</span>
</div>
);
}
if (logElement.time) {
const errorText = `Time: ${timeFromDate(logElement.time)}`;
const highlightedLine =
highlight !== ""
? errorText.toLowerCase().includes(highlight.toLowerCase())
: false;
errorElems.push(
<li key={`time-${logElement.key}`}>
<span className={classes.logerror}>
Time: {timeFromDate(logElement.time)}
</span>
</li>
<div
key={`time-${logElement.key}`}
className={`${highlightedLine ? classes.highlight : ""}`}
>
<span className={classes.logerror}>{errorText}</span>
</div>
);
}
if (logElement.deploymentid) {
const errorText = `DeploymentID: ${logElement.deploymentid}`;
const highlightedLine =
highlight !== ""
? errorText.toLowerCase().includes(highlight.toLowerCase())
: false;
errorElems.push(
<li key={`deploytmentid-${logElement.key}`}>
<span className={classes.logerror}>
DeploymentID: {logElement.deploymentid}
</span>
</li>
<div
key={`deploytmentid-${logElement.key}`}
className={`${highlightedLine ? classes.highlight : ""}`}
>
<span className={classes.logerror}>{errorText}</span>
</div>
);
}
if (logElement.requestID) {
const errorText = `RequestID: ${logElement.requestID}`;
const highlightedLine =
highlight !== ""
? errorText.toLowerCase().includes(highlight.toLowerCase())
: false;
errorElems.push(
<li key={`requestid-${logElement.key}`}>
<span className={classes.logerror}>
RequestID: {logElement.requestID}
</span>
</li>
<div
key={`requestid-${logElement.key}`}
className={`${highlightedLine ? classes.highlight : ""}`}
>
<span className={classes.logerror}>{errorText}</span>
</div>
);
}
if (logElement.remotehost) {
const errorText = `RemoteHost: ${logElement.remotehost}`;
const highlightedLine =
highlight !== ""
? errorText.toLowerCase().includes(highlight.toLowerCase())
: false;
errorElems.push(
<li key={`remotehost-${logElement.key}`}>
<span className={classes.logerror}>
RemoteHost: {logElement.remotehost}
</span>
</li>
<div
key={`remotehost-${logElement.key}`}
className={`${highlightedLine ? classes.highlight : ""}`}
>
<span className={classes.logerror}>{errorText}</span>
</div>
);
}
if (logElement.host) {
const errorText = `Host: ${logElement.host}`;
const highlightedLine =
highlight !== ""
? errorText.toLowerCase().includes(highlight.toLowerCase())
: false;
errorElems.push(
<li key={`host-${logElement.key}`}>
<span className={classes.logerror}>Host: {logElement.host}</span>
</li>
<div
key={`host-${logElement.key}`}
className={`${highlightedLine ? classes.highlight : ""}`}
>
<span className={classes.logerror}>{errorText}</span>
</div>
);
}
if (logElement.userAgent) {
const errorText = `UserAgent: ${logElement.userAgent}`;
const highlightedLine =
highlight !== ""
? errorText.toLowerCase().includes(highlight.toLowerCase())
: false;
errorElems.push(
<li key={`useragent-${logElement.key}`}>
<span className={classes.logerror}>
UserAgent: {logElement.userAgent}
</span>
</li>
<div
key={`useragent-${logElement.key}`}
className={`${highlightedLine ? classes.highlight : ""}`}
>
<span className={classes.logerror}>{errorText}</span>
</div>
);
}
if (logElement.error.message) {
const errorText = `Error: ${logElement.error.message}`;
const highlightedLine =
highlight !== ""
? errorText.toLowerCase().includes(highlight.toLowerCase())
: false;
errorElems.push(
<li key={`message-${logElement.key}`}>
<span className={classes.logerror}>
Error: {logElement.error.message}
</span>
</li>
<div
key={`message-${logElement.key}`}
className={`${highlightedLine ? classes.highlight : ""}`}
>
<span className={classes.logerror}>{errorText}</span>
</div>
);
}
if (logElement.error.source) {
// for all sources add padding
for (let s in logElement.error.source) {
const errorText = logElement.error.source[s];
const highlightedLine =
highlight !== ""
? errorText.toLowerCase().includes(highlight.toLowerCase())
: false;
errorElems.push(
<li key={`source-${logElement.key}-${s}`}>
<span className={classes.logerror_tab}>
{logElement.error.source[s]}
</span>
</li>
<div
key={`source-${logElement.key}-${s}`}
className={`${highlightedLine ? classes.highlight : ""}`}
>
<span className={classes.logerror_tab}>{errorText}</span>
</div>
);
}
}
@@ -226,38 +298,72 @@ const Logs = ({
// only to the first match.
let substr = logMessage.replace(tColorRegex, "");
// in case highlight is set, we select the line that contains the requested string
let highlightedLine =
highlight !== ""
? logMessage.toLowerCase().includes(highlight.toLowerCase())
: false;
// if starts with multiple spaces add padding
if (substr.startsWith(" ")) {
return (
<li key={logElement.key}>
<div
key={logElement.key}
className={`${highlightedLine ? classes.highlight : ""}`}
>
<span className={classes.tab}>{substr}</span>
</li>
</div>
);
} else if (!isNullOrUndefined(logElement.error)) {
} else if (logElement.error !== null && logElement.error !== undefined) {
// list error message and all sources and error elems
return renderError(logElement);
} else {
// for all remaining set default class
return (
<li key={logElement.key}>
<div
key={logElement.key}
className={`${highlightedLine ? classes.highlight : ""}`}
>
<span className={classes.ansidefault}>{substr}</span>
</li>
</div>
);
}
};
const renderLines = messages.map((m) => {
return renderLog(m);
});
return (
<React.Fragment>
<PageHeader label="Logs" />
<Grid container>
<Grid className={classes.container} item xs={12}>
<div className={classes.logList}>
<ul>
{messages.map((m) => {
return renderLog(m);
})}
</ul>
</div>
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Highlight Line"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setHighlight(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<div className={classes.logList}>{renderLines}</div>
</Grid>
</Grid>
</Grid>
</React.Fragment>

View File

@@ -42,6 +42,7 @@ import {
removeEmptyFields,
} from "../Configurations/utils";
import { IElementValue } from "../Configurations/types";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
const styles = (theme: Theme) =>
createStyles({
@@ -60,6 +61,69 @@ const styles = (theme: Theme) =>
logoButton: {
height: "80px",
},
lambdaNotif: {
border: "#393939 1px solid",
borderRadius: 5,
width: 101,
height: 91,
display: "flex",
alignItems: "center",
justifyContent: "center",
marginBottom: 16,
cursor: "pointer",
"& img": {
maxWidth: 71,
maxHeight: 71,
},
},
iconContainer: {
display: "flex",
flexDirection: "row",
width: 455,
justifyContent: "space-between",
flexWrap: "wrap",
},
nonIconContainer: {
marginBottom: 16,
"& button": {
marginRight: 16,
},
},
pickTitle: {
fontWeight: 600,
color: "#393939",
fontSize: 14,
marginBottom: 16,
},
lambdaFormIndicator: {
display: "flex",
marginBottom: 40,
},
lambdaName: {
fontSize: 18,
fontWeight: 700,
color: "#000",
marginBottom: 6,
},
lambdaSubname: {
fontSize: 12,
color: "#000",
fontWeight: 600,
},
lambdaIcon: {
borderRadius: 5,
border: "#393939 1px solid",
width: 53,
height: 48,
display: "flex",
justifyContent: "center",
alignItems: "center",
marginRight: 16,
"& img": {
width: 38,
},
},
...modalBasic,
});
interface IAddNotificationEndpointProps {
@@ -136,186 +200,111 @@ const AddNotificationEndpoint = ({
}
}
let targetTitle = "";
switch (service) {
case notifyNsq:
targetTitle = "NSQ";
break;
case notifyWebhooks:
targetTitle = "Webhooks";
break;
case notifyElasticsearch:
targetTitle = "Elastic Search";
break;
case notifyNats:
targetTitle = "NATS";
break;
case notifyMqtt:
targetTitle = "MQTT";
break;
case notifyRedis:
targetTitle = "Redis";
break;
case notifyKafka:
targetTitle = "Kafka";
break;
case notifyPostgres:
targetTitle = "Postgres";
break;
case notifyMysql:
targetTitle = "Mysql";
break;
case notifyAmqp:
targetTitle = "AMQP";
break;
}
const servicesList = [
{
actionTrigger: notifyPostgres,
targetTitle: "Postgres SQL",
logo: "/postgres.png",
},
{
actionTrigger: notifyKafka,
targetTitle: "Kafka",
logo: "/kafka.png",
},
{
actionTrigger: notifyAmqp,
targetTitle: "AMQP",
logo: "/amqp.png",
},
{
actionTrigger: notifyMqtt,
targetTitle: "MQTT",
logo: "/mqtt.png",
},
{
actionTrigger: notifyRedis,
targetTitle: "Redis",
logo: "/redis.png",
},
{
actionTrigger: notifyNats,
targetTitle: "NATS",
logo: "/nats.png",
},
{
actionTrigger: notifyMysql,
targetTitle: "Mysql",
logo: "/mysql.png",
},
{
actionTrigger: notifyElasticsearch,
targetTitle: "Elastic Search",
logo: "/elasticsearch.png",
},
{
actionTrigger: notifyWebhooks,
targetTitle: "Webhook",
logo: "",
},
{
actionTrigger: notifyNsq,
targetTitle: "NSQ",
logo: "",
},
];
const nonLogos = servicesList.filter((elService) => elService.logo === "");
const withLogos = servicesList.filter((elService) => elService.logo !== "");
const targetElement = servicesList.find(
(element) => element.actionTrigger === service
);
const goBack = () => {
setService("");
};
return (
<ModalWrapper
modalOpen={open}
onClose={closeModalAndRefresh}
title={`Add Lambda Notification Target ${targetTitle}`}
>
<ModalWrapper modalOpen={open} onClose={closeModalAndRefresh} title={""}>
{service === "" && (
<Grid container>
<Grid item xs={12}>
<p>Pick a supported service:</p>
<table className={classes.chooseTable} style={{ width: "100%" }}>
<tbody>
<tr>
<td>
<Button
onClick={() => {
setService(notifyPostgres);
}}
>
<img
src="/postgres.png"
className={classes.logoButton}
alt="postgres"
/>
</Button>
</td>
<td>
<Button
onClick={() => {
setService(notifyKafka);
}}
>
<img
src="/kafka.png"
className={classes.logoButton}
alt="kafka"
/>
</Button>
</td>
<td>
<Button
onClick={() => {
setService(notifyAmqp);
}}
>
<img
src="/amqp.png"
className={classes.logoButton}
alt="amqp"
/>
</Button>
</td>
</tr>
<tr>
<td>
<Button
onClick={() => {
setService(notifyMqtt);
}}
>
<img
src="/mqtt.png"
className={classes.logoButton}
alt="mqtt"
/>
</Button>
</td>
<td>
<Button
onClick={() => {
setService(notifyRedis);
}}
>
<img
src="/redis.png"
className={classes.logoButton}
alt="redis"
/>
</Button>
</td>
<td>
<Button
onClick={() => {
setService(notifyNats);
}}
>
<img
src="/nats.png"
className={classes.logoButton}
alt="nats"
/>
</Button>
</td>
</tr>
<tr>
<td>
<Button
onClick={() => {
setService(notifyMysql);
}}
>
<img
src="/mysql.png"
className={classes.logoButton}
alt="mysql"
/>
</Button>
</td>
<td>
<Button
onClick={() => {
setService(notifyElasticsearch);
}}
>
<img
src="/elasticsearch.png"
className={classes.logoButton}
alt="elasticsearch"
/>
</Button>
</td>
<td></td>
</tr>
<tr>
<td>
<Button
onClick={() => {
setService(notifyWebhooks);
}}
>
Webhook
</Button>
</td>
<td>
<Button
onClick={() => {
setService(notifyNsq);
}}
>
NSQ
</Button>
</td>
<td />
</tr>
</tbody>
</table>
<div className={classes.pickTitle}>Pick a supported service:</div>
<div className={classes.nonIconContainer}>
{nonLogos.map((item) => {
return (
<Button
variant="contained"
color="primary"
key={`non-icon-${item.targetTitle}`}
onClick={() => {
setService(item.actionTrigger);
}}
>
{item.targetTitle.toUpperCase()}
</Button>
);
})}
</div>
<div className={classes.iconContainer}>
{withLogos.map((item) => {
return (
<a
key={`icon-${item.targetTitle}`}
className={classes.lambdaNotif}
onClick={() => {
setService(item.actionTrigger);
}}
>
<img
src={item.logo}
className={classes.logoButton}
alt={item.targetTitle}
/>
</a>
);
})}
</div>
</Grid>
<Grid item xs={12}>
<br />
@@ -342,10 +331,37 @@ const AddNotificationEndpoint = ({
</Grid>
)}
<form noValidate onSubmit={submitForm}>
<Grid item xs={12} className={classes.lambdaFormIndicator}>
{targetElement && targetElement.logo !== "" && (
<div className={classes.lambdaIcon}>
<img
src={targetElement.logo}
alt={targetElement.targetTitle}
/>
</div>
)}
<div className={classes.lambdaTitle}>
<div className={classes.lambdaName}>
{targetElement ? targetElement.targetTitle : ""}
</div>
<div className={classes.lambdaSubname}>
Add Lambda Notification Target
</div>
</div>
</Grid>
<Grid item xs={12}>
{srvComponent}
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={goBack}
>
Back
</button>
<Button
type="submit"
variant="contained"

View File

@@ -16,19 +16,18 @@
import React from "react";
import Grid from "@material-ui/core/Grid";
import { UnControlled as CodeMirror } from "react-codemirror2";
import Typography from "@material-ui/core/Typography";
import { Button, LinearProgress } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import api from "../../../common/api";
import "codemirror/lib/codemirror.css";
import "codemirror/theme/material.css";
import { Policy } from "./types";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import {
fieldBasic,
modalBasic,
} from "../Common/FormComponents/common/styleLibrary";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
require("codemirror/mode/javascript/javascript");
import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
const styles = (theme: Theme) =>
createStyles({
@@ -39,26 +38,11 @@ const styles = (theme: Theme) =>
minHeight: 400,
width: "100%",
},
codeMirror: {
fontSize: 14,
"& .CodeMirror": {
color: "#fff",
backgroundColor: "#081C42",
},
"& .CodeMirror-gutter": {
backgroundColor: "#081C4280",
},
"& .CodeMirror-linenumber": {
color: "#000",
fontSize: 10,
height: 20,
lineHeight: "20px",
},
},
buttonContainer: {
textAlign: "right",
},
...modalBasic,
...fieldBasic,
});
interface IAddPolicyProps {
@@ -121,13 +105,26 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
if (policyEdit) {
this.setState({
policyName: policyEdit.name,
policyDefinition: policyEdit
? JSON.stringify(JSON.parse(policyEdit.policy), null, 4)
: "",
});
}
}
resetForm() {
this.setState({
policyName: "",
policyDefinition: "",
});
}
render() {
const { classes, open, policyEdit } = this.props;
const { addLoading, addError, policyName } = this.state;
const { addLoading, addError, policyName, policyDefinition } = this.state;
const validSave = policyName.trim() !== "";
return (
<ModalWrapper
modalOpen={open}
@@ -163,6 +160,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
id="policy-name"
name="policy-name"
label="Policy Name"
placeholder="Enter Policy Name"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ policyName: e.target.value });
}}
@@ -173,31 +171,32 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<CodeMirror
className={classes.codeMirror}
value={
policyEdit
? JSON.stringify(JSON.parse(policyEdit.policy), null, 4)
: ""
}
options={{
mode: "javascript",
lineNumbers: true,
}}
onChange={(editor, data, value) => {
this.setState({ policyDefinition: value });
}}
/>
</Grid>
<CodeMirrorWrapper
label="Write Policy"
value={policyDefinition}
onBeforeChange={(editor, data, value) => {
this.setState({ policyDefinition: value });
}}
readOnly={!!policyEdit}
/>
</Grid>
{!policyEdit && (
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={() => {
this.resetForm();
}}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
disabled={addLoading || !validSave}
>
Save
</Button>

View File

@@ -23,7 +23,7 @@ import {
DialogContent,
DialogContentText,
DialogTitle,
LinearProgress
LinearProgress,
} from "@material-ui/core";
import api from "../../../common/api";
import { PolicyList } from "./types";
@@ -32,8 +32,8 @@ import Typography from "@material-ui/core/Typography";
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
}
color: "red",
},
});
interface IDeletePolicyProps {
@@ -54,7 +54,7 @@ class DeletePolicy extends React.Component<
> {
state: IDeletePolicyState = {
deleteLoading: false,
deleteError: ""
deleteError: "",
};
removeRecord() {
const { deleteLoading } = this.state;
@@ -69,17 +69,17 @@ class DeletePolicy extends React.Component<
this.setState(
{
deleteLoading: false,
deleteError: ""
deleteError: "",
},
() => {
this.props.closeDeleteModalAndRefresh(true);
}
);
})
.catch(err => {
.catch((err) => {
this.setState({
deleteLoading: false,
deleteError: err
deleteError: err,
});
});
});
@@ -98,7 +98,7 @@ class DeletePolicy extends React.Component<
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Delete Bucket</DialogTitle>
<DialogTitle id="alert-dialog-title">Delete Policy</DialogTitle>
<DialogContent>
{deleteLoading && <LinearProgress />}
<DialogContentText id="alert-dialog-description">

View File

@@ -0,0 +1,204 @@
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2020 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { LinearProgress } from "@material-ui/core";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import TextField from "@material-ui/core/TextField";
import api from "../../../common/api";
import { policySort } from "../../../utils/sortFunctions";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import { actionsTray } from "../Common/FormComponents/common/styleLibrary";
import { PolicyList } from "./types";
interface ISelectPolicyProps {
classes: any;
selectedPolicy?: string;
setSelectedPolicy: any;
}
const styles = (theme: Theme) =>
createStyles({
seeMore: {
marginTop: theme.spacing(3),
},
paper: {
display: "flex",
overflow: "auto",
flexDirection: "column",
paddingTop: 15,
boxShadow: "none",
},
addSideBar: {
width: "320px",
padding: "20px",
},
errorBlock: {
color: "red",
},
tableToolbar: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(0),
},
wrapCell: {
maxWidth: "200px",
whiteSpace: "normal",
wordWrap: "break-word",
},
minTableHeader: {
color: "#393939",
"& tr": {
"& th": {
fontWeight: "bold",
},
},
},
noFound: {
textAlign: "center",
padding: "10px 0",
},
tableContainer: {
maxHeight: 200,
},
stickyHeader: {
backgroundColor: "#fff",
},
actionsTitle: {
fontWeight: 600,
color: "#000",
fontSize: 16,
alignSelf: "center",
},
tableBlock: {
marginTop: 15,
},
filterField: {
width: 375,
fontWeight: 600,
"& .input": {
"&::placeholder": {
fontWeight: 600,
color: "#000",
},
},
},
...actionsTray,
});
const PolicySelectors = ({
classes,
selectedPolicy = "",
setSelectedPolicy,
}: ISelectPolicyProps) => {
// Local State
const [records, setRecords] = useState<any[]>([]);
const [loading, isLoading] = useState<boolean>(false);
const [error, setError] = useState<string>("");
const [filter, setFilter] = useState<string>("");
//Effects
useEffect(() => {
isLoading(true);
}, []);
useEffect(() => {
if (loading) {
fetchPolicies();
}
}, [loading]);
const fetchPolicies = () => {
isLoading(true);
api
.invoke("GET", `/api/v1/policies?limit=1000`)
.then((res: PolicyList) => {
const policies = res.policies === null ? [] : res.policies;
isLoading(false);
setRecords(policies.sort(policySort));
setError("");
})
.catch((err) => {
isLoading(false);
setError(err);
});
};
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const targetD = e.target;
const value = targetD.value;
setSelectedPolicy(value);
};
const filteredRecords = records.filter((elementItem) =>
elementItem.name.includes(filter)
);
return (
<React.Fragment>
<Grid item xs={12}>
<Paper className={classes.paper}>
{loading && <LinearProgress />}
{error !== "" && <div>{error}</div>}
{records != null && records.length > 0 ? (
<React.Fragment>
<Grid item xs={12} className={classes.actionsTray}>
<span className={classes.actionsTitle}>Assign Policies</span>
<TextField
placeholder="Filter by Policy"
className={classes.filterField}
id="search-resource"
label=""
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
setFilter(e.target.value);
}}
/>
</Grid>
<Grid item xs={12} className={classes.tableBlock}>
<TableWrapper
columns={[{ label: "Policy", elementKey: "name" }]}
onSelect={selectionChanged}
selectedItems={[selectedPolicy]}
isLoading={loading}
records={filteredRecords}
entityName="Policies"
idField="name"
radioSelection
/>
</Grid>
</React.Fragment>
) : (
<div className={classes.noFound}>No Policies Available</div>
)}
</Paper>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(PolicySelectors);

View File

@@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useCallback, useEffect, useState } from "react";
import get from "lodash/get";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import {
Button,
@@ -28,13 +29,17 @@ import {
TableRow,
} from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import {
modalBasic,
predefinedList,
} from "../Common/FormComponents/common/styleLibrary";
import { User } from "../Users/types";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import { Policy, PolicyList } from "./types";
import api from "../../../common/api";
import { policySort } from "../../../utils/sortFunctions";
import { Group } from "../Groups/types";
import PolicySelectors from "./PolicySelectors";
interface ISetPolicyProps {
classes: any;
@@ -47,6 +52,7 @@ interface ISetPolicyProps {
const styles = (theme: Theme) =>
createStyles({
...modalBasic,
...predefinedList,
buttonContainer: {
textAlign: "right",
},
@@ -60,28 +66,12 @@ const SetPolicy = ({
open,
}: ISetPolicyProps) => {
//Local States
const [records, setRecords] = useState<Policy[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [actualPolicy, setActualPolicy] = useState<string>("");
const [selectedPolicy, setSelectedPolicy] = useState<string>("");
const [error, setError] = useState<string>("");
const fetchRecords = () => {
setLoading(true);
api
.invoke("GET", `/api/v1/policies?limit=1000`)
.then((res: PolicyList) => {
const policies = res.policies === null ? [] : res.policies;
setLoading(false);
setRecords(policies.sort(policySort));
setError("");
})
.catch((err) => {
setLoading(false);
setError(err);
});
};
const setPolicyAction = (policyName: string) => {
const setPolicyAction = () => {
let entity = "user";
let value = null;
if (selectedGroup !== null) {
@@ -96,7 +86,7 @@ const SetPolicy = ({
setLoading(true);
api
.invoke("PUT", `/api/v1/set-policy/${policyName}`, {
.invoke("PUT", `/api/v1/set-policy/${selectedPolicy}`, {
entityName: value,
entityType: entity,
})
@@ -111,71 +101,89 @@ const SetPolicy = ({
});
};
const fetchGroupInformation = () => {
if (selectedGroup) {
api
.invoke("GET", `/api/v1/groups/${selectedGroup}`)
.then((res: any) => {
const groupPolicy = get(res, "policy", "");
setActualPolicy(groupPolicy);
setSelectedPolicy(groupPolicy);
})
.catch((err) => {
setError(err);
setLoading(false);
});
}
};
const resetSelection = () => {
setSelectedPolicy(actualPolicy);
};
useEffect(() => {
if (open) {
fetchRecords();
if (selectedGroup !== null) {
fetchGroupInformation();
return;
}
const userPolicy = get(selectedUser, "policy", "");
setActualPolicy(userPolicy);
setSelectedPolicy(userPolicy);
}
}, [open]);
const userName = get(selectedUser, "accessKey", "");
return (
<ModalWrapper
onClose={() => {
closeModalAndRefresh();
}}
modalOpen={open}
title={
selectedUser !== null ? "Set Policy to User" : "Set Policy to Group"
}
title="Set Policies"
>
<Grid container className={classes.formScrollable}>
<Grid item xs={12}>
<TableContainer component={Paper}>
<Table
className={classes.table}
size="small"
aria-label="a dense table"
>
<TableHead>
<TableRow>
<TableCell>Policy</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{records.map((row) => (
<TableRow key={row.name}>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">
<Button
variant="contained"
color="primary"
size={"small"}
onClick={() => {
setPolicyAction(row.name);
}}
>
Set
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<Grid item xs={12}>
<Grid item xs={12} className={classes.predefinedTitle}>
Selected {selectedGroup !== null ? "Group" : "User"}
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{selectedGroup !== null ? selectedGroup : userName}
</Grid>
</Grid>
<Grid item xs={12}>
<Grid item xs={12} className={classes.predefinedTitle}>
Current Policy
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{actualPolicy}
</Grid>
</Grid>
<PolicySelectors
selectedPolicy={selectedPolicy}
setSelectedPolicy={setSelectedPolicy}
/>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetSelection}
>
Clear
</button>
<Button
type="submit"
type="button"
variant="contained"
color="primary"
onClick={() => {
closeModalAndRefresh();
}}
disabled={loading}
onClick={setPolicyAction}
>
Cancel
Save
</Button>
</Grid>
{loading && (

View File

@@ -16,19 +16,14 @@
import React, { useEffect, useState } from "react";
import Grid from "@material-ui/core/Grid";
import { UnControlled as CodeMirror } from "react-codemirror2";
import Typography from "@material-ui/core/Typography";
import { Button, LinearProgress, Tooltip } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import api from "../../../common/api";
import "codemirror/lib/codemirror.css";
import "codemirror/theme/material.css";
import { NewServiceAccount } from "../Common/CredentialsPrompt/types";
import HelpIcon from "@material-ui/icons/Help";
require("codemirror/mode/javascript/javascript");
import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
const styles = (theme: Theme) =>
createStyles({
@@ -39,9 +34,6 @@ const styles = (theme: Theme) =>
minHeight: 400,
width: "100%",
},
codeMirror: {
fontSize: 14,
},
buttonContainer: {
textAlign: "right",
},
@@ -92,6 +84,10 @@ const AddServiceAccount = ({
setAddSending(true);
};
const resetForm = () => {
setPolicyDefinition("");
};
return (
<ModalWrapper
modalOpen={open}
@@ -120,29 +116,24 @@ const AddServiceAccount = ({
</Typography>
</Grid>
)}
<Grid item xs={12}>
<Typography component="h5">
Optional Policy
<Tooltip
title="A policy that restricts this service account can be attached."
placement="top-start"
>
<HelpIcon />
</Tooltip>
</Typography>
<CodeMirror
className={classes.codeMirror}
options={{
mode: "javascript",
lineNumbers: true,
}}
onChange={(editor, data, value) => {
setPolicyDefinition(value);
}}
/>
</Grid>
<CodeMirrorWrapper
value={policyDefinition}
label="Optional Policy"
tooltip="A policy that restricts this service account can be attached."
onBeforeChange={(editor, data, value) => {
setPolicyDefinition(value);
}}
/>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetForm}
>
Clear
</button>
<Button
type="submit"
variant="contained"

View File

@@ -2113,6 +2113,7 @@ const AddTenant = ({
}}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
wideLimit={false}
>
{addSending && (
<Grid item xs={12}>

View File

@@ -248,7 +248,7 @@ const ListTenants = ({ classes }: ITenantsList) => {
entity="Tenant"
/>
)}
<PageHeader label={"Users"} />
<PageHeader label={"Tenants"} />
<Grid container>
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>

View File

@@ -26,12 +26,13 @@ import { wsProtocol } from "../../../utils/wsUtils";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
import { Grid } from "@material-ui/core";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
const styles = (theme: Theme) =>
createStyles({
logList: {
background: "white",
maxHeight: "400px",
height: "400px",
overflow: "auto",
"& ul": {
margin: "4px",
@@ -101,20 +102,50 @@ const Trace = ({
<PageHeader label={"Trace"} />
<Grid container>
<Grid item xs={12} className={classes.container}>
<div className={classes.logList}>
<ul>
{messages.map((m) => {
return (
<li key={m.key}>
{timeFromDate(m.time)} - {m.api}[{m.statusCode}{" "}
{m.statusMsg}] {m.api} {m.host} {m.client}{" "}
{m.callStats.duration} {niceBytes(m.callStats.rx + "")} {" "}
{niceBytes(m.callStats.tx + "")}
</li>
);
})}
</ul>
</div>
<TableWrapper
itemActions={[]}
columns={[
{
label: "Time",
elementKey: "time",
renderFunction: (time: Date) => {
const timeParse = new Date(time);
return timeFromDate(timeParse);
},
},
{ label: "Name", elementKey: "api" },
{
label: "Status",
elementKey: "",
renderFunction: (fullElement: TraceMessage) =>
`${fullElement.statusCode} ${fullElement.statusMsg}`,
renderFullObject: true,
},
{
label: "Location",
elementKey: "configuration_id",
renderFunction: (fullElement: TraceMessage) =>
`${fullElement.host} ${fullElement.client}`,
renderFullObject: true,
},
{ label: "Load Time", elementKey: "callStats.duration" },
{
label: "Upload",
elementKey: "callStats.rx",
renderFunction: niceBytes,
},
{
label: "Download",
elementKey: "callStats.tx",
renderFunction: niceBytes,
},
]}
isLoading={false}
records={messages}
entityName="Traces"
idField="api"
customEmptyMessage="There are no traced Elements yet"
/>
</Grid>
</Grid>
</React.Fragment>

View File

@@ -60,6 +60,7 @@ const AddToGroup = ({
}: IAddToGroup) => {
//Local States
const [saving, isSaving] = useState<boolean>(false);
const [accepted, setAccepted] = useState<boolean>(false);
const [updatingError, setError] = useState<string>("");
const [selectedGroups, setSelectedGroups] = useState<string[]>([]);
@@ -75,7 +76,7 @@ const AddToGroup = ({
.then((res) => {
isSaving(false);
setError("");
closeModalAndRefresh(true);
setAccepted(true);
})
.catch((err) => {
isSaving(false);
@@ -102,62 +103,100 @@ const AddToGroup = ({
isSaving(true);
};
const resetForm = () => {
setSelectedGroups([]);
};
return (
<ModalWrapper
modalOpen={open}
onClose={() => {
closeModalAndRefresh(false);
closeModalAndRefresh(accepted);
}}
title="Add Users to Group"
title={
accepted
? "The selected users were added to the following groups."
: "Add Users to Group"
}
>
<form noValidate autoComplete="off" onSubmit={setSaving}>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
{updatingError !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{updatingError}
</Typography>
</Grid>
)}
{accepted ? (
<React.Fragment>
<Grid container>
<Grid item xs={12} className={classes.predefinedTitle}>
Selected Users
Groups
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{selectedGroups.join(", ")}
</Grid>
<Grid item xs={12} className={classes.predefinedTitle}>
Users
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{checkedUsers.join(", ")}
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<GroupsSelectors
selectedGroups={selectedGroups}
setSelectedGroups={setSelectedGroups}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={saving}
>
Save
</Button>
</Grid>
{saving && (
<Grid item xs={12}>
<LinearProgress />
<br />
<br />
<br />
</React.Fragment>
) : (
<form noValidate autoComplete="off" onSubmit={setSaving}>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
{updatingError !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{updatingError}
</Typography>
</Grid>
)}
<Grid item xs={12} className={classes.predefinedTitle}>
Selected Users
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{checkedUsers.join(", ")}
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<GroupsSelectors
selectedGroups={selectedGroups}
setSelectedGroups={setSelectedGroups}
/>
</Grid>
</Grid>
)}
</Grid>
</form>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetForm}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={saving || selectedGroups.length < 1}
>
Save
</Button>
</Grid>
{saving && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</Grid>
</form>
)}
</ModalWrapper>
);
};

View File

@@ -19,13 +19,17 @@ import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { Button, LinearProgress } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import {
modalBasic,
predefinedList,
} from "../Common/FormComponents/common/styleLibrary";
import { User } from "./types";
import api from "../../../common/api";
import GroupsSelectors from "./GroupsSelectors";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
const styles = (theme: Theme) =>
createStyles({
@@ -42,6 +46,7 @@ const styles = (theme: Theme) =>
textAlign: "right",
},
...modalBasic,
...predefinedList,
});
interface IAddUserContentProps {
@@ -57,7 +62,8 @@ interface IAddUserContentState {
accessKey: string;
secretKey: string;
selectedGroups: string[];
enabled: string;
currentGroups: string[];
enabled: boolean;
}
class AddUserContent extends React.Component<
@@ -69,8 +75,9 @@ class AddUserContent extends React.Component<
addError: "",
accessKey: "",
secretKey: "",
enabled: "enabled",
enabled: false,
selectedGroups: [],
currentGroups: [],
};
componentDidMount(): void {
@@ -103,7 +110,7 @@ class AddUserContent extends React.Component<
if (selectedUser !== null) {
api
.invoke("PUT", `/api/v1/users/${selectedUser.accessKey}`, {
status: enabled,
status: enabled ? "enabled" : "disabled",
groups: selectedGroups,
})
.then((res) => {
@@ -167,7 +174,8 @@ class AddUserContent extends React.Component<
addError: "",
accessKey: res.accessKey,
selectedGroups: res.memberOf || [],
enabled: res.status,
currentGroups: res.memberOf || [],
enabled: res.status === "enabled",
});
})
.catch((err) => {
@@ -178,6 +186,16 @@ class AddUserContent extends React.Component<
});
}
resetForm() {
if (this.props.selectedUser !== null) {
this.setState({ selectedGroups: [] });
return;
}
this.setState({ accessKey: "", secretKey: "", selectedGroups: [] });
}
render() {
const { classes, selectedUser } = this.props;
const {
@@ -186,9 +204,14 @@ class AddUserContent extends React.Component<
accessKey,
secretKey,
selectedGroups,
currentGroups,
enabled,
} = this.state;
const sendEnabled =
accessKey.trim() !== "" &&
((secretKey.trim() !== "" && selectedUser === null) ||
selectedUser !== null);
return (
<ModalWrapper
onClose={() => {
@@ -197,6 +220,22 @@ class AddUserContent extends React.Component<
modalOpen={this.props.open}
title={selectedUser !== null ? "Edit User" : "Create User"}
>
{selectedUser !== null && (
<div className={classes.floatingEnabled}>
<FormSwitchWrapper
indicatorLabel={"Enabled"}
checked={enabled}
value={"user_enabled"}
id="user-status"
name="user-status"
onChange={(e) => {
this.setState({ enabled: e.target.checked });
}}
switchOnly
/>
</div>
)}
<React.Fragment>
<form
noValidate
@@ -231,19 +270,14 @@ class AddUserContent extends React.Component<
/>
{selectedUser !== null ? (
<RadioGroupSelector
currentSelection={enabled}
id="user-status"
name="user-status"
label="Status"
onChange={(e) => {
this.setState({ enabled: e.target.value });
}}
selectorOptions={[
{ label: "Enabled", value: "enabled" },
{ label: "Disabled", value: "disabled" },
]}
/>
<React.Fragment>
<Grid item xs={12} className={classes.predefinedTitle}>
Current Groups
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{currentGroups.join(", ")}
</Grid>
</React.Fragment>
) : (
<InputBoxWrapper
id="standard-multiline-static"
@@ -269,11 +303,21 @@ class AddUserContent extends React.Component<
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={() => {
this.resetForm();
}}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
disabled={addLoading || !sendEnabled}
>
Save
</Button>

View File

@@ -1,5 +1,5 @@
// This file is part of MinIO Console Server
// Copyright (c) 2019 MinIO, Inc.
// Copyright (c) 2020 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
@@ -283,6 +283,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
selectedGroup={null}
closeModalAndRefresh={() => {
this.setState({ setPolicyOpen: false });
this.fetchRecords();
}}
/>
)}

View File

@@ -22,6 +22,7 @@ export interface User {
enabled: boolean;
accessKey: string;
secretKey: string;
policy?: string;
}
export interface UsersList {

View File

@@ -14,7 +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, { useEffect, useState } from "react";
import { Button, Grid, Typography, TextField } from "@material-ui/core";
import {
Button,
Grid,
Typography,
TextField,
InputBase,
} from "@material-ui/core";
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
import { AppState } from "../../../store";
import { connect } from "react-redux";
@@ -25,14 +31,23 @@ import { niceBytes, timeFromDate } from "../../../common/utils";
import { wsProtocol } from "../../../utils/wsUtils";
import api from "../../../common/api";
import { FormControl, MenuItem, Select } from "@material-ui/core";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import {
actionsTray,
containerForHeader,
fieldBasic,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import { CreateIcon } from "../../../icons";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
const styles = (theme: Theme) =>
createStyles({
watchList: {
background: "white",
maxHeight: "400px",
height: "400px",
overflow: "auto",
"& ul": {
margin: "4px",
@@ -45,31 +60,34 @@ const styles = (theme: Theme) =>
borderBottom: "1px solid #dedede",
},
},
actionsTray: {
textAlign: "right",
"& button": {
marginLeft: 10,
},
},
inputField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
marginLeft: 10,
boxShadow: "0px 3px 6px #00000012",
},
fieldContainer: {
background: "#FFFFFF",
padding: 0,
borderRadius: 5,
marginLeft: 10,
textAlign: "left",
minWidth: "206px",
boxShadow: "0px 3px 6px #00000012",
searchPrefix: {
flexGrow: 1,
marginLeft: 15,
},
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
});
const SelectStyled = withStyles((theme: Theme) =>
createStyles({
root: {
width: 450,
lineHeight: 1,
"label + &": {
marginTop: theme.spacing(3),
},
"& .MuiSelect-select:focus": {
backgroundColor: "transparent",
},
},
input: {
fontSize: 13,
lineHeight: 15,
},
})
)(InputBase);
interface IWatch {
classes: any;
watchMessageReceived: typeof watchMessageReceived;
@@ -156,6 +174,7 @@ const Watch = ({
label: bucketName.name,
value: bucketName.name,
}));
return (
<React.Fragment>
<PageHeader label="Watch" />
@@ -170,8 +189,9 @@ const Watch = ({
onChange={(e) => {
setBucketName(e.target.value as string);
}}
className={classes.fieldContainer}
className={classes.searchField}
disabled={start}
input={<SelectStyled />}
>
<MenuItem
value={bucketName}
@@ -192,7 +212,7 @@ const Watch = ({
</FormControl>
<TextField
placeholder="Prefix"
className={classes.inputField}
className={`${classes.searchField} ${classes.searchPrefix}`}
id="prefix-resource"
label=""
disabled={start}
@@ -205,7 +225,7 @@ const Watch = ({
/>
<TextField
placeholder="Suffix"
className={classes.inputField}
className={`${classes.searchField} ${classes.searchPrefix}`}
id="suffix-resource"
label=""
disabled={start}
@@ -229,18 +249,27 @@ const Watch = ({
<Grid item xs={12}>
<br />
</Grid>
<div className={classes.watchList}>
<ul>
{messages.map((m) => {
return (
<li key={m.key}>
{timeFromDate(m.Time)} - {niceBytes(m.Size + "")} - {m.Type}{" "}
- {m.Path}
</li>
);
})}
</ul>
</div>
<TableWrapper
columns={[
{
label: "Time",
elementKey: "Time",
renderFunction: timeFromDate,
},
{
label: "Size",
elementKey: "Size",
renderFunction: niceBytes,
},
{ label: "Type", elementKey: "Type" },
{ label: "Path", elementKey: "Path" },
]}
records={messages}
entityName={"Watch"}
customEmptyMessage={"No Changes at this time"}
idField={"watch_table"}
isLoading={false}
/>
</Grid>
</Grid>
</React.Fragment>

File diff suppressed because it is too large Load Diff

View File

@@ -646,7 +646,6 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
},
Immutable: &imm,
Data: map[string][]byte{
"CONSOLE_HMAC_JWT_SECRET": []byte(RandomCharString(16)),
"CONSOLE_PBKDF_PASSPHRASE": []byte(RandomCharString(16)),
"CONSOLE_PBKDF_SALT": []byte(RandomCharString(8)),
"CONSOLE_ACCESS_KEY": []byte(consoleAccess),
@@ -680,7 +679,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
return nil, prepareError(errorGeneric)
}
const consoleVersion = "minio/console:v0.4.1"
const consoleVersion = "minio/console:v0.4.3"
minInst.Spec.Console = &operator.ConsoleConfiguration{
Replicas: 1,
Image: consoleVersion,

View File

@@ -1016,7 +1016,7 @@ func Test_UpdateTenantAction(t *testing.T) {
},
params: admin_api.UpdateTenantParams{
Body: &models.UpdateTenantRequest{
ConsoleImage: "minio/console:v0.4.1",
ConsoleImage: "minio/console:v0.4.3",
},
},
},

View File

@@ -58,6 +58,8 @@ type MinioClient interface {
listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo
getObjectRetention(ctx context.Context, bucketName, objectName, versionID string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error)
getObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error)
putObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error)
putObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error
}
// Interface implementation
@@ -128,6 +130,14 @@ func (c minioClient) getObjectLegalHold(ctx context.Context, bucketName, objectN
return c.client.GetObjectLegalHold(ctx, bucketName, objectName, opts)
}
func (c minioClient) putObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error) {
return c.client.PutObject(ctx, bucketName, objectName, reader, objectSize, opts)
}
func (c minioClient) putObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error {
return c.client.PutObjectLegalHold(ctx, bucketName, objectName, opts)
}
// MCClient interface with all functions to be implemented
// by mock when testing, it should include all mc/S3Client respective api calls
// that are used within this project.
@@ -138,6 +148,7 @@ type MCClient interface {
remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass bool, contentCh <-chan *mc.ClientContent) <-chan *probe.Error
list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent
get(ctx context.Context, opts mc.GetOptions) (io.ReadCloser, *probe.Error)
shareDownload(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error)
}
// Interface implementation
@@ -178,6 +189,10 @@ func (c mcClient) get(ctx context.Context, opts mc.GetOptions) (io.ReadCloser, *
return c.client.Get(ctx, opts)
}
func (c mcClient) shareDownload(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error) {
return c.client.ShareDownload(ctx, versionID, expires)
}
// ConsoleCredentials interface with all functions to be implemented
// by mock when testing, it should include all needed consoleCredentials.Login api calls
// that are used within this project.
@@ -255,7 +270,7 @@ func newConsoleCredentials(accessKey, secretKey, location string) (*credentials.
AccessKey: accessKey,
SecretKey: secretKey,
Location: location,
DurationSeconds: xjwt.GetConsoleSTSAndJWTDurationInSeconds(),
DurationSeconds: xjwt.GetConsoleSTSDurationInSeconds(),
}
stsClient := PrepareSTSClient(false)
stsAssumeRole := &credentials.STSAssumeRole{
@@ -269,23 +284,14 @@ func newConsoleCredentials(accessKey, secretKey, location string) (*credentials.
}
}
// GetClaimsFromJWT decrypt and returns the claims associated to a provided jwt
func GetClaimsFromJWT(jwt string) (*auth.DecryptedClaims, error) {
claims, err := auth.SessionTokenAuthenticate(jwt)
if err != nil {
return nil, err
}
return claims, nil
}
// getConsoleCredentialsFromSession returns the *consoleCredentials.Login associated to the
// provided jwt, this is useful for running the Expire() or IsExpired() operations
// provided session token, this is useful for running the Expire() or IsExpired() operations
func getConsoleCredentialsFromSession(claims *models.Principal) *credentials.Credentials {
return credentials.NewStaticV4(claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken)
}
// newMinioClient creates a new MinIO client based on the consoleCredentials extracted
// from the provided jwt
// from the provided session token
func newMinioClient(claims *models.Principal) (*minio.Client, error) {
creds := getConsoleCredentialsFromSession(claims)
stsClient := PrepareSTSClient(false)

View File

@@ -19,10 +19,12 @@
package restapi
import (
"bytes"
"crypto/tls"
"log"
"net/http"
"strings"
"time"
"github.com/minio/console/pkg/auth"
@@ -61,7 +63,7 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
// Applies when the "x-token" header is set
api.KeyAuth = func(token string, scopes []string) (*models.Principal, error) {
// we are validating the jwt by decrypting the claims inside, if the operation succed that means the jwt
// we are validating the session token by decrypting the claims inside, if the operation succeed that means the jwt
// was generated and signed by us in the first place
claims, err := auth.SessionTokenAuthenticate(token)
if err != nil {
@@ -229,8 +231,9 @@ func wrapHandlerSinglePageApplication(h http.Handler) http.HandlerFunc {
nfrw := &notFoundRedirectRespWr{ResponseWriter: w}
h.ServeHTTP(nfrw, r)
if nfrw.status == 404 {
log.Printf("Redirecting %s to index.html.", r.RequestURI)
http.Redirect(w, r, "/index.html", http.StatusFound)
indexPage, _ := portalUI.Asset("build/index.html")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
http.ServeContent(w, r, "index.html", time.Now(), bytes.NewReader(indexPage))
}
}
}

View File

@@ -27,6 +27,7 @@
//
// Consumes:
// - application/json
// - multipart/form-data
//
// Produces:
// - application/octet-stream

View File

@@ -469,6 +469,144 @@ func init() {
}
}
},
"/buckets/{bucket_name}/objects/legalhold": {
"put": {
"tags": [
"UserAPI"
],
"summary": "Put Object's legalhold status",
"operationId": "PutObjectLegalHold",
"parameters": [
{
"type": "string",
"name": "bucket_name",
"in": "path",
"required": true
},
{
"type": "string",
"name": "prefix",
"in": "query",
"required": true
},
{
"type": "string",
"name": "version_id",
"in": "query",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/putObjectLegalHoldRequest"
}
}
],
"responses": {
"200": {
"description": "A successful response."
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/buckets/{bucket_name}/objects/share": {
"get": {
"tags": [
"UserAPI"
],
"summary": "Shares an Object on a url",
"operationId": "ShareObject",
"parameters": [
{
"type": "string",
"name": "bucket_name",
"in": "path",
"required": true
},
{
"type": "string",
"name": "prefix",
"in": "query",
"required": true
},
{
"type": "string",
"name": "version_id",
"in": "query",
"required": true
},
{
"type": "string",
"name": "expires",
"in": "query"
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"type": "string"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/buckets/{bucket_name}/objects/upload": {
"post": {
"consumes": [
"multipart/form-data"
],
"tags": [
"UserAPI"
],
"summary": "Uploads an Object.",
"parameters": [
{
"type": "file",
"name": "upfile",
"in": "formData",
"required": true
},
{
"type": "string",
"name": "bucket_name",
"in": "path",
"required": true
},
{
"type": "string",
"name": "prefix",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "A successful response."
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/buckets/{bucket_name}/replication": {
"get": {
"tags": [
@@ -3605,6 +3743,13 @@ func init() {
"get"
]
},
"objectLegalHoldStatus": {
"type": "string",
"enum": [
"enabled",
"disabled"
]
},
"parityResponse": {
"type": "array",
"items": {
@@ -3735,6 +3880,17 @@ func init() {
}
}
},
"putObjectLegalHoldRequest": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"$ref": "#/definitions/objectLegalHoldStatus"
}
}
},
"remoteBucket": {
"type": "object",
"required": [
@@ -4920,6 +5076,144 @@ func init() {
}
}
},
"/buckets/{bucket_name}/objects/legalhold": {
"put": {
"tags": [
"UserAPI"
],
"summary": "Put Object's legalhold status",
"operationId": "PutObjectLegalHold",
"parameters": [
{
"type": "string",
"name": "bucket_name",
"in": "path",
"required": true
},
{
"type": "string",
"name": "prefix",
"in": "query",
"required": true
},
{
"type": "string",
"name": "version_id",
"in": "query",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/putObjectLegalHoldRequest"
}
}
],
"responses": {
"200": {
"description": "A successful response."
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/buckets/{bucket_name}/objects/share": {
"get": {
"tags": [
"UserAPI"
],
"summary": "Shares an Object on a url",
"operationId": "ShareObject",
"parameters": [
{
"type": "string",
"name": "bucket_name",
"in": "path",
"required": true
},
{
"type": "string",
"name": "prefix",
"in": "query",
"required": true
},
{
"type": "string",
"name": "version_id",
"in": "query",
"required": true
},
{
"type": "string",
"name": "expires",
"in": "query"
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"type": "string"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/buckets/{bucket_name}/objects/upload": {
"post": {
"consumes": [
"multipart/form-data"
],
"tags": [
"UserAPI"
],
"summary": "Uploads an Object.",
"parameters": [
{
"type": "file",
"name": "upfile",
"in": "formData",
"required": true
},
{
"type": "string",
"name": "bucket_name",
"in": "path",
"required": true
},
{
"type": "string",
"name": "prefix",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "A successful response."
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/buckets/{bucket_name}/replication": {
"get": {
"tags": [
@@ -8535,6 +8829,13 @@ func init() {
"get"
]
},
"objectLegalHoldStatus": {
"type": "string",
"enum": [
"enabled",
"disabled"
]
},
"parityResponse": {
"type": "array",
"items": {
@@ -8643,6 +8944,17 @@ func init() {
}
}
},
"putObjectLegalHoldRequest": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"$ref": "#/definitions/objectLegalHoldStatus"
}
}
},
"remoteBucket": {
"type": "object",
"required": [

View File

@@ -87,6 +87,10 @@ func prepareError(err ...error) *models.Error {
errorCode = 401
errorMessage = errorGenericInvalidSession.Error()
}
if madmin.ToErrorResponse(err[0]).Code == "InvalidAccessKeyId" {
errorCode = 401
errorMessage = errorGenericInvalidSession.Error()
}
// console invalid session error
if madmin.ToErrorResponse(err[0]).Code == "XMinioAdminNoSuchUser" {
errorCode = 401

View File

@@ -58,7 +58,8 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
APIKeyAuthenticator: security.APIKeyAuth,
BearerAuthenticator: security.BearerAuth,
JSONConsumer: runtime.JSONConsumer(),
JSONConsumer: runtime.JSONConsumer(),
MultipartformConsumer: runtime.DiscardConsumer,
BinProducer: runtime.ByteStreamProducer(),
JSONProducer: runtime.JSONProducer(),
@@ -213,12 +214,18 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
AdminAPIPolicyInfoHandler: admin_api.PolicyInfoHandlerFunc(func(params admin_api.PolicyInfoParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.PolicyInfo has not yet been implemented")
}),
UserAPIPostBucketsBucketNameObjectsUploadHandler: user_api.PostBucketsBucketNameObjectsUploadHandlerFunc(func(params user_api.PostBucketsBucketNameObjectsUploadParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.PostBucketsBucketNameObjectsUpload has not yet been implemented")
}),
AdminAPIProfilingStartHandler: admin_api.ProfilingStartHandlerFunc(func(params admin_api.ProfilingStartParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.ProfilingStart has not yet been implemented")
}),
AdminAPIProfilingStopHandler: admin_api.ProfilingStopHandlerFunc(func(params admin_api.ProfilingStopParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.ProfilingStop has not yet been implemented")
}),
UserAPIPutObjectLegalHoldHandler: user_api.PutObjectLegalHoldHandlerFunc(func(params user_api.PutObjectLegalHoldParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.PutObjectLegalHold has not yet been implemented")
}),
UserAPIRemoteBucketDetailsHandler: user_api.RemoteBucketDetailsHandlerFunc(func(params user_api.RemoteBucketDetailsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.RemoteBucketDetails has not yet been implemented")
}),
@@ -249,6 +256,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
AdminAPISetPolicyHandler: admin_api.SetPolicyHandlerFunc(func(params admin_api.SetPolicyParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.SetPolicy has not yet been implemented")
}),
UserAPIShareObjectHandler: user_api.ShareObjectHandlerFunc(func(params user_api.ShareObjectParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.ShareObject has not yet been implemented")
}),
AdminAPITenantAddZoneHandler: admin_api.TenantAddZoneHandlerFunc(func(params admin_api.TenantAddZoneParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.TenantAddZone has not yet been implemented")
}),
@@ -310,6 +320,9 @@ type ConsoleAPI struct {
// JSONConsumer registers a consumer for the following mime types:
// - application/json
JSONConsumer runtime.Consumer
// MultipartformConsumer registers a consumer for the following mime types:
// - multipart/form-data
MultipartformConsumer runtime.Consumer
// BinProducer registers a producer for the following mime types:
// - application/octet-stream
@@ -425,10 +438,14 @@ type ConsoleAPI struct {
AdminAPINotificationEndpointListHandler admin_api.NotificationEndpointListHandler
// AdminAPIPolicyInfoHandler sets the operation handler for the policy info operation
AdminAPIPolicyInfoHandler admin_api.PolicyInfoHandler
// UserAPIPostBucketsBucketNameObjectsUploadHandler sets the operation handler for the post buckets bucket name objects upload operation
UserAPIPostBucketsBucketNameObjectsUploadHandler user_api.PostBucketsBucketNameObjectsUploadHandler
// AdminAPIProfilingStartHandler sets the operation handler for the profiling start operation
AdminAPIProfilingStartHandler admin_api.ProfilingStartHandler
// AdminAPIProfilingStopHandler sets the operation handler for the profiling stop operation
AdminAPIProfilingStopHandler admin_api.ProfilingStopHandler
// UserAPIPutObjectLegalHoldHandler sets the operation handler for the put object legal hold operation
UserAPIPutObjectLegalHoldHandler user_api.PutObjectLegalHoldHandler
// UserAPIRemoteBucketDetailsHandler sets the operation handler for the remote bucket details operation
UserAPIRemoteBucketDetailsHandler user_api.RemoteBucketDetailsHandler
// AdminAPIRemoveGroupHandler sets the operation handler for the remove group operation
@@ -449,6 +466,8 @@ type ConsoleAPI struct {
AdminAPISetConfigHandler admin_api.SetConfigHandler
// AdminAPISetPolicyHandler sets the operation handler for the set policy operation
AdminAPISetPolicyHandler admin_api.SetPolicyHandler
// UserAPIShareObjectHandler sets the operation handler for the share object operation
UserAPIShareObjectHandler user_api.ShareObjectHandler
// AdminAPITenantAddZoneHandler sets the operation handler for the tenant add zone operation
AdminAPITenantAddZoneHandler admin_api.TenantAddZoneHandler
// AdminAPITenantInfoHandler sets the operation handler for the tenant info operation
@@ -528,6 +547,9 @@ func (o *ConsoleAPI) Validate() error {
if o.JSONConsumer == nil {
unregistered = append(unregistered, "JSONConsumer")
}
if o.MultipartformConsumer == nil {
unregistered = append(unregistered, "MultipartformConsumer")
}
if o.BinProducer == nil {
unregistered = append(unregistered, "BinProducer")
@@ -690,12 +712,18 @@ func (o *ConsoleAPI) Validate() error {
if o.AdminAPIPolicyInfoHandler == nil {
unregistered = append(unregistered, "admin_api.PolicyInfoHandler")
}
if o.UserAPIPostBucketsBucketNameObjectsUploadHandler == nil {
unregistered = append(unregistered, "user_api.PostBucketsBucketNameObjectsUploadHandler")
}
if o.AdminAPIProfilingStartHandler == nil {
unregistered = append(unregistered, "admin_api.ProfilingStartHandler")
}
if o.AdminAPIProfilingStopHandler == nil {
unregistered = append(unregistered, "admin_api.ProfilingStopHandler")
}
if o.UserAPIPutObjectLegalHoldHandler == nil {
unregistered = append(unregistered, "user_api.PutObjectLegalHoldHandler")
}
if o.UserAPIRemoteBucketDetailsHandler == nil {
unregistered = append(unregistered, "user_api.RemoteBucketDetailsHandler")
}
@@ -726,6 +754,9 @@ func (o *ConsoleAPI) Validate() error {
if o.AdminAPISetPolicyHandler == nil {
unregistered = append(unregistered, "admin_api.SetPolicyHandler")
}
if o.UserAPIShareObjectHandler == nil {
unregistered = append(unregistered, "user_api.ShareObjectHandler")
}
if o.AdminAPITenantAddZoneHandler == nil {
unregistered = append(unregistered, "admin_api.TenantAddZoneHandler")
}
@@ -794,6 +825,8 @@ func (o *ConsoleAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consum
switch mt {
case "application/json":
result["application/json"] = o.JSONConsumer
case "multipart/form-data":
result["multipart/form-data"] = o.MultipartformConsumer
}
if c, ok := o.customConsumers[mt]; ok {
@@ -1056,11 +1089,19 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/buckets/{bucket_name}/objects/upload"] = user_api.NewPostBucketsBucketNameObjectsUpload(o.context, o.UserAPIPostBucketsBucketNameObjectsUploadHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/profiling/start"] = admin_api.NewProfilingStart(o.context, o.AdminAPIProfilingStartHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/profiling/stop"] = admin_api.NewProfilingStop(o.context, o.AdminAPIProfilingStopHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/buckets/{bucket_name}/objects/legalhold"] = user_api.NewPutObjectLegalHold(o.context, o.UserAPIPutObjectLegalHoldHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
@@ -1101,6 +1142,10 @@ func (o *ConsoleAPI) initHandlerCache() {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/set-policy/{name}"] = admin_api.NewSetPolicy(o.context, o.AdminAPISetPolicyHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{bucket_name}/objects/share"] = user_api.NewShareObject(o.context, o.UserAPIShareObjectHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}

View File

@@ -0,0 +1,90 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// PostBucketsBucketNameObjectsUploadHandlerFunc turns a function with the right signature into a post buckets bucket name objects upload handler
type PostBucketsBucketNameObjectsUploadHandlerFunc func(PostBucketsBucketNameObjectsUploadParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn PostBucketsBucketNameObjectsUploadHandlerFunc) Handle(params PostBucketsBucketNameObjectsUploadParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// PostBucketsBucketNameObjectsUploadHandler interface for that can handle valid post buckets bucket name objects upload params
type PostBucketsBucketNameObjectsUploadHandler interface {
Handle(PostBucketsBucketNameObjectsUploadParams, *models.Principal) middleware.Responder
}
// NewPostBucketsBucketNameObjectsUpload creates a new http.Handler for the post buckets bucket name objects upload operation
func NewPostBucketsBucketNameObjectsUpload(ctx *middleware.Context, handler PostBucketsBucketNameObjectsUploadHandler) *PostBucketsBucketNameObjectsUpload {
return &PostBucketsBucketNameObjectsUpload{Context: ctx, Handler: handler}
}
/*PostBucketsBucketNameObjectsUpload swagger:route POST /buckets/{bucket_name}/objects/upload UserAPI postBucketsBucketNameObjectsUpload
Uploads an Object.
*/
type PostBucketsBucketNameObjectsUpload struct {
Context *middleware.Context
Handler PostBucketsBucketNameObjectsUploadHandler
}
func (o *PostBucketsBucketNameObjectsUpload) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewPostBucketsBucketNameObjectsUploadParams()
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)
}

View File

@@ -0,0 +1,156 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"mime/multipart"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// NewPostBucketsBucketNameObjectsUploadParams creates a new PostBucketsBucketNameObjectsUploadParams object
// no default values defined in spec.
func NewPostBucketsBucketNameObjectsUploadParams() PostBucketsBucketNameObjectsUploadParams {
return PostBucketsBucketNameObjectsUploadParams{}
}
// PostBucketsBucketNameObjectsUploadParams contains all the bound params for the post buckets bucket name objects upload operation
// typically these are obtained from a http.Request
//
// swagger:parameters PostBucketsBucketNameObjectsUpload
type PostBucketsBucketNameObjectsUploadParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: path
*/
BucketName string
/*
Required: true
In: query
*/
Prefix string
/*
Required: true
In: formData
*/
Upfile io.ReadCloser
}
// 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 NewPostBucketsBucketNameObjectsUploadParams() beforehand.
func (o *PostBucketsBucketNameObjectsUploadParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
if err := r.ParseMultipartForm(32 << 20); err != nil {
if err != http.ErrNotMultipart {
return errors.New(400, "%v", err)
} else if err := r.ParseForm(); err != nil {
return errors.New(400, "%v", err)
}
}
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
qPrefix, qhkPrefix, _ := qs.GetOK("prefix")
if err := o.bindPrefix(qPrefix, qhkPrefix, route.Formats); err != nil {
res = append(res, err)
}
upfile, upfileHeader, err := r.FormFile("upfile")
if err != nil {
res = append(res, errors.New(400, "reading file %q failed: %v", "upfile", err))
} else if err := o.bindUpfile(upfile, upfileHeader); err != nil {
// Required: true
res = append(res, err)
} else {
o.Upfile = &runtime.File{Data: upfile, Header: upfileHeader}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindBucketName binds and validates parameter BucketName from path.
func (o *PostBucketsBucketNameObjectsUploadParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.BucketName = raw
return nil
}
// bindPrefix binds and validates parameter Prefix from query.
func (o *PostBucketsBucketNameObjectsUploadParams) bindPrefix(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("prefix", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("prefix", "query", raw); err != nil {
return err
}
o.Prefix = raw
return nil
}
// bindUpfile binds file parameter Upfile.
//
// The only supported validations on files are MinLength and MaxLength
func (o *PostBucketsBucketNameObjectsUploadParams) bindUpfile(file multipart.File, header *multipart.FileHeader) error {
return nil
}

View File

@@ -0,0 +1,113 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// PostBucketsBucketNameObjectsUploadOKCode is the HTTP code returned for type PostBucketsBucketNameObjectsUploadOK
const PostBucketsBucketNameObjectsUploadOKCode int = 200
/*PostBucketsBucketNameObjectsUploadOK A successful response.
swagger:response postBucketsBucketNameObjectsUploadOK
*/
type PostBucketsBucketNameObjectsUploadOK struct {
}
// NewPostBucketsBucketNameObjectsUploadOK creates PostBucketsBucketNameObjectsUploadOK with default headers values
func NewPostBucketsBucketNameObjectsUploadOK() *PostBucketsBucketNameObjectsUploadOK {
return &PostBucketsBucketNameObjectsUploadOK{}
}
// WriteResponse to the client
func (o *PostBucketsBucketNameObjectsUploadOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(200)
}
/*PostBucketsBucketNameObjectsUploadDefault Generic error response.
swagger:response postBucketsBucketNameObjectsUploadDefault
*/
type PostBucketsBucketNameObjectsUploadDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewPostBucketsBucketNameObjectsUploadDefault creates PostBucketsBucketNameObjectsUploadDefault with default headers values
func NewPostBucketsBucketNameObjectsUploadDefault(code int) *PostBucketsBucketNameObjectsUploadDefault {
if code <= 0 {
code = 500
}
return &PostBucketsBucketNameObjectsUploadDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the post buckets bucket name objects upload default response
func (o *PostBucketsBucketNameObjectsUploadDefault) WithStatusCode(code int) *PostBucketsBucketNameObjectsUploadDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the post buckets bucket name objects upload default response
func (o *PostBucketsBucketNameObjectsUploadDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the post buckets bucket name objects upload default response
func (o *PostBucketsBucketNameObjectsUploadDefault) WithPayload(payload *models.Error) *PostBucketsBucketNameObjectsUploadDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the post buckets bucket name objects upload default response
func (o *PostBucketsBucketNameObjectsUploadDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PostBucketsBucketNameObjectsUploadDefault) 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
}
}
}

View File

@@ -0,0 +1,127 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// PostBucketsBucketNameObjectsUploadURL generates an URL for the post buckets bucket name objects upload operation
type PostBucketsBucketNameObjectsUploadURL struct {
BucketName string
Prefix string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *PostBucketsBucketNameObjectsUploadURL) WithBasePath(bp string) *PostBucketsBucketNameObjectsUploadURL {
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 *PostBucketsBucketNameObjectsUploadURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *PostBucketsBucketNameObjectsUploadURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{bucket_name}/objects/upload"
bucketName := o.BucketName
if bucketName != "" {
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
} else {
return nil, errors.New("bucketName is required on PostBucketsBucketNameObjectsUploadURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
prefixQ := o.Prefix
if prefixQ != "" {
qs.Set("prefix", prefixQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *PostBucketsBucketNameObjectsUploadURL) 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 *PostBucketsBucketNameObjectsUploadURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *PostBucketsBucketNameObjectsUploadURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on PostBucketsBucketNameObjectsUploadURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on PostBucketsBucketNameObjectsUploadURL")
}
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 *PostBucketsBucketNameObjectsUploadURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,90 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// PutObjectLegalHoldHandlerFunc turns a function with the right signature into a put object legal hold handler
type PutObjectLegalHoldHandlerFunc func(PutObjectLegalHoldParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn PutObjectLegalHoldHandlerFunc) Handle(params PutObjectLegalHoldParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// PutObjectLegalHoldHandler interface for that can handle valid put object legal hold params
type PutObjectLegalHoldHandler interface {
Handle(PutObjectLegalHoldParams, *models.Principal) middleware.Responder
}
// NewPutObjectLegalHold creates a new http.Handler for the put object legal hold operation
func NewPutObjectLegalHold(ctx *middleware.Context, handler PutObjectLegalHoldHandler) *PutObjectLegalHold {
return &PutObjectLegalHold{Context: ctx, Handler: handler}
}
/*PutObjectLegalHold swagger:route PUT /buckets/{bucket_name}/objects/legalhold UserAPI putObjectLegalHold
Put Object's legalhold status
*/
type PutObjectLegalHold struct {
Context *middleware.Context
Handler PutObjectLegalHoldHandler
}
func (o *PutObjectLegalHold) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewPutObjectLegalHoldParams()
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)
}

View File

@@ -0,0 +1,185 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewPutObjectLegalHoldParams creates a new PutObjectLegalHoldParams object
// no default values defined in spec.
func NewPutObjectLegalHoldParams() PutObjectLegalHoldParams {
return PutObjectLegalHoldParams{}
}
// PutObjectLegalHoldParams contains all the bound params for the put object legal hold operation
// typically these are obtained from a http.Request
//
// swagger:parameters PutObjectLegalHold
type PutObjectLegalHoldParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.PutObjectLegalHoldRequest
/*
Required: true
In: path
*/
BucketName string
/*
Required: true
In: query
*/
Prefix string
/*
Required: true
In: query
*/
VersionID 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 NewPutObjectLegalHoldParams() beforehand.
func (o *PutObjectLegalHoldParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.PutObjectLegalHoldRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
qPrefix, qhkPrefix, _ := qs.GetOK("prefix")
if err := o.bindPrefix(qPrefix, qhkPrefix, route.Formats); err != nil {
res = append(res, err)
}
qVersionID, qhkVersionID, _ := qs.GetOK("version_id")
if err := o.bindVersionID(qVersionID, qhkVersionID, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindBucketName binds and validates parameter BucketName from path.
func (o *PutObjectLegalHoldParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.BucketName = raw
return nil
}
// bindPrefix binds and validates parameter Prefix from query.
func (o *PutObjectLegalHoldParams) bindPrefix(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("prefix", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("prefix", "query", raw); err != nil {
return err
}
o.Prefix = raw
return nil
}
// bindVersionID binds and validates parameter VersionID from query.
func (o *PutObjectLegalHoldParams) bindVersionID(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("version_id", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("version_id", "query", raw); err != nil {
return err
}
o.VersionID = raw
return nil
}

View File

@@ -0,0 +1,113 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// PutObjectLegalHoldOKCode is the HTTP code returned for type PutObjectLegalHoldOK
const PutObjectLegalHoldOKCode int = 200
/*PutObjectLegalHoldOK A successful response.
swagger:response putObjectLegalHoldOK
*/
type PutObjectLegalHoldOK struct {
}
// NewPutObjectLegalHoldOK creates PutObjectLegalHoldOK with default headers values
func NewPutObjectLegalHoldOK() *PutObjectLegalHoldOK {
return &PutObjectLegalHoldOK{}
}
// WriteResponse to the client
func (o *PutObjectLegalHoldOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(200)
}
/*PutObjectLegalHoldDefault Generic error response.
swagger:response putObjectLegalHoldDefault
*/
type PutObjectLegalHoldDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewPutObjectLegalHoldDefault creates PutObjectLegalHoldDefault with default headers values
func NewPutObjectLegalHoldDefault(code int) *PutObjectLegalHoldDefault {
if code <= 0 {
code = 500
}
return &PutObjectLegalHoldDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the put object legal hold default response
func (o *PutObjectLegalHoldDefault) WithStatusCode(code int) *PutObjectLegalHoldDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the put object legal hold default response
func (o *PutObjectLegalHoldDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the put object legal hold default response
func (o *PutObjectLegalHoldDefault) WithPayload(payload *models.Error) *PutObjectLegalHoldDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the put object legal hold default response
func (o *PutObjectLegalHoldDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PutObjectLegalHoldDefault) 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
}
}
}

View File

@@ -0,0 +1,133 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// PutObjectLegalHoldURL generates an URL for the put object legal hold operation
type PutObjectLegalHoldURL struct {
BucketName string
Prefix string
VersionID 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 *PutObjectLegalHoldURL) WithBasePath(bp string) *PutObjectLegalHoldURL {
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 *PutObjectLegalHoldURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *PutObjectLegalHoldURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{bucket_name}/objects/legalhold"
bucketName := o.BucketName
if bucketName != "" {
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
} else {
return nil, errors.New("bucketName is required on PutObjectLegalHoldURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
prefixQ := o.Prefix
if prefixQ != "" {
qs.Set("prefix", prefixQ)
}
versionIDQ := o.VersionID
if versionIDQ != "" {
qs.Set("version_id", versionIDQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *PutObjectLegalHoldURL) 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 *PutObjectLegalHoldURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *PutObjectLegalHoldURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on PutObjectLegalHoldURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on PutObjectLegalHoldURL")
}
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 *PutObjectLegalHoldURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,90 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// ShareObjectHandlerFunc turns a function with the right signature into a share object handler
type ShareObjectHandlerFunc func(ShareObjectParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn ShareObjectHandlerFunc) Handle(params ShareObjectParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// ShareObjectHandler interface for that can handle valid share object params
type ShareObjectHandler interface {
Handle(ShareObjectParams, *models.Principal) middleware.Responder
}
// NewShareObject creates a new http.Handler for the share object operation
func NewShareObject(ctx *middleware.Context, handler ShareObjectHandler) *ShareObject {
return &ShareObject{Context: ctx, Handler: handler}
}
/*ShareObject swagger:route GET /buckets/{bucket_name}/objects/share UserAPI shareObject
Shares an Object on a url
*/
type ShareObject struct {
Context *middleware.Context
Handler ShareObjectHandler
}
func (o *ShareObject) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewShareObjectParams()
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)
}

View File

@@ -0,0 +1,182 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// NewShareObjectParams creates a new ShareObjectParams object
// no default values defined in spec.
func NewShareObjectParams() ShareObjectParams {
return ShareObjectParams{}
}
// ShareObjectParams contains all the bound params for the share object operation
// typically these are obtained from a http.Request
//
// swagger:parameters ShareObject
type ShareObjectParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: path
*/
BucketName string
/*
In: query
*/
Expires *string
/*
Required: true
In: query
*/
Prefix string
/*
Required: true
In: query
*/
VersionID 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 NewShareObjectParams() beforehand.
func (o *ShareObjectParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
qExpires, qhkExpires, _ := qs.GetOK("expires")
if err := o.bindExpires(qExpires, qhkExpires, route.Formats); err != nil {
res = append(res, err)
}
qPrefix, qhkPrefix, _ := qs.GetOK("prefix")
if err := o.bindPrefix(qPrefix, qhkPrefix, route.Formats); err != nil {
res = append(res, err)
}
qVersionID, qhkVersionID, _ := qs.GetOK("version_id")
if err := o.bindVersionID(qVersionID, qhkVersionID, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindBucketName binds and validates parameter BucketName from path.
func (o *ShareObjectParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.BucketName = raw
return nil
}
// bindExpires binds and validates parameter Expires from query.
func (o *ShareObjectParams) bindExpires(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
o.Expires = &raw
return nil
}
// bindPrefix binds and validates parameter Prefix from query.
func (o *ShareObjectParams) bindPrefix(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("prefix", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("prefix", "query", raw); err != nil {
return err
}
o.Prefix = raw
return nil
}
// bindVersionID binds and validates parameter VersionID from query.
func (o *ShareObjectParams) bindVersionID(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("version_id", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("version_id", "query", raw); err != nil {
return err
}
o.VersionID = raw
return nil
}

View File

@@ -0,0 +1,131 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// ShareObjectOKCode is the HTTP code returned for type ShareObjectOK
const ShareObjectOKCode int = 200
/*ShareObjectOK A successful response.
swagger:response shareObjectOK
*/
type ShareObjectOK struct {
/*
In: Body
*/
Payload string `json:"body,omitempty"`
}
// NewShareObjectOK creates ShareObjectOK with default headers values
func NewShareObjectOK() *ShareObjectOK {
return &ShareObjectOK{}
}
// WithPayload adds the payload to the share object o k response
func (o *ShareObjectOK) WithPayload(payload string) *ShareObjectOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the share object o k response
func (o *ShareObjectOK) SetPayload(payload string) {
o.Payload = payload
}
// WriteResponse to the client
func (o *ShareObjectOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
/*ShareObjectDefault Generic error response.
swagger:response shareObjectDefault
*/
type ShareObjectDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewShareObjectDefault creates ShareObjectDefault with default headers values
func NewShareObjectDefault(code int) *ShareObjectDefault {
if code <= 0 {
code = 500
}
return &ShareObjectDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the share object default response
func (o *ShareObjectDefault) WithStatusCode(code int) *ShareObjectDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the share object default response
func (o *ShareObjectDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the share object default response
func (o *ShareObjectDefault) WithPayload(payload *models.Error) *ShareObjectDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the share object default response
func (o *ShareObjectDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *ShareObjectDefault) 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
}
}
}

View File

@@ -0,0 +1,142 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// ShareObjectURL generates an URL for the share object operation
type ShareObjectURL struct {
BucketName string
Expires *string
Prefix string
VersionID 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 *ShareObjectURL) WithBasePath(bp string) *ShareObjectURL {
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 *ShareObjectURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *ShareObjectURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{bucket_name}/objects/share"
bucketName := o.BucketName
if bucketName != "" {
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
} else {
return nil, errors.New("bucketName is required on ShareObjectURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
var expiresQ string
if o.Expires != nil {
expiresQ = *o.Expires
}
if expiresQ != "" {
qs.Set("expires", expiresQ)
}
prefixQ := o.Prefix
if prefixQ != "" {
qs.Set("prefix", prefixQ)
}
versionIDQ := o.VersionID
if versionIDQ != "" {
qs.Set("version_id", versionIDQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *ShareObjectURL) 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 *ShareObjectURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *ShareObjectURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on ShareObjectURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on ShareObjectURL")
}
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 *ShareObjectURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -72,12 +72,12 @@ func login(credentials ConsoleCredentials, actions []string) (*string, error) {
return nil, err
}
// if we made it here, the consoleCredentials work, generate a jwt with claims
jwt, err := auth.NewEncryptedTokenForClient(&tokens, actions)
token, err := auth.NewEncryptedTokenForClient(&tokens, actions)
if err != nil {
log.Println("error authenticating user", err)
return nil, errInvalidCredentials
}
return &jwt, nil
return &token, nil
}
func getConfiguredRegionForLogin(client MinioAdmin) (string, error) {
@@ -224,19 +224,19 @@ func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.Logi
return nil, prepareError(err)
}
actions := acl.GetActionsStringFromPolicy(policy)
// User was created correctly, create a new session/JWT
// User was created correctly, create a new session
creds, err := newConsoleCredentials(accessKey, secretKey, location)
if err != nil {
return nil, prepareError(err)
}
credentials := consoleCredentials{consoleCredentials: creds}
jwt, err := login(credentials, actions)
token, err := login(credentials, actions)
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{
SessionID: *jwt,
SessionID: *token,
}
return loginResponse, nil
}
@@ -251,13 +251,13 @@ func getLoginOperatorResponse(lmr *models.LoginOperatorRequest) (*models.LoginRe
}
credentials := consoleCredentials{consoleCredentials: creds}
var actions []string
jwt, err := login(credentials, actions)
token, err := login(credentials, actions)
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{
SessionID: *jwt,
SessionID: *token,
}
return loginResponse, nil
}

View File

@@ -52,8 +52,8 @@ func TestLogin(t *testing.T) {
SignerType: 0,
}, nil
}
jwt, err := login(consoleCredentials, []string{""})
funcAssert.NotEmpty(jwt, "JWT was returned empty")
token, err := login(consoleCredentials, []string{""})
funcAssert.NotEmpty(token, "Token was returned empty")
funcAssert.Nil(err, "error creating a session")
// Test Case 2: Invalid credentials

View File

@@ -27,6 +27,8 @@ import (
"strings"
"time"
"errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
@@ -75,6 +77,28 @@ func registerObjectsHandlers(api *operations.ConsoleAPI) {
}
})
})
// upload object
api.UserAPIPostBucketsBucketNameObjectsUploadHandler = user_api.PostBucketsBucketNameObjectsUploadHandlerFunc(func(params user_api.PostBucketsBucketNameObjectsUploadParams, session *models.Principal) middleware.Responder {
if err := getUploadObjectResponse(session, params); err != nil {
return user_api.NewPostBucketsBucketNameObjectsUploadDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewPostBucketsBucketNameObjectsUploadOK()
})
// get share object url
api.UserAPIShareObjectHandler = user_api.ShareObjectHandlerFunc(func(params user_api.ShareObjectParams, session *models.Principal) middleware.Responder {
resp, err := getShareObjectResponse(session, params)
if err != nil {
return user_api.NewShareObjectDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewShareObjectOK().WithPayload(*resp)
})
// set object legalhold status
api.UserAPIPutObjectLegalHoldHandler = user_api.PutObjectLegalHoldHandlerFunc(func(params user_api.PutObjectLegalHoldParams, session *models.Principal) middleware.Responder {
if err := getSetObjectLegalHoldResponse(session, params); err != nil {
return user_api.NewPutObjectLegalHoldDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewPutObjectLegalHoldOK()
})
}
// getListObjectsResponse returns a list of objects
@@ -139,10 +163,9 @@ func listBucketObjects(ctx context.Context, client MinioClient, bucketName strin
legalHoldStatus, err := client.getObjectLegalHold(ctx, bucketName, lsObj.Key, minio.GetObjectLegalHoldOptions{VersionID: lsObj.VersionID})
if err != nil {
errResp := minio.ToErrorResponse(probe.NewError(err).ToGoError())
if errResp.Code != "NoSuchObjectLockConfiguration" {
if errResp.Code != "InvalidRequest" && errResp.Code != "NoSuchObjectLockConfiguration" {
log.Printf("error getting legal hold status for %s : %s", lsObj.VersionID, err)
}
} else {
if legalHoldStatus != nil {
obj.LegalHoldStatus = string(*legalHoldStatus)
@@ -246,7 +269,6 @@ func deleteMultipleObjects(ctx context.Context, client MCClient, recursive bool)
contentCh := make(chan *mc.ClientContent, 1)
errorCh := client.remove(ctx, isIncomplete, isRemoveBucket, isBypass, contentCh)
OUTER_LOOP:
for content := range client.list(ctx, listOpts) {
if content.Err != nil {
switch content.Err.ToGoError().(type) {
@@ -270,7 +292,7 @@ OUTER_LOOP:
// Ignore Permission error.
continue
}
break OUTER_LOOP
return pErr.Cause
}
}
}
@@ -315,6 +337,108 @@ func deleteSingleObject(ctx context.Context, client MCClient, bucket, object str
return nil
}
func getUploadObjectResponse(session *models.Principal, params user_api.PostBucketsBucketNameObjectsUploadParams) *models.Error {
ctx := context.Background()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
}
// get size from request form
var objectSize int64
if params.HTTPRequest.MultipartForm == nil {
return prepareError(errors.New("request MultipartForm is nil"))
}
if file, ok := params.HTTPRequest.MultipartForm.File["upfile"]; ok {
if len(file) > 0 {
objectSize = file[0].Size
} else {
return prepareError(errors.New("file not present in request"))
}
} else {
return prepareError(errors.New("`upfile` should be on MultipartForm.File map"))
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
if err := uploadObject(ctx, minioClient, params.BucketName, params.Prefix, objectSize, params.Upfile); err != nil {
return prepareError(err)
}
return nil
}
func uploadObject(ctx context.Context, client MinioClient, bucketName, prefix string, objectSize int64, object io.ReadCloser) error {
_, err := client.putObject(ctx, bucketName, prefix, object, objectSize, minio.PutObjectOptions{ContentType: "application/octet-stream"})
if err != nil {
return err
}
return nil
}
// getShareObjectResponse returns a share object url
func getShareObjectResponse(session *models.Principal, params user_api.ShareObjectParams) (*string, *models.Error) {
ctx := context.Background()
s3Client, err := newS3BucketClient(session, params.BucketName, params.Prefix)
if err != nil {
return nil, prepareError(err)
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
var expireDuration string
if params.Expires != nil {
expireDuration = *params.Expires
}
url, err := getShareObjectURL(ctx, mcClient, params.VersionID, expireDuration)
if err != nil {
return nil, prepareError(err)
}
return url, nil
}
func getShareObjectURL(ctx context.Context, client MCClient, versionID string, duration string) (url *string, err error) {
// default duration 7d if not defined
if strings.TrimSpace(duration) == "" {
duration = "168h"
}
expiresDuration, err := time.ParseDuration(duration)
if err != nil {
return nil, err
}
objURL, pErr := client.shareDownload(ctx, versionID, expiresDuration)
if pErr != nil {
return nil, pErr.Cause
}
return &objURL, nil
}
func getSetObjectLegalHoldResponse(session *models.Principal, params user_api.PutObjectLegalHoldParams) *models.Error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
err = setObjectLegalHold(ctx, minioClient, params.BucketName, params.Prefix, params.VersionID, params.Body.Status)
if err != nil {
return prepareError(err)
}
return nil
}
func setObjectLegalHold(ctx context.Context, client MinioClient, bucketName, prefix, versionID string, status models.ObjectLegalHoldStatus) error {
var lstatus minio.LegalHoldStatus
if status == models.ObjectLegalHoldStatusEnabled {
lstatus = minio.LegalHoldEnabled
} else {
lstatus = minio.LegalHoldDisabled
}
return client.putObjectLegalHold(ctx, bucketName, prefix, minio.PutObjectLegalHoldOptions{VersionID: versionID, Status: &lstatus})
}
// newClientURL returns an abstracted URL for filesystems and object storage.
func newClientURL(urlStr string) *mc.ClientURL {
scheme, rest := getScheme(urlStr)

View File

@@ -29,15 +29,19 @@ import (
mc "github.com/minio/mc/cmd"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio-go/v7"
"github.com/stretchr/testify/assert"
)
var minioListObjectsMock func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo
var minioGetObjectLegalHoldMock func(ctx context.Context, bucketName, objectName string, opts minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error)
var minioGetObjectRetentionMock func(ctx context.Context, bucketName, objectName, versionID string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error)
var minioPutObjectMock func(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error)
var minioPutObjectLegalHoldMock func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error
var mcListMock func(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent
var mcRemoveMock func(ctx context.Context, isIncomplete, isRemoveBucket, isBypass bool, contentCh <-chan *mc.ClientContent) <-chan *probe.Error
var mcGetMock func(ctx context.Context, opts mc.GetOptions) (io.ReadCloser, *probe.Error)
var mcShareDownloadMock func(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error)
// mock functions for minioClientMock
func (ac minioClientMock) listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
@@ -51,6 +55,14 @@ func (ac minioClientMock) getObjectRetention(ctx context.Context, bucketName, ob
return minioGetObjectRetentionMock(ctx, bucketName, objectName, versionID)
}
func (ac minioClientMock) putObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error) {
return minioPutObjectMock(ctx, bucketName, objectName, reader, objectSize, opts)
}
func (ac minioClientMock) putObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error {
return minioPutObjectLegalHoldMock(ctx, bucketName, objectName, opts)
}
// mock functions for s3ClientMock
func (c s3ClientMock) list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent {
return mcListMock(ctx, opts)
@@ -63,6 +75,10 @@ func (c s3ClientMock) get(ctx context.Context, opts mc.GetOptions) (io.ReadClose
return mcGetMock(ctx, opts)
}
func (c s3ClientMock) shareDownload(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error) {
return mcShareDownloadMock(ctx, versionID, expires)
}
func Test_listObjects(t *testing.T) {
ctx := context.Background()
t1 := time.Now()
@@ -540,3 +556,151 @@ func Test_deleteObjects(t *testing.T) {
})
}
}
func Test_shareObject(t *testing.T) {
assert := assert.New(t)
ctx := context.Background()
client := s3ClientMock{}
type args struct {
versionID string
expires string
shareFunc func(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error)
}
tests := []struct {
test string
args args
wantError error
expected string
}{
{
test: "Get share object url",
args: args{
versionID: "2121434",
expires: "30s",
shareFunc: func(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error) {
return "http://someurl", nil
},
},
wantError: nil,
expected: "http://someurl",
},
{
test: "handle invalid expire duration",
args: args{
versionID: "2121434",
expires: "invalid",
shareFunc: func(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error) {
return "http://someurl", nil
},
},
wantError: errors.New("time: invalid duration invalid"),
},
{
test: "handle empty expire duration",
args: args{
versionID: "2121434",
expires: "",
shareFunc: func(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error) {
return "http://someurl", nil
},
},
wantError: nil,
expected: "http://someurl",
},
{
test: "handle error on share func",
args: args{
versionID: "2121434",
expires: "3h",
shareFunc: func(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error) {
return "", probe.NewError(errors.New("probe error"))
},
},
wantError: errors.New("probe error"),
},
}
for _, tt := range tests {
t.Run(tt.test, func(t *testing.T) {
mcShareDownloadMock = tt.args.shareFunc
url, err := getShareObjectURL(ctx, client, tt.args.versionID, tt.args.expires)
if tt.wantError != nil {
if !reflect.DeepEqual(err, tt.wantError) {
t.Errorf("getShareObjectURL() error: %v, wantErr: %v", err, tt.wantError)
return
}
} else {
assert.Equal(*url, tt.expected)
}
})
}
}
func Test_putObjectLegalHold(t *testing.T) {
ctx := context.Background()
client := minioClientMock{}
type args struct {
bucket string
prefix string
versionID string
status models.ObjectLegalHoldStatus
legalHoldFunc func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error
}
tests := []struct {
test string
args args
wantError error
}{
{
test: "Put Object Legal hold enabled status",
args: args{
bucket: "buck1",
versionID: "someversion",
prefix: "folder/file.txt",
status: models.ObjectLegalHoldStatusEnabled,
legalHoldFunc: func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error {
return nil
},
},
wantError: nil,
},
{
test: "Put Object Legal hold disabled status",
args: args{
bucket: "buck1",
versionID: "someversion",
prefix: "folder/file.txt",
status: models.ObjectLegalHoldStatusDisabled,
legalHoldFunc: func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error {
return nil
},
},
wantError: nil,
},
{
test: "Handle error on legalhold func",
args: args{
bucket: "buck1",
versionID: "someversion",
prefix: "folder/file.txt",
status: models.ObjectLegalHoldStatusDisabled,
legalHoldFunc: func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error {
return errors.New("new error")
},
},
wantError: errors.New("new error"),
},
}
for _, tt := range tests {
t.Run(tt.test, func(t *testing.T) {
minioPutObjectLegalHoldMock = tt.args.legalHoldFunc
err := setObjectLegalHold(ctx, client, tt.args.bucket, tt.args.prefix, tt.args.versionID, tt.args.status)
if !reflect.DeepEqual(err, tt.wantError) {
t.Errorf("setObjectLegalHold() error: %v, wantErr: %v", err, tt.wantError)
return
}
})
}
}

View File

@@ -35,7 +35,7 @@ func registerSessionHandlers(api *operations.ConsoleAPI) {
})
}
// getSessionResponse parse the jwt of the current session and returns a list of allowed actions to render in the UI
// getSessionResponse parse the token of the current session and returns a list of allowed actions to render in the UI
func getSessionResponse(session *models.Principal) (*models.SessionResponse, *models.Error) {
// serialize output
if session == nil {

View File

@@ -107,6 +107,7 @@ func serveWS(w http.ResponseWriter, req *http.Request) {
errors.ServeError(w, req, errors.New(http.StatusUnauthorized, err.Error()))
return
}
// upgrades the HTTP server connection to the WebSocket protocol.
conn, err := upgrader.Upgrade(w, req, nil)
if err != nil {

View File

@@ -285,6 +285,34 @@ paths:
tags:
- UserAPI
/buckets/{bucket_name}/objects/upload:
post:
summary: Uploads an Object.
consumes:
- multipart/form-data
parameters:
- in: formData
name: upfile
type: file
required: true
- name: bucket_name
in: path
required: true
type: string
- name: prefix
in: query
required: true
type: string
responses:
200:
description: A successful response.
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- UserAPI
/buckets/{bucket_name}/objects/download:
get:
summary: Download Object
@@ -312,6 +340,71 @@ paths:
tags:
- UserAPI
/buckets/{bucket_name}/objects/share:
get:
summary: Shares an Object on a url
operationId: ShareObject
parameters:
- name: bucket_name
in: path
required: true
type: string
- name: prefix
in: query
required: true
type: string
- name: version_id
in: query
required: true
type: string
- name: expires
in: query
required: false
type: string
responses:
200:
description: A successful response.
schema:
type: string
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- UserAPI
/buckets/{bucket_name}/objects/legalhold:
put:
summary: Put Object's legalhold status
operationId: PutObjectLegalHold
parameters:
- name: bucket_name
in: path
required: true
type: string
- name: prefix
in: query
required: true
type: string
- name: version_id
in: query
required: true
type: string
- name: body
in: body
required: true
schema:
$ref: "#/definitions/putObjectLegalHoldRequest"
responses:
200:
description: A successful response.
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- UserAPI
/buckets/{name}/set-policy:
put:
summary: Bucket Set Policy
@@ -3224,3 +3317,17 @@ definitions:
type: array
items:
type: string
objectLegalHoldStatus:
type: string
enum:
- enabled
- disabled
putObjectLegalHoldRequest:
type: object
required:
- status
properties:
status:
$ref: "#/definitions/objectLegalHoldStatus"