mirror of
https://github.com/minio/minio.git
synced 2025-05-22 18:11:50 -04:00
Share button for public objects (#9162)
This commit is contained in:
parent
a6bdc086a2
commit
f7c91eff54
24
browser/app/js/browser/selectors.js
Normal file
24
browser/app/js/browser/selectors.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage (C) 2020 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createSelector } from "reselect"
|
||||||
|
|
||||||
|
export const getServerInfo = state => state.browser.serverInfo
|
||||||
|
|
||||||
|
export const hasServerPublicDomain = createSelector(
|
||||||
|
getServerInfo,
|
||||||
|
serverInfo => Boolean(serverInfo.info && serverInfo.info.domains && serverInfo.info.domains.length),
|
||||||
|
)
|
@ -77,7 +77,8 @@ export class ShareObjectModal extends React.Component {
|
|||||||
hideShareObject()
|
hideShareObject()
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { shareObjectDetails, shareObject, hideShareObject } = this.props
|
const { shareObjectDetails, hideShareObject } = this.props
|
||||||
|
const url = `${window.location.protocol}//${shareObjectDetails.url}`
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
show={true}
|
show={true}
|
||||||
@ -93,11 +94,12 @@ export class ShareObjectModal extends React.Component {
|
|||||||
type="text"
|
type="text"
|
||||||
ref={node => (this.copyTextInput = node)}
|
ref={node => (this.copyTextInput = node)}
|
||||||
readOnly="readOnly"
|
readOnly="readOnly"
|
||||||
value={window.location.protocol + "//" + shareObjectDetails.url}
|
value={url}
|
||||||
onClick={() => this.copyTextInput.select()}
|
onClick={() => this.copyTextInput.select()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
{shareObjectDetails.showExpiryDate && (
|
||||||
|
<div
|
||||||
className="input-group"
|
className="input-group"
|
||||||
style={{ display: web.LoggedIn() ? "block" : "none" }}
|
style={{ display: web.LoggedIn() ? "block" : "none" }}
|
||||||
>
|
>
|
||||||
@ -174,10 +176,11 @@ export class ShareObjectModal extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
text={window.location.protocol + "//" + shareObjectDetails.url}
|
text={url}
|
||||||
onCopy={this.onUrlCopied.bind(this)}
|
onCopy={this.onUrlCopied.bind(this)}
|
||||||
>
|
>
|
||||||
<button className="btn btn-success">Copy Link</button>
|
<button className="btn btn-success">Copy Link</button>
|
||||||
|
@ -34,7 +34,7 @@ describe("ShareObjectModal", () => {
|
|||||||
shallow(
|
shallow(
|
||||||
<ShareObjectModal
|
<ShareObjectModal
|
||||||
object={{ name: "obj1" }}
|
object={{ name: "obj1" }}
|
||||||
shareObjectDetails={{ show: true, object: "obj1", url: "test" }}
|
shareObjectDetails={{ show: true, object: "obj1", url: "test", showExpiryDate: true }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -44,7 +44,7 @@ describe("ShareObjectModal", () => {
|
|||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ShareObjectModal
|
<ShareObjectModal
|
||||||
object={{ name: "obj1" }}
|
object={{ name: "obj1" }}
|
||||||
shareObjectDetails={{ show: true, object: "obj1", url: "test" }}
|
shareObjectDetails={{ show: true, object: "obj1", url: "test", showExpiryDate: true }}
|
||||||
hideShareObject={hideShareObject}
|
hideShareObject={hideShareObject}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -59,7 +59,7 @@ describe("ShareObjectModal", () => {
|
|||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ShareObjectModal
|
<ShareObjectModal
|
||||||
object={{ name: "obj1" }}
|
object={{ name: "obj1" }}
|
||||||
shareObjectDetails={{ show: true, object: "obj1", url: "test" }}
|
shareObjectDetails={{ show: true, object: "obj1", url: "test", showExpiryDate: true }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
expect(
|
expect(
|
||||||
@ -76,7 +76,7 @@ describe("ShareObjectModal", () => {
|
|||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ShareObjectModal
|
<ShareObjectModal
|
||||||
object={{ name: "obj1" }}
|
object={{ name: "obj1" }}
|
||||||
shareObjectDetails={{ show: true, object: "obj1", url: "test" }}
|
shareObjectDetails={{ show: true, object: "obj1", url: "test", showExpiryDate: true }}
|
||||||
hideShareObject={hideShareObject}
|
hideShareObject={hideShareObject}
|
||||||
showCopyAlert={showCopyAlert}
|
showCopyAlert={showCopyAlert}
|
||||||
/>
|
/>
|
||||||
@ -89,8 +89,15 @@ describe("ShareObjectModal", () => {
|
|||||||
describe("Update expiry values", () => {
|
describe("Update expiry values", () => {
|
||||||
const props = {
|
const props = {
|
||||||
object: { name: "obj1" },
|
object: { name: "obj1" },
|
||||||
shareObjectDetails: { show: true, object: "obj1", url: "test" }
|
shareObjectDetails: { show: true, object: "obj1", url: "test", showExpiryDate: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it("should not show expiry values if shared with public link", () => {
|
||||||
|
const shareObjectDetails = { show: true, object: "obj1", url: "test", showExpiryDate: false }
|
||||||
|
const wrapper = shallow(<ShareObjectModal {...props} shareObjectDetails={shareObjectDetails} />)
|
||||||
|
expect(wrapper.find('.set-expire').exists()).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
it("should have default expiry values", () => {
|
it("should have default expiry values", () => {
|
||||||
const wrapper = shallow(<ShareObjectModal {...props} />)
|
const wrapper = shallow(<ShareObjectModal {...props} />)
|
||||||
expect(wrapper.state("expiry")).toEqual({
|
expect(wrapper.state("expiry")).toEqual({
|
||||||
|
@ -34,6 +34,7 @@ jest.mock("../../web", () => ({
|
|||||||
.mockReturnValueOnce(false)
|
.mockReturnValueOnce(false)
|
||||||
.mockReturnValueOnce(true)
|
.mockReturnValueOnce(true)
|
||||||
.mockReturnValueOnce(true)
|
.mockReturnValueOnce(true)
|
||||||
|
.mockReturnValueOnce(true)
|
||||||
.mockReturnValueOnce(false),
|
.mockReturnValueOnce(false),
|
||||||
ListObjects: jest.fn(({ bucketName }) => {
|
ListObjects: jest.fn(({ bucketName }) => {
|
||||||
if (bucketName === "test-deny") {
|
if (bucketName === "test-deny") {
|
||||||
@ -69,7 +70,14 @@ jest.mock("../../web", () => ({
|
|||||||
})
|
})
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
return Promise.resolve({ token: "test" })
|
return Promise.resolve({ token: "test" })
|
||||||
})
|
}),
|
||||||
|
GetBucketPolicy: jest.fn(({ bucketName, prefix }) => {
|
||||||
|
if (!bucketName) {
|
||||||
|
return Promise.reject({ message: "Invalid bucket" })
|
||||||
|
}
|
||||||
|
if (bucketName === 'test-public') return Promise.resolve({ policy: 'readonly' })
|
||||||
|
return Promise.resolve({})
|
||||||
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const middlewares = [thunk]
|
const middlewares = [thunk]
|
||||||
@ -295,7 +303,8 @@ describe("Objects actions", () => {
|
|||||||
type: "objects/SET_SHARE_OBJECT",
|
type: "objects/SET_SHARE_OBJECT",
|
||||||
show: true,
|
show: true,
|
||||||
object: "b.txt",
|
object: "b.txt",
|
||||||
url: "test"
|
url: "test",
|
||||||
|
showExpiryDate: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
store.dispatch(actionsObjects.showShareObject("b.txt", "test"))
|
store.dispatch(actionsObjects.showShareObject("b.txt", "test"))
|
||||||
@ -321,14 +330,16 @@ describe("Objects actions", () => {
|
|||||||
it("creates objects/SET_SHARE_OBJECT when object is shared", () => {
|
it("creates objects/SET_SHARE_OBJECT when object is shared", () => {
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
buckets: { currentBucket: "bk1" },
|
buckets: { currentBucket: "bk1" },
|
||||||
objects: { currentPrefix: "pre1/" }
|
objects: { currentPrefix: "pre1/" },
|
||||||
|
browser: { serverInfo: {} },
|
||||||
})
|
})
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{
|
{
|
||||||
type: "objects/SET_SHARE_OBJECT",
|
type: "objects/SET_SHARE_OBJECT",
|
||||||
show: true,
|
show: true,
|
||||||
object: "a.txt",
|
object: "a.txt",
|
||||||
url: "https://test.com/bk1/pre1/b.txt"
|
url: "https://test.com/bk1/pre1/b.txt",
|
||||||
|
showExpiryDate: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "alert/SET",
|
type: "alert/SET",
|
||||||
@ -347,10 +358,42 @@ describe("Objects actions", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("creates objects/SET_SHARE_OBJECT when object is shared with public link", () => {
|
||||||
|
const store = mockStore({
|
||||||
|
buckets: { currentBucket: "test-public" },
|
||||||
|
objects: { currentPrefix: "pre1/" },
|
||||||
|
browser: { serverInfo: { info: { domains: ['public.com'] }} },
|
||||||
|
})
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: "objects/SET_SHARE_OBJECT",
|
||||||
|
show: true,
|
||||||
|
object: "a.txt",
|
||||||
|
url: "public.com/test-public/pre1/a.txt",
|
||||||
|
showExpiryDate: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "alert/SET",
|
||||||
|
alert: {
|
||||||
|
type: "success",
|
||||||
|
message: "Object shared.",
|
||||||
|
id: alertActions.alertId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return store
|
||||||
|
.dispatch(actionsObjects.shareObject("a.txt", 1, 0, 0))
|
||||||
|
.then(() => {
|
||||||
|
const actions = store.getActions()
|
||||||
|
expect(actions).toEqual(expectedActions)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("creates alert/SET when shareObject is failed", () => {
|
it("creates alert/SET when shareObject is failed", () => {
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
buckets: { currentBucket: "" },
|
buckets: { currentBucket: "" },
|
||||||
objects: { currentPrefix: "pre1/" }
|
objects: { currentPrefix: "pre1/" },
|
||||||
|
browser: { serverInfo: {} },
|
||||||
})
|
})
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,6 @@ import {
|
|||||||
import { getCurrentBucket } from "../buckets/selectors"
|
import { getCurrentBucket } from "../buckets/selectors"
|
||||||
import { getCurrentPrefix, getCheckedList } from "./selectors"
|
import { getCurrentPrefix, getCheckedList } from "./selectors"
|
||||||
import * as alertActions from "../alert/actions"
|
import * as alertActions from "../alert/actions"
|
||||||
import * as bucketActions from "../buckets/actions"
|
|
||||||
import {
|
import {
|
||||||
minioBrowserPrefix,
|
minioBrowserPrefix,
|
||||||
SORT_BY_NAME,
|
SORT_BY_NAME,
|
||||||
@ -33,6 +32,7 @@ import {
|
|||||||
SORT_ORDER_ASC,
|
SORT_ORDER_ASC,
|
||||||
SORT_ORDER_DESC,
|
SORT_ORDER_DESC,
|
||||||
} from "../constants"
|
} from "../constants"
|
||||||
|
import { getServerInfo, hasServerPublicDomain } from '../browser/selectors'
|
||||||
|
|
||||||
export const SET_LIST = "objects/SET_LIST"
|
export const SET_LIST = "objects/SET_LIST"
|
||||||
export const RESET_LIST = "objects/RESET_LIST"
|
export const RESET_LIST = "objects/RESET_LIST"
|
||||||
@ -222,19 +222,38 @@ export const deleteCheckedObjects = () => {
|
|||||||
|
|
||||||
export const shareObject = (object, days, hours, minutes) => {
|
export const shareObject = (object, days, hours, minutes) => {
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
|
const hasServerDomain = hasServerPublicDomain(getState())
|
||||||
const currentBucket = getCurrentBucket(getState())
|
const currentBucket = getCurrentBucket(getState())
|
||||||
const currentPrefix = getCurrentPrefix(getState())
|
const currentPrefix = getCurrentPrefix(getState())
|
||||||
const objectName = `${currentPrefix}${object}`
|
const objectName = `${currentPrefix}${object}`
|
||||||
const expiry = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60
|
const expiry = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60
|
||||||
if (web.LoggedIn()) {
|
if (web.LoggedIn()) {
|
||||||
return web
|
return web
|
||||||
.PresignedGet({
|
.GetBucketPolicy({ bucketName: currentBucket, prefix: currentPrefix })
|
||||||
host: location.host,
|
.catch(() => ({ policy: null }))
|
||||||
bucket: currentBucket,
|
.then(({ policy }) => {
|
||||||
object: objectName,
|
if (hasServerDomain && ['readonly', 'readwrite'].includes(policy)) {
|
||||||
expiry: expiry,
|
const domain = getServerInfo(getState()).info.domains[0]
|
||||||
|
const url = `${domain}/${currentBucket}/${encodeURI(objectName)}`
|
||||||
|
dispatch(showShareObject(object, url, false))
|
||||||
|
dispatch(
|
||||||
|
alertActions.set({
|
||||||
|
type: "success",
|
||||||
|
message: "Object shared."
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return web
|
||||||
|
.PresignedGet({
|
||||||
|
host: location.host,
|
||||||
|
bucket: currentBucket,
|
||||||
|
object: objectName,
|
||||||
|
expiry: expiry
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then((obj) => {
|
.then((obj) => {
|
||||||
|
if (!obj) return
|
||||||
dispatch(showShareObject(object, obj.url))
|
dispatch(showShareObject(object, obj.url))
|
||||||
dispatch(
|
dispatch(
|
||||||
alertActions.set({
|
alertActions.set({
|
||||||
@ -272,11 +291,12 @@ export const shareObject = (object, days, hours, minutes) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showShareObject = (object, url) => ({
|
export const showShareObject = (object, url, showExpiryDate = true) => ({
|
||||||
type: SET_SHARE_OBJECT,
|
type: SET_SHARE_OBJECT,
|
||||||
show: true,
|
show: true,
|
||||||
object,
|
object,
|
||||||
url,
|
url,
|
||||||
|
showExpiryDate,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const hideShareObject = (object, url) => ({
|
export const hideShareObject = (object, url) => ({
|
||||||
|
@ -89,7 +89,8 @@ export default (
|
|||||||
shareObject: {
|
shareObject: {
|
||||||
show: action.show,
|
show: action.show,
|
||||||
object: action.object,
|
object: action.object,
|
||||||
url: action.url
|
url: action.url,
|
||||||
|
showExpiryDate: action.showExpiryDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case actionsObjects.CHECKED_LIST_ADD:
|
case actionsObjects.CHECKED_LIST_ADD:
|
||||||
|
@ -291,6 +291,7 @@ var (
|
|||||||
func getGlobalInfo() (globalInfo map[string]interface{}) {
|
func getGlobalInfo() (globalInfo map[string]interface{}) {
|
||||||
globalInfo = map[string]interface{}{
|
globalInfo = map[string]interface{}{
|
||||||
"serverRegion": globalServerRegion,
|
"serverRegion": globalServerRegion,
|
||||||
|
"domains": globalDomainNames,
|
||||||
// Add more relevant global settings here.
|
// Add more relevant global settings here.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,6 +242,7 @@ func testServerInfoWebHandler(obj ObjectLayer, instanceType string, t TestErrHan
|
|||||||
if serverInfoReply.MinioVersion != Version {
|
if serverInfoReply.MinioVersion != Version {
|
||||||
t.Fatalf("Cannot get minio version from server info handler")
|
t.Fatalf("Cannot get minio version from server info handler")
|
||||||
}
|
}
|
||||||
|
serverInfoReply.MinioGlobalInfo["domains"] = []string(nil)
|
||||||
globalInfo := getGlobalInfo()
|
globalInfo := getGlobalInfo()
|
||||||
if !reflect.DeepEqual(serverInfoReply.MinioGlobalInfo, globalInfo) {
|
if !reflect.DeepEqual(serverInfoReply.MinioGlobalInfo, globalInfo) {
|
||||||
t.Fatalf("Global info did not match got %#v, expected %#v", serverInfoReply.MinioGlobalInfo, globalInfo)
|
t.Fatalf("Global info did not match got %#v, expected %#v", serverInfoReply.MinioGlobalInfo, globalInfo)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user