diff --git a/browser/app/js/actions/__tests__/buckets.test.js b/browser/app/js/actions/__tests__/buckets.test.js
index 6218103f7..36bb6ba9f 100644
--- a/browser/app/js/actions/__tests__/buckets.test.js
+++ b/browser/app/js/actions/__tests__/buckets.test.js
@@ -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")
+ })
})
diff --git a/browser/app/js/actions/__tests__/common.test.js b/browser/app/js/actions/__tests__/common.test.js
new file mode 100644
index 000000000..caf7a8800
--- /dev/null
+++ b/browser/app/js/actions/__tests__/common.test.js
@@ -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)
+ })
+ })
+})
diff --git a/browser/app/js/actions/__tests__/objects.test.js b/browser/app/js/actions/__tests__/objects.test.js
new file mode 100644
index 000000000..ef1de722e
--- /dev/null
+++ b/browser/app/js/actions/__tests__/objects.test.js
@@ -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()
+ })
+})
diff --git a/browser/app/js/actions/buckets.js b/browser/app/js/actions/buckets.js
index 74866ea00..01b40f778 100644
--- a/browser/app/js/actions/buckets.js
+++ b/browser/app/js/actions/buckets.js
@@ -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,
diff --git a/browser/app/js/actions/common.js b/browser/app/js/actions/common.js
new file mode 100644
index 000000000..0164f7573
--- /dev/null
+++ b/browser/app/js/actions/common.js
@@ -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
+})
diff --git a/browser/app/js/actions/objects.js b/browser/app/js/actions/objects.js
new file mode 100644
index 000000000..736a192e1
--- /dev/null
+++ b/browser/app/js/actions/objects.js
@@ -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
+ }
+}
diff --git a/browser/app/js/components/Browser.js b/browser/app/js/components/Browser.js
index 948eefa6e..f2fb5c5f9 100644
--- a/browser/app/js/components/Browser.js
+++ b/browser/app/js/components/Browser.js
@@ -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 {
})}
>