Revert "deprecate embedded browser (#12163)"

This reverts commit 736d8cbac4.

Bring contrib files for older contributions
This commit is contained in:
Harshavardhana
2021-04-29 19:01:43 -07:00
parent 64f6020854
commit f7a87b30bf
300 changed files with 38540 additions and 1172 deletions
+60
View File
@@ -0,0 +1,60 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { Modal } from "react-bootstrap"
import logo from "../../img/logo.svg"
export const AboutModal = ({ serverInfo, hideAbout }) => {
const { version, platform, runtime } = serverInfo
return (
<Modal
className="modal-about modal-dark"
animation={false}
show={true}
onHide={hideAbout}
>
<button className="close" onClick={hideAbout}>
<span>×</span>
</button>
<div className="ma-inner">
<div className="mai-item hidden-xs">
<a href="https://min.io" target="_blank">
<img className="maii-logo" src={logo} alt="" />
</a>
</div>
<div className="mai-item">
<ul className="maii-list">
<li>
<div>Version</div>
<small>{version}</small>
</li>
<li>
<div>Platform</div>
<small>{platform}</small>
</li>
<li>
<div>Runtime</div>
<small>{runtime}</small>
</li>
</ul>
</div>
</div>
</Modal>
)
}
export default AboutModal
+40
View File
@@ -0,0 +1,40 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import classNames from "classnames"
import { connect } from "react-redux"
import SideBar from "./SideBar"
import MainContent from "./MainContent"
import AlertContainer from "../alert/AlertContainer"
class Browser extends React.Component {
render() {
return (
<div
className={classNames({
"file-explorer": true
})}
>
<SideBar />
<MainContent />
<AlertContainer />
</div>
)
}
}
export default connect(state => state)(Browser)
+135
View File
@@ -0,0 +1,135 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { connect } from "react-redux"
import { Dropdown } from "react-bootstrap"
import * as browserActions from "./actions"
import web from "../web"
import history from "../history"
import AboutModal from "./AboutModal"
import ChangePasswordModal from "./ChangePasswordModal"
export class BrowserDropdown extends React.Component {
constructor(props) {
super(props)
this.state = {
showAboutModal: false,
showChangePasswordModal: false
}
}
showAbout(e) {
e.preventDefault()
this.setState({
showAboutModal: true
})
}
hideAbout() {
this.setState({
showAboutModal: false
})
}
showChangePassword(e) {
e.preventDefault()
this.setState({
showChangePasswordModal: true
})
}
hideChangePassword() {
this.setState({
showChangePasswordModal: false
})
}
componentDidMount() {
const { fetchServerInfo } = this.props
fetchServerInfo()
}
logout(e) {
e.preventDefault()
web.Logout()
history.replace("/login")
}
render() {
const { serverInfo } = this.props
return (
<li>
<Dropdown pullRight id="top-right-menu">
<Dropdown.Toggle noCaret>
<i className="fas fa-bars" />
</Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu-right">
<li>
<a href="" onClick={this.showChangePassword.bind(this)}>
Change Password <i className="fas fa-cog" />
</a>
{this.state.showChangePasswordModal && (
<ChangePasswordModal
serverInfo={serverInfo}
hideChangePassword={this.hideChangePassword.bind(this)}
/>
)}
</li>
<li>
<a target="_blank" href="https://docs.min.io/?ref=ob">
Documentation <i className="fas fa-book" />
</a>
</li>
<li>
<a target="_blank" href="https://github.com/minio/minio">
GitHub <i className="fab fa-github" />
</a>
</li>
<li>
<a target="_blank" href="https://min.io/pricing?ref=ob">
Get Support <i className="fas fa-question-circle" />
</a>
</li>
<li>
<a href="" id="show-about" onClick={this.showAbout.bind(this)}>
About <i className="fas fa-info-circle" />
</a>
{this.state.showAboutModal && (
<AboutModal
serverInfo={serverInfo}
hideAbout={this.hideAbout.bind(this)}
/>
)}
</li>
<li>
<a href="" id="logout" onClick={this.logout}>
Logout <i className="fas fa-sign-out-alt" />
</a>
</li>
</Dropdown.Menu>
</Dropdown>
</li>
)
}
}
const mapStateToProps = state => {
return {
serverInfo: state.browser.serverInfo
}
}
const mapDispatchToProps = dispatch => {
return {
fetchServerInfo: () => dispatch(browserActions.fetchServerInfo())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(BrowserDropdown)
@@ -0,0 +1,260 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { connect } from "react-redux"
import web from "../web"
import * as alertActions from "../alert/actions"
import { getRandomAccessKey, getRandomSecretKey } from "../utils"
import jwtDecode from "jwt-decode"
import classNames from "classnames"
import { Modal, ModalBody, ModalHeader } from "react-bootstrap"
import InputGroup from "./InputGroup"
import { ACCESS_KEY_MIN_LENGTH, SECRET_KEY_MIN_LENGTH } from "../constants"
export class ChangePasswordModal extends React.Component {
constructor(props) {
super(props)
this.state = {
currentAccessKey: "",
currentSecretKey: "",
currentSecretKeyVisible: false,
newAccessKey: "",
newSecretKey: "",
newSecretKeyVisible: false
}
}
// When its shown, it loads the access key from JWT token
componentWillMount() {
const token = jwtDecode(web.GetToken())
this.setState({
currentAccessKey: token.sub,
newAccessKey: token.sub
})
}
// Save the auth params and set them.
setAuth(e) {
const { showAlert } = this.props
if (this.canUpdateCredentials()) {
const currentAccessKey = this.state.currentAccessKey
const currentSecretKey = this.state.currentSecretKey
const newAccessKey = this.state.newAccessKey
const newSecretKey = this.state.newSecretKey
web
.SetAuth({
currentAccessKey,
currentSecretKey,
newAccessKey,
newSecretKey
})
.then(data => {
showAlert({
type: "success",
message: "Credentials updated successfully."
})
})
.catch(err => {
showAlert({
type: "danger",
message: err.message
})
})
}
}
generateAuth(e) {
const { serverInfo } = this.props
this.setState({
newSecretKey: getRandomSecretKey(),
newSecretKeyVisible: true
})
}
canChangePassword() {
const { serverInfo } = this.props
// Password change is not allowed for temporary users(STS)
if(serverInfo.userInfo.isTempUser) {
return false
}
// Password change is only allowed for regular users
if (!serverInfo.userInfo.isIAMUser) {
return false
}
return true
}
canUpdateCredentials() {
return (
this.state.currentAccessKey.length > 0 &&
this.state.currentSecretKey.length > 0 &&
this.state.newAccessKey.length >= ACCESS_KEY_MIN_LENGTH &&
this.state.newSecretKey.length >= SECRET_KEY_MIN_LENGTH
)
}
render() {
const { hideChangePassword, serverInfo } = this.props
const allowChangePassword = this.canChangePassword()
if (!allowChangePassword) {
return (
<Modal bsSize="sm" animation={false} show={true}>
<ModalHeader>Change Password</ModalHeader>
<ModalBody>
Credentials of this user cannot be updated through MinIO Browser.
</ModalBody>
<div className="modal-footer">
<button
id="cancel-change-password"
className="btn btn-link"
onClick={hideChangePassword}
>
Close
</button>
</div>
</Modal>
)
}
return (
<Modal bsSize="sm" animation={false} show={true}>
<ModalHeader>Change Password</ModalHeader>
<ModalBody className="m-t-20">
<div className="has-toggle-password">
<InputGroup
value={this.state.currentAccessKey}
id="currentAccessKey"
label="Current Access Key"
name="currentAccesskey"
type="text"
spellCheck="false"
required="required"
autoComplete="false"
align="ig-left"
readonly={true}
/>
<i
onClick={() => {
this.setState({
currentSecretKeyVisible: !this.state.currentSecretKeyVisible
})
}}
className={
"toggle-password fas fa-eye " +
(this.state.currentSecretKeyVisible ? "toggled" : "")
}
/>
<InputGroup
value={this.state.currentSecretKey}
onChange={e => {
this.setState({ currentSecretKey: e.target.value })
}}
id="currentSecretKey"
label="Current Secret Key"
name="currentSecretKey"
type={this.state.currentSecretKeyVisible ? "text" : "password"}
spellCheck="false"
required="required"
autoComplete="false"
align="ig-left"
/>
</div>
<div className="has-toggle-password m-t-30">
<i
onClick={() => {
this.setState({
newSecretKeyVisible: !this.state.newSecretKeyVisible
})
}}
className={
"toggle-password fas fa-eye " +
(this.state.newSecretKeyVisible ? "toggled" : "")
}
/>
<InputGroup
value={this.state.newSecretKey}
onChange={e => {
this.setState({ newSecretKey: e.target.value })
}}
id="newSecretKey"
label="New Secret Key"
name="newSecretKey"
type={this.state.newSecretKeyVisible ? "text" : "password"}
spellCheck="false"
required="required"
autoComplete="false"
align="ig-left"
onChange={e => {
this.setState({ newSecretKey: e.target.value })
}}
/>
</div>
</ModalBody>
<div className="modal-footer">
<button
id="generate-keys"
className={"btn btn-primary"}
onClick={this.generateAuth.bind(this)}
>
Generate
</button>
<button
id="update-keys"
className={classNames({
btn: true,
"btn-success": this.canUpdateCredentials()
})}
disabled={!this.canUpdateCredentials()}
onClick={this.setAuth.bind(this)}
>
Update
</button>
<button
id="cancel-change-password"
className="btn btn-link"
onClick={hideChangePassword}
>
Cancel
</button>
</div>
</Modal>
)
}
}
const mapStateToProps = state => {
return {
serverInfo: state.browser.serverInfo
}
}
const mapDispatchToProps = dispatch => {
return {
showAlert: alert => dispatch(alertActions.set(alert))
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ChangePasswordModal)
+57
View File
@@ -0,0 +1,57 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { Modal, ModalBody } from "react-bootstrap"
let ConfirmModal = ({
baseClass,
icon,
text,
sub,
okText,
cancelText,
okHandler,
cancelHandler,
show
}) => {
return (
<Modal
bsSize="small"
animation={false}
show={show}
className={"modal-confirm " + (baseClass || "")}
>
<ModalBody>
<div className="mc-icon">
<i className={icon} />
</div>
<div className="mc-text">{text}</div>
<div className="mc-sub">{sub}</div>
</ModalBody>
<div className="modal-footer">
<button className="btn btn-danger" onClick={okHandler}>
{okText}
</button>
<button className="btn btn-link" onClick={cancelHandler}>
{cancelText}
</button>
</div>
</Modal>
)
}
export default ConfirmModal
+45
View File
@@ -0,0 +1,45 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import ObjectsSearch from "../objects/ObjectsSearch"
import Path from "../objects/Path"
import StorageInfo from "./StorageInfo"
import BrowserDropdown from "./BrowserDropdown"
import web from "../web"
import { minioBrowserPrefix } from "../constants"
export const Header = () => {
const loggedIn = web.LoggedIn()
return (
<header className="fe-header">
<Path />
{loggedIn && <StorageInfo />}
{loggedIn && <ObjectsSearch />}
<ul className="feh-actions">
{loggedIn ? (
<BrowserDropdown />
) : (
<a className="btn btn-danger" href={minioBrowserPrefix + "/login"}>
Login
</a>
)}
</ul>
</header>
)
}
export default Header
+26
View File
@@ -0,0 +1,26 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
export const Host = () => (
<div className="fes-host">
<i className="fas fa-globe-americas" />
<a href="/">{window.location.host}</a>
</div>
)
export default Host
+70
View File
@@ -0,0 +1,70 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
let InputGroup = ({
label,
id,
name,
value,
onChange,
type,
spellCheck,
required,
readonly,
autoComplete,
align,
className
}) => {
var input = (
<input
id={id}
name={name}
value={value}
onChange={onChange}
className="ig-text"
type={type}
spellCheck={spellCheck}
required={required}
autoComplete={autoComplete}
/>
)
if (readonly)
input = (
<input
id={id}
name={name}
value={value}
onChange={onChange}
className="ig-text"
type={type}
spellCheck={spellCheck}
required={required}
autoComplete={autoComplete}
disabled
/>
)
return (
<div className={"input-group " + align + " " + className}>
{input}
<i className="ig-helpers" />
<label className="ig-label">{label}</label>
</div>
)
}
export default InputGroup
+187
View File
@@ -0,0 +1,187 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { connect } from "react-redux"
import logo from "../../img/logo.svg"
import Alert from "../alert/Alert"
import * as actionsAlert from "../alert/actions"
import InputGroup from "./InputGroup"
import web from "../web"
import { Redirect, Link } from "react-router-dom"
import OpenIDLoginButton from './OpenIDLoginButton'
export class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
accessKey: "",
secretKey: "",
discoveryDoc: {},
clientId: ""
}
}
// Handle field changes
accessKeyChange(e) {
this.setState({
accessKey: e.target.value
})
}
secretKeyChange(e) {
this.setState({
secretKey: e.target.value
})
}
handleSubmit(event) {
event.preventDefault()
const { showAlert, clearAlert, history } = this.props
let message = ""
if (this.state.accessKey === "") {
message = "Access Key cannot be empty"
}
if (this.state.secretKey === "") {
message = "Secret Key cannot be empty"
}
if (message) {
showAlert("danger", message)
return
}
web
.Login({
username: this.state.accessKey,
password: this.state.secretKey
})
.then(res => {
// Clear alerts from previous login attempts
clearAlert()
history.push("/")
})
.catch(e => {
showAlert("danger", e.message)
})
}
componentWillMount() {
const { clearAlert } = this.props
// Clear out any stale message in the alert of previous page
clearAlert()
document.body.classList.add("is-guest")
}
componentDidMount() {
web.GetDiscoveryDoc().then(({ DiscoveryDoc, clientId }) => {
this.setState({
clientId,
discoveryDoc: DiscoveryDoc
})
})
}
componentWillUnmount() {
document.body.classList.remove("is-guest")
}
render() {
const { clearAlert, alert } = this.props
if (web.LoggedIn()) {
return <Redirect to={"/"} />
}
let alertBox = <Alert {...alert} onDismiss={clearAlert} />
// Make sure you don't show a fading out alert box on the initial web-page load.
if (!alert.message) alertBox = ""
const showOpenID = Boolean(this.state.discoveryDoc && this.state.discoveryDoc.authorization_endpoint)
return (
<div className="login">
{alertBox}
<div className="l-wrap">
<form onSubmit={this.handleSubmit.bind(this)}>
<InputGroup
value={this.state.accessKey}
onChange={this.accessKeyChange.bind(this)}
className="ig-dark"
label="Access Key"
id="accessKey"
name="username"
type="text"
spellCheck="false"
required="required"
autoComplete="username"
/>
<InputGroup
value={this.state.secretKey}
onChange={this.secretKeyChange.bind(this)}
className="ig-dark"
label="Secret Key"
id="secretKey"
name="password"
type="password"
spellCheck="false"
required="required"
/>
<button className="lw-btn" type="submit">
<i className="fas fa-sign-in-alt" />
</button>
</form>
{showOpenID && (
<div className="openid-login">
<div className="or">or</div>
{
this.state.clientId ? (
<OpenIDLoginButton
className="btn openid-btn"
clientId={this.state.clientId}
authEp={this.state.discoveryDoc.authorization_endpoint}
authScopes={this.state.discoveryDoc.scopes_supported}
>
Log in with OpenID
</OpenIDLoginButton>
) : (
<Link to={"/login/openid"} className="btn openid-btn">
Log in with OpenID
</Link>
)
}
</div>
)}
</div>
<div className="l-footer">
<a className="lf-logo" href="">
<img src={logo} alt="" />
</a>
<div className="lf-server">{window.location.host}</div>
</div>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
showAlert: (type, message) =>
dispatch(actionsAlert.set({ type: type, message: message })),
clearAlert: () => dispatch(actionsAlert.clear())
}
}
export default connect(
state => state,
mapDispatchToProps
)(Login)
+106
View File
@@ -0,0 +1,106 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { connect } from "react-redux"
import { Dropdown, OverlayTrigger, Tooltip } from "react-bootstrap"
import web from "../web"
import * as actionsBuckets from "../buckets/actions"
import * as uploadsActions from "../uploads/actions"
import { getPrefixWritable } from "../objects/selectors"
export const MainActions = ({
prefixWritable,
uploadFile,
showMakeBucketModal
}) => {
const uploadTooltip = <Tooltip id="tt-upload-file">Upload file</Tooltip>
const makeBucketTooltip = (
<Tooltip id="tt-create-bucket">Create bucket</Tooltip>
)
const onFileUpload = e => {
e.preventDefault()
let files = e.target.files
let filesToUploadCount = files.length
for (let i = 0; i < filesToUploadCount; i++) {
uploadFile(files.item(i))
}
e.target.value = null
}
const loggedIn = web.LoggedIn()
if (loggedIn || prefixWritable) {
return (
<Dropdown dropup className="feb-actions" id="fe-action-toggle">
<Dropdown.Toggle noCaret className="feba-toggle">
<span>
<i className="fas fa-plus" />
</span>
</Dropdown.Toggle>
<Dropdown.Menu>
<OverlayTrigger placement="left" overlay={uploadTooltip}>
<a href="#" className="feba-btn feba-upload">
<input
type="file"
onChange={onFileUpload}
style={{ display: "none" }}
id="file-input"
multiple={true}
/>
<label htmlFor="file-input">
{" "}
<i className="fas fa-cloud-upload-alt" />{" "}
</label>
</a>
</OverlayTrigger>
{loggedIn && (
<OverlayTrigger placement="left" overlay={makeBucketTooltip}>
<a
href="#"
id="show-make-bucket"
className="feba-btn feba-bucket"
onClick={e => {
e.preventDefault()
showMakeBucketModal()
}}
>
<i className="far fa-hdd" />
</a>
</OverlayTrigger>
)}
</Dropdown.Menu>
</Dropdown>
)
} else {
return <noscript />
}
}
const mapStateToProps = state => {
return {
prefixWritable: getPrefixWritable(state)
}
}
const mapDispatchToProps = dispatch => {
return {
uploadFile: file => dispatch(uploadsActions.uploadFile(file)),
showMakeBucketModal: () => dispatch(actionsBuckets.showMakeBucketModal())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MainActions)
+43
View File
@@ -0,0 +1,43 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import MobileHeader from "./MobileHeader"
import Header from "./Header"
import ObjectsSection from "../objects/ObjectsSection"
import MainActions from "./MainActions"
import BucketPolicyModal from "../buckets/BucketPolicyModal"
import MakeBucketModal from "../buckets/MakeBucketModal"
import UploadModal from "../uploads/UploadModal"
import ObjectsBulkActions from "../objects/ObjectsBulkActions"
import Dropzone from "../uploads/Dropzone"
export const MainContent = () => (
<div className="fe-body">
<ObjectsBulkActions />
<MobileHeader />
<Dropzone>
<Header />
<ObjectsSection />
</Dropzone>
<MainActions />
<BucketPolicyModal />
<MakeBucketModal />
<UploadModal />
</div>
)
export default MainContent
+60
View File
@@ -0,0 +1,60 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import classNames from "classnames"
import { connect } from "react-redux"
import logo from "../../img/logo.svg"
import * as actionsCommon from "./actions"
export const MobileHeader = ({ sidebarOpen, toggleSidebar }) => (
<header className="fe-header-mobile hidden-lg hidden-md">
<div
id="sidebar-toggle"
className={
"feh-trigger " +
classNames({
"feht-toggled": sidebarOpen
})
}
onClick={e => {
e.stopPropagation()
toggleSidebar()
}}
>
<div className="feht-lines">
<div className="top" />
<div className="center" />
<div className="bottom" />
</div>
</div>
<img className="mh-logo" src={logo} alt="" />
</header>
)
const mapStateToProps = state => {
return {
sidebarOpen: state.browser.sidebarOpen
}
}
const mapDispatchToProps = dispatch => {
return {
toggleSidebar: () => dispatch(actionsCommon.toggleSidebar())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MobileHeader)
+169
View File
@@ -0,0 +1,169 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { connect } from "react-redux"
import logo from "../../img/logo.svg"
import Alert from "../alert/Alert"
import * as actionsAlert from "../alert/actions"
import InputGroup from "./InputGroup"
import web from "../web"
import { Redirect } from "react-router-dom"
import qs from "query-string"
import { getRandomString } from "../utils"
import storage from "local-storage-fallback"
import jwtDecode from "jwt-decode"
import { buildOpenIDAuthURL, OPEN_ID_NONCE_KEY } from './utils'
export class OpenIDLogin extends React.Component {
constructor(props) {
super(props)
this.state = {
clientID: "",
discoveryDoc: {}
}
this.clientIDChange = this.clientIDChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
clientIDChange(e) {
this.setState({
clientID: e.target.value
})
}
handleSubmit(event) {
event.preventDefault()
const { showAlert } = this.props
let message = ""
if (this.state.clientID === "") {
message = "Client ID cannot be empty"
}
if (message) {
showAlert("danger", message)
return
}
if (this.state.discoveryDoc && this.state.discoveryDoc.authorization_endpoint) {
const redirectURI = window.location.href.split("#")[0]
// Store nonce in localstorage to check again after the redirect
const nonce = getRandomString(16)
storage.setItem(OPEN_ID_NONCE_KEY, nonce)
const authURL = buildOpenIDAuthURL(
this.state.discoveryDoc.authorization_endpoint,
this.state.discoveryDoc.scopes_supported,
redirectURI,
this.state.clientID,
nonce
)
window.location = authURL
}
}
componentWillMount() {
const { clearAlert } = this.props
// Clear out any stale message in the alert of previous page
clearAlert()
document.body.classList.add("is-guest")
web.GetDiscoveryDoc().then(({ DiscoveryDoc }) => {
this.setState({
discoveryDoc: DiscoveryDoc
})
})
}
componentDidMount() {
const values = qs.parse(this.props.location.hash)
if (values.error) {
this.props.showAlert("danger", values.error_description)
return
}
if (values.id_token) {
// Check nonce on the token to prevent replay attacks
const tokenJSON = jwtDecode(values.id_token)
if (storage.getItem(OPEN_ID_NONCE_KEY) !== tokenJSON.nonce) {
this.props.showAlert("danger", "Invalid auth token")
return
}
web.LoginSTS({ token: values.id_token }).then(() => {
storage.removeItem(OPEN_ID_NONCE_KEY)
this.forceUpdate()
return
})
}
}
componentWillUnmount() {
document.body.classList.remove("is-guest")
}
render() {
const { clearAlert, alert } = this.props
if (web.LoggedIn()) {
return <Redirect to={"/"} />
}
let alertBox = <Alert {...alert} onDismiss={clearAlert} />
// Make sure you don't show a fading out alert box on the initial web-page load.
if (!alert.message) alertBox = ""
return (
<div className="login">
{alertBox}
<div className="l-wrap">
<form onSubmit={this.handleSubmit}>
<InputGroup
value={this.state.clientID}
onChange={this.clientIDChange}
className="ig-dark"
label="Client ID"
id="clientID"
name="clientID"
type="text"
spellCheck="false"
required="required"
/>
<button className="lw-btn" type="submit">
<i className="fas fa-sign-in-alt" />
</button>
</form>
</div>
<div className="l-footer">
<a className="lf-logo" href="">
<img src={logo} alt="" />
</a>
<div className="lf-server">{window.location.host}</div>
</div>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
showAlert: (type, message) =>
dispatch(actionsAlert.set({ type: type, message: message })),
clearAlert: () => dispatch(actionsAlert.clear())
}
}
export default connect(
state => state,
mapDispatchToProps
)(OpenIDLogin)
@@ -0,0 +1,57 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { getRandomString } from "../utils"
import storage from "local-storage-fallback"
import { buildOpenIDAuthURL, OPEN_ID_NONCE_KEY } from './utils'
export class OpenIDLoginButton extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick(event) {
event.stopPropagation()
const { authEp, authScopes, clientId } = this.props
let redirectURI = window.location.href.split("#")[0]
if (redirectURI.endsWith('/')) {
redirectURI += 'openid'
} else {
redirectURI += '/openid'
}
// Store nonce in localstorage to check again after the redirect
const nonce = getRandomString(16)
storage.setItem(OPEN_ID_NONCE_KEY, nonce)
const authURL = buildOpenIDAuthURL(authEp, authScopes, redirectURI, clientId, nonce)
window.location = authURL
}
render() {
const { children, className } = this.props
return (
<div onClick={this.handleClick} className={className}>
{children}
</div>
)
}
}
export default OpenIDLoginButton
+73
View File
@@ -0,0 +1,73 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import classNames from "classnames"
import ClickOutHandler from "react-onclickout"
import { connect } from "react-redux"
import logo from "../../img/logo.svg"
import BucketSearch from "../buckets/BucketSearch"
import BucketList from "../buckets/BucketList"
import Host from "./Host"
import * as actionsCommon from "./actions"
import web from "../web"
export const SideBar = ({ sidebarOpen, clickOutside }) => {
const onClickOut = e => {
if (e.target.classList.contains("feh-trigger")) {
return
}
clickOutside()
}
return (
<ClickOutHandler onClickOut={onClickOut}>
<div
className={classNames({
"fe-sidebar": true,
toggled: sidebarOpen
})}
>
<div className="fes-header clearfix hidden-sm hidden-xs">
<img src={logo} alt="" />
<h2>MinIO Browser</h2>
</div>
<div className="fes-list">
{web.LoggedIn() && <BucketSearch />}
<BucketList />
</div>
<Host />
</div>
</ClickOutHandler>
)
}
const mapStateToProps = state => {
return {
sidebarOpen: state.browser.sidebarOpen
}
}
const mapDispatchToProps = dispatch => {
return {
clickOutside: () => dispatch(actionsCommon.closeSidebar())
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(SideBar)
+64
View File
@@ -0,0 +1,64 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { connect } from "react-redux"
import humanize from "humanize"
import * as actionsCommon from "./actions"
export class StorageInfo extends React.Component {
componentWillMount() {
const { fetchStorageInfo } = this.props
fetchStorageInfo()
}
render() {
const { used } = this.props.storageInfo
if (!used || used == 0) {
return <noscript />
}
return (
<div className="feh-used">
<div className="fehu-chart">
<div style={{ width: 0 }} />
</div>
<ul>
<li>
<span>Used: </span>
{humanize.filesize(used)}
</li>
</ul>
</div>
)
}
}
const mapStateToProps = state => {
return {
storageInfo: state.browser.storageInfo
}
}
const mapDispatchToProps = dispatch => {
return {
fetchStorageInfo: () => dispatch(actionsCommon.fetchStorageInfo())
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(StorageInfo)
@@ -0,0 +1,40 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow } from "enzyme"
import { AboutModal } from "../AboutModal"
describe("AboutModal", () => {
const serverInfo = {
version: "test",
platform: "test",
runtime: "test"
}
it("should render without crashing", () => {
shallow(<AboutModal serverInfo={serverInfo} />)
})
it("should call hideAbout when close button is clicked", () => {
const hideAbout = jest.fn()
const wrapper = shallow(
<AboutModal serverInfo={serverInfo} hideAbout={hideAbout} />
)
wrapper.find("button").simulate("click")
expect(hideAbout).toHaveBeenCalled()
})
})
@@ -0,0 +1,29 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow } from "enzyme"
import Browser from "../Browser"
import configureStore from "redux-mock-store"
const mockStore = configureStore()
describe("Browser", () => {
it("should render without crashing", () => {
const store = mockStore()
shallow(<Browser store={store}/>)
})
})
@@ -0,0 +1,62 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow } from "enzyme"
import { BrowserDropdown } from "../BrowserDropdown"
describe("BrowserDropdown", () => {
const serverInfo = {
version: "test",
platform: "test",
runtime: "test"
}
it("should render without crashing", () => {
shallow(
<BrowserDropdown serverInfo={serverInfo} fetchServerInfo={jest.fn()} />
)
})
it("should call fetchServerInfo after its mounted", () => {
const fetchServerInfo = jest.fn()
const wrapper = shallow(
<BrowserDropdown
serverInfo={serverInfo}
fetchServerInfo={fetchServerInfo}
/>
)
expect(fetchServerInfo).toHaveBeenCalled()
})
it("should show AboutModal when About link is clicked", () => {
const wrapper = shallow(
<BrowserDropdown serverInfo={serverInfo} fetchServerInfo={jest.fn()} />
)
wrapper.find("#show-about").simulate("click", { preventDefault: jest.fn() })
wrapper.update()
expect(wrapper.state("showAboutModal")).toBeTruthy()
expect(wrapper.find("AboutModal").length).toBe(1)
})
it("should logout and redirect to /login when logout is clicked", () => {
const wrapper = shallow(
<BrowserDropdown serverInfo={serverInfo} fetchServerInfo={jest.fn()} />
)
wrapper.find("#logout").simulate("click", { preventDefault: jest.fn() })
expect(window.location.pathname.endsWith("/login")).toBeTruthy()
})
})
@@ -0,0 +1,131 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow, mount } from "enzyme"
import { ChangePasswordModal } from "../ChangePasswordModal"
import jwtDecode from "jwt-decode"
jest.mock("jwt-decode")
jwtDecode.mockImplementation(() => ({ sub: "minio" }))
jest.mock("../../web", () => ({
SetAuth: jest.fn(
({ currentAccessKey, currentSecretKey, newAccessKey, newSecretKey }) => {
if (
currentAccessKey == "minio" &&
currentSecretKey == "minio123" &&
newAccessKey == "test" &&
newSecretKey == "test1234"
) {
return Promise.resolve({})
} else {
return Promise.reject({
message: "Error"
})
}
}
),
GetToken: jest.fn(() => "")
}))
jest.mock("../../utils", () => ({
getRandomAccessKey: () => "raccesskey",
getRandomSecretKey: () => "rsecretkey"
}))
describe("ChangePasswordModal", () => {
const serverInfo = {
version: "test",
platform: "test",
runtime: "test",
info: {},
userInfo: { isIAMUser: true }
}
it("should render without crashing", () => {
shallow(<ChangePasswordModal serverInfo={serverInfo} />)
})
it("should not allow changing password when not IAM user", () => {
const newServerInfo = {
...serverInfo,
userInfo: { isIAMUser: false }
}
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
expect(
wrapper
.find("ModalBody")
.childAt(0)
.text()
).toBe("Credentials of this user cannot be updated through MinIO Browser.")
})
it("should not allow changing password for STS user", () => {
const newServerInfo = {
...serverInfo,
userInfo: { isTempUser: true }
}
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
expect(
wrapper
.find("ModalBody")
.childAt(0)
.text()
).toBe("Credentials of this user cannot be updated through MinIO Browser.")
})
it("should not generate accessKey for IAM User", () => {
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />)
wrapper.find("#generate-keys").simulate("click")
setImmediate(() => {
expect(wrapper.state("newAccessKey")).toBe("minio")
expect(wrapper.state("newSecretKey")).toBe("rsecretkey")
})
})
it("should not show new accessKey field for IAM User", () => {
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />)
expect(wrapper.find("#newAccesskey").exists()).toBeFalsy()
})
it("should disable Update button for secretKey", () => {
const showAlert = jest.fn()
const wrapper = shallow(
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} />
)
wrapper
.find("#currentSecretKey")
.simulate("change", { target: { value: "minio123" } })
wrapper
.find("#newSecretKey")
.simulate("change", { target: { value: "t1" } })
expect(wrapper.find("#update-keys").prop("disabled")).toBeTruthy()
})
it("should call hideChangePassword when Cancel button is clicked", () => {
const hideChangePassword = jest.fn()
const wrapper = shallow(
<ChangePasswordModal
serverInfo={serverInfo}
hideChangePassword={hideChangePassword}
/>
)
wrapper.find("#cancel-change-password").simulate("click")
expect(hideChangePassword).toHaveBeenCalled()
})
})
@@ -0,0 +1,42 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow } from "enzyme"
import Header from "../Header"
jest.mock("../../web", () => ({
LoggedIn: jest
.fn(() => true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
}))
describe("Header", () => {
it("should render without crashing", () => {
shallow(<Header />)
})
it("should render Login button when the user has not LoggedIn", () => {
const wrapper = shallow(<Header />)
expect(wrapper.find("a").text()).toBe("Login")
})
it("should render StorageInfo and BrowserDropdown when the user has LoggedIn", () => {
const wrapper = shallow(<Header />)
expect(wrapper.find("Connect(BrowserDropdown)").length).toBe(1)
expect(wrapper.find("Connect(StorageInfo)").length).toBe(1)
})
})
@@ -0,0 +1,25 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow } from "enzyme"
import Host from "../Host"
describe("Host", () => {
it("should render without crashing", () => {
shallow(<Host />)
})
})
@@ -0,0 +1,108 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow, mount } from "enzyme"
import { Login } from "../Login"
import web from "../../web"
jest.mock("../../web", () => ({
Login: jest.fn(() => {
return Promise.resolve({ token: "test", uiVersion: "2018-02-01T01:17:47Z" })
}),
LoggedIn: jest.fn(),
GetDiscoveryDoc: jest.fn(() => {
return Promise.resolve({ DiscoveryDoc: {"authorization_endpoint": "test"} })
})
}))
describe("Login", () => {
const dispatchMock = jest.fn()
const showAlertMock = jest.fn()
const clearAlertMock = jest.fn()
it("should render without crashing", () => {
shallow(<Login
dispatch={dispatchMock}
alert={{ show: false, type: "danger"}}
showAlert={showAlertMock}
clearAlert={clearAlertMock}
/>)
})
it("should initially have the is-guest class", () => {
const wrapper = shallow(
<Login
dispatch={dispatchMock}
alert={{ show: false, type: "danger"}}
showAlert={showAlertMock}
clearAlert={clearAlertMock}
/>,
{ attachTo: document.body }
)
expect(document.body.classList.contains("is-guest")).toBeTruthy()
})
it("should throw an alert if the keys are empty in login form", () => {
const wrapper = mount(
<Login
dispatch={dispatchMock}
alert={{ show: false, type: "danger"}}
showAlert={showAlertMock}
clearAlert={clearAlertMock}
/>
)
// case where both keys are empty - displays the second warning
wrapper.find("form").simulate("submit")
expect(showAlertMock).toHaveBeenCalledWith("danger", "Secret Key cannot be empty")
// case where access key is empty
wrapper.setState({
accessKey: "",
secretKey: "secretKey"
})
wrapper.find("form").simulate("submit")
expect(showAlertMock).toHaveBeenCalledWith("danger", "Access Key cannot be empty")
// case where secret key is empty
wrapper.setState({
accessKey: "accessKey",
secretKey: ""
})
wrapper.find("form").simulate("submit")
expect(showAlertMock).toHaveBeenCalledWith("danger", "Secret Key cannot be empty")
})
it("should call web.Login with correct arguments if both keys are entered", () => {
const wrapper = mount(
<Login
dispatch={dispatchMock}
alert={{ show: false, type: "danger"}}
showAlert={showAlertMock}
clearAlert={clearAlertMock}
/>
)
wrapper.setState({
accessKey: "accessKey",
secretKey: "secretKey"
})
wrapper.find("form").simulate("submit")
expect(web.Login).toHaveBeenCalledWith({
"username": "accessKey",
"password": "secretKey"
})
})
})
@@ -0,0 +1,82 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow, mount } from "enzyme"
import { MainActions } from "../MainActions"
jest.mock("../../web", () => ({
LoggedIn: jest
.fn(() => true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
.mockReturnValueOnce(false)
}))
describe("MainActions", () => {
it("should render without crashing", () => {
shallow(<MainActions />)
})
it("should not show any actions when user has not LoggedIn and prefixWritable is false", () => {
const wrapper = shallow(<MainActions />)
expect(wrapper.find("#show-make-bucket").length).toBe(0)
expect(wrapper.find("#file-input").length).toBe(0)
})
it("should show only file upload action when user has not LoggedIn and prefixWritable is true", () => {
const wrapper = shallow(<MainActions prefixWritable={true} />)
expect(wrapper.find("#show-make-bucket").length).toBe(0)
expect(wrapper.find("#file-input").length).toBe(1)
})
it("should show make bucket upload file actions when user has LoggedIn", () => {
const wrapper = shallow(<MainActions />)
expect(wrapper.find("#show-make-bucket").length).toBe(1)
expect(wrapper.find("#file-input").length).toBe(1)
})
it("should call showMakeBucketModal when create bucket icon is clicked", () => {
const showMakeBucketModal = jest.fn()
const wrapper = shallow(
<MainActions showMakeBucketModal={showMakeBucketModal} />
)
wrapper
.find("#show-make-bucket")
.simulate("click", { preventDefault: jest.fn() })
expect(showMakeBucketModal).toHaveBeenCalled()
})
it("should call uploadFile when a file is selected for upload", () => {
const uploadFile = jest.fn()
const wrapper = shallow(<MainActions uploadFile={uploadFile} />)
const files = [new Blob(["file content"], { type: "text/plain" })]
const input = wrapper.find("#file-input")
const event = {
preventDefault: jest.fn(),
target: {
files: {
length: files.length,
item: function(index) {
return files[index]
}
}
}
}
input.simulate("change", event)
expect(uploadFile).toHaveBeenCalledWith(files[0])
})
})
@@ -0,0 +1,25 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow } from "enzyme"
import MainContent from "../MainContent"
describe("MainContent", () => {
it("should render without crashing", () => {
shallow(<MainContent />)
})
})
@@ -0,0 +1,36 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow } from "enzyme"
import { MobileHeader } from "../MobileHeader"
describe("Bucket", () => {
it("should render without crashing", () => {
shallow(<MobileHeader sidebarOpen={false} />)
})
it("should toggleSidebar when trigger is clicked", () => {
const toggleSidebar = jest.fn()
const wrapper = shallow(
<MobileHeader sidebarOpen={false} toggleSidebar={toggleSidebar} />
)
wrapper
.find("#sidebar-toggle")
.simulate("click", { stopPropagation: jest.fn() })
expect(toggleSidebar).toHaveBeenCalled()
})
})
@@ -0,0 +1,54 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow } from "enzyme"
import { SideBar } from "../SideBar"
jest.mock("../../web", () => ({
LoggedIn: jest.fn(() => false).mockReturnValueOnce(true)
}))
describe("SideBar", () => {
it("should render without crashing", () => {
shallow(<SideBar />)
})
it("should not render BucketSearch for non LoggedIn users", () => {
const wrapper = shallow(<SideBar />)
expect(wrapper.find("Connect(BucketSearch)").length).toBe(0)
})
it("should call clickOutside when the user clicks outside the sidebar", () => {
const clickOutside = jest.fn()
const wrapper = shallow(<SideBar clickOutside={clickOutside} />)
wrapper.simulate("clickOut", {
preventDefault: jest.fn(),
target: { classList: { contains: jest.fn(() => false) } }
})
expect(clickOutside).toHaveBeenCalled()
})
it("should not call clickOutside when user clicks on sidebar toggle", () => {
const clickOutside = jest.fn()
const wrapper = shallow(<SideBar clickOutside={clickOutside} />)
wrapper.simulate("clickOut", {
preventDefault: jest.fn(),
target: { classList: { contains: jest.fn(() => true) } }
})
expect(clickOutside).not.toHaveBeenCalled()
})
})
@@ -0,0 +1,49 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow } from "enzyme"
import { StorageInfo } from "../StorageInfo"
describe("StorageInfo", () => {
it("should render without crashing", () => {
shallow(
<StorageInfo storageInfo={ {used: 60} } fetchStorageInfo={jest.fn()} />
)
})
it("should fetchStorageInfo before component is mounted", () => {
const fetchStorageInfo = jest.fn()
shallow(
<StorageInfo
storageInfo={ {used: 60} }
fetchStorageInfo={fetchStorageInfo}
/>
)
expect(fetchStorageInfo).toHaveBeenCalled()
})
it("should not render anything if used is null", () => {
const fetchStorageInfo = jest.fn()
const wrapper = shallow(
<StorageInfo
storageInfo={ {used: 0} }
fetchStorageInfo={fetchStorageInfo}
/>
)
expect(wrapper.text()).toBe("")
})
})
@@ -0,0 +1,70 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import configureStore from "redux-mock-store"
import thunk from "redux-thunk"
import * as actionsCommon from "../actions"
jest.mock("../../web", () => ({
StorageInfo: jest.fn(() => {
return Promise.resolve({
used: 60
})
}),
ServerInfo: jest.fn(() => {
return Promise.resolve({
MinioVersion: "test",
MinioPlatform: "test",
MinioRuntime: "test",
MinioGlobalInfo: "test"
})
})
}))
const middlewares = [thunk]
const mockStore = configureStore(middlewares)
describe("Common actions", () => {
it("creates common/SET_STORAGE_INFO after fetching the storage details ", () => {
const store = mockStore()
const expectedActions = [
{ type: "common/SET_STORAGE_INFO", storageInfo: { used: 60 } }
]
return store.dispatch(actionsCommon.fetchStorageInfo()).then(() => {
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
})
it("creates common/SET_SERVER_INFO after fetching the server details", () => {
const store = mockStore()
const expectedActions = [
{
type: "common/SET_SERVER_INFO",
serverInfo: {
version: "test",
platform: "test",
runtime: "test",
info: "test"
}
}
]
return store.dispatch(actionsCommon.fetchServerInfo()).then(() => {
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
})
})
@@ -0,0 +1,87 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import reducer from "../reducer"
import * as actionsCommon from "../actions"
describe("common reducer", () => {
it("should return the initial state", () => {
expect(reducer(undefined, {})).toEqual({
sidebarOpen: false,
storageInfo: {used: 0},
serverInfo: {}
})
})
it("should handle TOGGLE_SIDEBAR", () => {
expect(
reducer(
{ sidebarOpen: false },
{
type: actionsCommon.TOGGLE_SIDEBAR
}
)
).toEqual({
sidebarOpen: true
})
})
it("should handle CLOSE_SIDEBAR", () => {
expect(
reducer(
{ sidebarOpen: true },
{
type: actionsCommon.CLOSE_SIDEBAR
}
)
).toEqual({
sidebarOpen: false
})
})
it("should handle SET_STORAGE_INFO", () => {
expect(
reducer(
{},
{
type: actionsCommon.SET_STORAGE_INFO,
storageInfo: { }
}
)
).toEqual({
storageInfo: { }
})
})
it("should handle SET_SERVER_INFO", () => {
expect(
reducer(undefined, {
type: actionsCommon.SET_SERVER_INFO,
serverInfo: {
version: "test",
platform: "test",
runtime: "test",
info: "test"
}
}).serverInfo
).toEqual({
version: "test",
platform: "test",
runtime: "test",
info: "test"
})
})
})
+66
View File
@@ -0,0 +1,66 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import web from "../web"
export const TOGGLE_SIDEBAR = "common/TOGGLE_SIDEBAR"
export const CLOSE_SIDEBAR = "common/CLOSE_SIDEBAR"
export const SET_STORAGE_INFO = "common/SET_STORAGE_INFO"
export const SET_SERVER_INFO = "common/SET_SERVER_INFO"
export const toggleSidebar = () => ({
type: TOGGLE_SIDEBAR
})
export const closeSidebar = () => ({
type: CLOSE_SIDEBAR
})
export const fetchStorageInfo = () => {
return function(dispatch) {
return web.StorageInfo().then(res => {
const storageInfo = {
used: res.used
}
dispatch(setStorageInfo(storageInfo))
})
}
}
export const setStorageInfo = storageInfo => ({
type: SET_STORAGE_INFO,
storageInfo
})
export const fetchServerInfo = () => {
return function(dispatch) {
return web.ServerInfo().then(res => {
const serverInfo = {
version: res.MinioVersion,
platform: res.MinioPlatform,
runtime: res.MinioRuntime,
info: res.MinioGlobalInfo,
userInfo: res.MinioUserInfo
}
dispatch(setServerInfo(serverInfo))
})
}
}
export const setServerInfo = serverInfo => ({
type: SET_SERVER_INFO,
serverInfo
})
+45
View File
@@ -0,0 +1,45 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as actionsCommon from "./actions"
export default (
state = {
sidebarOpen: false,
storageInfo: {used: 0},
serverInfo: {}
},
action
) => {
switch (action.type) {
case actionsCommon.TOGGLE_SIDEBAR:
return Object.assign({}, state, {
sidebarOpen: !state.sidebarOpen
})
case actionsCommon.CLOSE_SIDEBAR:
return Object.assign({}, state, {
sidebarOpen: false
})
case actionsCommon.SET_STORAGE_INFO:
return Object.assign({}, state, {
storageInfo: action.storageInfo
})
case actionsCommon.SET_SERVER_INFO:
return { ...state, serverInfo: action.serverInfo }
default:
return state
}
}
+24
View File
@@ -0,0 +1,24 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createSelector } from "reselect"
export const getServerInfo = state => state.browser.serverInfo
export const hasServerPublicDomain = createSelector(
getServerInfo,
serverInfo => Boolean(serverInfo.info && serverInfo.info.domains && serverInfo.info.domains.length),
)
+16
View File
@@ -0,0 +1,16 @@
/*
* MinIO Object Storage (c) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/