Browser: Update UI with new components and elements (#5671)

This commit is contained in:
Rushan
2018-03-21 23:39:23 +05:30
committed by Harshavardhana
parent 384b4fdf28
commit 1459c4be1e
199 changed files with 10549 additions and 4702 deletions
+22 -33
View File
@@ -21,40 +21,29 @@ import logo from "../../img/logo.svg"
export const AboutModal = ({ serverInfo, hideAbout }) => {
const { version, memory, 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://minio.io" target="_blank">
<img className="maii-logo" src={logo} alt="" />
</a>
<Modal className="about" animation={false} show={true} onHide={hideAbout}>
<i className="close" onClick={hideAbout} />
<div className="about__content">
<div className="about__logo hidden-xs">
<img src={logo} alt="" />
</div>
<div className="mai-item">
<ul className="maii-list">
<li>
<div>Version</div>
<small>{version}</small>
</li>
<li>
<div>Memory</div>
<small>{memory}</small>
</li>
<li>
<div>Platform</div>
<small>{platform}</small>
</li>
<li>
<div>Runtime</div>
<small>{runtime}</small>
</li>
</ul>
<div className="about__info">
<div className="about__item">
<strong>Version</strong>
<small>{version}</small>
</div>
<div className="about__item">
<strong>Memory</strong>
<small>{memory}</small>
</div>
<div className="about__item">
<strong>Platform</strong>
<small>{platform}</small>
</div>
<div className="about__item">
<strong>Runtime</strong>
<small>{runtime}</small>
</div>
</div>
</div>
</Modal>
+4 -7
View File
@@ -15,24 +15,21 @@
*/
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"
const Aux = props => props.children
class Browser extends React.Component {
render() {
return (
<div
className={classNames({
"file-explorer": true
})}
>
<Aux>
<SideBar />
<MainContent />
<AlertContainer />
</div>
</Aux>
)
}
}
+62 -63
View File
@@ -28,29 +28,29 @@ export class BrowserDropdown extends React.Component {
super(props)
this.state = {
showAboutModal: false,
showChangePasswordModal: false
showChangePasswordModal: false,
}
}
showAbout(e) {
e.preventDefault()
this.setState({
showAboutModal: true
showAboutModal: true,
})
}
hideAbout() {
this.setState({
showAboutModal: false
showAboutModal: false,
})
}
showChangePassword(e) {
e.preventDefault()
this.setState({
showChangePasswordModal: true
showChangePasswordModal: true,
})
}
hideChangePassword() {
this.setState({
showChangePasswordModal: false
showChangePasswordModal: false,
})
}
componentDidMount() {
@@ -81,75 +81,74 @@ export class BrowserDropdown extends React.Component {
render() {
const { serverInfo } = this.props
return (
<li>
<Dropdown pullRight id="top-right-menu">
<Dropdown.Toggle noCaret>
<i className="fa fa-reorder" />
</Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu-right">
<li>
<a target="_blank" href="https://github.com/minio/minio">
Github <i className="fa fa-github" />
</a>
</li>
<li>
<a href="" onClick={this.fullScreen}>
Fullscreen <i className="fa fa-expand" />
</a>
</li>
<li>
<a target="_blank" href="https://docs.minio.io/">
Documentation <i className="fa fa-book" />
</a>
</li>
<li>
<a target="_blank" href="https://slack.minio.io">
Ask for help <i className="fa fa-question-circle" />
</a>
</li>
<li>
<a href="" id="show-about" onClick={this.showAbout.bind(this)}>
About <i className="fa fa-info-circle" />
</a>
{this.state.showAboutModal && (
<AboutModal
serverInfo={serverInfo}
hideAbout={this.hideAbout.bind(this)}
/>
)}
</li>
<li>
<a href="" onClick={this.showChangePassword.bind(this)}>
Change Password <i className="fa fa-cog" />
</a>
{this.state.showChangePasswordModal && (
<ChangePasswordModal
serverInfo={serverInfo}
hideChangePassword={this.hideChangePassword.bind(this)}
/>
)}
</li>
<li>
<a href="" id="logout" onClick={this.logout}>
Sign Out <i className="fa fa-sign-out" />
</a>
</li>
</Dropdown.Menu>
</Dropdown>
</li>
<Dropdown pullRight id="toolbar-menu">
<Dropdown.Toggle
noCaret
className="toolbar__item zmdi zmdi-more-vert"
/>
<Dropdown.Menu>
<li>
<a target="_blank" href="https://github.com/minio/minio">
Github <i className="zmdi zmdi-github" />
</a>
</li>
<li>
<a href="" onClick={this.fullScreen}>
Fullscreen <i className="zmdi zmdi-fullscreen" />
</a>
</li>
<li>
<a target="_blank" href="https://docs.minio.io/">
Documentation <i className="zmdi zmdi-assignment" />
</a>
</li>
<li>
<a target="_blank" href="https://slack.minio.io">
Ask for help <i className="zmdi zmdi-help" />
</a>
</li>
<li>
<a href="" id="show-about" onClick={this.showAbout.bind(this)}>
About <i className="zmdi zmdi-info" />
</a>
{this.state.showAboutModal && (
<AboutModal
serverInfo={serverInfo}
hideAbout={this.hideAbout.bind(this)}
/>
)}
</li>
<li>
<a href="" onClick={this.showChangePassword.bind(this)}>
Change Password <i className="zmdi zmdi-settings" />
</a>
{this.state.showChangePasswordModal && (
<ChangePasswordModal
serverInfo={serverInfo}
hideChangePassword={this.hideChangePassword.bind(this)}
/>
)}
</li>
<li>
<a href="" id="logout" onClick={this.logout}>
Sign Out <i className="zmdi zmdi-sign-in" />
</a>
</li>
</Dropdown.Menu>
</Dropdown>
)
}
}
const mapStateToProps = state => {
return {
serverInfo: state.browser.serverInfo
serverInfo: state.browser.serverInfo,
}
}
const mapDispatchToProps = dispatch => {
return {
fetchServerInfo: () => dispatch(browserActions.fetchServerInfo())
fetchServerInfo: () => dispatch(browserActions.fetchServerInfo()),
}
}
+60 -57
View File
@@ -24,9 +24,8 @@ import {
Modal,
ModalBody,
ModalHeader,
OverlayTrigger
OverlayTrigger,
} from "react-bootstrap"
import InputGroup from "./InputGroup"
export class ChangePasswordModal extends React.Component {
constructor(props) {
@@ -34,7 +33,7 @@ export class ChangePasswordModal extends React.Component {
this.state = {
accessKey: "",
secretKey: "",
keysReadOnly: false
keysReadOnly: false,
}
}
// When its shown, it loads the access key and secret key.
@@ -46,13 +45,13 @@ export class ChangePasswordModal extends React.Component {
this.setState({
accessKey: "xxxxxxxxx",
secretKey: "xxxxxxxxx",
keysReadOnly: true
keysReadOnly: true,
})
} else {
web.GetAuth().then(data => {
this.setState({
accessKey: data.accessKey,
secretKey: data.secretKey
secretKey: data.secretKey,
})
})
}
@@ -61,19 +60,19 @@ export class ChangePasswordModal extends React.Component {
// Handle field changes from inside the modal.
accessKeyChange(e) {
this.setState({
accessKey: e.target.value
accessKey: e.target.value,
})
}
secretKeyChange(e) {
this.setState({
secretKey: e.target.value
secretKey: e.target.value,
})
}
secretKeyVisible(secretKeyVisible) {
this.setState({
secretKeyVisible
secretKeyVisible,
})
}
@@ -85,18 +84,18 @@ export class ChangePasswordModal extends React.Component {
web
.SetAuth({
accessKey,
secretKey
secretKey,
})
.then(data => {
showAlert({
type: "success",
message: "Changed credentials"
message: "Changed credentials",
})
})
.catch(err => {
showAlert({
type: "danger",
message: err.message
message: err.message,
})
})
}
@@ -106,7 +105,7 @@ export class ChangePasswordModal extends React.Component {
this.setState({
accessKey: data.accessKey,
secretKey: data.secretKey,
secretKeyVisible: true
secretKeyVisible: true,
})
})
}
@@ -114,51 +113,55 @@ export class ChangePasswordModal extends React.Component {
render() {
const { hideChangePassword } = this.props
return (
<Modal bsSize="sm" animation={false} show={true}>
<ModalHeader>Change Password</ModalHeader>
<ModalBody className="m-t-20">
<InputGroup
value={this.state.accessKey}
onChange={this.accessKeyChange.bind(this)}
id="accessKey"
label="Access Key"
name="accesskey"
type="text"
spellCheck="false"
required="required"
autoComplete="false"
align="ig-left"
readonly={this.state.keysReadOnly}
/>
<i
onClick={this.secretKeyVisible.bind(
this,
!this.state.secretKeyVisible
)}
className={
"toggle-password fa fa-eye " +
(this.state.secretKeyVisible ? "toggled" : "")
}
/>
<InputGroup
value={this.state.secretKey}
onChange={this.secretKeyChange.bind(this)}
id="secretKey"
label="Secret Key"
name="accesskey"
type={this.state.secretKeyVisible ? "text" : "password"}
spellCheck="false"
required="required"
autoComplete="false"
align="ig-left"
readonly={this.state.keysReadOnly}
/>
</ModalBody>
<Modal bsSize="sm" animation={false} show={true} className="settings">
<Modal.Header>Change Password</Modal.Header>
<Modal.Body className="m-t-20">
<div className="form-group">
<label className="form-group__label">Access key</label>
<input
type="text"
value={this.state.accessKey}
onChange={this.accessKeyChange.bind(this)}
id="accessKey"
className="form-group__field"
name="accesskey"
required="required"
autoComplete="false"
readonly={this.state.keysReadOnly}
/>
<i className="form-group__helper" />
</div>
<div className="form-group">
<label className="form-group__label">Secret key</label>
<input
type={this.state.secretKeyVisible ? "text" : "password"}
value={this.state.secretKey}
onChange={this.accessKeyChange.bind(this)}
id="secretKey"
className="form-group__field"
name="secretKey"
required="required"
autoComplete="false"
readonly={this.state.keysReadOnly}
/>
<i className="form-group__helper" />
<i
onClick={this.secretKeyVisible.bind(
this,
!this.state.secretKeyVisible,
)}
className={
"form-group__addon zmdi " +
(this.state.secretKeyVisible ? "zmdi-eye-off" : "zmdi-eye")
}
/>
</div>
</Modal.Body>
<div className="modal-footer">
<button
id="generate-keys"
className={
"btn btn-primary " + (this.state.keysReadOnly ? "hidden" : "")
"btn btn--link " + (this.state.keysReadOnly ? "hidden" : "")
}
onClick={this.generateAuth.bind(this)}
>
@@ -167,7 +170,7 @@ export class ChangePasswordModal extends React.Component {
<button
id="update-keys"
className={
"btn btn-success " + (this.state.keysReadOnly ? "hidden" : "")
"btn btn--link " + (this.state.keysReadOnly ? "hidden" : "")
}
onClick={this.setAuth.bind(this)}
>
@@ -175,7 +178,7 @@ export class ChangePasswordModal extends React.Component {
</button>
<button
id="cancel-change-password"
className="btn btn-link"
className="btn btn--link"
onClick={hideChangePassword}
>
Cancel
@@ -188,13 +191,13 @@ export class ChangePasswordModal extends React.Component {
const mapStateToProps = state => {
return {
serverInfo: state.browser.serverInfo
serverInfo: state.browser.serverInfo,
}
}
const mapDispatchToProps = dispatch => {
return {
showAlert: alert => dispatch(alertActions.set(alert))
showAlert: alert => dispatch(alertActions.set(alert)),
}
}
+15 -15
View File
@@ -26,30 +26,30 @@ let ConfirmModal = ({
cancelText,
okHandler,
cancelHandler,
show
show,
}) => {
return (
<Modal
bsSize="small"
animation={false}
show={show}
className={"modal-confirm " + (baseClass || "")}
className={"dialog " + (baseClass || "")}
>
<ModalBody>
<div className="mc-icon">
<Modal.Body>
<div className="dialog__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>
<div className="dialog__text">{text}</div>
<div className="dialog__sub">{sub}</div>
<div className="dialog__actions">
<button className="btn btn--link" onClick={okHandler}>
{okText}
</button>
<button className="btn btn--link" onClick={cancelHandler}>
{cancelText}
</button>
</div>
</Modal.Body>
</Modal>
)
}
+3 -16
View File
@@ -16,26 +16,13 @@
import React from "react"
import Path from "../objects/Path"
import StorageInfo from "./StorageInfo"
import BrowserDropdown from "./BrowserDropdown"
import web from "../web"
import { minioBrowserPrefix } from "../constants"
import ObjectsBulkActions from "../objects/ObjectsBulkActions"
export const Header = () => {
const loggedIn = web.LoggedIn()
return (
<header className="fe-header">
<header className="header">
<ObjectsBulkActions />
<Path />
{loggedIn && <StorageInfo />}
<ul className="feh-actions">
{loggedIn ? (
<BrowserDropdown />
) : (
<a className="btn btn-danger" href={minioBrowserPrefix + "/login"}>
Login
</a>
)}
</ul>
</header>
)
}
+1 -6
View File
@@ -16,11 +16,6 @@
import React from "react"
export const Host = () => (
<div className="fes-host">
<i className="fa fa-globe" />
<a href="/">{window.location.host}</a>
</div>
)
export const Host = () => <small>{window.location.host}</small>
export default Host
-70
View File
@@ -1,70 +0,0 @@
/*
* Minio Cloud Storage (C) 2016, 2018 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
+37 -36
View File
@@ -16,11 +16,9 @@
import React from "react"
import { connect } from "react-redux"
import classNames from "classnames"
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"
@@ -42,7 +40,7 @@ export class Login extends React.Component {
web
.Login({
username: document.getElementById("accessKey").value,
password: document.getElementById("secretKey").value
password: document.getElementById("secretKey").value,
})
.then(res => {
history.push("/")
@@ -74,39 +72,37 @@ export class Login extends React.Component {
return (
<div className="login">
{alertBox}
<div className="l-wrap">
<form onSubmit={this.handleSubmit.bind(this)}>
<InputGroup
className="ig-dark"
label="Access Key"
id="accessKey"
name="username"
type="text"
spellCheck="false"
required="required"
autoComplete="username"
/>
<InputGroup
className="ig-dark"
label="Secret Key"
id="secretKey"
name="password"
type="password"
spellCheck="false"
required="required"
autoComplete="new-password"
/>
<button className="lw-btn" type="submit">
<i className="fa fa-sign-in" />
</button>
<div className="login__inner">
<div className="login__header">
<img className="login__logo" src={logo} alt="" />
<div className="login__host">{window.location.host}</div>
</div>
<form className="login__form" onSubmit={this.handleSubmit.bind(this)}>
<div className="form-group">
<input
placeholder="Access Key"
type="text"
id="accessKey"
className="form-group__field"
spellCheck="false"
required="required"
/>
<i className="form-group__helper" />
</div>
<div className="form-group">
<input
placeholder="Secret Key"
type="password"
id="secretKey"
className="form-group__field"
spellCheck="false"
required="required"
/>
<i className="form-group__helper" />
</div>
<button className="login__btn" type="submit" />
</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>
)
}
@@ -115,8 +111,13 @@ export class Login extends React.Component {
const mapDispatchToProps = dispatch => {
return {
showAlert: (type, message) =>
dispatch(actionsAlert.set({ type: type, message: message })),
clearAlert: () => dispatch(actionsAlert.clear())
dispatch(
actionsAlert.set({
type: type,
message: message,
}),
),
clearAlert: () => dispatch(actionsAlert.clear()),
}
}
+19 -17
View File
@@ -25,11 +25,11 @@ import { getPrefixWritable } from "../objects/selectors"
export const MainActions = ({
prefixWritable,
uploadFile,
showMakeBucketModal
showMakeBucketModal,
}) => {
const uploadTooltip = <Tooltip id="tt-upload-file">Upload file</Tooltip>
const uploadTooltip = <Tooltip id="tooltip-upload-file">Upload file</Tooltip>
const makeBucketTooltip = (
<Tooltip id="tt-create-bucket">Create bucket</Tooltip>
<Tooltip id="tooltip-create-bucket">Create bucket</Tooltip>
)
const onFileUpload = e => {
e.preventDefault()
@@ -41,15 +41,20 @@ export const MainActions = ({
if (loggedIn || prefixWritable) {
return (
<Dropdown dropup className="feb-actions" id="fe-action-toggle">
<Dropdown.Toggle noCaret className="feba-toggle">
<span>
<i className="fa fa-plus" />
</span>
<Dropdown
dropup
pullRight
className="create"
id="main-actions"
componentClass="div"
>
<Dropdown.Toggle noCaret className="create__toggle" useAnchor={true}>
<i className="zmdi zmdi-plus" />
<i className="zmdi zmdi-close" />
</Dropdown.Toggle>
<Dropdown.Menu>
<OverlayTrigger placement="left" overlay={uploadTooltip}>
<a href="#" className="feba-btn feba-upload">
<a href="#" className="create__btn create__btn--upload">
<input
type="file"
onChange={onFileUpload}
@@ -57,8 +62,7 @@ export const MainActions = ({
id="file-input"
/>
<label htmlFor="file-input">
{" "}
<i className="fa fa-cloud-upload" />{" "}
<i className="zmdi zmdi-upload" />
</label>
</a>
</OverlayTrigger>
@@ -67,14 +71,12 @@ export const MainActions = ({
<a
href="#"
id="show-make-bucket"
className="feba-btn feba-bucket"
className="create__btn create__btn--bucket"
onClick={e => {
e.preventDefault()
showMakeBucketModal()
}}
>
<i className="fa fa-hdd-o" />
</a>
/>
</OverlayTrigger>
)}
</Dropdown.Menu>
@@ -87,14 +89,14 @@ export const MainActions = ({
const mapStateToProps = state => {
return {
prefixWritable: getPrefixWritable(state)
prefixWritable: getPrefixWritable(state),
}
}
const mapDispatchToProps = dispatch => {
return {
uploadFile: file => dispatch(uploadsActions.uploadFile(file)),
showMakeBucketModal: () => dispatch(actionsBuckets.showMakeBucketModal())
showMakeBucketModal: () => dispatch(actionsBuckets.showMakeBucketModal()),
}
}
+6 -8
View File
@@ -15,29 +15,27 @@
*/
import React from "react"
import MobileHeader from "./MobileHeader"
import Header from "./Header"
import SidebarBackdrop from "./SidebarBackdrop"
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 />
export const MainContent = ({ sidebarOpen }) => (
<section className="content">
<Header />
<Dropzone>
<Header />
<ObjectsSection />
</Dropzone>
<MainActions />
<BucketPolicyModal />
<MakeBucketModal />
<UploadModal />
</div>
<SidebarBackdrop />
</section>
)
export default MainContent
+20 -19
View File
@@ -16,49 +16,50 @@
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 Dropdown from "react-bootstrap/lib/Dropdown"
import BucketSearch from "../buckets/BucketSearch"
import BucketList from "../buckets/BucketList"
import StorageInfo from "./StorageInfo"
import Host from "./Host"
import * as actionsCommon from "./actions"
import web from "../web"
export const SideBar = ({ sidebarOpen, clickOutside }) => {
export const SideBar = ({ sidebarOpen }) => {
return (
<ClickOutHandler onClickOut={clickOutside}>
<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>
<aside
className={classNames({
sidebar: true,
"sidebar--toggled": sidebarOpen,
})}
>
<div className="sidebar__inner">
<div className="logo">
<img className="logo__img" src={logo} alt="" />
<div className="logo__title">
<h2>Minio Browser</h2>
<Host />
</div>
</div>
<div className="fes-list">
<div className="buckets">
{web.LoggedIn() && <BucketSearch />}
<BucketList />
</div>
<Host />
<StorageInfo />
</div>
</ClickOutHandler>
</aside>
)
}
const mapStateToProps = state => {
return {
sidebarOpen: state.browser.sidebarOpen
sidebarOpen: state.browser.sidebarOpen,
}
}
const mapDispatchToProps = dispatch => {
return {
clickOutside: () => dispatch(actionsCommon.closeSidebar())
toggleSidebar: () => dispatch(actionsCommon.toggleSidebar()),
}
}
@@ -15,46 +15,29 @@
*/
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>
export const SidebarBackdrop = ({ sidebarOpen, toggleSidebar }) => (
<div
className="sidebar-backdrop"
onClick={e => {
e.stopPropagation()
toggleSidebar()
}}
/>
)
const mapStateToProps = state => {
return {
sidebarOpen: state.browser.sidebarOpen
sidebarOpen: state.browser.sidebarOpen,
}
}
const mapDispatchToProps = dispatch => {
return {
toggleSidebar: () => dispatch(actionsCommon.toggleSidebar())
toggleSidebar: () => dispatch(actionsCommon.toggleSidebar()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MobileHeader)
export default connect(mapStateToProps, mapDispatchToProps)(SidebarBackdrop)
+43
View File
@@ -0,0 +1,43 @@
/*
* Minio Cloud Storage (C) 2018 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 * as actionsCommon from "./actions"
export const SidebarToggle = ({ sidebarOpen, toggleSidebar }) => (
<button
className="toolbar__item zmdi zmdi-menu"
onClick={e => {
e.stopPropagation()
toggleSidebar()
}}
/>
)
const mapStateToProps = state => {
return {
sidebarOpen: state.browser.sidebarOpen,
}
}
const mapDispatchToProps = dispatch => {
return {
toggleSidebar: () => dispatch(actionsCommon.toggleSidebar()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SidebarToggle)
+9 -16
View File
@@ -28,22 +28,15 @@ export class StorageInfo extends React.Component {
const { total, free } = this.props.storageInfo
const used = total - free
const usedPercent = used / total * 100 + "%"
const freePercent = free * 100 / total
return (
<div className="feh-usage">
<div className="fehu-chart">
<div style={{ width: usedPercent }} />
<div className="storage hidden-sm hidden-xs">
<small>
{humanize.filesize(total - free)} of {humanize.filesize(total)} Used
</small>
<div className="storage__progress">
<span style={{ width: usedPercent }} />
</div>
<ul>
<li>
<span>Used: </span>
{humanize.filesize(total - free)}
</li>
<li className="pull-right">
<span>Free: </span>
{humanize.filesize(total - used)}
</li>
</ul>
</div>
)
}
@@ -51,13 +44,13 @@ export class StorageInfo extends React.Component {
const mapStateToProps = state => {
return {
storageInfo: state.browser.storageInfo
storageInfo: state.browser.storageInfo,
}
}
const mapDispatchToProps = dispatch => {
return {
fetchStorageInfo: () => dispatch(actionsCommon.fetchStorageInfo())
fetchStorageInfo: () => dispatch(actionsCommon.fetchStorageInfo()),
}
}
@@ -23,7 +23,7 @@ describe("AboutModal", () => {
version: "test",
memory: "test",
platform: "test",
runtime: "test"
runtime: "test",
}
it("should render without crashing", () => {
@@ -33,7 +33,7 @@ describe("AboutModal", () => {
it("should call hideAbout when close button is clicked", () => {
const hideAbout = jest.fn()
const wrapper = shallow(
<AboutModal serverInfo={serverInfo} hideAbout={hideAbout} />
<AboutModal serverInfo={serverInfo} hideAbout={hideAbout} />,
)
wrapper.find("button").simulate("click")
expect(hideAbout).toHaveBeenCalled()
@@ -24,6 +24,6 @@ const mockStore = configureStore()
describe("Browser", () => {
it("should render without crashing", () => {
const store = mockStore()
shallow(<Browser store={store}/>)
shallow(<Browser store={store} />)
})
})
@@ -23,12 +23,12 @@ describe("BrowserDropdown", () => {
version: "test",
memory: "test",
platform: "test",
runtime: "test"
runtime: "test",
}
it("should render without crashing", () => {
shallow(
<BrowserDropdown serverInfo={serverInfo} fetchServerInfo={jest.fn()} />
<BrowserDropdown serverInfo={serverInfo} fetchServerInfo={jest.fn()} />,
)
})
@@ -38,16 +38,18 @@ describe("BrowserDropdown", () => {
<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()} />
<BrowserDropdown serverInfo={serverInfo} fetchServerInfo={jest.fn()} />,
)
wrapper.find("#show-about").simulate("click", { preventDefault: 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)
@@ -55,9 +57,11 @@ describe("BrowserDropdown", () => {
it("should logout and redirect to /login when logout is clicked", () => {
const wrapper = shallow(
<BrowserDropdown serverInfo={serverInfo} fetchServerInfo={jest.fn()} />
<BrowserDropdown serverInfo={serverInfo} fetchServerInfo={jest.fn()} />,
)
wrapper.find("#logout").simulate("click", { preventDefault: jest.fn() })
wrapper.find("#logout").simulate("click", {
preventDefault: jest.fn(),
})
expect(window.location.pathname.endsWith("/login")).toBeTruthy()
})
})
@@ -20,18 +20,26 @@ import { ChangePasswordModal } from "../ChangePasswordModal"
jest.mock("../../web", () => ({
GetAuth: jest.fn(() => {
return Promise.resolve({ accessKey: "test1", secretKey: "test2" })
return Promise.resolve({
accessKey: "test1",
secretKey: "test2",
})
}),
GenerateAuth: jest.fn(() => {
return Promise.resolve({ accessKey: "gen1", secretKey: "gen2" })
return Promise.resolve({
accessKey: "gen1",
secretKey: "gen2",
})
}),
SetAuth: jest.fn(({ accessKey, secretKey }) => {
if (accessKey == "test3" && secretKey == "test4") {
return Promise.resolve({})
} else {
return Promise.reject({ message: "Error" })
return Promise.reject({
message: "Error",
})
}
})
}),
}))
describe("ChangePasswordModal", () => {
@@ -40,7 +48,9 @@ describe("ChangePasswordModal", () => {
memory: "test",
platform: "test",
runtime: "test",
info: { isEnvCreds: false }
info: {
isEnvCreds: false,
},
}
it("should render without crashing", () => {
@@ -56,7 +66,12 @@ describe("ChangePasswordModal", () => {
})
it("should show readonly keys when isEnvCreds is true", () => {
const newServerInfo = { ...serverInfo, info: { isEnvCreds: true } }
const newServerInfo = {
...serverInfo,
info: {
isEnvCreds: true,
},
}
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
expect(wrapper.state("accessKey")).toBe("xxxxxxxxx")
expect(wrapper.state("secretKey")).toBe("xxxxxxxxx")
@@ -78,19 +93,23 @@ describe("ChangePasswordModal", () => {
it("should update accessKey and secretKey when Update button is clicked", () => {
const showAlert = jest.fn()
const wrapper = shallow(
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} />
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} />,
)
wrapper
.find("#accessKey")
.simulate("change", { target: { value: "test3" } })
wrapper
.find("#secretKey")
.simulate("change", { target: { value: "test4" } })
wrapper.find("#accessKey").simulate("change", {
target: {
value: "test3",
},
})
wrapper.find("#secretKey").simulate("change", {
target: {
value: "test4",
},
})
wrapper.find("#update-keys").simulate("click")
setImmediate(() => {
expect(showAlert).toHaveBeenCalledWith({
type: "success",
message: "Changed credentials"
message: "Changed credentials",
})
})
})
@@ -101,7 +120,7 @@ describe("ChangePasswordModal", () => {
<ChangePasswordModal
serverInfo={serverInfo}
hideChangePassword={hideChangePassword}
/>
/>,
)
wrapper.find("#cancel-change-password").simulate("click")
expect(hideChangePassword).toHaveBeenCalled()
@@ -22,7 +22,7 @@ jest.mock("../../web", () => ({
LoggedIn: jest
.fn(() => true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
.mockReturnValueOnce(false),
}))
describe("Header", () => {
it("should render without crashing", () => {
+47 -27
View File
@@ -19,81 +19,101 @@ import { shallow, mount } from "enzyme"
import { Login } from "../Login"
import web from "../../web"
jest.mock('../../web', () => ({
jest.mock("../../web", () => ({
Login: jest.fn(() => {
return Promise.resolve({ token: "test", uiVersion: "2018-02-01T01:17:47Z" })
return Promise.resolve({
token: "test",
uiVersion: "2018-02-01T01:17:47Z",
})
}),
LoggedIn: jest.fn()
LoggedIn: jest.fn(),
}))
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}
/>)
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"}}
<Login
dispatch={dispatchMock}
alert={{ show: false, type: "danger" }}
showAlert={showAlertMock}
clearAlert={clearAlertMock}
/>,
{ attachTo: document.body }
{
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"}}
<Login
dispatch={dispatchMock}
alert={{ show: false, type: "danger" }}
showAlert={showAlertMock}
clearAlert={clearAlertMock}
/>,
{ attachTo: document.body }
{
attachTo: document.body,
},
)
// case where both keys are empty - displays the second warning
wrapper.find("form").simulate("submit")
expect(showAlertMock).toHaveBeenCalledWith("danger", "Secret Key cannot be empty")
expect(showAlertMock).toHaveBeenCalledWith(
"danger",
"Secret Key cannot be empty",
)
// case where access key is empty
document.getElementById("secretKey").value = "secretKey"
wrapper.find("form").simulate("submit")
expect(showAlertMock).toHaveBeenCalledWith("danger", "Access Key cannot be empty")
expect(showAlertMock).toHaveBeenCalledWith(
"danger",
"Access Key cannot be empty",
)
// case where secret key is empty
document.getElementById("accessKey").value = "accessKey"
wrapper.find("form").simulate("submit")
expect(showAlertMock).toHaveBeenCalledWith("danger", "Secret Key cannot be empty")
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"}}
<Login
dispatch={dispatchMock}
alert={{ show: false, type: "danger" }}
showAlert={showAlertMock}
clearAlert={clearAlertMock}
/>,
{ attachTo: document.body }
{
attachTo: document.body,
},
)
document.getElementById("accessKey").value = "accessKey"
document.getElementById("secretKey").value = "secretKey"
wrapper.find("form").simulate("submit")
expect(web.Login).toHaveBeenCalledWith({
"username": "accessKey",
"password": "secretKey"
username: "accessKey",
password: "secretKey",
})
})
})
@@ -23,7 +23,7 @@ jest.mock("../../web", () => ({
.fn(() => true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
.mockReturnValueOnce(false)
.mockReturnValueOnce(false),
}))
describe("MainActions", () => {
@@ -52,21 +52,25 @@ describe("MainActions", () => {
it("should call showMakeBucketModal when create bucket icon is clicked", () => {
const showMakeBucketModal = jest.fn()
const wrapper = shallow(
<MainActions showMakeBucketModal={showMakeBucketModal} />
<MainActions showMakeBucketModal={showMakeBucketModal} />,
)
wrapper
.find("#show-make-bucket")
.simulate("click", { preventDefault: jest.fn() })
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 file = new Blob(["file content"], { type: "text/plain" })
const file = new Blob(["file content"], {
type: "text/plain",
})
wrapper.find("#file-input").simulate("change", {
preventDefault: jest.fn(),
target: { files: [file] }
target: {
files: [file],
},
})
expect(uploadFile).toHaveBeenCalledWith(file)
})
@@ -26,11 +26,11 @@ describe("Bucket", () => {
it("should toggleSidebar when trigger is clicked", () => {
const toggleSidebar = jest.fn()
const wrapper = shallow(
<MobileHeader sidebarOpen={false} toggleSidebar={toggleSidebar} />
<MobileHeader sidebarOpen={false} toggleSidebar={toggleSidebar} />,
)
wrapper
.find("#sidebar-toggle")
.simulate("click", { stopPropagation: jest.fn() })
wrapper.find("#sidebar-toggle").simulate("click", {
stopPropagation: jest.fn(),
})
expect(toggleSidebar).toHaveBeenCalled()
})
})
@@ -19,7 +19,7 @@ import { shallow } from "enzyme"
import { SideBar } from "../SideBar"
jest.mock("../../web", () => ({
LoggedIn: jest.fn(() => false).mockReturnValueOnce(true)
LoggedIn: jest.fn(() => false).mockReturnValueOnce(true),
}))
describe("SideBar", () => {
@@ -35,7 +35,9 @@ describe("SideBar", () => {
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() })
wrapper.simulate("clickOut", {
preventDefault: jest.fn(),
})
expect(clickOutside).toHaveBeenCalled()
})
})
@@ -24,7 +24,7 @@ describe("StorageInfo", () => {
<StorageInfo
storageInfo={{ total: 100, free: 60 }}
fetchStorageInfo={jest.fn()}
/>
/>,
)
})
@@ -34,7 +34,7 @@ describe("StorageInfo", () => {
<StorageInfo
storageInfo={{ total: 100, free: 60 }}
fetchStorageInfo={fetchStorageInfo}
/>
/>,
)
expect(fetchStorageInfo).toHaveBeenCalled()
})
@@ -20,7 +20,12 @@ import * as actionsCommon from "../actions"
jest.mock("../../web", () => ({
StorageInfo: jest.fn(() => {
return Promise.resolve({ storageInfo: { Total: 100, Free: 60 } })
return Promise.resolve({
storageInfo: {
Total: 100,
Free: 60,
},
})
}),
ServerInfo: jest.fn(() => {
return Promise.resolve({
@@ -28,9 +33,9 @@ jest.mock("../../web", () => ({
MinioMemory: "test",
MinioPlatform: "test",
MinioRuntime: "test",
MinioGlobalInfo: "test"
MinioGlobalInfo: "test",
})
})
}),
}))
const middlewares = [thunk]
@@ -40,7 +45,13 @@ 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: { total: 100, free: 60 } }
{
type: "common/SET_STORAGE_INFO",
storageInfo: {
total: 100,
free: 60,
},
},
]
return store.dispatch(actionsCommon.fetchStorageInfo()).then(() => {
const actions = store.getActions()
@@ -58,9 +69,9 @@ describe("Common actions", () => {
memory: "test",
platform: "test",
runtime: "test",
info: "test"
}
}
info: "test",
},
},
]
return store.dispatch(actionsCommon.fetchServerInfo()).then(() => {
const actions = store.getActions()
@@ -23,35 +23,39 @@ describe("common reducer", () => {
sidebarOpen: false,
storageInfo: {
total: 0,
free: 0
free: 0,
},
serverInfo: {}
serverInfo: {},
})
})
it("should handle TOGGLE_SIDEBAR", () => {
expect(
reducer(
{ sidebarOpen: false },
{
type: actionsCommon.TOGGLE_SIDEBAR
}
)
sidebarOpen: false,
},
{
type: actionsCommon.TOGGLE_SIDEBAR,
},
),
).toEqual({
sidebarOpen: true
sidebarOpen: true,
})
})
it("should handle CLOSE_SIDEBAR", () => {
expect(
reducer(
{ sidebarOpen: true },
{
type: actionsCommon.CLOSE_SIDEBAR
}
)
sidebarOpen: true,
},
{
type: actionsCommon.CLOSE_SIDEBAR,
},
),
).toEqual({
sidebarOpen: false
sidebarOpen: false,
})
})
@@ -61,11 +65,17 @@ describe("common reducer", () => {
{},
{
type: actionsCommon.SET_STORAGE_INFO,
storageInfo: { total: 100, free: 40 }
}
)
storageInfo: {
total: 100,
free: 40,
},
},
),
).toEqual({
storageInfo: { total: 100, free: 40 }
storageInfo: {
total: 100,
free: 40,
},
})
})
@@ -78,15 +88,15 @@ describe("common reducer", () => {
memory: "test",
platform: "test",
runtime: "test",
info: "test"
}
}).serverInfo
info: "test",
},
}).serverInfo,
).toEqual({
version: "test",
memory: "test",
platform: "test",
runtime: "test",
info: "test"
info: "test",
})
})
})
+6 -6
View File
@@ -22,11 +22,11 @@ export const SET_STORAGE_INFO = "common/SET_STORAGE_INFO"
export const SET_SERVER_INFO = "common/SET_SERVER_INFO"
export const toggleSidebar = () => ({
type: TOGGLE_SIDEBAR
type: TOGGLE_SIDEBAR,
})
export const closeSidebar = () => ({
type: CLOSE_SIDEBAR
type: CLOSE_SIDEBAR,
})
export const fetchStorageInfo = () => {
@@ -34,7 +34,7 @@ export const fetchStorageInfo = () => {
return web.StorageInfo().then(res => {
const storageInfo = {
total: res.storageInfo.Total,
free: res.storageInfo.Free
free: res.storageInfo.Free,
}
dispatch(setStorageInfo(storageInfo))
})
@@ -43,7 +43,7 @@ export const fetchStorageInfo = () => {
export const setStorageInfo = storageInfo => ({
type: SET_STORAGE_INFO,
storageInfo
storageInfo,
})
export const fetchServerInfo = () => {
@@ -54,7 +54,7 @@ export const fetchServerInfo = () => {
memory: res.MinioMemory,
platform: res.MinioPlatform,
runtime: res.MinioRuntime,
info: res.MinioGlobalInfo
info: res.MinioGlobalInfo,
}
dispatch(setServerInfo(serverInfo))
})
@@ -63,5 +63,5 @@ export const fetchServerInfo = () => {
export const setServerInfo = serverInfo => ({
type: SET_SERVER_INFO,
serverInfo
serverInfo,
})
+13 -7
View File
@@ -19,26 +19,32 @@ import * as actionsCommon from "./actions"
export default (
state = {
sidebarOpen: false,
storageInfo: { total: 0, free: 0 },
serverInfo: {}
storageInfo: {
total: 0,
free: 0,
},
serverInfo: {},
},
action
action,
) => {
switch (action.type) {
case actionsCommon.TOGGLE_SIDEBAR:
return Object.assign({}, state, {
sidebarOpen: !state.sidebarOpen
sidebarOpen: !state.sidebarOpen,
})
case actionsCommon.CLOSE_SIDEBAR:
return Object.assign({}, state, {
sidebarOpen: false
sidebarOpen: false,
})
case actionsCommon.SET_STORAGE_INFO:
return Object.assign({}, state, {
storageInfo: action.storageInfo
storageInfo: action.storageInfo,
})
case actionsCommon.SET_SERVER_INFO:
return { ...state, serverInfo: action.serverInfo }
return {
...state,
serverInfo: action.serverInfo,
}
default:
return state
}