Refactor of components (#5499)

This commit is contained in:
Kanagaraj M 2018-02-10 08:04:14 +05:30 committed by Harshavardhana
parent 9ab6a8035f
commit feb726dd98
40 changed files with 1733 additions and 127 deletions

View File

@ -28,14 +28,26 @@ const middlewares = [thunk]
const mockStore = configureStore(middlewares)
describe("Buckets actions", () => {
it("creates buckets/SET_LIST after fetching the buckets", () => {
it("creates buckets/SET_LIST and buckets/SET_CURRENT_BUCKET after fetching the buckets", () => {
const store = mockStore()
const expectedActions = [
{ type: "buckets/SET_LIST", buckets: ["test1", "test2"] }
{ type: "buckets/SET_LIST", buckets: ["test1", "test2"] },
{ type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" }
]
return store.dispatch(actionsBuckets.fetchBuckets()).then(() => {
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
})
it("should update browser url and creates buckets/SET_CURRENT_BUCKET action when selectBucket is called", () => {
const store = mockStore()
const expectedActions = [
{ type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" }
]
store.dispatch(actionsBuckets.selectBucket("test1"))
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
expect(window.location.pathname).toBe("/test1")
})
})

View File

@ -0,0 +1,41 @@
/*
* 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 configureStore from "redux-mock-store"
import thunk from "redux-thunk"
import * as actionsCommon from "../common"
jest.mock("../../web", () => ({
StorageInfo: jest.fn(() => {
return Promise.resolve({ storageInfo: { Total: 100, Free: 60 } })
})
}))
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: { total: 100, free: 60 } }
]
return store.dispatch(actionsCommon.fetchStorageInfo()).then(() => {
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
})
})

View File

@ -0,0 +1,171 @@
/*
* 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 configureStore from "redux-mock-store"
import thunk from "redux-thunk"
import * as actionsObjects from "../objects"
jest.mock("../../web", () => ({
ListObjects: jest.fn(() => {
return Promise.resolve({
objects: [{ name: "test1" }, { name: "test2" }],
istruncated: false,
nextmarker: "test2"
})
})
}))
const middlewares = [thunk]
const mockStore = configureStore(middlewares)
describe("Objects actions", () => {
it("creates objects/SET_LIST action", () => {
const store = mockStore()
const expectedActions = [
{
type: "objects/SET_LIST",
objects: [{ name: "test1" }, { name: "test2" }],
isTruncated: false,
marker: "test2"
}
]
store.dispatch(
actionsObjects.setList(
[{ name: "test1" }, { name: "test2" }],
"test2",
false
)
)
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
it("creates objects/SET_SORT_BY action", () => {
const store = mockStore()
const expectedActions = [
{
type: "objects/SET_SORT_BY",
sortBy: "name"
}
]
store.dispatch(actionsObjects.setSortBy("name"))
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
it("creates objects/SET_SORT_ORDER action", () => {
const store = mockStore()
const expectedActions = [
{
type: "objects/SET_SORT_ORDER",
sortOrder: true
}
]
store.dispatch(actionsObjects.setSortOrder(true))
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
it("creates objects/SET_LIST after fetching the objects", () => {
const store = mockStore({
buckets: { currentBucket: "bk1" },
objects: { currentPrefix: "" }
})
const expectedActions = [
{
type: "objects/SET_LIST",
objects: [{ name: "test1" }, { name: "test2" }],
marker: "test2",
isTruncated: false
},
{
type: "objects/SET_SORT_BY",
sortBy: ""
},
{
type: "objects/SET_SORT_ORDER",
sortOrder: false
}
]
return store.dispatch(actionsObjects.fetchObjects()).then(() => {
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
})
it("creates objects/APPEND_LIST after fetching more objects", () => {
const store = mockStore({
buckets: { currentBucket: "bk1" },
objects: { currentPrefix: "" }
})
const expectedActions = [
{
type: "objects/APPEND_LIST",
objects: [{ name: "test1" }, { name: "test2" }],
marker: "test2",
isTruncated: false
}
]
return store.dispatch(actionsObjects.fetchObjects(true)).then(() => {
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
})
it("creates objects/SET_SORT_BY and objects/SET_SORT_ORDER when sortObjects is called", () => {
const store = mockStore({
objects: {
list: [],
sortBy: "",
sortOrder: false,
isTruncated: false,
marker: ""
}
})
const expectedActions = [
{
type: "objects/SET_SORT_BY",
sortBy: "name"
},
{
type: "objects/SET_SORT_ORDER",
sortOrder: true
},
{
type: "objects/SET_LIST",
objects: [],
isTruncated: false,
marker: ""
}
]
store.dispatch(actionsObjects.sortObjects("name"))
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
it("should update browser url and creates objects/SET_CURRENT_PREFIX action when selectPrefix is called", () => {
const store = mockStore({
buckets: { currentBucket: "test" }
})
const expectedActions = [
{ type: "objects/SET_CURRENT_PREFIX", prefix: "abc/" }
]
store.dispatch(actionsObjects.selectPrefix("abc/"))
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
expect(window.location.pathname.endsWith("/test/abc/")).toBeTruthy()
})
})

View File

@ -15,6 +15,7 @@
*/
import web from "../web"
import history from "../history"
export const SET_LIST = "buckets/SET_LIST"
export const SET_FILTER = "buckets/SET_FILTER"
@ -25,6 +26,9 @@ export const fetchBuckets = () => {
return web.ListBuckets().then(res => {
const buckets = res.buckets ? res.buckets.map(bucket => bucket.name) : []
dispatch(setList(buckets))
if (buckets.length > 0) {
dispatch(selectBucket(buckets[0]))
}
})
}
}
@ -43,6 +47,13 @@ export const setFilter = filter => {
}
}
export const selectBucket = bucket => {
return function(dispatch) {
dispatch(setCurrentBucket(bucket))
history.push(`/${bucket}`)
}
}
export const setCurrentBucket = bucket => {
return {
type: SET_CURRENT_BUCKET,

View File

@ -0,0 +1,46 @@
/*
* 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 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 toggleSidebar = () => ({
type: TOGGLE_SIDEBAR
})
export const closeSidebar = () => ({
type: CLOSE_SIDEBAR
})
export const fetchStorageInfo = () => {
return function(dispatch) {
return web.StorageInfo().then(res => {
const storageInfo = {
total: res.storageInfo.Total,
free: res.storageInfo.Free
}
dispatch(setStorageInfo(storageInfo))
})
}
}
export const setStorageInfo = storageInfo => ({
type: SET_STORAGE_INFO,
storageInfo
})

View File

@ -0,0 +1,126 @@
/*
* 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 web from "../web"
import history from "../history"
import {
sortObjectsByName,
sortObjectsBySize,
sortObjectsByDate
} from "../utils"
export const SET_LIST = "objects/SET_LIST"
export const APPEND_LIST = "objects/APPEND_LIST"
export const SET_SORT_BY = "objects/SET_SORT_BY"
export const SET_SORT_ORDER = "objects/SET_SORT_ORDER"
export const SET_CURRENT_PREFIX = "objects/SET_CURRENT_PREFIX"
export const setList = (objects, marker, isTruncated) => ({
type: SET_LIST,
objects,
marker,
isTruncated
})
export const appendList = (objects, marker, isTruncated) => ({
type: APPEND_LIST,
objects,
marker,
isTruncated
})
export const fetchObjects = append => {
return function(dispatch, getState) {
const {
buckets: { currentBucket },
objects: { currentPrefix, marker }
} = getState()
return web
.ListObjects({
bucketName: currentBucket,
prefix: currentPrefix,
marker: marker
})
.then(res => {
let objects = []
if (res.objects) {
objects = res.objects.map(object => {
return {
...object,
name: object.name.replace(currentPrefix, "")
}
})
}
if (append) {
dispatch(appendList(objects, res.nextmarker, res.istruncated))
} else {
dispatch(setList(objects, res.nextmarker, res.istruncated))
dispatch(setSortBy(""))
dispatch(setSortOrder(false))
}
})
}
}
export const sortObjects = sortBy => {
return function(dispatch, getState) {
const { objects } = getState()
const sortOrder = objects.sortBy == sortBy ? !objects.sortOrder : true
dispatch(setSortBy(sortBy))
dispatch(setSortOrder(sortOrder))
let list
switch (sortBy) {
case "name":
list = sortObjectsByName(objects.list, sortOrder)
break
case "size":
list = sortObjectsBySize(objects.list, sortOrder)
break
case "last-modified":
list = sortObjectsByDate(objects.list, sortOrder)
break
default:
list = objects.list
break
}
dispatch(setList(list, objects.marker, objects.isTruncated))
}
}
export const setSortBy = sortBy => ({
type: SET_SORT_BY,
sortBy
})
export const setSortOrder = sortOrder => ({
type: SET_SORT_ORDER,
sortOrder
})
export const selectPrefix = prefix => {
return function(dispatch, getState) {
dispatch(setCurrentPrefix(prefix))
const currentBucket = getState().buckets.currentBucket
history.replace(`/${currentBucket}/${prefix}`)
}
}
export const setCurrentPrefix = prefix => {
return {
type: SET_CURRENT_PREFIX,
prefix
}
}

View File

@ -18,6 +18,7 @@ import React from "react"
import classNames from "classnames"
import { connect } from "react-redux"
import SideBar from "./SideBar"
import MainContent from "./MainContent"
class Browser extends React.Component {
render() {
@ -28,6 +29,7 @@ class Browser extends React.Component {
})}
>
<SideBar />
<MainContent />
</div>
)
}

View File

@ -28,7 +28,7 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = dispatch => {
return {
selectBucket: bucket => dispatch(actionsBuckets.setCurrentBucket(bucket))
selectBucket: bucket => dispatch(actionsBuckets.selectBucket(bucket))
}
}

View File

@ -0,0 +1,28 @@
/*
* 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 Path from "./Path"
import StorageInfo from "./StorageInfo"
export const Header = () => (
<header className="fe-header">
<Path />
<StorageInfo />
</header>
)
export default Header

View File

@ -0,0 +1,30 @@
/*
* 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 MobileHeader from "./MobileHeader"
import Header from "./Header"
import ObjectsSection from "./ObjectsSection"
export const MainContent = () => (
<div className="fe-body">
<MobileHeader />
<Header />
<ObjectsSection />
</div>
)
export default MainContent

View File

@ -0,0 +1,60 @@
/*
* 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 classNames from "classnames"
import { connect } from "react-redux"
import logo from "../../img/logo.svg"
import * as actionsCommon from "../actions/common"
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.common.sidebarOpen
}
}
const mapDispatchToProps = dispatch => {
return {
toggleSidebar: () => dispatch(actionsCommon.toggleSidebar())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MobileHeader)

View File

@ -0,0 +1,35 @@
/*
* 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 humanize from "humanize"
import Moment from "moment"
import ObjectItem from "./ObjectItem"
import * as actionsObjects from "../actions/objects"
export const ObjectContainer = ({ object }) => {
const actionButtons = []
const props = {
name: object.name,
contentType: object.contentType,
size: humanize.filesize(object.size),
lastModified: Moment(object.lastModified).format("lll"),
actionButtons: []
}
return <ObjectItem {...props} />
}
export default ObjectContainer

View File

@ -0,0 +1,58 @@
/*
* 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 humanize from "humanize"
import Moment from "moment"
import { getDataType } from "../mime"
export const ObjectItem = ({
name,
contentType,
size,
lastModified,
actionButtons,
onClick
}) => {
return (
<div className={"fesl-row"} data-type={getDataType(name, contentType)}>
<div className="fesl-item fesl-item-icon">
<div className="fi-select">
<input type="checkbox" name={name} checked={false} />
<i className="fis-icon" />
<i className="fis-helper" />
</div>
</div>
<div className="fesl-item fesl-item-name">
<a
href="#"
onClick={e => {
e.preventDefault()
onClick()
}}
>
{name}
</a>
</div>
<div className="fesl-item fesl-item-size">{size}</div>
<div className="fesl-item fesl-item-modified">{lastModified}</div>
<div className="fesl-item fesl-item-actions">{actionButtons}</div>
</div>
)
}
export default ObjectItem

View File

@ -0,0 +1,99 @@
/*
* 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 classNames from "classnames"
import { connect } from "react-redux"
import * as actionsObjects from "../actions/objects"
export const ObjectsHeader = ({
sortNameOrder,
sortSizeOrder,
sortLastModifiedOrder,
sortObjects
}) => (
<div className="feb-container">
<header className="fesl-row" data-type="folder">
<div className="fesl-item fesl-item-icon" />
<div
className="fesl-item fesl-item-name"
id="sort-by-name"
onClick={() => sortObjects("name")}
data-sort="name"
>
Name
<i
className={classNames({
"fesli-sort": true,
fa: true,
"fa-sort-alpha-desc": sortNameOrder,
"fa-sort-alpha-asc": !sortNameOrder
})}
/>
</div>
<div
className="fesl-item fesl-item-size"
id="sort-by-size"
onClick={() => sortObjects("size")}
data-sort="size"
>
Size
<i
className={classNames({
"fesli-sort": true,
fa: true,
"fa-sort-amount-desc": sortSizeOrder,
"fa-sort-amount-asc": !sortSizeOrder
})}
/>
</div>
<div
className="fesl-item fesl-item-modified"
id="sort-by-last-modified"
onClick={() => sortObjects("last-modified")}
data-sort="last-modified"
>
Last Modified
<i
className={classNames({
"fesli-sort": true,
fa: true,
"fa-sort-numeric-desc": sortLastModifiedOrder,
"fa-sort-numeric-asc": !sortLastModifiedOrder
})}
/>
</div>
<div className="fesl-item fesl-item-actions" />
</header>
</div>
)
const mapStateToProps = state => {
return {
sortNameOrder: state.objects.sortBy == "name" && state.objects.sortOrder,
sortSizeOrder: state.objects.sortBy == "size" && state.objects.sortOrder,
sortLastModifiedOrder:
state.objects.sortBy == "last-modified" && state.objects.sortOrder
}
}
const mapDispatchToProps = dispatch => {
return {
sortObjects: sortBy => dispatch(actionsObjects.sortObjects(sortBy))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ObjectsHeader)

View File

@ -1,5 +1,5 @@
/*
* Minio Cloud Storage (C) 2016 Minio, Inc.
* 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.
@ -14,83 +14,19 @@
* limitations under the License.
*/
import React from 'react'
import Moment from 'moment'
import humanize from 'humanize'
import connect from 'react-redux/lib/components/connect'
import Dropdown from 'react-bootstrap/lib/Dropdown'
import React from "react"
import ObjectContainer from "./ObjectContainer"
import PrefixContainer from "./PrefixContainer"
let ObjectsList = ({objects, currentPath, selectPrefix, dataType, showDeleteConfirmation, shareObject, loadPath, checkObject, checkedObjectsArray}) => {
const list = objects.map((object, i) => {
let size = object.name.endsWith('/') ? '-' : humanize.filesize(object.size)
let lastModified = object.name.endsWith('/') ? '-' : Moment(object.lastModified).format('lll')
let loadingClass = loadPath === `${currentPath}${object.name}` ? 'fesl-loading' : ''
let actionButtons = ''
let deleteButton = ''
if (web.LoggedIn())
deleteButton = <a href="" className="fiad-action" onClick={ (e) => showDeleteConfirmation(e, `${currentPath}${object.name}`) }><i className="fa fa-trash"></i></a>
if (!checkedObjectsArray.length > 0) {
if (!object.name.endsWith('/')) {
actionButtons = <Dropdown id={ "fia-dropdown-" + object.name.replace('.', '-') }>
<Dropdown.Toggle noCaret className="fia-toggle"></Dropdown.Toggle>
<Dropdown.Menu>
<a href="" className="fiad-action" onClick={ (e) => shareObject(e, `${currentPath}${object.name}`) }><i className="fa fa-copy"></i></a>
{ deleteButton }
</Dropdown.Menu>
</Dropdown>
}
export const ObjectsList = ({ objects }) => {
const list = objects.map(object => {
if (object.name.endsWith("/")) {
return <PrefixContainer object={object} key={object.name} />
} else {
return <ObjectContainer object={object} key={object.name} />
}
let activeClass = ''
let isChecked = ''
if (checkedObjectsArray.indexOf(object.name) > -1) {
activeClass = ' fesl-row-selected'
isChecked = true
}
return (
<div key={ i } className={ "fesl-row " + loadingClass + activeClass } data-type={ dataType(object.name, object.contentType) }>
<div className="fesl-item fesl-item-icon">
<div className="fi-select">
<input type="checkbox"
name={ object.name }
checked={ isChecked }
onChange={ (e) => checkObject(e, object.name) } />
<i className="fis-icon"></i>
<i className="fis-helper"></i>
</div>
</div>
<div className="fesl-item fesl-item-name">
<a href="" onClick={ (e) => selectPrefix(e, `${currentPath}${object.name}`) }>
{ object.name }
</a>
</div>
<div className="fesl-item fesl-item-size">
{ size }
</div>
<div className="fesl-item fesl-item-modified">
{ lastModified }
</div>
<div className="fesl-item fesl-item-actions">
{ actionButtons }
</div>
</div>
)
})
return (
<div>
{ list }
</div>
)
return <div>{list}</div>
}
// Subscribe it to state changes.
export default connect(state => {
return {
objects: state.objects,
currentPath: state.currentPath,
loadPath: state.loadPath
}
})(ObjectsList)
export default ObjectsList

View File

@ -0,0 +1,75 @@
/*
* 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 classNames from "classnames"
import { connect } from "react-redux"
import InfiniteScroll from "react-infinite-scroller"
import * as actionsObjects from "../actions/objects"
import ObjectsList from "./ObjectsList"
export class ObjectsListContainer extends React.Component {
componentWillReceiveProps(nextProps) {
const { currentBucket, currentPrefix, loadObjects } = this.props
if (
currentBucket != nextProps.currentBucket ||
currentPrefix != nextProps.currentPrefix
) {
loadObjects()
}
}
render() {
const { objects, isTruncated, currentBucket, loadObjects } = this.props
return (
<div className="feb-container">
<InfiniteScroll
pageStart={0}
loadMore={() => loadObjects(true)}
hasMore={isTruncated}
useWindow={true}
initialLoad={false}
>
<ObjectsList objects={objects} />
</InfiniteScroll>
<div
className="text-center"
style={{ display: isTruncated && currentBucket ? "block" : "none" }}
>
<span>Loading...</span>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
currentBucket: state.buckets.currentBucket,
currentPrefix: state.objects.currentPrefix,
objects: state.objects.list,
isTruncated: state.objects.isTruncated
}
}
const mapDispatchToProps = dispatch => {
return {
loadObjects: append => dispatch(actionsObjects.fetchObjects(append))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(
ObjectsListContainer
)

View File

@ -0,0 +1,28 @@
/*
* 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 ObjectsHeader from "./ObjectsHeader"
import ObjectsListContainer from "./ObjectsListContainer"
export const ObjectsSection = () => (
<div>
<ObjectsHeader />
<ObjectsListContainer />
</div>
)
export default ObjectsSection

View File

@ -14,28 +14,57 @@
* limitations under the License.
*/
import React from 'react'
import connect from 'react-redux/lib/components/connect'
import React from "react"
import { connect } from "react-redux"
import { getCurrentBucket, getCurrentPrefix } from "../selectors/buckets"
import * as actionsObjects from "../actions/objects"
let Path = ({currentBucket, currentPath, selectPrefix}) => {
export const Path = ({ currentBucket, currentPrefix, selectPrefix }) => {
const onPrefixClick = (e, prefix) => {
e.preventDefault()
selectPrefix(prefix)
}
let dirPath = []
let path = ''
if (currentPath) {
path = currentPath.split('/').map((dir, i) => {
dirPath.push(dir)
let dirPath_ = dirPath.join('/') + '/'
return <span key={ i }><a href="" onClick={ (e) => selectPrefix(e, dirPath_) }>{ dir }</a></span>
let path = ""
if (currentPrefix) {
path = currentPrefix.split("/").map((dir, i) => {
if (dir) {
dirPath.push(dir)
let dirPath_ = dirPath.join("/") + "/"
return (
<span key={i}>
<a href="" onClick={e => onPrefixClick(e, dirPath_)}>
{dir}
</a>
</span>
)
}
})
}
return (
<h2><span className="main"><a onClick={ (e) => selectPrefix(e, '') } href="">{ currentBucket }</a></span>{ path }</h2>
<h2>
<span className="main">
<a onClick={e => onPrefixClick(e, "")} href="">
{currentBucket}
</a>
</span>
{path}
</h2>
)
}
export default connect(state => {
const mapStateToProps = state => {
return {
currentBucket: state.currentBucket,
currentPath: state.currentPath
currentBucket: getCurrentBucket(state),
currentPrefix: state.objects.currentPrefix
}
})(Path)
}
const mapDispatchToProps = dispatch => {
return {
selectPrefix: prefix => dispatch(actionsObjects.selectPrefix(prefix))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Path)

View File

@ -0,0 +1,45 @@
/*
* 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 ObjectItem from "./ObjectItem"
import * as actionsObjects from "../actions/objects"
export const PrefixContainer = ({ object, currentPrefix, selectPrefix }) => {
const props = {
name: object.name,
contentType: object.contentType,
onClick: () => selectPrefix(`${currentPrefix}${object.name}`)
}
return <ObjectItem {...props} />
}
const mapStateToProps = (state, ownProps) => {
return {
object: ownProps.object,
currentPrefix: state.objects.currentPrefix
}
}
const mapDispatchToProps = dispatch => {
return {
selectPrefix: prefix => dispatch(actionsObjects.selectPrefix(prefix))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PrefixContainer)

View File

@ -24,13 +24,15 @@ import Dropdown from "react-bootstrap/lib/Dropdown"
import BucketSearch from "./BucketSearch"
import BucketList from "./BucketList"
import Host from "./Host"
import * as actionsCommon from "../actions/common"
export const SideBar = () => {
export const SideBar = ({ sidebarOpen, clickOutside }) => {
return (
<ClickOutHandler>
<ClickOutHandler onClickOut={clickOutside}>
<div
className={classNames({
"fe-sidebar": true
"fe-sidebar": true,
toggled: sidebarOpen
})}
>
<div className="fes-header clearfix hidden-sm hidden-xs">
@ -47,4 +49,16 @@ export const SideBar = () => {
)
}
export default connect(state => state)(SideBar)
const mapStateToProps = state => {
return {
sidebarOpen: state.common.sidebarOpen
}
}
const mapDispatchToProps = dispatch => {
return {
clickOutside: () => dispatch(actionsCommon.closeSidebar())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SideBar)

View File

@ -0,0 +1,64 @@
/*
* 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 humanize from "humanize"
import * as actionsCommon from "../actions/common"
export class StorageInfo extends React.Component {
componentWillMount() {
const { fetchStorageInfo } = this.props
fetchStorageInfo()
}
render() {
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>
<ul>
<li>
<span>Used: </span>
{humanize.filesize(total - free)}
</li>
<li className="pull-right">
<span>Free: </span>
{humanize.filesize(total - used)}
</li>
</ul>
</div>
)
}
}
const mapStateToProps = state => {
return {
storageInfo: state.common.storageInfo
}
}
const mapDispatchToProps = dispatch => {
return {
fetchStorageInfo: () => dispatch(actionsCommon.fetchStorageInfo())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(StorageInfo)

View File

@ -0,0 +1,36 @@
/*
* 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 { 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()
})
})

View File

@ -0,0 +1,61 @@
/*
* 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 { shallow } from "enzyme"
import { ObjectsHeader } from "../ObjectsHeader"
describe("ObjectsHeader", () => {
it("should render without crashing", () => {
const sortObjects = jest.fn()
shallow(<ObjectsHeader sortObjects={sortObjects} />)
})
it("should render columns with asc classes by default", () => {
const sortObjects = jest.fn()
const wrapper = shallow(<ObjectsHeader sortObjects={sortObjects} />)
expect(
wrapper.find("#sort-by-name i").hasClass("fa-sort-alpha-asc")
).toBeTruthy()
expect(
wrapper.find("#sort-by-size i").hasClass("fa-sort-amount-asc")
).toBeTruthy()
expect(
wrapper.find("#sort-by-last-modified i").hasClass("fa-sort-numeric-asc")
).toBeTruthy()
})
it("should render name column with desc class when objects are sorted by name", () => {
const sortObjects = jest.fn()
const wrapper = shallow(
<ObjectsHeader sortObjects={sortObjects} sortNameOrder={true} />
)
expect(
wrapper.find("#sort-by-name i").hasClass("fa-sort-alpha-desc")
).toBeTruthy()
})
it("should call sortObjects when a column is clicked", () => {
const sortObjects = jest.fn()
const wrapper = shallow(<ObjectsHeader sortObjects={sortObjects} />)
wrapper.find("#sort-by-name").simulate("click")
expect(sortObjects).toHaveBeenCalledWith("name")
wrapper.find("#sort-by-size").simulate("click")
expect(sortObjects).toHaveBeenCalledWith("size")
wrapper.find("#sort-by-last-modified").simulate("click")
expect(sortObjects).toHaveBeenCalledWith("last-modified")
})
})

View File

@ -0,0 +1,39 @@
/*
* 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 { shallow } from "enzyme"
import { ObjectsList } from "../ObjectsList"
describe("ObjectsList", () => {
it("should render without crashing", () => {
shallow(<ObjectsList objects={[]} />)
})
it("should render ObjectContainer for every object", () => {
const wrapper = shallow(
<ObjectsList objects={[{ name: "test1.jpg" }, { name: "test2.jpg" }]} />
)
expect(wrapper.find("ObjectContainer").length).toBe(2)
})
it("should render PrefixContainer for every prefix", () => {
const wrapper = shallow(
<ObjectsList objects={[{ name: "abc/" }, { name: "xyz/" }]} />
)
expect(wrapper.find("Connect(PrefixContainer)").length).toBe(2)
})
})

View File

@ -0,0 +1,57 @@
/*
* 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 { shallow } from "enzyme"
import { ObjectsListContainer } from "../ObjectsListContainer"
describe("ObjectsList", () => {
it("should render without crashing", () => {
shallow(<ObjectsListContainer loadObjects={jest.fn()} />)
})
it("should render ObjectsList with objects", () => {
const wrapper = shallow(
<ObjectsListContainer
objects={[{ name: "test1.jpg" }, { name: "test2.jpg" }]}
loadObjects={jest.fn()}
/>
)
expect(wrapper.find("ObjectsList").length).toBe(1)
expect(wrapper.find("ObjectsList").prop("objects")).toEqual([
{ name: "test1.jpg" },
{ name: "test2.jpg" }
])
})
it("should call loadObjects when currentBucket is changed", () => {
const loadObjects = jest.fn()
const wrapper = shallow(
<ObjectsListContainer currentBucket="test1" loadObjects={loadObjects} />
)
wrapper.setProps({ currentBucket: "test2" })
expect(loadObjects).toHaveBeenCalled()
})
it("should call loadObjects when currentPrefix is changed", () => {
const loadObjects = jest.fn()
const wrapper = shallow(
<ObjectsListContainer currentPrefix="abc/" loadObjects={loadObjects} />
)
wrapper.setProps({ currentPrefix: "abc/xyz/" })
expect(loadObjects).toHaveBeenCalled()
})
})

View File

@ -0,0 +1,37 @@
/*
* 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 { shallow } from "enzyme"
import { ObjectItem } from "../ObjectItem"
describe("ObjectItem", () => {
it("should render without crashing", () => {
shallow(<ObjectItem name={"test"} />)
})
it("should render with content type", () => {
const wrapper = shallow(<ObjectItem name={"test.jpg"} contentType={""} />)
expect(wrapper.prop("data-type")).toBe("image")
})
it("should call onClick when the object isclicked", () => {
const onClick = jest.fn()
const wrapper = shallow(<ObjectItem name={"test"} onClick={onClick} />)
wrapper.find("a").simulate("click", { preventDefault: jest.fn() })
expect(onClick).toHaveBeenCalled()
})
})

View File

@ -0,0 +1,72 @@
/*
* 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 { shallow } from "enzyme"
import { Path } from "../Path"
describe("Path", () => {
it("should render without crashing", () => {
shallow(<Path currentBucket={"test1"} currentPrefix={"test2"} />)
})
it("should render only bucket if there is no prefix", () => {
const wrapper = shallow(<Path currentBucket={"test1"} currentPrefix={""} />)
expect(wrapper.find("span").length).toBe(1)
expect(wrapper.text()).toBe("test1")
})
it("should render bucket and prefix", () => {
const wrapper = shallow(
<Path currentBucket={"test1"} currentPrefix={"a/b/"} />
)
expect(wrapper.find("span").length).toBe(3)
expect(
wrapper
.find("span")
.at(0)
.text()
).toBe("test1")
expect(
wrapper
.find("span")
.at(1)
.text()
).toBe("a")
expect(
wrapper
.find("span")
.at(2)
.text()
).toBe("b")
})
it("should call selectPrefix when a prefix part is clicked", () => {
const selectPrefix = jest.fn()
const wrapper = shallow(
<Path
currentBucket={"test1"}
currentPrefix={"a/b/"}
selectPrefix={selectPrefix}
/>
)
wrapper
.find("a")
.at(2)
.simulate("click", { preventDefault: jest.fn() })
expect(selectPrefix).toHaveBeenCalledWith("a/b/")
})
})

View File

@ -0,0 +1,44 @@
/*
* 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 { shallow } from "enzyme"
import { PrefixContainer } from "../PrefixContainer"
describe("PrefixContainer", () => {
it("should render without crashing", () => {
shallow(<PrefixContainer object={{ name: "abc/" }} />)
})
it("should render ObjectItem with props", () => {
const wrapper = shallow(<PrefixContainer object={{ name: "abc/" }} />)
expect(wrapper.find("ObjectItem").length).toBe(1)
expect(wrapper.find("ObjectItem").prop("name")).toBe("abc/")
})
it("should call selectPrefix when the prefix is clicked", () => {
const selectPrefix = jest.fn()
const wrapper = shallow(
<PrefixContainer
object={{ name: "abc/" }}
currentPrefix={"xyz/"}
selectPrefix={selectPrefix}
/>
)
wrapper.find("ObjectItem").prop("onClick")()
expect(selectPrefix).toHaveBeenCalledWith("xyz/abc/")
})
})

View File

@ -0,0 +1,41 @@
/*
* 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 { shallow } from "enzyme"
import { StorageInfo } from "../StorageInfo"
describe("StorageInfo", () => {
it("should render without crashing", () => {
shallow(
<StorageInfo
storageInfo={{ total: 100, free: 60 }}
fetchStorageInfo={jest.fn()}
/>
)
})
it("should fetchStorageInfo before component is mounted", () => {
const fetchStorageInfo = jest.fn()
shallow(
<StorageInfo
storageInfo={{ total: 100, free: 60 }}
fetchStorageInfo={fetchStorageInfo}
/>
)
expect(fetchStorageInfo).toHaveBeenCalled()
})
})

View File

@ -20,6 +20,6 @@
var p = window.location.pathname
export const minioBrowserPrefix = p.slice(0, p.indexOf("/", 1))
export const READ_ONLY = 'readonly'
export const WRITE_ONLY = 'writeonly'
export const READ_WRITE = 'readwrite'
export const READ_ONLY = "readonly"
export const WRITE_ONLY = "writeonly"
export const READ_WRITE = "readwrite"

24
browser/app/js/history.js Normal file
View File

@ -0,0 +1,24 @@
/*
* 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 createHistory from "history/createBrowserHistory"
import { minioBrowserPrefix } from "./constants"
const history = createHistory({
basename: minioBrowserPrefix
})
export default history

View File

@ -19,7 +19,8 @@ import * as actions from "../../actions/buckets"
describe("buckets reducer", () => {
it("should return the initial state", () => {
expect(reducer(undefined, {})).toEqual({
const initialState = reducer(undefined, {})
expect(initialState).toEqual({
list: [],
filter: "",
currentBucket: ""
@ -27,38 +28,26 @@ describe("buckets reducer", () => {
})
it("should handle SET_BUCKETS", () => {
expect(
reducer(undefined, { type: actions.SET_LIST, buckets: ["bk1", "bk2"] })
).toEqual({
list: ["bk1", "bk2"],
filter: "",
currentBucket: ""
const newState = reducer(undefined, {
type: actions.SET_LIST,
buckets: ["bk1", "bk2"]
})
expect(newState.list).toEqual(["bk1", "bk2"])
})
it("should handle SET_BUCKETS_FILTER", () => {
expect(
reducer(undefined, {
type: actions.SET_FILTER,
filter: "test"
})
).toEqual({
list: [],
filter: "test",
currentBucket: ""
const newState = reducer(undefined, {
type: actions.SET_FILTER,
filter: "test"
})
expect(newState.filter).toEqual("test")
})
it("should handle SELECT_BUCKET", () => {
expect(
reducer(undefined, {
type: actions.SET_CURRENT_BUCKET,
bucket: "test"
})
).toEqual({
list: [],
filter: "",
currentBucket: "test"
const newState = reducer(undefined, {
type: actions.SET_CURRENT_BUCKET,
bucket: "test"
})
expect(newState.currentBucket).toEqual("test")
})
})

View File

@ -0,0 +1,70 @@
/*
* 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 reducer from "../common"
import * as actionsCommon from "../../actions/common"
describe("common reducer", () => {
it("should return the initial state", () => {
expect(reducer(undefined, {})).toEqual({
sidebarOpen: false,
storageInfo: {
total: 0,
free: 0
}
})
})
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: { total: 100, free: 40 }
}
)
).toEqual({
storageInfo: { total: 100, free: 40 }
})
})
})

View File

@ -0,0 +1,97 @@
/*
* 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 reducer from "../objects"
import * as actions from "../../actions/objects"
describe("objects reducer", () => {
it("should return the initial state", () => {
const initialState = reducer(undefined, {})
expect(initialState).toEqual({
list: [],
sortBy: "",
sortOrder: false,
currentPrefix: "",
marker: "",
isTruncated: false
})
})
it("should handle SET_LIST", () => {
const newState = reducer(undefined, {
type: actions.SET_LIST,
objects: [{ name: "obj1" }, { name: "obj2" }],
marker: "obj2",
isTruncated: false
})
expect(newState.list).toEqual([{ name: "obj1" }, { name: "obj2" }])
expect(newState.marker).toBe("obj2")
expect(newState.isTruncated).toBeFalsy()
})
it("should handle APPEND_LIST", () => {
const newState = reducer(
{
list: [{ name: "obj1" }, { name: "obj2" }],
marker: "obj2",
isTruncated: true
},
{
type: actions.APPEND_LIST,
objects: [{ name: "obj3" }, { name: "obj4" }],
marker: "obj4",
isTruncated: false
}
)
expect(newState.list).toEqual([
{ name: "obj1" },
{ name: "obj2" },
{ name: "obj3" },
{ name: "obj4" }
])
expect(newState.marker).toBe("obj4")
expect(newState.isTruncated).toBeFalsy()
})
it("should handle SET_SORT_BY", () => {
const newState = reducer(undefined, {
type: actions.SET_SORT_BY,
sortBy: "name"
})
expect(newState.sortBy).toEqual("name")
})
it("should handle SET_SORT_ORDER", () => {
const newState = reducer(undefined, {
type: actions.SET_SORT_ORDER,
sortOrder: true
})
expect(newState.sortOrder).toEqual(true)
})
it("should handle SET_CURRENT_PREFIX", () => {
const newState = reducer(
{ currentPrefix: "test1/", marker: "abc", isTruncated: true },
{
type: actions.SET_CURRENT_PREFIX,
prefix: "test2/"
}
)
expect(newState.currentPrefix).toEqual("test2/")
expect(newState.marker).toEqual("")
expect(newState.isTruncated).toBeFalsy()
})
})

View File

@ -0,0 +1,39 @@
/*
* 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 * as actionsCommon from "../actions/common"
export default (
state = { sidebarOpen: false, storageInfo: { total: 0, free: 0 } },
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
})
default:
return state
}
}

View File

@ -15,12 +15,16 @@
*/
import { combineReducers } from "redux"
import common from "./common"
import alert from "./alert"
import buckets from "./buckets"
import objects from "./objects"
const rootReducer = combineReducers({
common,
alert,
buckets
buckets,
objects
})
export default rootReducer

View File

@ -0,0 +1,65 @@
/*
* 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 * as actionsObjects from "../actions/objects"
export default (
state = {
list: [],
sortBy: "",
sortOrder: false,
currentPrefix: "",
marker: "",
isTruncated: false
},
action
) => {
switch (action.type) {
case actionsObjects.SET_LIST:
return {
...state,
list: action.objects,
marker: action.marker,
isTruncated: action.isTruncated
}
case actionsObjects.APPEND_LIST:
return {
...state,
list: [...state.list, ...action.objects],
marker: action.marker,
isTruncated: action.isTruncated
}
case actionsObjects.SET_SORT_BY:
return {
...state,
sortBy: action.sortBy
}
case actionsObjects.SET_SORT_ORDER:
return {
...state,
sortOrder: action.sortOrder
}
case actionsObjects.SET_CURRENT_PREFIX:
return {
...state,
currentPrefix: action.prefix,
marker: "",
isTruncated: false
}
default:
return state
}
}

View File

@ -27,6 +27,13 @@
color: @text-color;
}
}
&:last-child {
&:after {
content: '/';
margin: 0 4px;
color: @text-color;
}
}
}
}

View File

@ -11,7 +11,10 @@
},
"jest": {
"setupTestFrameworkScriptFile": "./app/js/jest/setup.js",
"testURL": "https://localhost:8080"
"testURL": "https://localhost:8080",
"moduleNameMapper": {
"\\.(css|scss|svg)$": "identity-obj-proxy"
}
},
"repository": {
"type": "git",
@ -60,6 +63,7 @@
"font-awesome": "^4.7.0",
"history": "^4.7.2",
"humanize": "0.0.9",
"identity-obj-proxy": "^3.0.0",
"json-loader": "^0.5.4",
"local-storage-fallback": "^4.0.2",
"material-design-iconic-font": "^2.2.0",

View File

@ -2992,6 +2992,10 @@ har-validator@~5.0.3:
ajv "^5.1.0"
har-schema "^2.0.0"
harmony-reflect@^1.4.6:
version "1.5.1"
resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.5.1.tgz#b54ca617b00cc8aef559bbb17b3d85431dc7e329"
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
@ -3271,6 +3275,12 @@ icss-replace-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
identity-obj-proxy@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14"
dependencies:
harmony-reflect "^1.4.6"
ieee754@^1.1.4:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"