|
|
|
/*
|
|
|
|
* Minio Cloud Storage (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'
|
|
|
|
import InfiniteScroll from 'react-infinite-scroller';
|
|
|
|
|
|
|
|
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,
|
|
|
|
info: res.MinioGlobalInfo
|
|
|
|
})
|
|
|
|
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 + '/') {
|
|
|
|
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)))
|
|
|
|
}
|
|
|
|
|
|
|
|
listObjects() {
|
|
|
|
const {dispatch} = this.props
|
|
|
|
dispatch(actions.listObjects())
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
if (!web.LoggedIn()) {
|
|
|
|
let url = `${window.location.origin}/minio/download/${currentBucket}/${encPrefix}?token=''`
|
|
|
|
window.location = url
|
|
|
|
} else {
|
|
|
|
// Download the selected file.
|
|
|
|
web.CreateURLToken()
|
|
|
|
.then(res => {
|
|
|
|
let url = `${window.location.origin}/minio/download/${currentBucket}/${encPrefix}?token=${res.token}`
|
|
|
|
window.location = url
|
|
|
|
})
|
|
|
|
.catch(err => dispatch(actions.showAlert({
|
|
|
|
type: 'danger',
|
|
|
|
message: err.message
|
|
|
|
})))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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, checkedObjects} = this.props
|
|
|
|
let objects = []
|
|
|
|
if (checkedObjects.length > 0) {
|
|
|
|
objects = checkedObjects.map(obj => `${currentPath}${obj}`)
|
|
|
|
} else {
|
|
|
|
objects = [deleteConfirmation.object]
|
|
|
|
}
|
|
|
|
|
|
|
|
web.RemoveObject({
|
|
|
|
bucketname: currentBucket,
|
|
|
|
objects: objects
|
|
|
|
})
|
|
|
|
.then(() => {
|
|
|
|
this.hideDeleteConfirmation()
|
|
|
|
if (checkedObjects.length > 0) {
|
|
|
|
for (let i = 0; i < checkedObjects.length; i++) {
|
|
|
|
dispatch(actions.removeObject(checkedObjects[i].replace(currentPath, '')))
|
|
|
|
}
|
|
|
|
dispatch(actions.checkedObjectsReset())
|
|
|
|
} else {
|
|
|
|
let delObject = deleteConfirmation.object.replace(currentPath, '')
|
|
|
|
dispatch(actions.removeObject(delObject))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.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
|
|
|
|
// let expiry = 5 * 24 * 60 * 60 // 5 days expiry by default
|
|
|
|
dispatch(actions.shareObject(object, 5, 0, 0))
|
|
|
|
}
|
|
|
|
|
|
|
|
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`)
|
|
|
|
}
|
|
|
|
|
|
|
|
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, object) {
|
|
|
|
let value = this.refs[targetInput].value
|
|
|
|
let maxValue = (targetInput == 'expireHours') ? 23 : (targetInput == 'expireMins') ? 59 : (targetInput == 'expireDays') ? 7 : 0
|
|
|
|
value = isNaN(value) ? 0 : value
|
|
|
|
|
|
|
|
// Use custom step count to support browser Edge
|
|
|
|
if((inc === -1)) {
|
|
|
|
if(value != 0) {
|
|
|
|
value--
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if(value != maxValue) {
|
|
|
|
value++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.refs[targetInput].value = value
|
|
|
|
|
|
|
|
// Reset hours and mins when days reaches it's max value
|
|
|
|
if (this.refs.expireDays.value == 7) {
|
|
|
|
this.refs.expireHours.value = 0
|
|
|
|
this.refs.expireMins.value = 0
|
|
|
|
}
|
|
|
|
if (this.refs.expireDays.value + this.refs.expireHours.value + this.refs.expireMins.value == 0) {
|
|
|
|
this.refs.expireDays.value = 7
|
|
|
|
}
|
|
|
|
|
|
|
|
const {dispatch} = this.props
|
|
|
|
dispatch(actions.shareObject(object, this.refs.expireDays.value, this.refs.expireHours.value, this.refs.expireMins.value))
|
|
|
|
}
|
|
|
|
|
|
|
|
checkObject(e, objectName) {
|
|
|
|
const {dispatch} = this.props
|
|
|
|
e.target.checked ? dispatch(actions.checkedObjectsAdd(objectName)) : dispatch(actions.checkedObjectsRemove(objectName))
|
|
|
|
}
|
|
|
|
|
|
|
|
downloadSelected() {
|
|
|
|
const {dispatch, web} = this.props
|
|
|
|
let req = {
|
|
|
|
bucketName: this.props.currentBucket,
|
|
|
|
objects: this.props.checkedObjects,
|
|
|
|
prefix: this.props.currentPath
|
|
|
|
}
|
|
|
|
if (!web.LoggedIn()) {
|
|
|
|
let requestUrl = location.origin + "/minio/zip?token=''"
|
|
|
|
this.xhr = new XMLHttpRequest()
|
|
|
|
dispatch(actions.downloadSelected(requestUrl, req, this.xhr))
|
|
|
|
} else {
|
|
|
|
web.CreateURLToken()
|
|
|
|
.then(res => {
|
|
|
|
let requestUrl = location.origin + "/minio/zip?token=" + res.token
|
|
|
|
|
|
|
|
this.xhr = new XMLHttpRequest()
|
|
|
|
dispatch(actions.downloadSelected(requestUrl, req, this.xhr))
|
|
|
|
})
|
|
|
|
.catch(err => dispatch(actions.showAlert({
|
|
|
|
type: 'danger',
|
|
|
|
message: err.message
|
|
|
|
})))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
clearSelected() {
|
|
|
|
const {dispatch} = this.props
|
|
|
|
dispatch(actions.checkedObjectsReset())
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const {total, free} = this.props.storageInfo
|
|
|
|
const {showMakeBucketModal, alert, sortNameOrder, sortSizeOrder, sortDateOrder, showAbout, showBucketPolicy, checkedObjects} = 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, istruncated} = 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 fullScreenFunc={ this.fullScreen.bind(this) }
|
|
|
|
aboutFunc={ this.showAbout.bind(this) }
|
|
|
|
settingsFunc={ this.showSettings.bind(this) }
|
|
|
|
logoutFunc={ this.logout.bind(this) } />
|
|
|
|
} else {
|
|
|
|
loginButton = <a className='btn btn-danger' href='/minio/login'>Login</a>
|
|
|
|
}
|
|
|
|
|
|
|
|
if (web.LoggedIn()) {
|
|
|
|
if (!(used === 0 && free === 0)) {
|
|
|
|
storageUsageDetails = <div className="feh-usage">
|
|
|
|
<div className="fehu-chart">
|
|
|
|
<div style={ { width: usedPercent } }></div>
|
|
|
|
</div>
|
|
|
|
<ul>
|
|
|
|
<li>
|
|
|
|
<span>Used: </span>
|
|
|
|
{ humanize.filesize(total - free) }
|
|
|
|
</li>
|
|
|
|
<li className="pull-right">
|
|
|
|
<span>Free: </span>
|
|
|
|
{ humanize.filesize(total - used) }
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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 searchBuckets={ this.searchBuckets.bind(this) }
|
|
|
|
selectBucket={ this.selectBucket.bind(this) }
|
|
|
|
clickOutside={ this.hideSidebar.bind(this) }
|
|
|
|
showPolicy={ this.showBucketPolicy.bind(this) } />
|
|
|
|
<div className="fe-body">
|
|
|
|
<div className={ 'list-actions' + (classNames({
|
|
|
|
' list-actions-toggled': checkedObjects.length > 0
|
|
|
|
})) }>
|
|
|
|
<span className="la-label"><i className="fa fa-check-circle" /> { checkedObjects.length } Objects selected</span>
|
|
|
|
<span className="la-actions pull-right"><button onClick={ this.downloadSelected.bind(this) }> Download all as zip </button></span>
|
|
|
|
<span className="la-actions pull-right"><button onClick={ this.showDeleteConfirmation.bind(this) }> Delete selected </button></span>
|
|
|
|
<i className="la-close fa fa-times" onClick={ this.clearSelected.bind(this) }></i>
|
|
|
|
</div>
|
|
|
|
<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 fesl-item-icon"></div>
|
|
|
|
<div className="fesl-item fesl-item-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 fesl-item-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 fesl-item-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 fesl-item-actions"></div>
|
|
|
|
</header>
|
|
|
|
</div>
|
|
|
|
<div className="feb-container">
|
|
|
|
<InfiniteScroll loadMore={ this.listObjects.bind(this) }
|
|
|
|
hasMore={ istruncated }
|
|
|
|
useWindow={ true }
|
|
|
|
initialLoad={ false }>
|
|
|
|
<ObjectsList dataType={ this.dataType.bind(this) }
|
|
|
|
selectPrefix={ this.selectPrefix.bind(this) }
|
|
|
|
showDeleteConfirmation={ this.showDeleteConfirmation.bind(this) }
|
|
|
|
shareObject={ this.shareObject.bind(this) }
|
|
|
|
checkObject={ this.checkObject.bind(this) }
|
|
|
|
checkedObjectsArray={ checkedObjects } />
|
|
|
|
</InfiniteScroll>
|
|
|
|
<div className="text-center" style={ { display: (istruncated && currentBucket) ? 'block' : 'none' } }>
|
|
|
|
<span>Loading...</span>
|
|
|
|
</div>
|
|
|
|
</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 (Max 7 days)
|
|
|
|
</label>
|
|
|
|
<div className="set-expire">
|
|
|
|
<div className="set-expire-item">
|
|
|
|
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireDays', 1, shareObject.object) } />
|
|
|
|
<div className="set-expire-title">
|
|
|
|
Days
|
|
|
|
</div>
|
|
|
|
<div className="set-expire-value">
|
|
|
|
<input ref="expireDays"
|
|
|
|
type="number"
|
|
|
|
min={ 0 }
|
|
|
|
max={ 7 }
|
|
|
|
defaultValue={ 5 }
|
|
|
|
readOnly="readOnly"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireDays', -1, shareObject.object) } />
|
|
|
|
</div>
|
|
|
|
<div className="set-expire-item">
|
|
|
|
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireHours', 1, shareObject.object) } />
|
|
|
|
<div className="set-expire-title">
|
|
|
|
Hours
|
|
|
|
</div>
|
|
|
|
<div className="set-expire-value">
|
|
|
|
<input ref="expireHours"
|
|
|
|
type="number"
|
|
|
|
min={ 0 }
|
|
|
|
max={ 23 }
|
|
|
|
defaultValue={ 0 }
|
|
|
|
readOnly="readOnly"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireHours', -1, shareObject.object) } />
|
|
|
|
</div>
|
|
|
|
<div className="set-expire-item">
|
|
|
|
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireMins', 1, shareObject.object) } />
|
|
|
|
<div className="set-expire-title">
|
|
|
|
Minutes
|
|
|
|
</div>
|
|
|
|
<div className="set-expire-value">
|
|
|
|
<input ref="expireMins"
|
|
|
|
type="number"
|
|
|
|
min={ 0 }
|
|
|
|
max={ 59 }
|
|
|
|
defaultValue={ 0 }
|
|
|
|
readOnly="readOnly"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireMins', -1, shareObject.object) } />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</ModalBody>
|
|
|
|
<div className="modal-footer">
|
|
|
|
<CopyToClipboard text={ window.location.protocol + '//' + 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>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|