mirror of
https://github.com/minio/minio.git
synced 2025-11-10 22:10:12 -05:00
miniobrowser: Bring Minio browser source into minio repo. (#3617)
This commit is contained in:
committed by
Harshavardhana
parent
8489f22fe2
commit
cead24b0f7
43
browser/app/js/__tests__/jsonrpc-test.js
Normal file
43
browser/app/js/__tests__/jsonrpc-test.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 expect from 'expect';
|
||||
import JSONrpc from '../jsonrpc';
|
||||
|
||||
describe('jsonrpc', () => {
|
||||
it('should fail with invalid endpoint', (done) => {
|
||||
try {
|
||||
let jsonRPC = new JSONrpc({
|
||||
endpoint: 'htt://localhost:9000',
|
||||
namespace: 'Test'
|
||||
});
|
||||
} catch (e) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
it('should succeed with valid endpoint', () => {
|
||||
let jsonRPC = new JSONrpc({
|
||||
endpoint: 'http://localhost:9000/webrpc',
|
||||
namespace: 'Test'
|
||||
});
|
||||
expect(jsonRPC.version).toEqual('2.0');
|
||||
expect(jsonRPC.host).toEqual('localhost');
|
||||
expect(jsonRPC.port).toEqual('9000');
|
||||
expect(jsonRPC.path).toEqual('/webrpc');
|
||||
expect(jsonRPC.scheme).toEqual('http');
|
||||
});
|
||||
});
|
||||
|
||||
509
browser/app/js/actions.js
Normal file
509
browser/app/js/actions.js
Normal file
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 url from 'url'
|
||||
import Moment from 'moment'
|
||||
import web from './web'
|
||||
import * as utils from './utils'
|
||||
import storage from 'local-storage-fallback'
|
||||
|
||||
export const SET_WEB = 'SET_WEB'
|
||||
export const SET_CURRENT_BUCKET = 'SET_CURRENT_BUCKET'
|
||||
export const SET_CURRENT_PATH = 'SET_CURRENT_PATH'
|
||||
export const SET_BUCKETS = 'SET_BUCKETS'
|
||||
export const ADD_BUCKET = 'ADD_BUCKET'
|
||||
export const ADD_OBJECT = 'ADD_OBJECT'
|
||||
export const SET_VISIBLE_BUCKETS = 'SET_VISIBLE_BUCKETS'
|
||||
export const SET_OBJECTS = 'SET_OBJECTS'
|
||||
export const SET_STORAGE_INFO = 'SET_STORAGE_INFO'
|
||||
export const SET_SERVER_INFO = 'SET_SERVER_INFO'
|
||||
export const SHOW_MAKEBUCKET_MODAL = 'SHOW_MAKEBUCKET_MODAL'
|
||||
export const ADD_UPLOAD = 'ADD_UPLOAD'
|
||||
export const STOP_UPLOAD = 'STOP_UPLOAD'
|
||||
export const UPLOAD_PROGRESS = 'UPLOAD_PROGRESS'
|
||||
export const SET_ALERT = 'SET_ALERT'
|
||||
export const SET_LOGIN_ERROR = 'SET_LOGIN_ERROR'
|
||||
export const SET_SHOW_ABORT_MODAL = 'SET_SHOW_ABORT_MODAL'
|
||||
export const SHOW_ABOUT = 'SHOW_ABOUT'
|
||||
export const SET_SORT_NAME_ORDER = 'SET_SORT_NAME_ORDER'
|
||||
export const SET_SORT_SIZE_ORDER = 'SET_SORT_SIZE_ORDER'
|
||||
export const SET_SORT_DATE_ORDER = 'SET_SORT_DATE_ORDER'
|
||||
export const SET_LATEST_UI_VERSION = 'SET_LATEST_UI_VERSION'
|
||||
export const SET_SIDEBAR_STATUS = 'SET_SIDEBAR_STATUS'
|
||||
export const SET_LOGIN_REDIRECT_PATH = 'SET_LOGIN_REDIRECT_PATH'
|
||||
export const SET_LOAD_BUCKET = 'SET_LOAD_BUCKET'
|
||||
export const SET_LOAD_PATH = 'SET_LOAD_PATH'
|
||||
export const SHOW_SETTINGS = 'SHOW_SETTINGS'
|
||||
export const SET_SETTINGS = 'SET_SETTINGS'
|
||||
export const SHOW_BUCKET_POLICY = 'SHOW_BUCKET_POLICY'
|
||||
export const SET_POLICIES = 'SET_POLICIES'
|
||||
export const SET_SHARE_OBJECT = 'SET_SHARE_OBJECT'
|
||||
export const DELETE_CONFIRMATION = 'DELETE_CONFIRMATION'
|
||||
export const SET_PREFIX_WRITABLE = 'SET_PREFIX_WRITABLE'
|
||||
|
||||
export const showDeleteConfirmation = (object) => {
|
||||
return {
|
||||
type: DELETE_CONFIRMATION,
|
||||
payload: {
|
||||
object,
|
||||
show: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const hideDeleteConfirmation = () => {
|
||||
return {
|
||||
type: DELETE_CONFIRMATION,
|
||||
payload: {
|
||||
object: '',
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const showShareObject = url => {
|
||||
return {
|
||||
type: SET_SHARE_OBJECT,
|
||||
shareObject: {
|
||||
url: url,
|
||||
show: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const hideShareObject = () => {
|
||||
return {
|
||||
type: SET_SHARE_OBJECT,
|
||||
shareObject: {
|
||||
url: '',
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const shareObject = (object, expiry) => (dispatch, getState) => {
|
||||
const {currentBucket, web} = getState()
|
||||
let host = location.host
|
||||
let bucket = currentBucket
|
||||
|
||||
if (!web.LoggedIn()) {
|
||||
dispatch(showShareObject(`${host}/${bucket}/${object}`))
|
||||
return
|
||||
}
|
||||
web.PresignedGet({
|
||||
host,
|
||||
bucket,
|
||||
object,
|
||||
expiry
|
||||
})
|
||||
.then(obj => {
|
||||
dispatch(showShareObject(obj.url))
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
export const setLoginRedirectPath = (path) => {
|
||||
return {
|
||||
type: SET_LOGIN_REDIRECT_PATH,
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
export const setLoadPath = (loadPath) => {
|
||||
return {
|
||||
type: SET_LOAD_PATH,
|
||||
loadPath
|
||||
}
|
||||
}
|
||||
|
||||
export const setLoadBucket = (loadBucket) => {
|
||||
return {
|
||||
type: SET_LOAD_BUCKET,
|
||||
loadBucket
|
||||
}
|
||||
}
|
||||
|
||||
export const setWeb = web => {
|
||||
return {
|
||||
type: SET_WEB,
|
||||
web
|
||||
}
|
||||
}
|
||||
|
||||
export const setBuckets = buckets => {
|
||||
return {
|
||||
type: SET_BUCKETS,
|
||||
buckets
|
||||
}
|
||||
}
|
||||
|
||||
export const addBucket = bucket => {
|
||||
return {
|
||||
type: ADD_BUCKET,
|
||||
bucket
|
||||
}
|
||||
}
|
||||
|
||||
export const showMakeBucketModal = () => {
|
||||
return {
|
||||
type: SHOW_MAKEBUCKET_MODAL,
|
||||
showMakeBucketModal: true
|
||||
}
|
||||
}
|
||||
|
||||
export const hideAlert = () => {
|
||||
return {
|
||||
type: SET_ALERT,
|
||||
alert: {
|
||||
show: false,
|
||||
message: '',
|
||||
type: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const showAlert = alert => {
|
||||
return (dispatch, getState) => {
|
||||
let alertTimeout = null
|
||||
if (alert.type !== 'danger') {
|
||||
alertTimeout = setTimeout(() => {
|
||||
dispatch({
|
||||
type: SET_ALERT,
|
||||
alert: {
|
||||
show: false
|
||||
}
|
||||
})
|
||||
}, 5000)
|
||||
}
|
||||
dispatch({
|
||||
type: SET_ALERT,
|
||||
alert: Object.assign({}, alert, {
|
||||
show: true,
|
||||
alertTimeout
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const setSidebarStatus = (status) => {
|
||||
return {
|
||||
type: SET_SIDEBAR_STATUS,
|
||||
sidebarStatus: status
|
||||
}
|
||||
}
|
||||
|
||||
export const hideMakeBucketModal = () => {
|
||||
return {
|
||||
type: SHOW_MAKEBUCKET_MODAL,
|
||||
showMakeBucketModal: false
|
||||
}
|
||||
}
|
||||
|
||||
export const setVisibleBuckets = visibleBuckets => {
|
||||
return {
|
||||
type: SET_VISIBLE_BUCKETS,
|
||||
visibleBuckets
|
||||
}
|
||||
}
|
||||
|
||||
export const setObjects = (objects) => {
|
||||
return {
|
||||
type: SET_OBJECTS,
|
||||
objects
|
||||
}
|
||||
}
|
||||
|
||||
export const setCurrentBucket = currentBucket => {
|
||||
return {
|
||||
type: SET_CURRENT_BUCKET,
|
||||
currentBucket
|
||||
}
|
||||
}
|
||||
|
||||
export const setCurrentPath = currentPath => {
|
||||
return {
|
||||
type: SET_CURRENT_PATH,
|
||||
currentPath
|
||||
}
|
||||
}
|
||||
|
||||
export const setStorageInfo = storageInfo => {
|
||||
return {
|
||||
type: SET_STORAGE_INFO,
|
||||
storageInfo
|
||||
}
|
||||
}
|
||||
|
||||
export const setServerInfo = serverInfo => {
|
||||
return {
|
||||
type: SET_SERVER_INFO,
|
||||
serverInfo
|
||||
}
|
||||
}
|
||||
|
||||
const setPrefixWritable = prefixWritable => {
|
||||
return {
|
||||
type: SET_PREFIX_WRITABLE,
|
||||
prefixWritable,
|
||||
}
|
||||
}
|
||||
|
||||
export const selectBucket = (newCurrentBucket, prefix) => {
|
||||
if (!prefix)
|
||||
prefix = ''
|
||||
return (dispatch, getState) => {
|
||||
let web = getState().web
|
||||
let currentBucket = getState().currentBucket
|
||||
|
||||
if (currentBucket !== newCurrentBucket) dispatch(setLoadBucket(newCurrentBucket))
|
||||
|
||||
dispatch(setCurrentBucket(newCurrentBucket))
|
||||
dispatch(selectPrefix(prefix))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export const selectPrefix = prefix => {
|
||||
return (dispatch, getState) => {
|
||||
const {currentBucket, web} = getState()
|
||||
dispatch(setLoadPath(prefix))
|
||||
web.ListObjects({
|
||||
bucketName: currentBucket,
|
||||
prefix
|
||||
})
|
||||
.then(res => {
|
||||
let objects = res.objects
|
||||
if (!objects)
|
||||
objects = []
|
||||
dispatch(setObjects(
|
||||
utils.sortObjectsByName(objects.map(object => {
|
||||
object.name = object.name.replace(`${prefix}`, ''); return object
|
||||
}))
|
||||
))
|
||||
dispatch(setPrefixWritable(res.writable))
|
||||
dispatch(setSortNameOrder(false))
|
||||
dispatch(setCurrentPath(prefix))
|
||||
dispatch(setLoadBucket(''))
|
||||
dispatch(setLoadPath(''))
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
dispatch(setLoadBucket(''))
|
||||
dispatch(setLoadPath(''))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const addUpload = options => {
|
||||
return {
|
||||
type: ADD_UPLOAD,
|
||||
slug: options.slug,
|
||||
size: options.size,
|
||||
xhr: options.xhr,
|
||||
name: options.name
|
||||
}
|
||||
}
|
||||
|
||||
export const stopUpload = options => {
|
||||
return {
|
||||
type: STOP_UPLOAD,
|
||||
slug: options.slug
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadProgress = options => {
|
||||
return {
|
||||
type: UPLOAD_PROGRESS,
|
||||
slug: options.slug,
|
||||
loaded: options.loaded
|
||||
}
|
||||
}
|
||||
|
||||
export const setShowAbortModal = showAbortModal => {
|
||||
return {
|
||||
type: SET_SHOW_ABORT_MODAL,
|
||||
showAbortModal
|
||||
}
|
||||
}
|
||||
|
||||
export const setLoginError = () => {
|
||||
return {
|
||||
type: SET_LOGIN_ERROR,
|
||||
loginError: true
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadFile = (file, xhr) => {
|
||||
return (dispatch, getState) => {
|
||||
const {currentBucket, currentPath} = getState()
|
||||
const objectName = `${currentPath}${file.name}`
|
||||
const uploadUrl = `${window.location.origin}/minio/upload/${currentBucket}/${objectName}`
|
||||
// The slug is a unique identifer for the file upload.
|
||||
const slug = `${currentBucket}-${currentPath}-${file.name}`
|
||||
|
||||
xhr.open('PUT', uploadUrl, true)
|
||||
xhr.withCredentials = false
|
||||
const token = storage.getItem('token')
|
||||
if (token) xhr.setRequestHeader("Authorization", 'Bearer ' + storage.getItem('token'))
|
||||
xhr.setRequestHeader('x-amz-date', Moment().utc().format('YYYYMMDDTHHmmss') + 'Z')
|
||||
dispatch(addUpload({
|
||||
slug,
|
||||
xhr,
|
||||
size: file.size,
|
||||
name: file.name
|
||||
}))
|
||||
|
||||
xhr.onload = function(event) {
|
||||
if (xhr.status == 401 || xhr.status == 403 || xhr.status == 500) {
|
||||
setShowAbortModal(false)
|
||||
dispatch(stopUpload({
|
||||
slug
|
||||
}))
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: 'Unauthorized request.'
|
||||
}))
|
||||
}
|
||||
if (xhr.status == 200) {
|
||||
setShowAbortModal(false)
|
||||
dispatch(stopUpload({
|
||||
slug
|
||||
}))
|
||||
dispatch(showAlert({
|
||||
type: 'success',
|
||||
message: 'File \'' + file.name + '\' uploaded successfully.'
|
||||
}))
|
||||
dispatch(selectPrefix(currentPath))
|
||||
}
|
||||
}
|
||||
|
||||
xhr.upload.addEventListener('error', event => {
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: 'Error occurred uploading \'' + file.name + '\'.'
|
||||
}))
|
||||
dispatch(stopUpload({
|
||||
slug
|
||||
}))
|
||||
})
|
||||
|
||||
xhr.upload.addEventListener('progress', event => {
|
||||
if (event.lengthComputable) {
|
||||
let loaded = event.loaded
|
||||
let total = event.total
|
||||
|
||||
// Update the counter.
|
||||
dispatch(uploadProgress({
|
||||
slug,
|
||||
loaded
|
||||
}))
|
||||
}
|
||||
})
|
||||
xhr.send(file)
|
||||
}
|
||||
}
|
||||
|
||||
export const showAbout = () => {
|
||||
return {
|
||||
type: SHOW_ABOUT,
|
||||
showAbout: true
|
||||
}
|
||||
}
|
||||
|
||||
export const hideAbout = () => {
|
||||
return {
|
||||
type: SHOW_ABOUT,
|
||||
showAbout: false
|
||||
}
|
||||
}
|
||||
|
||||
export const setSortNameOrder = (sortNameOrder) => {
|
||||
return {
|
||||
type: SET_SORT_NAME_ORDER,
|
||||
sortNameOrder
|
||||
}
|
||||
}
|
||||
|
||||
export const setSortSizeOrder = (sortSizeOrder) => {
|
||||
return {
|
||||
type: SET_SORT_SIZE_ORDER,
|
||||
sortSizeOrder
|
||||
}
|
||||
}
|
||||
|
||||
export const setSortDateOrder = (sortDateOrder) => {
|
||||
return {
|
||||
type: SET_SORT_DATE_ORDER,
|
||||
sortDateOrder
|
||||
}
|
||||
}
|
||||
|
||||
export const setLatestUIVersion = (latestUiVersion) => {
|
||||
return {
|
||||
type: SET_LATEST_UI_VERSION,
|
||||
latestUiVersion
|
||||
}
|
||||
}
|
||||
|
||||
export const showSettings = () => {
|
||||
return {
|
||||
type: SHOW_SETTINGS,
|
||||
showSettings: true
|
||||
}
|
||||
}
|
||||
|
||||
export const hideSettings = () => {
|
||||
return {
|
||||
type: SHOW_SETTINGS,
|
||||
showSettings: false
|
||||
}
|
||||
}
|
||||
|
||||
export const setSettings = (settings) => {
|
||||
return {
|
||||
type: SET_SETTINGS,
|
||||
settings
|
||||
}
|
||||
}
|
||||
|
||||
export const showBucketPolicy = () => {
|
||||
return {
|
||||
type: SHOW_BUCKET_POLICY,
|
||||
showBucketPolicy: true
|
||||
}
|
||||
}
|
||||
|
||||
export const hideBucketPolicy = () => {
|
||||
return {
|
||||
type: SHOW_BUCKET_POLICY,
|
||||
showBucketPolicy: false
|
||||
}
|
||||
}
|
||||
|
||||
export const setPolicies = (policies) => {
|
||||
return {
|
||||
type: SET_POLICIES,
|
||||
policies
|
||||
}
|
||||
}
|
||||
734
browser/app/js/components/Browse.js
Normal file
734
browser/app/js/components/Browse.js
Normal file
@@ -0,0 +1,734 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 browserHistory from 'react-router/lib/browserHistory'
|
||||
import humanize from 'humanize'
|
||||
import Moment from 'moment'
|
||||
import Modal from 'react-bootstrap/lib/Modal'
|
||||
import ModalBody from 'react-bootstrap/lib/ModalBody'
|
||||
import ModalHeader from 'react-bootstrap/lib/ModalHeader'
|
||||
import Alert from 'react-bootstrap/lib/Alert'
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip'
|
||||
import Dropdown from 'react-bootstrap/lib/Dropdown'
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem'
|
||||
|
||||
import InputGroup from '../components/InputGroup'
|
||||
import Dropzone from '../components/Dropzone'
|
||||
import ObjectsList from '../components/ObjectsList'
|
||||
import SideBar from '../components/SideBar'
|
||||
import Path from '../components/Path'
|
||||
import BrowserUpdate from '../components/BrowserUpdate'
|
||||
import UploadModal from '../components/UploadModal'
|
||||
import SettingsModal from '../components/SettingsModal'
|
||||
import PolicyInput from '../components/PolicyInput'
|
||||
import Policy from '../components/Policy'
|
||||
import BrowserDropdown from '../components/BrowserDropdown'
|
||||
import ConfirmModal from './ConfirmModal'
|
||||
import logo from '../../img/logo.svg'
|
||||
import * as actions from '../actions'
|
||||
import * as utils from '../utils'
|
||||
import * as mime from '../mime'
|
||||
import { minioBrowserPrefix } from '../constants'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
import storage from 'local-storage-fallback'
|
||||
|
||||
export default class Browse extends React.Component {
|
||||
componentDidMount() {
|
||||
const {web, dispatch, currentBucket} = this.props
|
||||
if (!web.LoggedIn()) return
|
||||
web.StorageInfo()
|
||||
.then(res => {
|
||||
let storageInfo = Object.assign({}, {
|
||||
total: res.storageInfo.Total,
|
||||
free: res.storageInfo.Free
|
||||
})
|
||||
storageInfo.used = storageInfo.total - storageInfo.free
|
||||
dispatch(actions.setStorageInfo(storageInfo))
|
||||
return web.ServerInfo()
|
||||
})
|
||||
.then(res => {
|
||||
let serverInfo = Object.assign({}, {
|
||||
version: res.MinioVersion,
|
||||
memory: res.MinioMemory,
|
||||
platform: res.MinioPlatform,
|
||||
runtime: res.MinioRuntime,
|
||||
envVars: res.MinioEnvVars
|
||||
})
|
||||
dispatch(actions.setServerInfo(serverInfo))
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const {dispatch} = this.props
|
||||
// Clear out any stale message in the alert of Login page
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: ''
|
||||
}))
|
||||
if (web.LoggedIn()) {
|
||||
web.ListBuckets()
|
||||
.then(res => {
|
||||
let buckets
|
||||
if (!res.buckets)
|
||||
buckets = []
|
||||
else
|
||||
buckets = res.buckets.map(bucket => bucket.name)
|
||||
if (buckets.length) {
|
||||
dispatch(actions.setBuckets(buckets))
|
||||
dispatch(actions.setVisibleBuckets(buckets))
|
||||
if (location.pathname === minioBrowserPrefix || location.pathname === minioBrowserPrefix + '/') {
|
||||
browserHistory.push(utils.pathJoin(buckets[0]))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
this.history = browserHistory.listen(({pathname}) => {
|
||||
let decPathname = decodeURI(pathname)
|
||||
if (decPathname === `${minioBrowserPrefix}/login`) return // FIXME: better organize routes and remove this
|
||||
if (!decPathname.endsWith('/'))
|
||||
decPathname += '/'
|
||||
if (decPathname === minioBrowserPrefix + '/') {
|
||||
dispatch(actions.setCurrentBucket(''))
|
||||
dispatch(actions.setCurrentPath(''))
|
||||
dispatch(actions.setObjects([]))
|
||||
return
|
||||
}
|
||||
let obj = utils.pathSlice(decPathname)
|
||||
if (!web.LoggedIn()) {
|
||||
dispatch(actions.setBuckets([obj.bucket]))
|
||||
dispatch(actions.setVisibleBuckets([obj.bucket]))
|
||||
}
|
||||
dispatch(actions.selectBucket(obj.bucket, obj.prefix))
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.history()
|
||||
}
|
||||
|
||||
selectBucket(e, bucket) {
|
||||
e.preventDefault()
|
||||
if (bucket === this.props.currentBucket) return
|
||||
browserHistory.push(utils.pathJoin(bucket))
|
||||
}
|
||||
|
||||
searchBuckets(e) {
|
||||
e.preventDefault()
|
||||
let {buckets} = this.props
|
||||
this.props.dispatch(actions.setVisibleBuckets(buckets.filter(bucket => bucket.indexOf(e.target.value) > -1)))
|
||||
}
|
||||
|
||||
selectPrefix(e, prefix) {
|
||||
e.preventDefault()
|
||||
const {dispatch, currentPath, web, currentBucket} = this.props
|
||||
const encPrefix = encodeURI(prefix)
|
||||
if (prefix.endsWith('/') || prefix === '') {
|
||||
if (prefix === currentPath) return
|
||||
browserHistory.push(utils.pathJoin(currentBucket, encPrefix))
|
||||
} else {
|
||||
window.location = `${window.location.origin}/minio/download/${currentBucket}/${encPrefix}?token=${storage.getItem('token')}`
|
||||
}
|
||||
}
|
||||
|
||||
makeBucket(e) {
|
||||
e.preventDefault()
|
||||
const bucketName = this.refs.makeBucketRef.value
|
||||
this.refs.makeBucketRef.value = ''
|
||||
const {web, dispatch} = this.props
|
||||
this.hideMakeBucketModal()
|
||||
web.MakeBucket({
|
||||
bucketName
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(actions.addBucket(bucketName))
|
||||
dispatch(actions.selectBucket(bucketName))
|
||||
})
|
||||
.catch(err => dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
})))
|
||||
}
|
||||
|
||||
hideMakeBucketModal() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideMakeBucketModal())
|
||||
}
|
||||
|
||||
showMakeBucketModal(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showMakeBucketModal())
|
||||
}
|
||||
|
||||
showAbout(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showAbout())
|
||||
}
|
||||
|
||||
hideAbout(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideAbout())
|
||||
}
|
||||
|
||||
showBucketPolicy(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showBucketPolicy())
|
||||
}
|
||||
|
||||
hideBucketPolicy(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideBucketPolicy())
|
||||
}
|
||||
|
||||
uploadFile(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch, buckets} = this.props
|
||||
|
||||
if (buckets.length === 0) {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: "Bucket needs to be created before trying to upload files."
|
||||
}))
|
||||
return
|
||||
}
|
||||
let file = e.target.files[0]
|
||||
e.target.value = null
|
||||
this.xhr = new XMLHttpRequest()
|
||||
dispatch(actions.uploadFile(file, this.xhr))
|
||||
}
|
||||
|
||||
removeObject() {
|
||||
const {web, dispatch, currentPath, currentBucket, deleteConfirmation} = this.props
|
||||
web.RemoveObject({
|
||||
bucketName: currentBucket,
|
||||
objectName: deleteConfirmation.object
|
||||
})
|
||||
.then(() => {
|
||||
this.hideDeleteConfirmation()
|
||||
dispatch(actions.selectPrefix(currentPath))
|
||||
})
|
||||
.catch(e => dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message
|
||||
})))
|
||||
}
|
||||
|
||||
hideAlert(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideAlert())
|
||||
}
|
||||
|
||||
showDeleteConfirmation(e, object) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showDeleteConfirmation(object))
|
||||
}
|
||||
|
||||
hideDeleteConfirmation() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideDeleteConfirmation())
|
||||
}
|
||||
|
||||
shareObject(e, object) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.shareObject(object))
|
||||
}
|
||||
|
||||
hideShareObjectModal() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideShareObject())
|
||||
}
|
||||
|
||||
dataType(name, contentType) {
|
||||
return mime.getDataType(name, contentType)
|
||||
}
|
||||
|
||||
sortObjectsByName(e) {
|
||||
const {dispatch, objects, sortNameOrder} = this.props
|
||||
dispatch(actions.setObjects(utils.sortObjectsByName(objects, !sortNameOrder)))
|
||||
dispatch(actions.setSortNameOrder(!sortNameOrder))
|
||||
}
|
||||
|
||||
sortObjectsBySize() {
|
||||
const {dispatch, objects, sortSizeOrder} = this.props
|
||||
dispatch(actions.setObjects(utils.sortObjectsBySize(objects, !sortSizeOrder)))
|
||||
dispatch(actions.setSortSizeOrder(!sortSizeOrder))
|
||||
}
|
||||
|
||||
sortObjectsByDate() {
|
||||
const {dispatch, objects, sortDateOrder} = this.props
|
||||
dispatch(actions.setObjects(utils.sortObjectsByDate(objects, !sortDateOrder)))
|
||||
dispatch(actions.setSortDateOrder(!sortDateOrder))
|
||||
}
|
||||
|
||||
logout(e) {
|
||||
const {web} = this.props
|
||||
e.preventDefault()
|
||||
web.Logout()
|
||||
browserHistory.push(`${minioBrowserPrefix}/login`)
|
||||
}
|
||||
|
||||
landingPage(e) {
|
||||
e.preventDefault()
|
||||
this.props.dispatch(actions.selectBucket(this.props.buckets[0]))
|
||||
}
|
||||
|
||||
fullScreen(e) {
|
||||
e.preventDefault()
|
||||
let el = document.documentElement
|
||||
if (el.requestFullscreen) {
|
||||
el.requestFullscreen()
|
||||
}
|
||||
if (el.mozRequestFullScreen) {
|
||||
el.mozRequestFullScreen()
|
||||
}
|
||||
if (el.webkitRequestFullscreen) {
|
||||
el.webkitRequestFullscreen()
|
||||
}
|
||||
if (el.msRequestFullscreen) {
|
||||
el.msRequestFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
toggleSidebar(status) {
|
||||
this.props.dispatch(actions.setSidebarStatus(status))
|
||||
}
|
||||
|
||||
hideSidebar(event) {
|
||||
let e = event || window.event;
|
||||
|
||||
// Support all browsers.
|
||||
let target = e.srcElement || e.target;
|
||||
if (target.nodeType === 3) // Safari support.
|
||||
target = target.parentNode;
|
||||
|
||||
let targetID = target.id;
|
||||
if (!(targetID === 'feh-trigger')) {
|
||||
this.props.dispatch(actions.setSidebarStatus(false))
|
||||
}
|
||||
}
|
||||
|
||||
showSettings(e) {
|
||||
e.preventDefault()
|
||||
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showSettings())
|
||||
}
|
||||
|
||||
showMessage() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showAlert({
|
||||
type: 'success',
|
||||
message: 'Link copied to clipboard!'
|
||||
}))
|
||||
this.hideShareObjectModal()
|
||||
}
|
||||
|
||||
selectTexts() {
|
||||
this.refs.copyTextInput.select()
|
||||
}
|
||||
|
||||
handleExpireValue(targetInput, inc) {
|
||||
inc === -1 ? this.refs[targetInput].stepDown(1) : this.refs[targetInput].stepUp(1)
|
||||
|
||||
if (this.refs.expireDays.value == 7) {
|
||||
this.refs.expireHours.value = 0
|
||||
this.refs.expireMins.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const {total, free} = this.props.storageInfo
|
||||
const {showMakeBucketModal, alert, sortNameOrder, sortSizeOrder, sortDateOrder, showAbout, showBucketPolicy} = this.props
|
||||
const {version, memory, platform, runtime} = this.props.serverInfo
|
||||
const {sidebarStatus} = this.props
|
||||
const {showSettings} = this.props
|
||||
const {policies, currentBucket, currentPath} = this.props
|
||||
const {deleteConfirmation} = this.props
|
||||
const {shareObject} = this.props
|
||||
const {web, prefixWritable} = this.props
|
||||
|
||||
// Don't always show the SettingsModal. This is done here instead of in
|
||||
// SettingsModal.js so as to allow for #componentWillMount to handle
|
||||
// the loading of the settings.
|
||||
let settingsModal = showSettings ? <SettingsModal /> : <noscript></noscript>
|
||||
|
||||
let alertBox = <Alert className={ classNames({
|
||||
'alert': true,
|
||||
'animated': true,
|
||||
'fadeInDown': alert.show,
|
||||
'fadeOutUp': !alert.show
|
||||
}) } bsStyle={ alert.type } onDismiss={ this.hideAlert.bind(this) }>
|
||||
<div className='text-center'>
|
||||
{ alert.message }
|
||||
</div>
|
||||
</Alert>
|
||||
// Make sure you don't show a fading out alert box on the initial web-page load.
|
||||
if (!alert.message)
|
||||
alertBox = ''
|
||||
|
||||
let signoutTooltip = <Tooltip id="tt-sign-out">
|
||||
Sign out
|
||||
</Tooltip>
|
||||
let uploadTooltip = <Tooltip id="tt-upload-file">
|
||||
Upload file
|
||||
</Tooltip>
|
||||
let makeBucketTooltip = <Tooltip id="tt-create-bucket">
|
||||
Create bucket
|
||||
</Tooltip>
|
||||
let loginButton = ''
|
||||
let browserDropdownButton = ''
|
||||
let storageUsageDetails = ''
|
||||
|
||||
let used = total - free
|
||||
let usedPercent = (used / total) * 100 + '%'
|
||||
let freePercent = free * 100 / total
|
||||
|
||||
if (web.LoggedIn()) {
|
||||
browserDropdownButton = <BrowserDropdown fullScreen={ this.fullScreen.bind(this) }
|
||||
showAbout={ this.showAbout.bind(this) }
|
||||
showSettings={ this.showSettings.bind(this) }
|
||||
logout={ this.logout.bind(this) } />
|
||||
} else {
|
||||
loginButton = <a className='btn btn-danger' href='/minio/login'>Login</a>
|
||||
}
|
||||
|
||||
if (web.LoggedIn()) {
|
||||
storageUsageDetails = <div className="feh-usage">
|
||||
<div className="fehu-chart">
|
||||
<div style={ { width: usedPercent } }></div>
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
Used:
|
||||
{ humanize.filesize(total - free) }
|
||||
</li>
|
||||
<li className="pull-right">
|
||||
Free:
|
||||
{ humanize.filesize(total - used) }
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
let createButton = ''
|
||||
if (web.LoggedIn()) {
|
||||
createButton = <Dropdown dropup className="feb-actions" id="fe-action-toggle">
|
||||
<Dropdown.Toggle noCaret className="feba-toggle">
|
||||
<span><i className="fa fa-plus"></i></span>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<OverlayTrigger placement="left" overlay={ uploadTooltip }>
|
||||
<a href="#" className="feba-btn feba-upload">
|
||||
<input type="file"
|
||||
onChange={ this.uploadFile.bind(this) }
|
||||
style={ { display: 'none' } }
|
||||
id="file-input"></input>
|
||||
<label htmlFor="file-input"> <i className="fa fa-cloud-upload"></i> </label>
|
||||
</a>
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger placement="left" overlay={ makeBucketTooltip }>
|
||||
<a href="#" className="feba-btn feba-bucket" onClick={ this.showMakeBucketModal.bind(this) }><i className="fa fa-hdd-o"></i></a>
|
||||
</OverlayTrigger>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
|
||||
} else {
|
||||
if (prefixWritable)
|
||||
createButton = <Dropdown dropup className="feb-actions" id="fe-action-toggle">
|
||||
<Dropdown.Toggle noCaret className="feba-toggle">
|
||||
<span><i className="fa fa-plus"></i></span>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<OverlayTrigger placement="left" overlay={ uploadTooltip }>
|
||||
<a href="#" className="feba-btn feba-upload">
|
||||
<input type="file"
|
||||
onChange={ this.uploadFile.bind(this) }
|
||||
style={ { display: 'none' } }
|
||||
id="file-input"></input>
|
||||
<label htmlFor="file-input"> <i className="fa fa-cloud-upload"></i> </label>
|
||||
</a>
|
||||
</OverlayTrigger>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ classNames({
|
||||
'file-explorer': true,
|
||||
'toggled': sidebarStatus
|
||||
}) }>
|
||||
<SideBar landingPage={ this.landingPage.bind(this) }
|
||||
searchBuckets={ this.searchBuckets.bind(this) }
|
||||
selectBucket={ this.selectBucket.bind(this) }
|
||||
clickOutside={ this.hideSidebar.bind(this) }
|
||||
showPolicy={ this.showBucketPolicy.bind(this) } />
|
||||
<div className="fe-body">
|
||||
<Dropzone>
|
||||
{ alertBox }
|
||||
<header className="fe-header-mobile hidden-lg hidden-md">
|
||||
<div id="feh-trigger" className={ 'feh-trigger ' + (classNames({
|
||||
'feht-toggled': sidebarStatus
|
||||
})) } onClick={ this.toggleSidebar.bind(this, !sidebarStatus) }>
|
||||
<div className="feht-lines">
|
||||
<div className="top"></div>
|
||||
<div className="center"></div>
|
||||
<div className="bottom"></div>
|
||||
</div>
|
||||
</div>
|
||||
<img className="mh-logo" src={ logo } alt="" />
|
||||
</header>
|
||||
<header className="fe-header">
|
||||
<Path selectPrefix={ this.selectPrefix.bind(this) } />
|
||||
{ storageUsageDetails }
|
||||
<ul className="feh-actions">
|
||||
<BrowserUpdate />
|
||||
{ loginButton }
|
||||
{ browserDropdownButton }
|
||||
</ul>
|
||||
</header>
|
||||
<div className="feb-container">
|
||||
<header className="fesl-row" data-type="folder">
|
||||
<div className="fesl-item fi-name" onClick={ this.sortObjectsByName.bind(this) } 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 fi-size" onClick={ this.sortObjectsBySize.bind(this) } 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 fi-modified" onClick={ this.sortObjectsByDate.bind(this) } data-sort="last-modified">
|
||||
Last Modified
|
||||
<i className={ classNames({
|
||||
'fesli-sort': true,
|
||||
'fa': true,
|
||||
'fa-sort-numeric-desc': sortDateOrder,
|
||||
'fa-sort-numeric-asc': !sortDateOrder
|
||||
}) } />
|
||||
</div>
|
||||
<div className="fesl-item fi-actions"></div>
|
||||
</header>
|
||||
</div>
|
||||
<div className="feb-container">
|
||||
<ObjectsList dataType={ this.dataType.bind(this) }
|
||||
selectPrefix={ this.selectPrefix.bind(this) }
|
||||
showDeleteConfirmation={ this.showDeleteConfirmation.bind(this) }
|
||||
shareObject={ this.shareObject.bind(this) } />
|
||||
</div>
|
||||
<UploadModal />
|
||||
{ createButton }
|
||||
<Modal className="modal-create-bucket"
|
||||
bsSize="small"
|
||||
animation={ false }
|
||||
show={ showMakeBucketModal }
|
||||
onHide={ this.hideMakeBucketModal.bind(this) }>
|
||||
<button className="close close-alt" onClick={ this.hideMakeBucketModal.bind(this) }>
|
||||
<span>×</span>
|
||||
</button>
|
||||
<ModalBody>
|
||||
<form onSubmit={ this.makeBucket.bind(this) }>
|
||||
<div className="input-group">
|
||||
<input className="ig-text"
|
||||
type="text"
|
||||
ref="makeBucketRef"
|
||||
placeholder="Bucket Name"
|
||||
autoFocus/>
|
||||
<i className="ig-helpers"></i>
|
||||
</div>
|
||||
</form>
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
<Modal className="modal-about modal-dark"
|
||||
animation={ false }
|
||||
show={ showAbout }
|
||||
onHide={ this.hideAbout.bind(this) }>
|
||||
<button className="close" onClick={ this.hideAbout.bind(this) }>
|
||||
<span>×</span>
|
||||
</button>
|
||||
<div className="ma-inner">
|
||||
<div className="mai-item hidden-xs">
|
||||
<a href="https://minio.io" target="_blank"><img className="maii-logo" src={ logo } alt="" /></a>
|
||||
</div>
|
||||
<div className="mai-item">
|
||||
<ul className="maii-list">
|
||||
<li>
|
||||
<div>
|
||||
Version
|
||||
</div>
|
||||
<small>{ version }</small>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Memory
|
||||
</div>
|
||||
<small>{ memory }</small>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Platform
|
||||
</div>
|
||||
<small>{ platform }</small>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Runtime
|
||||
</div>
|
||||
<small>{ runtime }</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal className="modal-policy"
|
||||
animation={ false }
|
||||
show={ showBucketPolicy }
|
||||
onHide={ this.hideBucketPolicy.bind(this) }>
|
||||
<ModalHeader>
|
||||
Bucket Policy (
|
||||
{ currentBucket })
|
||||
<button className="close close-alt" onClick={ this.hideBucketPolicy.bind(this) }>
|
||||
<span>×</span>
|
||||
</button>
|
||||
</ModalHeader>
|
||||
<div className="pm-body">
|
||||
<PolicyInput bucket={ currentBucket } />
|
||||
{ policies.map((policy, i) => <Policy key={ i } prefix={ policy.prefix } policy={ policy.policy } />
|
||||
) }
|
||||
</div>
|
||||
</Modal>
|
||||
<ConfirmModal show={ deleteConfirmation.show }
|
||||
icon='fa fa-exclamation-triangle mci-red'
|
||||
text='Are you sure you want to delete?'
|
||||
sub='This cannot be undone!'
|
||||
okText='Delete'
|
||||
cancelText='Cancel'
|
||||
okHandler={ this.removeObject.bind(this) }
|
||||
cancelHandler={ this.hideDeleteConfirmation.bind(this) }>
|
||||
</ConfirmModal>
|
||||
<Modal show={ shareObject.show }
|
||||
animation={ false }
|
||||
onHide={ this.hideShareObjectModal.bind(this) }
|
||||
bsSize="small">
|
||||
<ModalHeader>
|
||||
Share Object
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="input-group copy-text">
|
||||
<label>
|
||||
Shareable Link
|
||||
</label>
|
||||
<input type="text"
|
||||
ref="copyTextInput"
|
||||
readOnly="readOnly"
|
||||
value={ window.location.protocol + '//' + shareObject.url }
|
||||
onClick={ this.selectTexts.bind(this) } />
|
||||
</div>
|
||||
<div className="input-group" style={ { display: web.LoggedIn() ? 'block' : 'none' } }>
|
||||
<label>
|
||||
Expires in
|
||||
</label>
|
||||
<div className="set-expire">
|
||||
<div className="set-expire-item">
|
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireDays', 1) }></i>
|
||||
<div className="set-expire-title">
|
||||
Days
|
||||
</div>
|
||||
<div className="set-expire-value">
|
||||
<input ref="expireDays"
|
||||
type="number"
|
||||
min={ 0 }
|
||||
max={ 7 }
|
||||
defaultValue={ 0 } />
|
||||
</div>
|
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireDays', -1) }></i>
|
||||
</div>
|
||||
<div className="set-expire-item">
|
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireHours', 1) }></i>
|
||||
<div className="set-expire-title">
|
||||
Hours
|
||||
</div>
|
||||
<div className="set-expire-value">
|
||||
<input ref="expireHours"
|
||||
type="number"
|
||||
min={ 0 }
|
||||
max={ 24 }
|
||||
defaultValue={ 0 } />
|
||||
</div>
|
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireHours', -1) }></i>
|
||||
</div>
|
||||
<div className="set-expire-item">
|
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireMins', 1) }></i>
|
||||
<div className="set-expire-title">
|
||||
Minutes
|
||||
</div>
|
||||
<div className="set-expire-value">
|
||||
<input ref="expireMins"
|
||||
type="number"
|
||||
min={ 1 }
|
||||
max={ 60 }
|
||||
defaultValue={ 45 } />
|
||||
</div>
|
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireMins', -1) }></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<div className="modal-footer">
|
||||
<CopyToClipboard text={ shareObject.url } onCopy={ this.showMessage.bind(this) }>
|
||||
<button className="btn btn-success">
|
||||
Copy Link
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
<button className="btn btn-link" onClick={ this.hideShareObjectModal.bind(this) }>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
{ settingsModal }
|
||||
</Dropzone>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
56
browser/app/js/components/BrowserDropdown.js
Normal file
56
browser/app/js/components/BrowserDropdown.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016, 2017 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/lib/components/connect'
|
||||
import Dropdown from 'react-bootstrap/lib/Dropdown'
|
||||
|
||||
let BrowserDropdown = ({fullScreen, showAbout, showSettings, logout}) => {
|
||||
return (
|
||||
<li>
|
||||
<Dropdown pullRight id="top-right-menu">
|
||||
<Dropdown.Toggle noCaret>
|
||||
<i className="fa fa-reorder"></i>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
<li>
|
||||
<a target="_blank" href="https://github.com/minio/miniobrowser">Github <i className="fa fa-github"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ fullScreen }>Fullscreen <i className="fa fa-expand"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://docs.minio.io/">Documentation <i className="fa fa-book"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://slack.minio.io">Ask for help <i className="fa fa-question-circle"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ showAbout }>About <i className="fa fa-info-circle"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ showSettings }>Settings <i className="fa fa-cog"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ logout }>Sign Out <i className="fa fa-sign-out"></i></a>
|
||||
</li>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => state)(BrowserDropdown)
|
||||
42
browser/app/js/components/BrowserUpdate.js
Normal file
42
browser/app/js/components/BrowserUpdate.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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/lib/components/connect'
|
||||
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip'
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'
|
||||
|
||||
let BrowserUpdate = ({latestUiVersion}) => {
|
||||
// Don't show an update if we're already updated!
|
||||
if (latestUiVersion === currentUiVersion) return ( <noscript></noscript> )
|
||||
|
||||
return (
|
||||
<li className="hidden-xs hidden-sm">
|
||||
<a href="">
|
||||
<OverlayTrigger placement="left" overlay={ <Tooltip id="tt-version-update">
|
||||
New update available. Click to refresh.
|
||||
</Tooltip> }> <i className="fa fa-refresh"></i> </OverlayTrigger>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
latestUiVersion: state.latestUiVersion
|
||||
}
|
||||
})(BrowserUpdate)
|
||||
50
browser/app/js/components/ConfirmModal.js
Normal file
50
browser/app/js/components/ConfirmModal.js
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 Modal from 'react-bootstrap/lib/Modal'
|
||||
import ModalBody from 'react-bootstrap/lib/ModalBody'
|
||||
|
||||
let ConfirmModal = ({baseClass, icon, text, sub, okText, cancelText, okHandler, cancelHandler, show}) => {
|
||||
return (
|
||||
<Modal bsSize="small"
|
||||
animation={ false }
|
||||
show={ show }
|
||||
className={ "modal-confirm " + (baseClass || '') }>
|
||||
<ModalBody>
|
||||
<div className="mc-icon">
|
||||
<i className={ icon }></i>
|
||||
</div>
|
||||
<div className="mc-text">
|
||||
{ text }
|
||||
</div>
|
||||
<div className="mc-sub">
|
||||
{ sub }
|
||||
</div>
|
||||
</ModalBody>
|
||||
<div className="modal-footer">
|
||||
<button className="btn btn-danger" onClick={ okHandler }>
|
||||
{ okText }
|
||||
</button>
|
||||
<button className="btn btn-link" onClick={ cancelHandler }>
|
||||
{ cancelText }
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfirmModal
|
||||
65
browser/app/js/components/Dropzone.js
Normal file
65
browser/app/js/components/Dropzone.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 ReactDropzone from 'react-dropzone'
|
||||
import * as actions from '../actions'
|
||||
|
||||
// Dropzone is a drag-and-drop element for uploading files. It will create a
|
||||
// landing zone of sorts that automatically receives the files.
|
||||
export default class Dropzone extends React.Component {
|
||||
|
||||
onDrop(files) {
|
||||
// FIXME: Currently you can upload multiple files, but only one abort
|
||||
// modal will be shown, and progress updates will only occur for one
|
||||
// file at a time. See #171.
|
||||
files.forEach(file => {
|
||||
let req = new XMLHttpRequest()
|
||||
|
||||
// Dispatch the upload.
|
||||
web.dispatch(actions.uploadFile(file, req))
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
// Overwrite the default styling from react-dropzone; otherwise it
|
||||
// won't handle child elements correctly.
|
||||
const style = {
|
||||
height: '100%',
|
||||
borderWidth: '2px',
|
||||
borderStyle: 'dashed',
|
||||
borderColor: '#fff'
|
||||
}
|
||||
const activeStyle = {
|
||||
borderColor: '#777'
|
||||
}
|
||||
const rejectStyle = {
|
||||
backgroundColor: '#ffdddd'
|
||||
}
|
||||
|
||||
// disableClick means that it won't trigger a file upload box when
|
||||
// the user clicks on a file.
|
||||
return (
|
||||
<ReactDropzone style={ style }
|
||||
activeStyle={ activeStyle }
|
||||
rejectStyle={ rejectStyle }
|
||||
disableClick={ true }
|
||||
onDrop={ this.onDrop }>
|
||||
{ this.props.children }
|
||||
</ReactDropzone>
|
||||
)
|
||||
}
|
||||
}
|
||||
49
browser/app/js/components/InputGroup.js
Normal file
49
browser/app/js/components/InputGroup.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
|
||||
let InputGroup = ({label, id, name, value, onChange, type, spellCheck, required, readonly, autoComplete, align, className}) => {
|
||||
var input = <input id={ id }
|
||||
name={ name }
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
className="ig-text"
|
||||
type={ type }
|
||||
spellCheck={ spellCheck }
|
||||
required={ required }
|
||||
autoComplete={ autoComplete } />
|
||||
if (readonly)
|
||||
input = <input id={ id }
|
||||
name={ name }
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
className="ig-text"
|
||||
type={ type }
|
||||
spellCheck={ spellCheck }
|
||||
required={ required }
|
||||
autoComplete={ autoComplete }
|
||||
disabled />
|
||||
return <div className={ "input-group " + align + ' ' + className }>
|
||||
{ input }
|
||||
<i className="ig-helpers"></i>
|
||||
<label className="ig-label">
|
||||
{ label }
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default InputGroup
|
||||
133
browser/app/js/components/Login.js
Normal file
133
browser/app/js/components/Login.js
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 logo from '../../img/logo.svg'
|
||||
import Alert from 'react-bootstrap/lib/Alert'
|
||||
import * as actions from '../actions'
|
||||
import InputGroup from '../components/InputGroup'
|
||||
|
||||
export default class Login extends React.Component {
|
||||
handleSubmit(event) {
|
||||
event.preventDefault()
|
||||
const {web, dispatch, loginRedirectPath} = this.props
|
||||
let message = ''
|
||||
if (!document.getElementById('accessKey').value) {
|
||||
message = 'Secret Key cannot be empty'
|
||||
}
|
||||
if (!document.getElementById('secretKey').value) {
|
||||
message = 'Access Key cannot be empty'
|
||||
}
|
||||
if (message) {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message
|
||||
}))
|
||||
return
|
||||
}
|
||||
web.Login({
|
||||
username: document.getElementById('accessKey').value,
|
||||
password: document.getElementById('secretKey').value
|
||||
})
|
||||
.then((res) => {
|
||||
this.context.router.push(loginRedirectPath)
|
||||
})
|
||||
.catch(e => {
|
||||
dispatch(actions.setLoginError())
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const {dispatch} = this.props
|
||||
// Clear out any stale message in the alert of previous page
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: ''
|
||||
}))
|
||||
document.body.classList.add('is-guest')
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.body.classList.remove('is-guest')
|
||||
}
|
||||
|
||||
hideAlert() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideAlert())
|
||||
}
|
||||
|
||||
render() {
|
||||
const {alert} = this.props
|
||||
let alertBox = <Alert className={ 'alert animated ' + (alert.show ? 'fadeInDown' : 'fadeOutUp') } bsStyle={ alert.type } onDismiss={ this.hideAlert.bind(this) }>
|
||||
<div className='text-center'>
|
||||
{ alert.message }
|
||||
</div>
|
||||
</Alert>
|
||||
// Make sure you don't show a fading out alert box on the initial web-page load.
|
||||
if (!alert.message)
|
||||
alertBox = ''
|
||||
return (
|
||||
<div className="login">
|
||||
{ alertBox }
|
||||
<div className="l-wrap">
|
||||
<form onSubmit={ this.handleSubmit.bind(this) }>
|
||||
<input name="fixBrowser"
|
||||
autoComplete="username"
|
||||
type="text"
|
||||
style={ { display: 'none' } } />
|
||||
<InputGroup className="ig-dark"
|
||||
label="Access Key"
|
||||
id="accessKey"
|
||||
name="username"
|
||||
type="text"
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="username">
|
||||
</InputGroup>
|
||||
<input type="text" autoComplete="new-password" style={ { display: 'none' } } />
|
||||
<InputGroup className="ig-dark"
|
||||
label="Secret Key"
|
||||
id="secretKey"
|
||||
name="password"
|
||||
type="password"
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="new-password">
|
||||
</InputGroup>
|
||||
<button className="lw-btn" type="submit">
|
||||
<i className="fa fa-sign-in"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="l-footer">
|
||||
<a className="lf-logo" href=""><img src={ logo } alt="" /></a>
|
||||
<div className="lf-server">
|
||||
{ window.location.host }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Login.contextTypes = {
|
||||
router: React.PropTypes.object.isRequired
|
||||
}
|
||||
75
browser/app/js/components/ObjectsList.js
Normal file
75
browser/app/js/components/ObjectsList.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 Moment from 'moment'
|
||||
import humanize from 'humanize'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import Dropdown from 'react-bootstrap/lib/Dropdown'
|
||||
|
||||
|
||||
let ObjectsList = ({objects, currentPath, selectPrefix, dataType, showDeleteConfirmation, shareObject, loadPath}) => {
|
||||
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 (!object.name.endsWith('/')) {
|
||||
actionButtons = <Dropdown id="fia-dropdown">
|
||||
<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>
|
||||
}
|
||||
return (
|
||||
<div key={ i } className={ "fesl-row " + loadingClass } data-type={ dataType(object.name, object.contentType) }>
|
||||
<div className="fesl-item fi-name">
|
||||
<a href="" onClick={ (e) => selectPrefix(e, `${currentPath}${object.name}`) }>
|
||||
{ object.name }
|
||||
</a>
|
||||
</div>
|
||||
<div className="fesl-item fi-size">
|
||||
{ size }
|
||||
</div>
|
||||
<div className="fesl-item fi-modified">
|
||||
{ lastModified }
|
||||
</div>
|
||||
<div className="fesl-item fi-actions">
|
||||
{ actionButtons }
|
||||
</div>
|
||||
</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)
|
||||
41
browser/app/js/components/Path.js
Normal file
41
browser/app/js/components/Path.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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/lib/components/connect'
|
||||
|
||||
let Path = ({currentBucket, currentPath, selectPrefix}) => {
|
||||
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>
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<h2><span className="main"><a onClick={ (e) => selectPrefix(e, '') } href="">{ currentBucket }</a></span>{ path }</h2>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
currentBucket: state.currentBucket,
|
||||
currentPath: state.currentPath
|
||||
}
|
||||
})(Path)
|
||||
80
browser/app/js/components/Policy.js
Normal file
80
browser/app/js/components/Policy.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants'
|
||||
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import classnames from 'classnames'
|
||||
import * as actions from '../actions'
|
||||
|
||||
class Policy extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
handlePolicyChange(e) {
|
||||
this.setState({
|
||||
policy: {
|
||||
policy: e.target.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
removePolicy(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch, currentBucket, prefix} = this.props
|
||||
let newPrefix = prefix.replace(currentBucket + '/', '')
|
||||
newPrefix = newPrefix.replace('*', '')
|
||||
web.SetBucketPolicy({
|
||||
bucketName: currentBucket,
|
||||
prefix: newPrefix,
|
||||
policy: 'none'
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(actions.setPolicies(this.props.policies.filter(policy => policy.prefix != prefix)))
|
||||
})
|
||||
.catch(e => dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message,
|
||||
})))
|
||||
}
|
||||
|
||||
render() {
|
||||
const {policy, prefix, currentBucket} = this.props
|
||||
let newPrefix = prefix.replace(currentBucket + '/', '')
|
||||
newPrefix = newPrefix.replace('*', '')
|
||||
|
||||
if (!newPrefix)
|
||||
newPrefix = '*'
|
||||
|
||||
return (
|
||||
<div className="pmb-list">
|
||||
<div className="pmbl-item">
|
||||
{ newPrefix }
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<select className="form-control"
|
||||
disabled
|
||||
value={ policy }
|
||||
onChange={ this.handlePolicyChange.bind(this) }>
|
||||
<option value={ READ_ONLY }>
|
||||
Read Only
|
||||
</option>
|
||||
<option value={ WRITE_ONLY }>
|
||||
Write Only
|
||||
</option>
|
||||
<option value={ READ_WRITE }>
|
||||
Read and Write
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<button className="btn btn-block btn-danger" onClick={ this.removePolicy.bind(this) }>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => state)(Policy)
|
||||
83
browser/app/js/components/PolicyInput.js
Normal file
83
browser/app/js/components/PolicyInput.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import classnames from 'classnames'
|
||||
import * as actions from '../actions'
|
||||
|
||||
class PolicyInput extends Component {
|
||||
componentDidMount() {
|
||||
const {web, dispatch} = this.props
|
||||
web.ListAllBucketPolicies({
|
||||
bucketName: this.props.currentBucket
|
||||
}).then(res => {
|
||||
let policies = res.policies
|
||||
if (policies) dispatch(actions.setPolicies(policies))
|
||||
}).catch(err => {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setPolicies([]))
|
||||
}
|
||||
|
||||
handlePolicySubmit(e) {
|
||||
e.preventDefault()
|
||||
const {web, dispatch} = this.props
|
||||
|
||||
web.SetBucketPolicy({
|
||||
bucketName: this.props.currentBucket,
|
||||
prefix: this.prefix.value,
|
||||
policy: this.policy.value
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(actions.setPolicies([{
|
||||
policy: this.policy.value,
|
||||
prefix: this.prefix.value + '*',
|
||||
}, ...this.props.policies]))
|
||||
this.prefix.value = ''
|
||||
})
|
||||
.catch(e => dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message,
|
||||
})))
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<header className="pmb-list">
|
||||
<div className="pmbl-item">
|
||||
<input type="text"
|
||||
ref={ prefix => this.prefix = prefix }
|
||||
className="form-control"
|
||||
placeholder="Prefix"
|
||||
editable={ true } />
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<select ref={ policy => this.policy = policy } className="form-control">
|
||||
<option value={ READ_ONLY }>
|
||||
Read Only
|
||||
</option>
|
||||
<option value={ WRITE_ONLY }>
|
||||
Write Only
|
||||
</option>
|
||||
<option value={ READ_WRITE }>
|
||||
Read and Write
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<button className="btn btn-block btn-primary" onClick={ this.handlePolicySubmit.bind(this) }>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => state)(PolicyInput)
|
||||
215
browser/app/js/components/SettingsModal.js
Normal file
215
browser/app/js/components/SettingsModal.js
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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/lib/components/connect'
|
||||
import * as actions from '../actions'
|
||||
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip'
|
||||
import Modal from 'react-bootstrap/lib/Modal'
|
||||
import ModalBody from 'react-bootstrap/lib/ModalBody'
|
||||
import ModalHeader from 'react-bootstrap/lib/ModalHeader'
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'
|
||||
import InputGroup from './InputGroup'
|
||||
|
||||
class SettingsModal extends React.Component {
|
||||
|
||||
// When the settings are shown, it loads the access key and secret key.
|
||||
componentWillMount() {
|
||||
const {web, dispatch} = this.props
|
||||
const {serverInfo} = this.props
|
||||
|
||||
let accessKeyEnv = ''
|
||||
let secretKeyEnv = ''
|
||||
// Check environment variables first. They may or may not have been
|
||||
// loaded already; they load in Browse#componentDidMount.
|
||||
if (serverInfo.envVars) {
|
||||
serverInfo.envVars.forEach(envVar => {
|
||||
let keyVal = envVar.split('=')
|
||||
if (keyVal[0] == 'MINIO_ACCESS_KEY') {
|
||||
accessKeyEnv = keyVal[1]
|
||||
} else if (keyVal[0] == 'MINIO_SECRET_KEY') {
|
||||
secretKeyEnv = keyVal[1]
|
||||
}
|
||||
})
|
||||
}
|
||||
if (accessKeyEnv != '' || secretKeyEnv != '') {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: accessKeyEnv,
|
||||
secretKey: secretKeyEnv,
|
||||
keysReadOnly: true
|
||||
}))
|
||||
} else {
|
||||
web.GetAuth()
|
||||
.then(data => {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: data.accessKey,
|
||||
secretKey: data.secretKey
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// When they are re-hidden, the keys are unloaded from memory.
|
||||
componentWillUnmount() {
|
||||
const {dispatch} = this.props
|
||||
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
secretKeyVisible: false
|
||||
}))
|
||||
dispatch(actions.hideSettings())
|
||||
}
|
||||
|
||||
// Handle field changes from inside the modal.
|
||||
accessKeyChange(e) {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: e.target.value
|
||||
}))
|
||||
}
|
||||
|
||||
secretKeyChange(e) {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setSettings({
|
||||
secretKey: e.target.value
|
||||
}))
|
||||
}
|
||||
|
||||
secretKeyVisible(secretKeyVisible) {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setSettings({
|
||||
secretKeyVisible
|
||||
}))
|
||||
}
|
||||
|
||||
// Save the auth params and set them.
|
||||
setAuth(e) {
|
||||
e.preventDefault()
|
||||
const {web, dispatch} = this.props
|
||||
|
||||
let accessKey = document.getElementById('accessKey').value
|
||||
let secretKey = document.getElementById('secretKey').value
|
||||
web.SetAuth({
|
||||
accessKey,
|
||||
secretKey
|
||||
})
|
||||
.then(data => {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
secretKeyVisible: false
|
||||
}))
|
||||
dispatch(actions.hideSettings())
|
||||
dispatch(actions.showAlert({
|
||||
type: 'success',
|
||||
message: 'Changed credentials'
|
||||
}))
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
secretKeyVisible: false
|
||||
}))
|
||||
dispatch(actions.hideSettings())
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
generateAuth(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
|
||||
web.GenerateAuth()
|
||||
.then(data => {
|
||||
dispatch(actions.setSettings({
|
||||
secretKeyVisible: true
|
||||
}))
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: data.accessKey,
|
||||
secretKey: data.secretKey
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
hideSettings(e) {
|
||||
e.preventDefault()
|
||||
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideSettings())
|
||||
}
|
||||
|
||||
render() {
|
||||
let {settings} = this.props
|
||||
|
||||
return (
|
||||
<Modal bsSize="sm" animation={ false } show={ true }>
|
||||
<ModalHeader>
|
||||
Change Password
|
||||
</ModalHeader>
|
||||
<ModalBody className="m-t-20">
|
||||
<InputGroup value={ settings.accessKey }
|
||||
onChange={ this.accessKeyChange.bind(this) }
|
||||
id="accessKey"
|
||||
label="Access Key"
|
||||
name="accesskey"
|
||||
type="text"
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="false"
|
||||
align="ig-left"
|
||||
readonly={ settings.keysReadOnly }></InputGroup>
|
||||
<i onClick={ this.secretKeyVisible.bind(this, !settings.secretKeyVisible) } className={ "toggle-password fa fa-eye " + (settings.secretKeyVisible ? "toggled" : "") } />
|
||||
<InputGroup value={ settings.secretKey }
|
||||
onChange={ this.secretKeyChange.bind(this) }
|
||||
id="secretKey"
|
||||
label="Secret Key"
|
||||
name="accesskey"
|
||||
type={ settings.secretKeyVisible ? "text" : "password" }
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="false"
|
||||
align="ig-left"
|
||||
readonly={ settings.keysReadOnly }></InputGroup>
|
||||
</ModalBody>
|
||||
<div className="modal-footer">
|
||||
<button className={ "btn btn-primary " + (settings.keysReadOnly ? "hidden" : "") } onClick={ this.generateAuth.bind(this) }>
|
||||
Generate
|
||||
</button>
|
||||
<button href="" className={ "btn btn-success " + (settings.keysReadOnly ? "hidden" : "") } onClick={ this.setAuth.bind(this) }>
|
||||
Update
|
||||
</button>
|
||||
<button href="" className="btn btn-link" onClick={ this.hideSettings.bind(this) }>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
web: state.web,
|
||||
settings: state.settings,
|
||||
serverInfo: state.serverInfo
|
||||
}
|
||||
})(SettingsModal)
|
||||
85
browser/app/js/components/SideBar.js
Normal file
85
browser/app/js/components/SideBar.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 ClickOutHandler from 'react-onclickout'
|
||||
import Scrollbars from 'react-custom-scrollbars/lib/Scrollbars'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
|
||||
import logo from '../../img/logo.svg'
|
||||
|
||||
let SideBar = ({visibleBuckets, loadBucket, currentBucket, selectBucket, searchBuckets, landingPage, sidebarStatus, clickOutside, showPolicy}) => {
|
||||
|
||||
const list = visibleBuckets.map((bucket, i) => {
|
||||
return <li className={ classNames({
|
||||
'active': bucket === currentBucket
|
||||
}) } key={ i } onClick={ (e) => selectBucket(e, bucket) }>
|
||||
<a href="" className={ classNames({
|
||||
'fesli-loading': bucket === loadBucket
|
||||
}) }>
|
||||
{ bucket }
|
||||
</a>
|
||||
<i className="fesli-trigger" onClick={ showPolicy }></i>
|
||||
</li>
|
||||
})
|
||||
|
||||
return (
|
||||
<ClickOutHandler onClickOut={ clickOutside }>
|
||||
<div className={ classNames({
|
||||
'fe-sidebar': true,
|
||||
'toggled': sidebarStatus
|
||||
}) }>
|
||||
<div className="fes-header clearfix hidden-sm hidden-xs">
|
||||
<a href="" onClick={ landingPage }><img src={ logo } alt="" />
|
||||
<h2>Minio Browser</h2></a>
|
||||
</div>
|
||||
<div className="fes-list">
|
||||
<div className="input-group ig-dark ig-left ig-search" style={ { display: web.LoggedIn() ? 'block' : 'none' } }>
|
||||
<input className="ig-text"
|
||||
type="text"
|
||||
onChange={ searchBuckets }
|
||||
placeholder="Search Buckets..." />
|
||||
<i className="ig-helpers"></i>
|
||||
</div>
|
||||
<div className="fesl-inner">
|
||||
<Scrollbars renderScrollbarVertical={ props => <div className="scrollbar-vertical" /> }>
|
||||
<ul>
|
||||
{ list }
|
||||
</ul>
|
||||
</Scrollbars>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fes-host">
|
||||
<i className="fa fa-globe"></i>
|
||||
<a href="/">
|
||||
{ window.location.host }
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</ClickOutHandler>
|
||||
)
|
||||
}
|
||||
|
||||
// Subscribe it to state changes that affect only the sidebar.
|
||||
export default connect(state => {
|
||||
return {
|
||||
visibleBuckets: state.visibleBuckets,
|
||||
loadBucket: state.loadBucket,
|
||||
currentBucket: state.currentBucket,
|
||||
sidebarStatus: state.sidebarStatus
|
||||
}
|
||||
})(SideBar)
|
||||
141
browser/app/js/components/UploadModal.js
Normal file
141
browser/app/js/components/UploadModal.js
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 classNames from 'classnames'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
|
||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar'
|
||||
import ConfirmModal from './ConfirmModal'
|
||||
|
||||
import * as actions from '../actions'
|
||||
|
||||
// UploadModal is a modal that handles multiple file uploads.
|
||||
// During the upload, it displays a progress bar, and can transform into an
|
||||
// abort modal if the user decides to abort the uploads.
|
||||
class UploadModal extends React.Component {
|
||||
|
||||
// Abort all the current uploads.
|
||||
abortUploads(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch, uploads} = this.props
|
||||
|
||||
for (var slug in uploads) {
|
||||
let upload = uploads[slug]
|
||||
upload.xhr.abort()
|
||||
dispatch(actions.stopUpload({
|
||||
slug
|
||||
}))
|
||||
}
|
||||
|
||||
this.hideAbort(e)
|
||||
}
|
||||
|
||||
// Show the abort modal instead of the progress modal.
|
||||
showAbort(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
|
||||
dispatch(actions.setShowAbortModal(true))
|
||||
}
|
||||
|
||||
// Show the progress modal instead of the abort modal.
|
||||
hideAbort(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
|
||||
dispatch(actions.setShowAbortModal(false))
|
||||
}
|
||||
|
||||
render() {
|
||||
const {uploads, showAbortModal} = this.props
|
||||
|
||||
// Show the abort modal.
|
||||
if (showAbortModal) {
|
||||
let baseClass = classNames({
|
||||
'abort-upload': true
|
||||
})
|
||||
let okIcon = classNames({
|
||||
'fa': true,
|
||||
'fa-times': true
|
||||
})
|
||||
let cancelIcon = classNames({
|
||||
'fa': true,
|
||||
'fa-cloud-upload': true
|
||||
})
|
||||
|
||||
return (
|
||||
<ConfirmModal show={ true }
|
||||
baseClass={ baseClass }
|
||||
text='Abort uploads in progress?'
|
||||
icon='fa fa-info-circle mci-amber'
|
||||
sub='This cannot be undone!'
|
||||
okText='Abort'
|
||||
okIcon={ okIcon }
|
||||
cancelText='Upload'
|
||||
cancelIcon={ cancelIcon }
|
||||
okHandler={ this.abortUploads.bind(this) }
|
||||
cancelHandler={ this.hideAbort.bind(this) }>
|
||||
</ConfirmModal>
|
||||
)
|
||||
}
|
||||
|
||||
// If we don't have any files uploading, don't show anything.
|
||||
let numberUploading = Object.keys(uploads).length
|
||||
if (numberUploading == 0)
|
||||
return ( <noscript></noscript> )
|
||||
|
||||
let totalLoaded = 0
|
||||
let totalSize = 0
|
||||
|
||||
// Iterate over each upload, adding together the total size and that
|
||||
// which has been uploaded.
|
||||
for (var slug in uploads) {
|
||||
let upload = uploads[slug]
|
||||
totalLoaded += upload.loaded
|
||||
totalSize += upload.size
|
||||
}
|
||||
|
||||
let percent = (totalLoaded / totalSize) * 100
|
||||
|
||||
// If more than one: "Uploading files (5)..."
|
||||
// If only one: "Uploading myfile.txt..."
|
||||
let text = 'Uploading ' + (numberUploading == 1 ? `'${uploads[Object.keys(uploads)[0]].name}'` : `files (${numberUploading})`) + '...'
|
||||
|
||||
return (
|
||||
<div className="alert alert-info progress animated fadeInUp ">
|
||||
<button type="button" className="close" onClick={ this.showAbort.bind(this) }>
|
||||
<span>×</span>
|
||||
</button>
|
||||
<div className="text-center">
|
||||
<small>{ text }</small>
|
||||
</div>
|
||||
<ProgressBar now={ percent } />
|
||||
<div className="text-center">
|
||||
<small>{ humanize.filesize(totalLoaded) } ({ percent.toFixed(2) } %)</small>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
uploads: state.uploads,
|
||||
showAbortModal: state.showAbortModal
|
||||
}
|
||||
})(UploadModal)
|
||||
54
browser/app/js/components/__tests__/Login-test.js
Normal file
54
browser/app/js/components/__tests__/Login-test.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 ReactTestUtils, {renderIntoDocument} from 'react-addons-test-utils'
|
||||
|
||||
import expect from 'expect'
|
||||
import Login from '../Login'
|
||||
|
||||
describe('Login', () => {
|
||||
it('it should have empty credentials', () => {
|
||||
const alert = {
|
||||
show: false
|
||||
}
|
||||
const dispatch = () => {}
|
||||
let loginComponent = renderIntoDocument(<Login alert={alert} dispatch={dispatch} />)
|
||||
const accessKey = document.getElementById('accessKey')
|
||||
const secretKey = document.getElementById('secretKey')
|
||||
// Validate default value.
|
||||
expect(accessKey.value).toEqual('')
|
||||
expect(secretKey.value).toEqual('')
|
||||
})
|
||||
it('it should set accessKey and secretKey', () => {
|
||||
const alert = {
|
||||
show: false
|
||||
}
|
||||
const dispatch = () => {}
|
||||
let loginComponent = renderIntoDocument(<Login alert={alert} dispatch={dispatch} />)
|
||||
let accessKey = loginComponent.refs.accessKey
|
||||
let secretKey = loginComponent.refs.secretKey
|
||||
accessKey.value = 'demo-username'
|
||||
secretKey.value = 'demo-password'
|
||||
ReactTestUtils.Simulate.change(accessKey)
|
||||
ReactTestUtils.Simulate.change(secretKey)
|
||||
// Validate if the change has occurred.
|
||||
expect(loginComponent.refs.accessKey.value).toEqual('demo-username')
|
||||
expect(loginComponent.refs.secretKey.value).toEqual('demo-password')
|
||||
})
|
||||
});
|
||||
*/
|
||||
23
browser/app/js/constants.js
Normal file
23
browser/app/js/constants.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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.
|
||||
*/
|
||||
|
||||
// File for all the browser constants.
|
||||
|
||||
// minioBrowserPrefix absolute path.
|
||||
export const minioBrowserPrefix = '/minio'
|
||||
export const READ_ONLY = 'readonly'
|
||||
export const WRITE_ONLY = 'writeonly'
|
||||
export const READ_WRITE = 'readwrite'
|
||||
91
browser/app/js/jsonrpc.js
Normal file
91
browser/app/js/jsonrpc.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 SuperAgent from 'superagent-es6-promise';
|
||||
import url from 'url'
|
||||
import Moment from 'moment'
|
||||
|
||||
export default class JSONrpc {
|
||||
constructor(params) {
|
||||
this.endpoint = params.endpoint
|
||||
this.namespace = params.namespace
|
||||
this.version = '2.0';
|
||||
const parsedUrl = url.parse(this.endpoint)
|
||||
this.host = parsedUrl.hostname
|
||||
this.path = parsedUrl.path
|
||||
this.port = parsedUrl.port
|
||||
|
||||
switch (parsedUrl.protocol) {
|
||||
case 'http:': {
|
||||
this.scheme = 'http'
|
||||
if (parsedUrl.port === 0) {
|
||||
this.port = 80
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'https:': {
|
||||
this.scheme = 'https'
|
||||
if (parsedUrl.port === 0) {
|
||||
this.port = 443
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
throw new Error('Unknown protocol: ' + parsedUrl.protocol)
|
||||
}
|
||||
}
|
||||
}
|
||||
// call('Get', {id: NN, params: [...]}, function() {})
|
||||
call(method, options, token) {
|
||||
if (!options) {
|
||||
options = {}
|
||||
}
|
||||
if (!options.id) {
|
||||
options.id = 1;
|
||||
}
|
||||
if (!options.params) {
|
||||
options.params = {};
|
||||
}
|
||||
const dataObj = {
|
||||
id: options.id,
|
||||
jsonrpc: this.version,
|
||||
params: options.params ? options.params : {},
|
||||
method: this.namespace ? this.namespace + '.' + method : method
|
||||
}
|
||||
let requestParams = {
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
path: this.path,
|
||||
scheme: this.scheme,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-amz-date': Moment().utc().format('YYYYMMDDTHHmmss') + 'Z'
|
||||
}
|
||||
}
|
||||
|
||||
if (token) {
|
||||
requestParams.headers.Authorization = 'Bearer ' + token
|
||||
}
|
||||
|
||||
let req = SuperAgent.post(this.endpoint)
|
||||
for (let key in requestParams.headers) {
|
||||
req.set(key, requestParams.headers[key])
|
||||
}
|
||||
// req.set('Access-Control-Allow-Origin', 'http://localhost:8080')
|
||||
return req.send(JSON.stringify(dataObj)).then(res => res)
|
||||
}
|
||||
}
|
||||
106
browser/app/js/mime.js
Normal file
106
browser/app/js/mime.js
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 mimedb from 'mime-types'
|
||||
|
||||
const isFolder = (name, contentType) => {
|
||||
if (name.endsWith('/')) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const isPdf = (name, contentType) => {
|
||||
if (contentType === 'application/pdf') return true
|
||||
return false
|
||||
}
|
||||
|
||||
const isZip = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
if (contentType.split('/')[1].includes('zip')) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const isCode = (name, contentType) => {
|
||||
const codeExt = ['c', 'cpp', 'go', 'py', 'java', 'rb', 'js', 'pl', 'fs',
|
||||
'php', 'css', 'less', 'scss', 'coffee', 'net', 'html',
|
||||
'rs', 'exs', 'scala', 'hs', 'clj', 'el', 'scm', 'lisp',
|
||||
'asp', 'aspx']
|
||||
const ext = name.split('.').reverse()[0]
|
||||
for (var i in codeExt) {
|
||||
if (ext === codeExt[i]) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isExcel = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
const types = ['excel', 'spreadsheet']
|
||||
const subType = contentType.split('/')[1]
|
||||
for (var i in types) {
|
||||
if (subType.includes(types[i])) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isDoc = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
const types = ['word', '.document']
|
||||
const subType = contentType.split('/')[1]
|
||||
for (var i in types) {
|
||||
if (subType.includes(types[i])) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isPresentation = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
var types = ['powerpoint', 'presentation']
|
||||
const subType = contentType.split('/')[1]
|
||||
for (var i in types) {
|
||||
if (subType.includes(types[i])) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const typeToIcon = (type) => {
|
||||
return (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
if (contentType.split('/')[0] === type) return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const getDataType = (name, contentType) => {
|
||||
if (contentType === "") {
|
||||
contentType = mimedb.lookup(name) || 'application/octet-stream'
|
||||
}
|
||||
const check = [
|
||||
['folder', isFolder],
|
||||
['code', isCode],
|
||||
['audio', typeToIcon('audio')],
|
||||
['image', typeToIcon('image')],
|
||||
['video', typeToIcon('video')],
|
||||
['text', typeToIcon('text')],
|
||||
['pdf', isPdf],
|
||||
['zip', isZip],
|
||||
['excel', isExcel],
|
||||
['doc', isDoc],
|
||||
['presentation', isPresentation]
|
||||
]
|
||||
for (var i in check) {
|
||||
if (check[i][1](name, contentType)) return check[i][0]
|
||||
}
|
||||
return 'other'
|
||||
}
|
||||
176
browser/app/js/reducers.js
Normal file
176
browser/app/js/reducers.js
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 actions from './actions'
|
||||
import { minioBrowserPrefix } from './constants'
|
||||
|
||||
export default (state = {
|
||||
buckets: [],
|
||||
visibleBuckets: [],
|
||||
objects: [],
|
||||
storageInfo: {},
|
||||
serverInfo: {},
|
||||
currentBucket: '',
|
||||
currentPath: '',
|
||||
showMakeBucketModal: false,
|
||||
uploads: {},
|
||||
alert: {
|
||||
show: false,
|
||||
type: 'danger',
|
||||
message: ''
|
||||
},
|
||||
loginError: false,
|
||||
sortNameOrder: false,
|
||||
sortSizeOrder: false,
|
||||
sortDateOrder: false,
|
||||
latestUiVersion: currentUiVersion,
|
||||
sideBarActive: false,
|
||||
loginRedirectPath: minioBrowserPrefix,
|
||||
settings: {
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
secretKeyVisible: false
|
||||
},
|
||||
showSettings: false,
|
||||
policies: [],
|
||||
deleteConfirmation: {
|
||||
object: '',
|
||||
show: false
|
||||
},
|
||||
shareObject: {
|
||||
show: false,
|
||||
url: '',
|
||||
expiry: 604800
|
||||
},
|
||||
prefixWritable: false
|
||||
}, action) => {
|
||||
let newState = Object.assign({}, state)
|
||||
switch (action.type) {
|
||||
case actions.SET_WEB:
|
||||
newState.web = action.web
|
||||
break
|
||||
case actions.SET_BUCKETS:
|
||||
newState.buckets = action.buckets
|
||||
break
|
||||
case actions.ADD_BUCKET:
|
||||
newState.buckets = [action.bucket, ...newState.buckets]
|
||||
newState.visibleBuckets = [action.bucket, ...newState.visibleBuckets]
|
||||
break
|
||||
case actions.SET_VISIBLE_BUCKETS:
|
||||
newState.visibleBuckets = action.visibleBuckets
|
||||
break
|
||||
case actions.SET_CURRENT_BUCKET:
|
||||
newState.currentBucket = action.currentBucket
|
||||
break
|
||||
case actions.SET_OBJECTS:
|
||||
newState.objects = action.objects
|
||||
break
|
||||
case actions.SET_CURRENT_PATH:
|
||||
newState.currentPath = action.currentPath
|
||||
break
|
||||
case actions.SET_STORAGE_INFO:
|
||||
newState.storageInfo = action.storageInfo
|
||||
break
|
||||
case actions.SET_SERVER_INFO:
|
||||
newState.serverInfo = action.serverInfo
|
||||
break
|
||||
case actions.SHOW_MAKEBUCKET_MODAL:
|
||||
newState.showMakeBucketModal = action.showMakeBucketModal
|
||||
break
|
||||
case actions.UPLOAD_PROGRESS:
|
||||
newState.uploads = Object.assign({}, newState.uploads)
|
||||
newState.uploads[action.slug].loaded = action.loaded
|
||||
break
|
||||
case actions.ADD_UPLOAD:
|
||||
newState.uploads = Object.assign({}, newState.uploads, {
|
||||
[action.slug]: {
|
||||
loaded: 0,
|
||||
size: action.size,
|
||||
xhr: action.xhr,
|
||||
name: action.name
|
||||
}
|
||||
})
|
||||
break
|
||||
case actions.STOP_UPLOAD:
|
||||
newState.uploads = Object.assign({}, newState.uploads)
|
||||
delete newState.uploads[action.slug]
|
||||
break
|
||||
case actions.SET_ALERT:
|
||||
if (newState.alert.alertTimeout) clearTimeout(newState.alert.alertTimeout)
|
||||
if (!action.alert.show) {
|
||||
newState.alert = Object.assign({}, newState.alert, {
|
||||
show: false
|
||||
})
|
||||
} else {
|
||||
newState.alert = action.alert
|
||||
}
|
||||
break
|
||||
case actions.SET_LOGIN_ERROR:
|
||||
newState.loginError = true
|
||||
break
|
||||
case actions.SET_SHOW_ABORT_MODAL:
|
||||
newState.showAbortModal = action.showAbortModal
|
||||
break
|
||||
case actions.SHOW_ABOUT:
|
||||
newState.showAbout = action.showAbout
|
||||
break
|
||||
case actions.SET_SORT_NAME_ORDER:
|
||||
newState.sortNameOrder = action.sortNameOrder
|
||||
break
|
||||
case actions.SET_SORT_SIZE_ORDER:
|
||||
newState.sortSizeOrder = action.sortSizeOrder
|
||||
break
|
||||
case actions.SET_SORT_DATE_ORDER:
|
||||
newState.sortDateOrder = action.sortDateOrder
|
||||
break
|
||||
case actions.SET_LATEST_UI_VERSION:
|
||||
newState.latestUiVersion = action.latestUiVersion
|
||||
break
|
||||
case actions.SET_SIDEBAR_STATUS:
|
||||
newState.sidebarStatus = action.sidebarStatus
|
||||
break
|
||||
case actions.SET_LOGIN_REDIRECT_PATH:
|
||||
newState.loginRedirectPath = action.path
|
||||
case actions.SET_LOAD_BUCKET:
|
||||
newState.loadBucket = action.loadBucket
|
||||
break
|
||||
case actions.SET_LOAD_PATH:
|
||||
newState.loadPath = action.loadPath
|
||||
break
|
||||
case actions.SHOW_SETTINGS:
|
||||
newState.showSettings = action.showSettings
|
||||
break
|
||||
case actions.SET_SETTINGS:
|
||||
newState.settings = Object.assign({}, newState.settings, action.settings)
|
||||
break
|
||||
case actions.SHOW_BUCKET_POLICY:
|
||||
newState.showBucketPolicy = action.showBucketPolicy
|
||||
break
|
||||
case actions.SET_POLICIES:
|
||||
newState.policies = action.policies
|
||||
break
|
||||
case actions.DELETE_CONFIRMATION:
|
||||
newState.deleteConfirmation = Object.assign({}, action.payload)
|
||||
break
|
||||
case actions.SET_SHARE_OBJECT:
|
||||
newState.shareObject = Object.assign({}, action.shareObject)
|
||||
break
|
||||
case actions.SET_PREFIX_WRITABLE:
|
||||
newState.prefixWritable = action.prefixWritable
|
||||
break
|
||||
}
|
||||
return newState
|
||||
}
|
||||
85
browser/app/js/utils.js
Normal file
85
browser/app/js/utils.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 { minioBrowserPrefix } from './constants.js'
|
||||
|
||||
export const sortObjectsByName = (objects, order) => {
|
||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
||||
folders = folders.sort((a, b) => {
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
|
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
||||
return 0
|
||||
})
|
||||
files = files.sort((a, b) => {
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
|
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
||||
return 0
|
||||
})
|
||||
if (order) {
|
||||
folders = folders.reverse()
|
||||
files = files.reverse()
|
||||
}
|
||||
return [...folders, ...files]
|
||||
}
|
||||
|
||||
export const sortObjectsBySize = (objects, order) => {
|
||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
||||
files = files.sort((a, b) => a.size - b.size)
|
||||
if (order)
|
||||
files = files.reverse()
|
||||
return [...folders, ...files]
|
||||
}
|
||||
|
||||
export const sortObjectsByDate = (objects, order) => {
|
||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
||||
files = files.sort((a, b) => new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime())
|
||||
if (order)
|
||||
files = files.reverse()
|
||||
return [...folders, ...files]
|
||||
}
|
||||
|
||||
export const pathSlice = (path) => {
|
||||
path = path.replace(minioBrowserPrefix, '')
|
||||
let prefix = ''
|
||||
let bucket = ''
|
||||
if (!path) return {
|
||||
bucket,
|
||||
prefix
|
||||
}
|
||||
let objectIndex = path.indexOf('/', 1)
|
||||
if (objectIndex == -1) {
|
||||
bucket = path.slice(1)
|
||||
return {
|
||||
bucket,
|
||||
prefix
|
||||
}
|
||||
}
|
||||
bucket = path.slice(1, objectIndex)
|
||||
prefix = path.slice(objectIndex + 1)
|
||||
return {
|
||||
bucket,
|
||||
prefix
|
||||
}
|
||||
}
|
||||
|
||||
export const pathJoin = (bucket, prefix) => {
|
||||
if (!prefix)
|
||||
prefix = ''
|
||||
return minioBrowserPrefix + '/' + bucket + '/' + prefix
|
||||
}
|
||||
124
browser/app/js/web.js
Normal file
124
browser/app/js/web.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 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 { browserHistory } from 'react-router'
|
||||
import JSONrpc from './jsonrpc'
|
||||
import * as actions from './actions'
|
||||
import { minioBrowserPrefix } from './constants.js'
|
||||
import Moment from 'moment'
|
||||
import storage from 'local-storage-fallback'
|
||||
|
||||
export default class Web {
|
||||
constructor(endpoint, dispatch) {
|
||||
const namespace = 'Web'
|
||||
this.dispatch = dispatch
|
||||
this.JSONrpc = new JSONrpc({
|
||||
endpoint,
|
||||
namespace
|
||||
})
|
||||
}
|
||||
makeCall(method, options) {
|
||||
return this.JSONrpc.call(method, {
|
||||
params: options
|
||||
}, storage.getItem('token'))
|
||||
.catch(err => {
|
||||
if (err.status === 401) {
|
||||
storage.removeItem('token')
|
||||
browserHistory.push(`${minioBrowserPrefix}/login`)
|
||||
throw new Error('Please re-login.')
|
||||
}
|
||||
if (err.status)
|
||||
throw new Error(`Server returned error [${err.status}]`)
|
||||
throw new Error('Minio server is unreachable')
|
||||
})
|
||||
.then(res => {
|
||||
let json = JSON.parse(res.text)
|
||||
let result = json.result
|
||||
let error = json.error
|
||||
if (error) {
|
||||
throw new Error(error.message)
|
||||
}
|
||||
if (!Moment(result.uiVersion).isValid()) {
|
||||
throw new Error("Invalid UI version in the JSON-RPC response")
|
||||
}
|
||||
if (result.uiVersion !== currentUiVersion
|
||||
&& currentUiVersion !== 'MINIO_UI_VERSION') {
|
||||
storage.setItem('newlyUpdated', true)
|
||||
location.reload()
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
LoggedIn() {
|
||||
return !!storage.getItem('token')
|
||||
}
|
||||
Login(args) {
|
||||
return this.makeCall('Login', args)
|
||||
.then(res => {
|
||||
storage.setItem('token', `${res.token}`)
|
||||
return res
|
||||
})
|
||||
}
|
||||
Logout() {
|
||||
storage.removeItem('token')
|
||||
}
|
||||
ServerInfo() {
|
||||
return this.makeCall('ServerInfo')
|
||||
}
|
||||
StorageInfo() {
|
||||
return this.makeCall('StorageInfo')
|
||||
}
|
||||
ListBuckets() {
|
||||
return this.makeCall('ListBuckets')
|
||||
}
|
||||
MakeBucket(args) {
|
||||
return this.makeCall('MakeBucket', args)
|
||||
}
|
||||
ListObjects(args) {
|
||||
return this.makeCall('ListObjects', args)
|
||||
}
|
||||
PresignedGet(args) {
|
||||
return this.makeCall('PresignedGet', args)
|
||||
}
|
||||
PutObjectURL(args) {
|
||||
return this.makeCall('PutObjectURL', args)
|
||||
}
|
||||
RemoveObject(args) {
|
||||
return this.makeCall('RemoveObject', args)
|
||||
}
|
||||
GetAuth() {
|
||||
return this.makeCall('GetAuth')
|
||||
}
|
||||
GenerateAuth() {
|
||||
return this.makeCall('GenerateAuth')
|
||||
}
|
||||
SetAuth(args) {
|
||||
return this.makeCall('SetAuth', args)
|
||||
.then(res => {
|
||||
storage.setItem('token', `${res.token}`)
|
||||
return res
|
||||
})
|
||||
}
|
||||
GetBucketPolicy(args) {
|
||||
return this.makeCall('GetBucketPolicy', args)
|
||||
}
|
||||
SetBucketPolicy(args) {
|
||||
return this.makeCall('SetBucketPolicy', args)
|
||||
}
|
||||
ListAllBucketPolicies(args) {
|
||||
return this.makeCall('ListAllBucketPolicies', args)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user