diff --git a/browser/app/index.js b/browser/app/index.js
index 8162022c0..9f1e70d2f 100644
--- a/browser/app/index.js
+++ b/browser/app/index.js
@@ -21,10 +21,10 @@ import "material-design-iconic-font/dist/css/material-design-iconic-font.min.css
import React from "react"
import ReactDOM from "react-dom"
-import { BrowserRouter, Route } from "react-router-dom"
+import { Router, Route } from "react-router-dom"
import { Provider } from "react-redux"
-import { minioBrowserPrefix } from "./js/constants"
+import history from "./js/history"
import configureStore from "./js/store/configure-store"
import hideLoader from "./js/loader"
import App from "./js/App"
@@ -33,9 +33,9 @@ const store = configureStore()
ReactDOM.render(
-
-
-
+
+
+
,
document.getElementById("root")
)
diff --git a/browser/app/js/App.js b/browser/app/js/App.js
index eb833e400..5eeeaa734 100644
--- a/browser/app/js/App.js
+++ b/browser/app/js/App.js
@@ -34,13 +34,15 @@ const AuthorizedRoute = ({ component: Component, ...rest }) => (
/>
)
-export const App = ({ match }) => (
-
-
-
-
-
-
-)
+export const App = () => {
+ return (
+
+
+
+
+
+
+ )
+}
export default App
diff --git a/browser/app/js/browser/Login.js b/browser/app/js/browser/Login.js
index f2c003fea..f8ceceb64 100644
--- a/browser/app/js/browser/Login.js
+++ b/browser/app/js/browser/Login.js
@@ -21,7 +21,6 @@ import logo from "../../img/logo.svg"
import Alert from "../alert/Alert"
import * as actionsAlert from "../alert/actions"
import InputGroup from "./InputGroup"
-import { minioBrowserPrefix } from "../constants"
import web from "../web"
import { Redirect } from "react-router-dom"
@@ -46,7 +45,7 @@ export class Login extends React.Component {
password: document.getElementById("secretKey").value
})
.then(res => {
- history.push(minioBrowserPrefix)
+ history.push("/")
})
.catch(e => {
showAlert("danger", e.message)
@@ -67,7 +66,7 @@ export class Login extends React.Component {
render() {
const { clearAlert, alert } = this.props
if (web.LoggedIn()) {
- return
+ return
}
let alertBox =
// Make sure you don't show a fading out alert box on the initial web-page load.
diff --git a/browser/app/js/browser/MainActions.js b/browser/app/js/browser/MainActions.js
index 996a1339b..35c3da796 100644
--- a/browser/app/js/browser/MainActions.js
+++ b/browser/app/js/browser/MainActions.js
@@ -17,10 +17,16 @@
import React from "react"
import { connect } from "react-redux"
import { Dropdown, OverlayTrigger, Tooltip } from "react-bootstrap"
+import web from "../web"
import * as actionsBuckets from "../buckets/actions"
import * as uploadsActions from "../uploads/actions"
+import { getPrefixWritable } from "../objects/selectors"
-export const MainActions = ({ uploadFile, showMakeBucketModal }) => {
+export const MainActions = ({
+ prefixWritable,
+ uploadFile,
+ showMakeBucketModal
+}) => {
const uploadTooltip = Upload file
const makeBucketTooltip = (
Create bucket
@@ -31,47 +37,59 @@ export const MainActions = ({ uploadFile, showMakeBucketModal }) => {
e.target.value = null
}
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- e.preventDefault()
- showMakeBucketModal()
- }}
- >
-
-
-
-
-
- )
+ const loggedIn = web.LoggedIn()
+
+ if (loggedIn || prefixWritable) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {loggedIn && (
+
+ {
+ e.preventDefault()
+ showMakeBucketModal()
+ }}
+ >
+
+
+
+ )}
+
+
+ )
+ } else {
+ return
+ }
}
-const mapStateToProps = state => state
+const mapStateToProps = state => {
+ return {
+ prefixWritable: getPrefixWritable(state)
+ }
+}
const mapDispatchToProps = dispatch => {
return {
diff --git a/browser/app/js/browser/SideBar.js b/browser/app/js/browser/SideBar.js
index 0932ca555..30886b783 100644
--- a/browser/app/js/browser/SideBar.js
+++ b/browser/app/js/browser/SideBar.js
@@ -25,6 +25,7 @@ import BucketSearch from "../buckets/BucketSearch"
import BucketList from "../buckets/BucketList"
import Host from "./Host"
import * as actionsCommon from "./actions"
+import web from "../web"
export const SideBar = ({ sidebarOpen, clickOutside }) => {
return (
@@ -40,7 +41,7 @@ export const SideBar = ({ sidebarOpen, clickOutside }) => {
Minio Browser
-
+ {web.LoggedIn() && }
diff --git a/browser/app/js/browser/__tests__/MainActions.test.js b/browser/app/js/browser/__tests__/MainActions.test.js
index 88ff84144..ad220764c 100644
--- a/browser/app/js/browser/__tests__/MainActions.test.js
+++ b/browser/app/js/browser/__tests__/MainActions.test.js
@@ -18,11 +18,37 @@ import React from "react"
import { shallow, mount } from "enzyme"
import { MainActions } from "../MainActions"
+jest.mock("../../web", () => ({
+ LoggedIn: jest
+ .fn(() => true)
+ .mockReturnValueOnce(true)
+ .mockReturnValueOnce(false)
+ .mockReturnValueOnce(false)
+}))
+
describe("MainActions", () => {
it("should render without crashing", () => {
shallow()
})
+ it("should not show any actions when user has not LoggedIn and prefixWritable is false", () => {
+ const wrapper = shallow()
+ expect(wrapper.find("#show-make-bucket").length).toBe(0)
+ expect(wrapper.find("#file-input").length).toBe(0)
+ })
+
+ it("should show only file upload action when user has not LoggedIn and prefixWritable is true", () => {
+ const wrapper = shallow()
+ expect(wrapper.find("#show-make-bucket").length).toBe(0)
+ expect(wrapper.find("#file-input").length).toBe(1)
+ })
+
+ it("should show make bucket upload file actions when user has LoggedIn", () => {
+ const wrapper = shallow()
+ expect(wrapper.find("#show-make-bucket").length).toBe(1)
+ expect(wrapper.find("#file-input").length).toBe(1)
+ })
+
it("should call showMakeBucketModal when create bucket icon is clicked", () => {
const showMakeBucketModal = jest.fn()
const wrapper = shallow(
diff --git a/browser/app/js/browser/__tests__/SideBar.test.js b/browser/app/js/browser/__tests__/SideBar.test.js
index 45d3897e5..2a2132638 100644
--- a/browser/app/js/browser/__tests__/SideBar.test.js
+++ b/browser/app/js/browser/__tests__/SideBar.test.js
@@ -18,11 +18,20 @@ import React from "react"
import { shallow } from "enzyme"
import { SideBar } from "../SideBar"
+jest.mock("../../web", () => ({
+ LoggedIn: jest.fn(() => false).mockReturnValueOnce(true)
+}))
+
describe("SideBar", () => {
it("should render without crashing", () => {
shallow()
})
+ it("should not render BucketSearch for non LoggedIn users", () => {
+ const wrapper = shallow()
+ expect(wrapper.find("Connect(BucketSearch)").length).toBe(0)
+ })
+
it("should call clickOutside when the user clicks outside the sidebar", () => {
const clickOutside = jest.fn()
const wrapper = shallow()
diff --git a/browser/app/js/buckets/BucketList.js b/browser/app/js/buckets/BucketList.js
index 75df7e6ca..303b7e73c 100644
--- a/browser/app/js/buckets/BucketList.js
+++ b/browser/app/js/buckets/BucketList.js
@@ -20,11 +20,22 @@ import { Scrollbars } from "react-custom-scrollbars"
import * as actionsBuckets from "./actions"
import { getVisibleBuckets } from "./selectors"
import BucketContainer from "./BucketContainer"
+import web from "../web"
+import history from "../history"
+import { pathSlice } from "../utils"
export class BucketList extends React.Component {
componentWillMount() {
- const { fetchBuckets } = this.props
- fetchBuckets()
+ const { fetchBuckets, setBucketList, selectBucket } = this.props
+ if (web.LoggedIn()) {
+ fetchBuckets()
+ } else {
+ const { bucket, prefix } = pathSlice(history.location.pathname)
+ if (bucket) {
+ setBucketList([bucket])
+ selectBucket(bucket, prefix)
+ }
+ }
}
render() {
const { visibleBuckets } = this.props
@@ -52,7 +63,9 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => {
return {
- fetchBuckets: () => dispatch(actionsBuckets.fetchBuckets())
+ fetchBuckets: () => dispatch(actionsBuckets.fetchBuckets()),
+ setBucketList: buckets => dispatch(actionsBuckets.setList(buckets)),
+ selectBucket: bucket => dispatch(actionsBuckets.selectBucket(bucket))
}
}
diff --git a/browser/app/js/buckets/__tests__/BucketList.test.js b/browser/app/js/buckets/__tests__/BucketList.test.js
index b64d29192..3c88fb215 100644
--- a/browser/app/js/buckets/__tests__/BucketList.test.js
+++ b/browser/app/js/buckets/__tests__/BucketList.test.js
@@ -16,8 +16,16 @@
import React from "react"
import { shallow } from "enzyme"
+import history from "../../history"
import { BucketList } from "../BucketList"
+jest.mock("../../web", () => ({
+ LoggedIn: jest
+ .fn(() => false)
+ .mockReturnValueOnce(true)
+ .mockReturnValueOnce(true)
+}))
+
describe("BucketList", () => {
it("should render without crashing", () => {
const fetchBuckets = jest.fn()
@@ -31,4 +39,19 @@ describe("BucketList", () => {
)
expect(fetchBuckets).toHaveBeenCalled()
})
+
+ it("should call setBucketList and selectBucket before component is mounted when the user has not loggedIn", () => {
+ const setBucketList = jest.fn()
+ const selectBucket = jest.fn()
+ history.push("/bk1/pre1")
+ const wrapper = shallow(
+
+ )
+ expect(setBucketList).toHaveBeenCalledWith(["bk1"])
+ expect(selectBucket).toHaveBeenCalledWith("bk1", "pre1")
+ })
})
diff --git a/browser/app/js/buckets/__tests__/actions.test.js b/browser/app/js/buckets/__tests__/actions.test.js
index d5b80521c..323c9316f 100644
--- a/browser/app/js/buckets/__tests__/actions.test.js
+++ b/browser/app/js/buckets/__tests__/actions.test.js
@@ -18,6 +18,7 @@ import configureStore from "redux-mock-store"
import thunk from "redux-thunk"
import * as actionsBuckets from "../actions"
import * as objectActions from "../../objects/actions"
+import history from "../../history"
jest.mock("../../web", () => ({
ListBuckets: jest.fn(() => {
@@ -36,7 +37,7 @@ const middlewares = [thunk]
const mockStore = configureStore(middlewares)
describe("Buckets actions", () => {
- it("creates buckets/SET_LIST and buckets/SET_CURRENT_BUCKET after fetching the buckets", () => {
+ it("creates buckets/SET_LIST and buckets/SET_CURRENT_BUCKET with first bucket after fetching the buckets", () => {
const store = mockStore()
const expectedActions = [
{ type: "buckets/SET_LIST", buckets: ["test1", "test2"] },
@@ -48,7 +49,35 @@ describe("Buckets actions", () => {
})
})
- it("should update browser url and creates buckets/SET_CURRENT_BUCKET action when selectBucket is called", () => {
+ it("creates buckets/SET_CURRENT_BUCKET with bucket name in the url after fetching buckets", () => {
+ history.push("/test2")
+ const store = mockStore()
+ const expectedActions = [
+ { type: "buckets/SET_LIST", buckets: ["test1", "test2"] },
+ { type: "buckets/SET_CURRENT_BUCKET", bucket: "test2" }
+ ]
+ window.location
+ return store.dispatch(actionsBuckets.fetchBuckets()).then(() => {
+ const actions = store.getActions()
+ expect(actions).toEqual(expectedActions)
+ })
+ })
+
+ it("creates buckets/SET_CURRENT_BUCKET with first bucket when the bucket in url is not exists after fetching buckets", () => {
+ history.push("/test3")
+ const store = mockStore()
+ const expectedActions = [
+ { type: "buckets/SET_LIST", buckets: ["test1", "test2"] },
+ { type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" }
+ ]
+ window.location
+ return store.dispatch(actionsBuckets.fetchBuckets()).then(() => {
+ const actions = store.getActions()
+ expect(actions).toEqual(expectedActions)
+ })
+ })
+
+ it("creates buckets/SET_CURRENT_BUCKET action when selectBucket is called", () => {
const store = mockStore()
const expectedActions = [
{ type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" }
@@ -56,7 +85,6 @@ describe("Buckets actions", () => {
store.dispatch(actionsBuckets.selectBucket("test1"))
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
- expect(window.location.pathname).toBe("/test1")
})
it("creates buckets/SHOW_MAKE_BUCKET_MODAL for showMakeBucketModal", () => {
diff --git a/browser/app/js/buckets/actions.js b/browser/app/js/buckets/actions.js
index 250dd839d..79e580d63 100644
--- a/browser/app/js/buckets/actions.js
+++ b/browser/app/js/buckets/actions.js
@@ -18,6 +18,7 @@ import web from "../web"
import history from "../history"
import * as alertActions from "../alert/actions"
import * as objectsActions from "../objects/actions"
+import { pathSlice } from "../utils"
export const SET_LIST = "buckets/SET_LIST"
export const ADD = "buckets/ADD"
@@ -31,7 +32,12 @@ export const fetchBuckets = () => {
const buckets = res.buckets ? res.buckets.map(bucket => bucket.name) : []
dispatch(setList(buckets))
if (buckets.length > 0) {
- dispatch(selectBucket(buckets[0]))
+ const { bucket, prefix } = pathSlice(history.location.pathname)
+ if (bucket && buckets.indexOf(bucket) > -1) {
+ dispatch(selectBucket(bucket, prefix))
+ } else {
+ dispatch(selectBucket(buckets[0]))
+ }
}
})
}
@@ -51,11 +57,10 @@ export const setFilter = filter => {
}
}
-export const selectBucket = bucket => {
+export const selectBucket = (bucket, prefix) => {
return function(dispatch) {
dispatch(setCurrentBucket(bucket))
- dispatch(objectsActions.selectPrefix(""))
- history.push(`/${bucket}`)
+ dispatch(objectsActions.selectPrefix(prefix || ""))
}
}
diff --git a/browser/app/js/objects/__tests__/actions.test.js b/browser/app/js/objects/__tests__/actions.test.js
index 7fa256895..4987a46ea 100644
--- a/browser/app/js/objects/__tests__/actions.test.js
+++ b/browser/app/js/objects/__tests__/actions.test.js
@@ -26,7 +26,8 @@ jest.mock("../../web", () => ({
return Promise.resolve({
objects: [{ name: "test1" }, { name: "test2" }],
istruncated: false,
- nextmarker: "test2"
+ nextmarker: "test2",
+ writable: false
})
}),
RemoveObject: jest.fn(({ bucketName, objects }) => {
@@ -124,6 +125,10 @@ describe("Objects actions", () => {
{
type: "objects/SET_SORT_ORDER",
sortOrder: false
+ },
+ {
+ type: "objects/SET_PREFIX_WRITABLE",
+ prefixWritable: false
}
]
return store.dispatch(actionsObjects.fetchObjects()).then(() => {
@@ -143,6 +148,10 @@ describe("Objects actions", () => {
objects: [{ name: "test1" }, { name: "test2" }],
marker: "test2",
isTruncated: false
+ },
+ {
+ type: "objects/SET_PREFIX_WRITABLE",
+ prefixWritable: false
}
]
return store.dispatch(actionsObjects.fetchObjects(true)).then(() => {
@@ -197,6 +206,16 @@ describe("Objects actions", () => {
expect(window.location.pathname.endsWith("/test/abc/")).toBeTruthy()
})
+ it("create objects/SET_PREFIX_WRITABLE action", () => {
+ const store = mockStore()
+ const expectedActions = [
+ { type: "objects/SET_PREFIX_WRITABLE", prefixWritable: true }
+ ]
+ store.dispatch(actionsObjects.setPrefixWritable(true))
+ const actions = store.getActions()
+ expect(actions).toEqual(expectedActions)
+ })
+
it("creates objects/REMOVE action", () => {
const store = mockStore()
const expectedActions = [{ type: "objects/REMOVE", object: "obj1" }]
diff --git a/browser/app/js/objects/__tests__/reducer.test.js b/browser/app/js/objects/__tests__/reducer.test.js
index d3ea3ba07..26943d986 100644
--- a/browser/app/js/objects/__tests__/reducer.test.js
+++ b/browser/app/js/objects/__tests__/reducer.test.js
@@ -27,6 +27,7 @@ describe("objects reducer", () => {
currentPrefix: "",
marker: "",
isTruncated: false,
+ prefixWritable: false,
shareObject: {
show: false,
object: "",
@@ -123,6 +124,14 @@ describe("objects reducer", () => {
expect(newState.isTruncated).toBeFalsy()
})
+ it("should handle SET_PREFIX_WRITABLE", () => {
+ const newState = reducer(undefined, {
+ type: actions.SET_PREFIX_WRITABLE,
+ prefixWritable: true
+ })
+ expect(newState.prefixWritable).toBeTruthy()
+ })
+
it("should handle SET_SHARE_OBJECT", () => {
const newState = reducer(undefined, {
type: actions.SET_SHARE_OBJECT,
diff --git a/browser/app/js/objects/actions.js b/browser/app/js/objects/actions.js
index 788a4c91d..2fe63dc50 100644
--- a/browser/app/js/objects/actions.js
+++ b/browser/app/js/objects/actions.js
@@ -32,6 +32,7 @@ export const REMOVE = "objects/REMOVE"
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 SET_PREFIX_WRITABLE = "objects/SET_PREFIX_WRITABLE"
export const SET_SHARE_OBJECT = "objects/SET_SHARE_OBJECT"
export const CHECKED_LIST_ADD = "objects/CHECKED_LIST_ADD"
export const CHECKED_LIST_REMOVE = "objects/CHECKED_LIST_REMOVE"
@@ -80,6 +81,11 @@ export const fetchObjects = append => {
dispatch(setSortBy(""))
dispatch(setSortOrder(false))
}
+ dispatch(setPrefixWritable(res.writable))
+ })
+ .catch(err => {
+ dispatch(alertActions.set({ type: "danger", message: err.message }))
+ history.push("/login")
})
}
}
@@ -136,6 +142,11 @@ export const setCurrentPrefix = prefix => {
}
}
+export const setPrefixWritable = prefixWritable => ({
+ type: SET_PREFIX_WRITABLE,
+ prefixWritable
+})
+
export const deleteObject = object => {
return function(dispatch, getState) {
const currentBucket = getCurrentBucket(getState())
diff --git a/browser/app/js/objects/reducer.js b/browser/app/js/objects/reducer.js
index 5d5941ebd..1eab6a7b7 100644
--- a/browser/app/js/objects/reducer.js
+++ b/browser/app/js/objects/reducer.js
@@ -32,6 +32,7 @@ export default (
currentPrefix: "",
marker: "",
isTruncated: false,
+ prefixWritable: false,
shareObject: {
show: false,
object: "",
@@ -78,6 +79,11 @@ export default (
marker: "",
isTruncated: false
}
+ case actionsObjects.SET_PREFIX_WRITABLE:
+ return {
+ ...state,
+ prefixWritable: action.prefixWritable
+ }
case actionsObjects.SET_SHARE_OBJECT:
return {
...state,
diff --git a/browser/app/js/objects/selectors.js b/browser/app/js/objects/selectors.js
index 1e9895a51..f1978acf6 100644
--- a/browser/app/js/objects/selectors.js
+++ b/browser/app/js/objects/selectors.js
@@ -19,3 +19,5 @@ import { createSelector } from "reselect"
export const getCurrentPrefix = state => state.objects.currentPrefix
export const getCheckedList = state => state.objects.checkedList
+
+export const getPrefixWritable = state => state.objects.prefixWritable