mirror of
https://github.com/minio/minio.git
synced 2024-12-23 21:55:53 -05:00
- upgraded react from v16.2.0 - upgraded react-router to v4.2.0 and re-writen the routes - using prettier to format the code - added jest to unit test components/reducers/selectors This provides a skeleton to start of with. Only basic unit test cases are added, remaining needs to be added. In terms of functionality, it provides login, listing and searching buckets. Remaining functionalities will be added in upcoming patches.
This commit is contained in:
parent
76be54551f
commit
9ab6a8035f
@ -3,7 +3,7 @@ root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
3
browser/.prettierrc
Normal file
3
browser/.prettierrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"semi": false
|
||||
}
|
@ -54,7 +54,7 @@
|
||||
|
||||
.pl-inner > img {
|
||||
width: 30px;
|
||||
margin-top: 28px;
|
||||
margin-top: 21px;
|
||||
}
|
||||
|
||||
@-webkit-keyframes fade-in {
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
@ -1,57 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewbox="0 0 160 256"
|
||||
version="1.1"
|
||||
id="svg3092"
|
||||
height="218.14844"
|
||||
width="137">
|
||||
<defs
|
||||
id="defs3094" />
|
||||
<metadata
|
||||
id="metadata3097">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0.99999967,-982.85149)"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="matrix(1.0112586,0,0,1.0112586,5.4603732,-13.223714)"
|
||||
id="g4144">
|
||||
<g
|
||||
id="g4140">
|
||||
<g
|
||||
style="image-rendering:auto"
|
||||
id="minio"
|
||||
transform="matrix(1.0000023,0,0,0.99999799,-739.31646,295.2269)">
|
||||
<title
|
||||
id="title3337">Minio Logo</title>
|
||||
<path
|
||||
d="m 803.42903,801.80813 c 12.40802,4.17067 27.0499,9.11665 37.95186,12.80906 -12.01295,-21.20683 -27.84305,-34.11687 -37.95186,-40.78578 l 0,27.97672 z m 0,93.72113 -14.22303,8.96217 0,-92.45864 c -1.52985,-0.5139 -2.97948,-0.9981 -4.33405,-1.45259 -19.98593,-6.67189 -32.7207,-17.95703 -35.85168,-31.77904 -2.54577,-11.21386 1.55064,-23.02184 11.24654,-32.39691 8.84929,-8.55225 21.22761,-18.39964 31.17304,-26.31619 3.60329,-2.86658 6.73129,-5.3173 9.2028,-7.39669 2.31406,-1.93977 1.61598,-4.95488 0.57033,-6.21441 -1.74073,-2.09127 -4.61921,-1.74669 -6.56195,-0.32379 -0.10398,0.0802 -5.65595,4.40832 -5.65595,4.40832 l -8.58195,-11.57033 c 0,0 5.60843,-4.14096 5.8223,-4.30137 8.39777,-6.155 19.54034,-4.98758 25.92406,2.71509 3.19039,3.84093 4.68459,8.69779 4.20929,13.67051 -0.47232,4.9549 -2.84579,9.43153 -6.68078,12.5922 -2.58439,2.12988 -5.73912,4.64298 -9.39291,7.54522 -9.70779,7.72641 -21.78905,17.33915 -30.14226,25.41907 -6.1253,5.9233 -8.70671,12.67834 -7.26896,19.02345 1.9873,8.75424 11.33268,16.34105 26.32213,21.37911 l 0,-46.22486 c 40.29563,13.62298 68.76248,61.22321 78.20589,87.64039 0,0 -41.76308,-14.15768 -63.98286,-21.64051 l 0,78.7198"
|
||||
style="fill:#f8f8f8;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path48" />
|
||||
<path
|
||||
d="m 803.42903,826.12513 -14.22303,-4.78261 0,-9.30973 14.22303,4.77667 0,9.31567"
|
||||
style="fill:#cdccca;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path50" />
|
||||
<path
|
||||
d="m 734.75566,745.06155 c -0.23469,0.16636 -0.54956,0.14853 -0.73077,-0.0743 -0.17823,-0.22576 -0.13063,-0.53766 0.0802,-0.73373 4.93113,-4.51525 24.45661,-23.86844 46.30805,-45.2624 l 8.58193,11.57033 c 0,0 -53.54135,34.01288 -54.23942,34.50007"
|
||||
style="fill:#f14621;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path52" />
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="302px" height="579px" viewBox="0 0 302 579" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Untitled 2</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Minio_Logo_White" fill-rule="nonzero">
|
||||
<g id="Group">
|
||||
<path d="M225.835,28.882 C225.835,28.882 282.569,120.259 301.531,152.735 C301.837,153.264 301.707,153.963 301.251,154.419 C300.676,154.994 299.81,155.056 299.24,154.481 L201.484,52.243 L225.835,28.882 Z" id="Shape" fill="#F15A29"></path>
|
||||
<path d="M58.706,361.854 C75.299,326.75 97.681,294.989 125.38,267.29 C135.915,256.76 147.036,247.002 158.701,238.037 L158.701,310.385 L58.706,361.854 Z M0.971,428.672 L158.7,348.375 L158.7,531.932 L194.244,578.623 L194.244,330.092 L215.833,318.987 C225.778,313.991 235.105,307.343 243.402,299.041 C284.517,257.931 285.17,191.574 245.226,149.749 L163.742,64.855 C160.493,61.383 158.788,56.838 158.938,52.06 C159.088,47.251 161.099,42.784 164.597,39.488 C171.811,32.704 183.305,33.161 190.088,40.369 L201.484,52.247 L225.835,28.881 C197.064,-8.431 161.861,-3.746 141.47,14.92 C131.417,24.383 125.644,37.198 125.208,51.003 C124.773,64.829 129.753,77.981 139.221,88.04 L139.33,88.159 L221.239,173.483 C248.176,202.151 247.637,247.235 219.617,275.255 C215.248,279.623 210.46,283.324 205.387,286.36 L194.245,292.091 L194.245,176.126 C160.167,193.688 129.09,216.251 101.692,243.655 C67.568,277.774 40.776,317.516 22.058,361.772 C12.875,383.479 5.837,405.829 0.971,428.672 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
<polygon id="Shape" fill="#BCBEC0" points="194.246 330.09 194.246 367.609 158.701 385.648 158.701 348.373"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.9 KiB |
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2016 Minio, Inc.
|
||||
* Minio Cloud Storage (C) 2016, 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -13,106 +13,31 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import 'babel-polyfill'
|
||||
|
||||
import './less/main.less'
|
||||
import "babel-polyfill"
|
||||
import "./less/main.less"
|
||||
import "font-awesome/css/font-awesome.css"
|
||||
import "material-design-iconic-font/dist/css/material-design-iconic-font.min.css"
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import thunkMiddleware from 'redux-thunk'
|
||||
import createStore from 'redux/lib/createStore'
|
||||
import applyMiddleware from 'redux/lib/applyMiddleware'
|
||||
import React from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import { BrowserRouter, Route } from "react-router-dom"
|
||||
import { Provider } from "react-redux"
|
||||
|
||||
import Route from 'react-router/lib/Route'
|
||||
import Router from 'react-router/lib/Router'
|
||||
import browserHistory from 'react-router/lib/browserHistory'
|
||||
import IndexRoute from 'react-router/lib/IndexRoute'
|
||||
import { minioBrowserPrefix } from "./js/constants"
|
||||
import configureStore from "./js/store/configure-store"
|
||||
import hideLoader from "./js/loader"
|
||||
import App from "./js/components/App"
|
||||
|
||||
import Provider from 'react-redux/lib/components/Provider'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
const store = configureStore()
|
||||
|
||||
import Moment from 'moment'
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<Route path={minioBrowserPrefix} component={App} />
|
||||
</BrowserRouter>
|
||||
</Provider>,
|
||||
document.getElementById("root")
|
||||
)
|
||||
|
||||
import { minioBrowserPrefix } from './js/constants.js'
|
||||
import * as actions from './js/actions.js'
|
||||
import reducer from './js/reducers.js'
|
||||
|
||||
import _Login from './js/components/Login.js'
|
||||
import _Browse from './js/components/Browse.js'
|
||||
import fontAwesome from 'font-awesome/css/font-awesome.css'
|
||||
|
||||
import MaterialDesignIconicFonts from 'material-design-iconic-font/dist/css/material-design-iconic-font.min.css'
|
||||
|
||||
import Web from './js/web'
|
||||
window.Web = Web
|
||||
|
||||
import storage from 'local-storage-fallback'
|
||||
const store = applyMiddleware(thunkMiddleware)(createStore)(reducer)
|
||||
const Browse = connect(state => state)(_Browse)
|
||||
const Login = connect(state => state)(_Login)
|
||||
|
||||
let web = new Web(`${window.location.protocol}//${window.location.host}${minioBrowserPrefix}/webrpc`, store.dispatch)
|
||||
|
||||
window.web = web
|
||||
|
||||
store.dispatch(actions.setWeb(web))
|
||||
|
||||
function authNeeded(nextState, replace, cb) {
|
||||
if (web.LoggedIn()) {
|
||||
return cb()
|
||||
}
|
||||
if (location.pathname === minioBrowserPrefix || location.pathname === minioBrowserPrefix + '/') {
|
||||
replace(`${minioBrowserPrefix}/login`)
|
||||
}
|
||||
return cb()
|
||||
}
|
||||
|
||||
function authNotNeeded(nextState, replace) {
|
||||
if (web.LoggedIn()) {
|
||||
replace(`${minioBrowserPrefix}`)
|
||||
}
|
||||
}
|
||||
|
||||
const App = (props) => {
|
||||
return <div>
|
||||
{ props.children }
|
||||
</div>
|
||||
}
|
||||
|
||||
ReactDOM.render((
|
||||
<Provider store={ store } web={ web }>
|
||||
<Router history={ browserHistory }>
|
||||
<Route path='/' component={ App }>
|
||||
<Route path={ minioBrowserPrefix } component={ App }>
|
||||
<IndexRoute component={ Browse } onEnter={ authNeeded } />
|
||||
<Route path='login' component={ Login } onEnter={ authNotNeeded } />
|
||||
<Route path=':bucket' component={ Browse } onEnter={ authNeeded } />
|
||||
<Route path=':bucket/*' component={ Browse } onEnter={ authNeeded } />
|
||||
</Route>
|
||||
</Route>
|
||||
</Router>
|
||||
</Provider>
|
||||
), document.getElementById('root'))
|
||||
|
||||
//Page loader
|
||||
let delay = [0, 400]
|
||||
let i = 0
|
||||
|
||||
function handleLoader() {
|
||||
if (i < 2) {
|
||||
setTimeout(function() {
|
||||
document.querySelector('.page-load').classList.add('pl-' + i)
|
||||
i++
|
||||
handleLoader()
|
||||
}, delay[i])
|
||||
}
|
||||
}
|
||||
handleLoader()
|
||||
|
||||
if (storage.getItem('newlyUpdated')) {
|
||||
store.dispatch(actions.showAlert({
|
||||
type: 'success',
|
||||
message: "Updated to the latest UI Version."
|
||||
}))
|
||||
storage.removeItem('newlyUpdated')
|
||||
}
|
||||
hideLoader()
|
||||
|
@ -14,30 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import expect from 'expect';
|
||||
import JSONrpc from '../jsonrpc';
|
||||
import JSONrpc from "../jsonrpc"
|
||||
|
||||
describe('jsonrpc', () => {
|
||||
it('should fail with invalid endpoint', (done) => {
|
||||
describe("jsonrpc", () => {
|
||||
it("should fail with invalid endpoint", done => {
|
||||
try {
|
||||
let jsonRPC = new JSONrpc({
|
||||
endpoint: 'htt://localhost:9000',
|
||||
namespace: 'Test'
|
||||
});
|
||||
endpoint: "htt://localhost:9000",
|
||||
namespace: "Test"
|
||||
})
|
||||
} catch (e) {
|
||||
done();
|
||||
done()
|
||||
}
|
||||
});
|
||||
it('should succeed with valid endpoint', () => {
|
||||
})
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
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")
|
||||
})
|
||||
})
|
||||
|
@ -1,695 +0,0 @@
|
||||
/*
|
||||
* 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 Moment from 'moment'
|
||||
import browserHistory from 'react-router/lib/browserHistory'
|
||||
import storage from 'local-storage-fallback'
|
||||
import { minioBrowserPrefix } from './constants'
|
||||
|
||||
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 REMOVE_BUCKET = 'REMOVE_BUCKET'
|
||||
export const SHOW_BUCKET_DROPDOWN = 'SHOW_BUCKET_DROPDOWN'
|
||||
export const SET_VISIBLE_BUCKETS = 'SET_VISIBLE_BUCKETS'
|
||||
export const SET_OBJECTS = 'SET_OBJECTS'
|
||||
export const APPEND_OBJECTS = 'APPEND_OBJECTS'
|
||||
export const RESET_OBJECTS = 'RESET_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 REMOVE_OBJECT = 'REMOVE_OBJECT'
|
||||
export const CHECKED_OBJECTS_ADD = 'CHECKED_OBJECTS_ADD'
|
||||
export const CHECKED_OBJECTS_REMOVE = 'CHECKED_OBJECTS_REMOVE'
|
||||
export const CHECKED_OBJECTS_RESET = 'CHECKED_OBJECTS_RESET'
|
||||
|
||||
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 = (object, url) => {
|
||||
return {
|
||||
type: SET_SHARE_OBJECT,
|
||||
shareObject: {
|
||||
object,
|
||||
url,
|
||||
show: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const hideShareObject = () => {
|
||||
return {
|
||||
type: SET_SHARE_OBJECT,
|
||||
shareObject: {
|
||||
url: '',
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const shareObject = (object, days, hours, minutes) => (dispatch, getState) => {
|
||||
const {currentBucket, web} = getState()
|
||||
let host = location.host
|
||||
let bucket = currentBucket
|
||||
|
||||
if (!web.LoggedIn()) {
|
||||
dispatch(showShareObject(object, `${host}/${bucket}/${object}`))
|
||||
return
|
||||
}
|
||||
|
||||
let expiry = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60
|
||||
web.PresignedGet({
|
||||
host,
|
||||
bucket,
|
||||
object,
|
||||
expiry
|
||||
})
|
||||
.then(obj => {
|
||||
dispatch(showShareObject(object, obj.url))
|
||||
dispatch(showAlert({
|
||||
type: 'success',
|
||||
message: `Object shared. Expires in ${days} days ${hours} hours ${minutes} minutes.`
|
||||
}))
|
||||
})
|
||||
.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 removeBucket = bucket => {
|
||||
return {
|
||||
type: REMOVE_BUCKET,
|
||||
bucket
|
||||
}
|
||||
}
|
||||
|
||||
export const showBucketDropdown = bucket => {
|
||||
return {
|
||||
type: SHOW_BUCKET_DROPDOWN,
|
||||
showBucketDropdown: true
|
||||
}
|
||||
}
|
||||
|
||||
export const hideBucketDropdown = bucket => {
|
||||
return {
|
||||
type: SHOW_BUCKET_DROPDOWN,
|
||||
showBucketDropdown: false
|
||||
}
|
||||
}
|
||||
|
||||
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 removeObject = object => {
|
||||
return {
|
||||
type: REMOVE_OBJECT,
|
||||
object
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
const appendObjects = (objects, marker, istruncated) => {
|
||||
return {
|
||||
type: APPEND_OBJECTS,
|
||||
objects,
|
||||
marker,
|
||||
istruncated
|
||||
}
|
||||
}
|
||||
|
||||
export const setObjects = (objects) => {
|
||||
return {
|
||||
type: SET_OBJECTS,
|
||||
objects,
|
||||
}
|
||||
}
|
||||
|
||||
export const resetObjects = () => {
|
||||
return {
|
||||
type: RESET_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 deleteBucket = (bucket) => {
|
||||
return (dispatch, getState) => {
|
||||
// DeleteBucket() RPC call will ONLY delete a bucket if it is empty of
|
||||
// objects. This means a call can just be sent, as it is entirely reversable
|
||||
// and won't do any permanent damage.
|
||||
web.DeleteBucket({
|
||||
bucketName: bucket
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(showAlert({
|
||||
type: 'info',
|
||||
message: `Bucket '${bucket}' has been deleted.`
|
||||
}))
|
||||
dispatch(removeBucket(bucket))
|
||||
})
|
||||
.catch(err => {
|
||||
let message = err.message
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: message
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const listObjects = () => {
|
||||
return (dispatch, getState) => {
|
||||
const {buckets, currentBucket, currentPath, marker, objects, istruncated, web} = getState()
|
||||
if (!istruncated || buckets.length === 0) return
|
||||
web.ListObjects({
|
||||
bucketName: currentBucket,
|
||||
prefix: currentPath,
|
||||
marker: marker
|
||||
})
|
||||
.then(res => {
|
||||
let objects = res.objects
|
||||
if (!objects)
|
||||
objects = []
|
||||
objects = objects.map(object => {
|
||||
object.name = object.name.replace(`${currentPath}`, '');
|
||||
return object
|
||||
})
|
||||
dispatch(appendObjects(objects, res.nextmarker, res.istruncated))
|
||||
dispatch(setPrefixWritable(res.writable))
|
||||
dispatch(setLoadBucket(''))
|
||||
dispatch(setLoadPath(''))
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
dispatch(setLoadBucket(''))
|
||||
dispatch(setLoadPath(''))
|
||||
// Use browserHistory.replace instead of push so that browser back button works fine.
|
||||
browserHistory.replace(`${minioBrowserPrefix}/login`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const selectPrefix = prefix => {
|
||||
return (dispatch, getState) => {
|
||||
const {currentBucket, web} = getState()
|
||||
dispatch(setLoadPath(prefix))
|
||||
web.ListObjects({
|
||||
bucketName: currentBucket,
|
||||
prefix,
|
||||
marker: ""
|
||||
})
|
||||
.then(res => {
|
||||
let objects = res.objects
|
||||
if (!objects)
|
||||
objects = []
|
||||
objects = objects.map(object => {
|
||||
object.name = object.name.replace(`${prefix}`, '');
|
||||
return object
|
||||
})
|
||||
dispatch(resetObjects())
|
||||
dispatch(appendObjects(
|
||||
objects,
|
||||
res.nextmarker,
|
||||
res.istruncated
|
||||
))
|
||||
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(''))
|
||||
// Use browserHistory.replace instead of push so that browser back button works fine.
|
||||
browserHistory.replace(`${minioBrowserPrefix}/login`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 downloadSelected = (url, req, xhr) => {
|
||||
return (dispatch) => {
|
||||
var anchor = document.createElement('a')
|
||||
document.body.appendChild(anchor);
|
||||
xhr.open('POST', url, true)
|
||||
xhr.responseType = 'blob'
|
||||
|
||||
xhr.onload = function(e) {
|
||||
if (this.status == 200) {
|
||||
dispatch(checkedObjectsReset())
|
||||
var blob = new Blob([this.response], {
|
||||
type: 'octet/stream'
|
||||
})
|
||||
var blobUrl = window.URL.createObjectURL(blob);
|
||||
var separator = req.prefix.length > 1 ? '-' : ''
|
||||
|
||||
anchor.href = blobUrl
|
||||
anchor.download = req.bucketName+separator+req.prefix.slice(0, -1)+'.zip';
|
||||
|
||||
|
||||
|
||||
|
||||
anchor.click()
|
||||
window.URL.revokeObjectURL(blobUrl)
|
||||
anchor.remove()
|
||||
}
|
||||
};
|
||||
xhr.send(JSON.stringify(req));
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadFile = (file, xhr) => {
|
||||
return (dispatch, getState) => {
|
||||
const {currentBucket, currentPath} = getState()
|
||||
const objectName = `${currentPath}${file.name}`
|
||||
const uploadUrl = `${window.location.origin}${minioBrowserPrefix}/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) {
|
||||
setShowAbortModal(false)
|
||||
dispatch(stopUpload({
|
||||
slug
|
||||
}))
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: 'Unauthorized request.'
|
||||
}))
|
||||
}
|
||||
if (xhr.status == 500) {
|
||||
setShowAbortModal(false)
|
||||
dispatch(stopUpload({
|
||||
slug
|
||||
}))
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: xhr.responseText
|
||||
}))
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
export const checkedObjectsAdd = (objectName) => {
|
||||
return {
|
||||
type: CHECKED_OBJECTS_ADD,
|
||||
objectName
|
||||
}
|
||||
}
|
||||
|
||||
export const checkedObjectsRemove = (objectName) => {
|
||||
return {
|
||||
type: CHECKED_OBJECTS_REMOVE,
|
||||
objectName
|
||||
}
|
||||
}
|
||||
|
||||
export const checkedObjectsReset = (objectName) => {
|
||||
return {
|
||||
type: CHECKED_OBJECTS_RESET,
|
||||
objectName
|
||||
}
|
||||
}
|
57
browser/app/js/actions/__tests__/alert.test.js
Normal file
57
browser/app/js/actions/__tests__/alert.test.js
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import configureStore from "redux-mock-store"
|
||||
import thunk from "redux-thunk"
|
||||
import * as actionsAlert from "../alert"
|
||||
|
||||
const middlewares = [thunk]
|
||||
const mockStore = configureStore(middlewares)
|
||||
|
||||
jest.useFakeTimers()
|
||||
|
||||
describe("Alert actions", () => {
|
||||
it("creates alert/SET action", () => {
|
||||
const store = mockStore()
|
||||
const expectedActions = [
|
||||
{
|
||||
type: "alert/SET",
|
||||
alert: { id: 0, message: "Test alert", type: "danger" }
|
||||
}
|
||||
]
|
||||
store.dispatch(actionsAlert.set({ message: "Test alert", type: "danger" }))
|
||||
const actions = store.getActions()
|
||||
expect(actions).toEqual(expectedActions)
|
||||
})
|
||||
|
||||
it("creates alert/CLEAR action for non danger alerts", () => {
|
||||
const store = mockStore()
|
||||
const expectedActions = [
|
||||
{
|
||||
type: "alert/SET",
|
||||
alert: { id: 1, message: "Test alert" }
|
||||
},
|
||||
{
|
||||
type: "alert/CLEAR",
|
||||
alert: { id: 1 }
|
||||
}
|
||||
]
|
||||
store.dispatch(actionsAlert.set({ message: "Test alert" }))
|
||||
jest.runAllTimers()
|
||||
const actions = store.getActions()
|
||||
expect(actions).toEqual(expectedActions)
|
||||
})
|
||||
})
|
41
browser/app/js/actions/__tests__/buckets.test.js
Normal file
41
browser/app/js/actions/__tests__/buckets.test.js
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import configureStore from "redux-mock-store"
|
||||
import thunk from "redux-thunk"
|
||||
import * as actionsBuckets from "../buckets"
|
||||
|
||||
jest.mock("../../web", () => ({
|
||||
ListBuckets: jest.fn(() => {
|
||||
return Promise.resolve({ buckets: [{ name: "test1" }, { name: "test2" }] })
|
||||
})
|
||||
}))
|
||||
|
||||
const middlewares = [thunk]
|
||||
const mockStore = configureStore(middlewares)
|
||||
|
||||
describe("Buckets actions", () => {
|
||||
it("creates buckets/SET_LIST after fetching the buckets", () => {
|
||||
const store = mockStore()
|
||||
const expectedActions = [
|
||||
{ type: "buckets/SET_LIST", buckets: ["test1", "test2"] }
|
||||
]
|
||||
return store.dispatch(actionsBuckets.fetchBuckets()).then(() => {
|
||||
const actions = store.getActions()
|
||||
expect(actions).toEqual(expectedActions)
|
||||
})
|
||||
})
|
||||
})
|
46
browser/app/js/actions/alert.js
Normal file
46
browser/app/js/actions/alert.js
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const SET = "alert/SET"
|
||||
export const CLEAR = "alert/CLEAR"
|
||||
|
||||
let alertId = 0
|
||||
|
||||
export const set = alert => {
|
||||
const id = alertId++
|
||||
return (dispatch, getState) => {
|
||||
if (alert.type !== "danger") {
|
||||
setTimeout(() => {
|
||||
dispatch({
|
||||
type: CLEAR,
|
||||
alert: {
|
||||
id
|
||||
}
|
||||
})
|
||||
}, 5000)
|
||||
}
|
||||
dispatch({
|
||||
type: SET,
|
||||
alert: Object.assign({}, alert, {
|
||||
id
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const clear = () => {
|
||||
return { type: CLEAR }
|
||||
}
|
51
browser/app/js/actions/buckets.js
Normal file
51
browser/app/js/actions/buckets.js
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import web from "../web"
|
||||
|
||||
export const SET_LIST = "buckets/SET_LIST"
|
||||
export const SET_FILTER = "buckets/SET_FILTER"
|
||||
export const SET_CURRENT_BUCKET = "buckets/SET_CURRENT_BUCKET"
|
||||
|
||||
export const fetchBuckets = () => {
|
||||
return function(dispatch) {
|
||||
return web.ListBuckets().then(res => {
|
||||
const buckets = res.buckets ? res.buckets.map(bucket => bucket.name) : []
|
||||
dispatch(setList(buckets))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const setList = buckets => {
|
||||
return {
|
||||
type: SET_LIST,
|
||||
buckets
|
||||
}
|
||||
}
|
||||
|
||||
export const setFilter = filter => {
|
||||
return {
|
||||
type: SET_FILTER,
|
||||
filter
|
||||
}
|
||||
}
|
||||
|
||||
export const setCurrentBucket = bucket => {
|
||||
return {
|
||||
type: SET_CURRENT_BUCKET,
|
||||
bucket
|
||||
}
|
||||
}
|
30
browser/app/js/components/Alert.js
Normal file
30
browser/app/js/components/Alert.js
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import AlertComponent from "react-bootstrap/lib/Alert"
|
||||
|
||||
const Alert = ({ show, type, message, onDismiss }) => (
|
||||
<AlertComponent
|
||||
className={"alert animated " + (show ? "fadeInDown" : "fadeOutUp")}
|
||||
bsStyle={type}
|
||||
onDismiss={onDismiss}
|
||||
>
|
||||
<div className="text-center">{message}</div>
|
||||
</AlertComponent>
|
||||
)
|
||||
|
||||
export default Alert
|
46
browser/app/js/components/App.js
Normal file
46
browser/app/js/components/App.js
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import { Route, Switch, Redirect } from "react-router-dom"
|
||||
import Browser from "../components/Browser"
|
||||
import Login from "../components/Login"
|
||||
import web from "../web"
|
||||
import { minioBrowserPrefix } from "../constants"
|
||||
|
||||
const AuthorizedRoute = ({ component: Component, ...rest }) => (
|
||||
<Route
|
||||
{...rest}
|
||||
render={props =>
|
||||
web.LoggedIn() ? (
|
||||
<Component {...props} />
|
||||
) : (
|
||||
<Redirect to={`${minioBrowserPrefix}/login`} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
||||
export const App = ({ match }) => (
|
||||
<Switch>
|
||||
<AuthorizedRoute exact path={match.url} component={Browser} />
|
||||
<Route path={`${match.url}/login`} component={Login} />
|
||||
<AuthorizedRoute path={`${match.url}/:bucket/*`} component={Browser} />
|
||||
<AuthorizedRoute path={`${match.url}/:bucket`} component={Browser} />
|
||||
</Switch>
|
||||
)
|
||||
|
||||
export default App
|
@ -1,869 +0,0 @@
|
||||
/*
|
||||
* 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}${minioBrowserPrefix}/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())
|
||||
}
|
||||
|
||||
toggleBucketDropdown(e) {
|
||||
const {dispatch, showBucketDropdown} = this.props
|
||||
if (showBucketDropdown) {
|
||||
dispatch(actions.hideBucketDropdown())
|
||||
} else {
|
||||
dispatch(actions.showBucketDropdown())
|
||||
}
|
||||
}
|
||||
|
||||
showBucketPolicy(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
this.toggleBucketDropdown(e)
|
||||
dispatch(actions.showBucketPolicy())
|
||||
}
|
||||
|
||||
hideBucketPolicy(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideBucketPolicy())
|
||||
}
|
||||
|
||||
deleteBucket(e, bucket) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
this.toggleBucketDropdown(e)
|
||||
dispatch(actions.deleteBucket(bucket))
|
||||
browserHistory.push(`${minioBrowserPrefix}/`)
|
||||
}
|
||||
|
||||
uploadFile(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch, buckets, currentBucket} = this.props
|
||||
if (buckets.length === 0) {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: "Bucket needs to be created before trying to upload files."
|
||||
}))
|
||||
return
|
||||
}
|
||||
if (currentBucket === '') {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: "Please choose a bucket 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 + minioBrowserPrefix + "/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={minioBrowserPrefix+'/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) }
|
||||
deleteBucket={ this.deleteBucket.bind(this) }
|
||||
toggleBucketDropdown={ this.toggleBucketDropdown.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>
|
||||
)
|
||||
}
|
||||
}
|
36
browser/app/js/components/Browser.js
Normal file
36
browser/app/js/components/Browser.js
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import classNames from "classnames"
|
||||
import { connect } from "react-redux"
|
||||
import SideBar from "./SideBar"
|
||||
|
||||
class Browser extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={classNames({
|
||||
"file-explorer": true
|
||||
})}
|
||||
>
|
||||
<SideBar />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => state)(Browser)
|
43
browser/app/js/components/Bucket.js
Normal file
43
browser/app/js/components/Bucket.js
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import classNames from "classnames"
|
||||
|
||||
export const Bucket = ({ bucket, isActive, selectBucket }) => {
|
||||
return (
|
||||
<li
|
||||
className={classNames({
|
||||
active: isActive
|
||||
})}
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
selectBucket(bucket)
|
||||
}}
|
||||
>
|
||||
<a
|
||||
href=""
|
||||
className={classNames({
|
||||
"fesli-loading": false
|
||||
})}
|
||||
>
|
||||
{bucket}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default Bucket
|
35
browser/app/js/components/BucketContainer.js
Normal file
35
browser/app/js/components/BucketContainer.js
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import * as actionsBuckets from "../actions/buckets"
|
||||
import { getCurrentBucket } from "../selectors/buckets"
|
||||
import Bucket from "./Bucket"
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
isActive: getCurrentBucket(state) === ownProps.bucket
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
selectBucket: bucket => dispatch(actionsBuckets.setCurrentBucket(bucket))
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Bucket)
|
59
browser/app/js/components/BucketList.js
Normal file
59
browser/app/js/components/BucketList.js
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { Scrollbars } from "react-custom-scrollbars"
|
||||
import * as actionsBuckets from "../actions/buckets"
|
||||
import { getVisibleBuckets } from "../selectors/buckets"
|
||||
import BucketContainer from "./BucketContainer"
|
||||
|
||||
export class BucketList extends React.Component {
|
||||
componentWillMount() {
|
||||
const { fetchBuckets } = this.props
|
||||
fetchBuckets()
|
||||
}
|
||||
render() {
|
||||
const { visibleBuckets } = this.props
|
||||
return (
|
||||
<div className="fesl-inner">
|
||||
<Scrollbars
|
||||
renderTrackVertical={props => <div className="scrollbar-vertical" />}
|
||||
>
|
||||
<ul>
|
||||
{visibleBuckets.map(bucket => (
|
||||
<BucketContainer key={bucket} bucket={bucket} />
|
||||
))}
|
||||
</ul>
|
||||
</Scrollbars>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
visibleBuckets: getVisibleBuckets(state)
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchBuckets: () => dispatch(actionsBuckets.fetchBuckets())
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BucketList)
|
44
browser/app/js/components/BucketSearch.js
Normal file
44
browser/app/js/components/BucketSearch.js
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import * as actionsBuckets from "../actions/buckets"
|
||||
|
||||
export const BucketSearch = ({ onChange }) => (
|
||||
<div
|
||||
className="input-group ig-dark ig-left ig-search"
|
||||
style={{ display: "block" }}
|
||||
>
|
||||
<input
|
||||
className="ig-text"
|
||||
type="text"
|
||||
onChange={e => onChange(e.target.value)}
|
||||
placeholder="Search Buckets..."
|
||||
/>
|
||||
<i className="ig-helpers" />
|
||||
</div>
|
||||
)
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
onChange: filter => {
|
||||
dispatch(actionsBuckets.setFilter(filter))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(undefined, mapDispatchToProps)(BucketSearch)
|
26
browser/app/js/components/Host.js
Normal file
26
browser/app/js/components/Host.js
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2016, 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
|
||||
export const Host = () => (
|
||||
<div className="fes-host">
|
||||
<i className="fa fa-globe" />
|
||||
<a href="/">{window.location.host}</a>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Host
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2016 Minio, Inc.
|
||||
* Minio Cloud Storage (C) 2016, 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,115 +14,118 @@
|
||||
* 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'
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import classNames from "classnames"
|
||||
import logo from "../../img/logo.svg"
|
||||
import Alert from "./Alert"
|
||||
import * as actionsAlert from "../actions/alert"
|
||||
import InputGroup from "../components/InputGroup"
|
||||
import { minioBrowserPrefix } from "../constants"
|
||||
import web from "../web"
|
||||
import { Redirect } from "react-router-dom"
|
||||
|
||||
export default class Login extends React.Component {
|
||||
export 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'
|
||||
const { dispatch, history } = 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 (!document.getElementById("secretKey").value) {
|
||||
message = "Access Key cannot be empty"
|
||||
}
|
||||
if (message) {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message
|
||||
}))
|
||||
dispatch(
|
||||
actionsAlert.set({
|
||||
type: "danger",
|
||||
message
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
web.Login({
|
||||
username: document.getElementById('accessKey').value,
|
||||
password: document.getElementById('secretKey').value
|
||||
})
|
||||
.then((res) => {
|
||||
this.context.router.push(loginRedirectPath)
|
||||
web
|
||||
.Login({
|
||||
username: document.getElementById("accessKey").value,
|
||||
password: document.getElementById("secretKey").value
|
||||
})
|
||||
.then(res => {
|
||||
history.push(minioBrowserPrefix)
|
||||
})
|
||||
.catch(e => {
|
||||
dispatch(actions.setLoginError())
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message
|
||||
}))
|
||||
dispatch(
|
||||
actionsAlert.set({
|
||||
type: "danger",
|
||||
message: e.message
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const {dispatch} = this.props
|
||||
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')
|
||||
dispatch(actionsAlert.clear())
|
||||
document.body.classList.add("is-guest")
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.body.classList.remove('is-guest')
|
||||
document.body.classList.remove("is-guest")
|
||||
}
|
||||
|
||||
hideAlert() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideAlert())
|
||||
clearAlert() {
|
||||
const { dispatch } = this.props
|
||||
dispatch(actionsAlert.clear())
|
||||
}
|
||||
|
||||
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>
|
||||
const { alert } = this.props
|
||||
if (web.LoggedIn()) {
|
||||
return <Redirect to={minioBrowserPrefix} />
|
||||
}
|
||||
let alertBox = <Alert {...alert} onDismiss={this.clearAlert.bind(this)} />
|
||||
// Make sure you don't show a fading out alert box on the initial web-page load.
|
||||
if (!alert.message)
|
||||
alertBox = ''
|
||||
if (!alert.message) alertBox = ""
|
||||
return (
|
||||
<div className="login">
|
||||
{ alertBox }
|
||||
{alertBox}
|
||||
<div className="l-wrap">
|
||||
<form onSubmit={ this.handleSubmit.bind(this) }>
|
||||
<InputGroup className="ig-dark"
|
||||
<form onSubmit={this.handleSubmit.bind(this)}>
|
||||
<InputGroup
|
||||
className="ig-dark"
|
||||
label="Access Key"
|
||||
id="accessKey"
|
||||
name="username"
|
||||
type="text"
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="username">
|
||||
</InputGroup>
|
||||
<InputGroup className="ig-dark"
|
||||
autoComplete="username"
|
||||
/>
|
||||
<InputGroup
|
||||
className="ig-dark"
|
||||
label="Secret Key"
|
||||
id="secretKey"
|
||||
name="password"
|
||||
type="password"
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="new-password">
|
||||
</InputGroup>
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
<button className="lw-btn" type="submit">
|
||||
<i className="fa fa-sign-in"></i>
|
||||
<i className="fa fa-sign-in" />
|
||||
</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>
|
||||
<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
|
||||
}
|
||||
export default connect(state => state)(Login)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2016 Minio, Inc.
|
||||
* Minio Cloud Storage (C) 2016, 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,86 +14,37 @@
|
||||
* 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 React from "react"
|
||||
import classNames from "classnames"
|
||||
import ClickOutHandler from "react-onclickout"
|
||||
import { connect } from "react-redux"
|
||||
|
||||
import logo from '../../img/logo.svg'
|
||||
import Dropdown from 'react-bootstrap/lib/Dropdown'
|
||||
|
||||
let SideBar = ({visibleBuckets, loadBucket, currentBucket, selectBucket, searchBuckets, sidebarStatus, clickOutside, showPolicy, deleteBucket, toggleBucketDropdown, showBucketDropdown}) => {
|
||||
|
||||
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>
|
||||
<Dropdown open={bucket === currentBucket && showBucketDropdown} onToggle={toggleBucketDropdown} className="bucket-dropdown" id="bucket-dropdown">
|
||||
<Dropdown.Toggle noCaret>
|
||||
<i className="zmdi zmdi-more-vert" />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
<li>
|
||||
<a onClick={ showPolicy }>Edit policy</a>
|
||||
</li>
|
||||
<li>
|
||||
<a onClick={ (e) => deleteBucket(e, bucket) }>Delete</a>
|
||||
</li>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</li>
|
||||
})
|
||||
import logo from "../../img/logo.svg"
|
||||
import Dropdown from "react-bootstrap/lib/Dropdown"
|
||||
import BucketSearch from "./BucketSearch"
|
||||
import BucketList from "./BucketList"
|
||||
import Host from "./Host"
|
||||
|
||||
export const SideBar = () => {
|
||||
return (
|
||||
<ClickOutHandler onClickOut={ clickOutside }>
|
||||
<div className={ classNames({
|
||||
'fe-sidebar': true,
|
||||
'toggled': sidebarStatus
|
||||
}) }>
|
||||
<ClickOutHandler>
|
||||
<div
|
||||
className={classNames({
|
||||
"fe-sidebar": true
|
||||
})}
|
||||
>
|
||||
<div className="fes-header clearfix hidden-sm hidden-xs">
|
||||
<img src={ logo } alt="" />
|
||||
<img src={logo} alt="" />
|
||||
<h2>Minio Browser</h2>
|
||||
</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>
|
||||
<BucketSearch />
|
||||
<BucketList />
|
||||
</div>
|
||||
<Host />
|
||||
</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,
|
||||
showBucketDropdown: state.showBucketDropdown,
|
||||
sidebarStatus: state.sidebarStatus
|
||||
}
|
||||
})(SideBar)
|
||||
export default connect(state => state)(SideBar)
|
||||
|
39
browser/app/js/components/__tests__/Bucket.test.js
Normal file
39
browser/app/js/components/__tests__/Bucket.test.js
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import { shallow } from "enzyme"
|
||||
import { Bucket } from "../Bucket"
|
||||
|
||||
describe("Bucket", () => {
|
||||
it("should render without crashing", () => {
|
||||
shallow(<Bucket />)
|
||||
})
|
||||
|
||||
it("should call selectBucket when clicked", () => {
|
||||
const selectBucket = jest.fn()
|
||||
const wrapper = shallow(
|
||||
<Bucket bucket={"test"} selectBucket={selectBucket} />
|
||||
)
|
||||
wrapper.find("li").simulate("click", { preventDefault: jest.fn() })
|
||||
expect(selectBucket).toHaveBeenCalledWith("test")
|
||||
})
|
||||
|
||||
it("should highlight the selected bucket", () => {
|
||||
const wrapper = shallow(<Bucket bucket={"test"} isActive={true} />)
|
||||
expect(wrapper.find("li").hasClass("active")).toBeTruthy()
|
||||
})
|
||||
})
|
29
browser/app/js/components/__tests__/BucketList.test.js
Normal file
29
browser/app/js/components/__tests__/BucketList.test.js
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import { shallow } from "enzyme"
|
||||
import { BucketList } from "../BucketList"
|
||||
|
||||
describe("Bucket", () => {
|
||||
it("should call fetchBuckets before component is mounted", () => {
|
||||
const fetchBuckets = jest.fn()
|
||||
const wrapper = shallow(
|
||||
<BucketList visibleBuckets={[]} fetchBuckets={fetchBuckets} />
|
||||
)
|
||||
expect(fetchBuckets).toHaveBeenCalled()
|
||||
})
|
||||
})
|
32
browser/app/js/components/__tests__/BucketSearch.test.js
Normal file
32
browser/app/js/components/__tests__/BucketSearch.test.js
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import { shallow } from "enzyme"
|
||||
import { BucketSearch } from "../BucketSearch"
|
||||
|
||||
describe("BucketSearch", () => {
|
||||
it("should render without crashing", () => {
|
||||
shallow(<BucketSearch />)
|
||||
})
|
||||
|
||||
it("should call onChange with search text", () => {
|
||||
const onChange = jest.fn()
|
||||
const wrapper = shallow(<BucketSearch onChange={onChange} />)
|
||||
wrapper.find("input").simulate("change", { target: { value: "test" } })
|
||||
expect(onChange).toHaveBeenCalledWith("test")
|
||||
})
|
||||
})
|
@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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 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')
|
||||
})
|
||||
});
|
||||
*/
|
21
browser/app/js/jest/setup.js
Normal file
21
browser/app/js/jest/setup.js
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import "jest-enzyme"
|
||||
import { configure } from "enzyme"
|
||||
import Adapter from "enzyme-adapter-react-16"
|
||||
|
||||
configure({ adapter: new Adapter() })
|
30
browser/app/js/loader.js
Normal file
30
browser/app/js/loader.js
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
let delay = [0, 400]
|
||||
|
||||
function handleLoader(i) {
|
||||
if (i < 2) {
|
||||
setTimeout(function() {
|
||||
document.querySelector(".page-load").classList.add("pl-" + i)
|
||||
handleLoader(i + 1)
|
||||
}, delay[i])
|
||||
}
|
||||
}
|
||||
|
||||
const hideLoader = () => handleLoader(0)
|
||||
|
||||
export default hideLoader
|
@ -1,215 +0,0 @@
|
||||
/*
|
||||
* 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 * as actions from './actions'
|
||||
import { minioBrowserPrefix } from './constants'
|
||||
|
||||
export default (state = {
|
||||
buckets: [],
|
||||
visibleBuckets: [],
|
||||
objects: [],
|
||||
istruncated: true,
|
||||
storageInfo: {},
|
||||
serverInfo: {},
|
||||
currentBucket: '',
|
||||
showBucketDropdown: false,
|
||||
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: '',
|
||||
object: ''
|
||||
},
|
||||
prefixWritable: false,
|
||||
checkedObjects: []
|
||||
}, 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.REMOVE_BUCKET:
|
||||
newState.buckets = newState.buckets.filter(bucket => bucket != action.bucket)
|
||||
newState.visibleBuckets = newState.visibleBuckets.filter(bucket => bucket != action.bucket)
|
||||
newState.currentBucket = ""
|
||||
break
|
||||
case actions.SHOW_BUCKET_DROPDOWN:
|
||||
newState.showBucketDropdown = action.showBucketDropdown
|
||||
break
|
||||
case actions.SET_VISIBLE_BUCKETS:
|
||||
newState.visibleBuckets = action.visibleBuckets
|
||||
break
|
||||
case actions.SET_CURRENT_BUCKET:
|
||||
newState.currentBucket = action.currentBucket
|
||||
break
|
||||
case actions.APPEND_OBJECTS:
|
||||
newState.objects = [...newState.objects, ...action.objects]
|
||||
newState.marker = action.marker
|
||||
newState.istruncated = action.istruncated
|
||||
break
|
||||
case actions.SET_OBJECTS:
|
||||
newState.objects = [...action.objects]
|
||||
break
|
||||
case actions.RESET_OBJECTS:
|
||||
newState.objects = []
|
||||
newState.marker = ""
|
||||
newState.istruncated = false
|
||||
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
|
||||
case actions.REMOVE_OBJECT:
|
||||
let idx = newState.objects.findIndex(object => object.name === action.object)
|
||||
if (idx == -1) break
|
||||
newState.objects = [...newState.objects.slice(0, idx), ...newState.objects.slice(idx + 1)]
|
||||
break
|
||||
|
||||
case actions.CHECKED_OBJECTS_ADD:
|
||||
newState.checkedObjects = [...newState.checkedObjects, action.objectName]
|
||||
break
|
||||
case actions.CHECKED_OBJECTS_REMOVE:
|
||||
let index = newState.checkedObjects.indexOf(action.objectName)
|
||||
if (index == -1) break
|
||||
newState.checkedObjects = [...newState.checkedObjects.slice(0, index), ...newState.checkedObjects.slice(index + 1)]
|
||||
break
|
||||
case actions.CHECKED_OBJECTS_RESET:
|
||||
newState.checkedObjects = []
|
||||
break
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
87
browser/app/js/reducers/__tests__/alert.test.js
Normal file
87
browser/app/js/reducers/__tests__/alert.test.js
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import reducer from "../alert"
|
||||
import * as actionsAlert from "../../actions/alert"
|
||||
|
||||
describe("buckets reducer", () => {
|
||||
it("should return the initial state", () => {
|
||||
expect(reducer(undefined, {})).toEqual({
|
||||
show: false,
|
||||
type: "danger"
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle SET_ALERT", () => {
|
||||
expect(
|
||||
reducer(undefined, {
|
||||
type: actionsAlert.SET,
|
||||
alert: { id: 1, type: "danger", message: "Test message" }
|
||||
})
|
||||
).toEqual({
|
||||
show: true,
|
||||
id: 1,
|
||||
type: "danger",
|
||||
message: "Test message"
|
||||
})
|
||||
})
|
||||
|
||||
it("should clear alert if id not passed", () => {
|
||||
expect(
|
||||
reducer(
|
||||
{ show: true, type: "danger", message: "Test message" },
|
||||
{
|
||||
type: actionsAlert.CLEAR
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
show: false,
|
||||
type: "danger"
|
||||
})
|
||||
})
|
||||
|
||||
it("should clear alert if id is matching", () => {
|
||||
expect(
|
||||
reducer(
|
||||
{ show: true, id: 1, type: "danger", message: "Test message" },
|
||||
{
|
||||
type: actionsAlert.CLEAR,
|
||||
alert: { id: 1 }
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
show: false,
|
||||
type: "danger"
|
||||
})
|
||||
})
|
||||
|
||||
it("should not clear alert if id is not matching", () => {
|
||||
expect(
|
||||
reducer(
|
||||
{ show: true, id: 1, type: "danger", message: "Test message" },
|
||||
{
|
||||
type: actionsAlert.CLEAR,
|
||||
alert: { id: 2 }
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
show: true,
|
||||
id: 1,
|
||||
type: "danger",
|
||||
message: "Test message"
|
||||
})
|
||||
})
|
||||
})
|
64
browser/app/js/reducers/__tests__/buckets.test.js
Normal file
64
browser/app/js/reducers/__tests__/buckets.test.js
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import reducer from "../buckets"
|
||||
import * as actions from "../../actions/buckets"
|
||||
|
||||
describe("buckets reducer", () => {
|
||||
it("should return the initial state", () => {
|
||||
expect(reducer(undefined, {})).toEqual({
|
||||
list: [],
|
||||
filter: "",
|
||||
currentBucket: ""
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle SET_BUCKETS", () => {
|
||||
expect(
|
||||
reducer(undefined, { type: actions.SET_LIST, buckets: ["bk1", "bk2"] })
|
||||
).toEqual({
|
||||
list: ["bk1", "bk2"],
|
||||
filter: "",
|
||||
currentBucket: ""
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle SET_BUCKETS_FILTER", () => {
|
||||
expect(
|
||||
reducer(undefined, {
|
||||
type: actions.SET_FILTER,
|
||||
filter: "test"
|
||||
})
|
||||
).toEqual({
|
||||
list: [],
|
||||
filter: "test",
|
||||
currentBucket: ""
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle SELECT_BUCKET", () => {
|
||||
expect(
|
||||
reducer(undefined, {
|
||||
type: actions.SET_CURRENT_BUCKET,
|
||||
bucket: "test"
|
||||
})
|
||||
).toEqual({
|
||||
list: [],
|
||||
filter: "",
|
||||
currentBucket: "test"
|
||||
})
|
||||
})
|
||||
})
|
41
browser/app/js/reducers/alert.js
Normal file
41
browser/app/js/reducers/alert.js
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as actionsAlert from "../actions/alert"
|
||||
|
||||
const initialState = {
|
||||
show: false,
|
||||
type: "danger"
|
||||
}
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case actionsAlert.SET:
|
||||
return {
|
||||
show: true,
|
||||
id: action.alert.id,
|
||||
type: action.alert.type,
|
||||
message: action.alert.message
|
||||
}
|
||||
case actionsAlert.CLEAR:
|
||||
if (action.alert && action.alert.id != state.id) {
|
||||
return state
|
||||
} else {
|
||||
return initialState
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
42
browser/app/js/reducers/buckets.js
Normal file
42
browser/app/js/reducers/buckets.js
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as actionsBuckets from "../actions/buckets"
|
||||
|
||||
export default (
|
||||
state = { list: [], filter: "", currentBucket: "" },
|
||||
action
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case actionsBuckets.SET_LIST:
|
||||
return {
|
||||
...state,
|
||||
list: action.buckets
|
||||
}
|
||||
case actionsBuckets.SET_FILTER:
|
||||
return {
|
||||
...state,
|
||||
filter: action.filter
|
||||
}
|
||||
case actionsBuckets.SET_CURRENT_BUCKET:
|
||||
return {
|
||||
...state,
|
||||
currentBucket: action.bucket
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
26
browser/app/js/reducers/index.js
Normal file
26
browser/app/js/reducers/index.js
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { combineReducers } from "redux"
|
||||
import alert from "./alert"
|
||||
import buckets from "./buckets"
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
alert,
|
||||
buckets
|
||||
})
|
||||
|
||||
export default rootReducer
|
38
browser/app/js/selectors/__tests__/buckets.test.js
Normal file
38
browser/app/js/selectors/__tests__/buckets.test.js
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getVisibleBuckets, getCurrentBucket } from "../buckets"
|
||||
|
||||
describe("getVisibleBuckets", () => {
|
||||
let state
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
buckets: {
|
||||
list: ["test1", "test11", "test2"]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("should return all buckets if no filter specified", () => {
|
||||
state.buckets.filter = ""
|
||||
expect(getVisibleBuckets(state)).toEqual(["test1", "test11", "test2"])
|
||||
})
|
||||
|
||||
it("should return all matching buckets if filter is specified", () => {
|
||||
state.buckets.filter = "test1"
|
||||
expect(getVisibleBuckets(state)).toEqual(["test1", "test11"])
|
||||
})
|
||||
})
|
28
browser/app/js/selectors/buckets.js
Normal file
28
browser/app/js/selectors/buckets.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createSelector } from "reselect"
|
||||
|
||||
const bucketsSelector = state => state.buckets.list
|
||||
const bucketsFilterSelector = state => state.buckets.filter
|
||||
|
||||
export const getVisibleBuckets = createSelector(
|
||||
bucketsSelector,
|
||||
bucketsFilterSelector,
|
||||
(buckets, filter) => buckets.filter(bucket => bucket.indexOf(filter) > -1)
|
||||
)
|
||||
|
||||
export const getCurrentBucket = state => state.buckets.currentBucket
|
26
browser/app/js/store/configure-store.js
Normal file
26
browser/app/js/store/configure-store.js
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createStore, applyMiddleware } from "redux"
|
||||
import thunkMiddleware from "redux-thunk"
|
||||
import reducers from "../reducers/index"
|
||||
|
||||
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore)
|
||||
|
||||
export default function configureStore(initialState) {
|
||||
const store = createStoreWithMiddleware(reducers, initialState)
|
||||
return store
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2016 Minio, Inc.
|
||||
* Minio Cloud Storage (C) 2016, 2018 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,17 +14,14 @@
|
||||
* 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) {
|
||||
class Web {
|
||||
constructor(endpoint) {
|
||||
const namespace = 'Web'
|
||||
this.dispatch = dispatch
|
||||
this.JSONrpc = new JSONrpc({
|
||||
endpoint,
|
||||
namespace
|
||||
@ -37,7 +34,7 @@ export default class Web {
|
||||
.catch(err => {
|
||||
if (err.status === 401) {
|
||||
storage.removeItem('token')
|
||||
browserHistory.push(`${minioBrowserPrefix}/login`)
|
||||
location.reload()
|
||||
throw new Error('Please re-login.')
|
||||
}
|
||||
if (err.status)
|
||||
@ -128,3 +125,7 @@ export default class Web {
|
||||
return this.makeCall('ListAllBucketPolicies', args)
|
||||
}
|
||||
}
|
||||
|
||||
const web = new Web(`${window.location.protocol}//${window.location.host}${minioBrowserPrefix}/webrpc`);
|
||||
|
||||
export default web;
|
@ -3,12 +3,16 @@
|
||||
"version": "0.0.1",
|
||||
"description": "Minio Browser",
|
||||
"scripts": {
|
||||
"test": "karma start",
|
||||
"test": "jest",
|
||||
"dev": "NODE_ENV=dev webpack-dev-server --devtool cheap-module-eval-source-map --progress --colors --hot --content-base dev",
|
||||
"build": "NODE_ENV=dev node build.js",
|
||||
"release": "NODE_ENV=production MINIO_UI_BUILD=RELEASE node build.js",
|
||||
"format": "esformatter -i 'app/**/*.js'"
|
||||
},
|
||||
"jest": {
|
||||
"setupTestFrameworkScriptFile": "./app/js/jest/setup.js",
|
||||
"testURL": "https://localhost:8080"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/minio/minio"
|
||||
@ -23,6 +27,7 @@
|
||||
"async": "^1.5.2",
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-core": "^6.14.0",
|
||||
"babel-jest": "^22.1.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.8.0",
|
||||
@ -32,20 +37,17 @@
|
||||
"babel-register": "^6.14.0",
|
||||
"copy-webpack-plugin": "^0.3.3",
|
||||
"css-loader": "^0.23.1",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-16": "^1.1.1",
|
||||
"esformatter": "^0.10.0",
|
||||
"esformatter-jsx": "^7.4.1",
|
||||
"esformatter-jsx-ignore": "^1.0.6",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"jest": "^22.1.4",
|
||||
"jest-enzyme": "^4.0.2",
|
||||
"json-loader": "^0.5.4",
|
||||
"karma": "^0.13.22",
|
||||
"karma-chrome-launcher": "^0.2.3",
|
||||
"karma-cli": "^0.1.2",
|
||||
"karma-firefox-launcher": "^0.1.7",
|
||||
"karma-mocha": "^0.2.2",
|
||||
"karma-webpack": "^2.0.9",
|
||||
"less": "^2.7.1",
|
||||
"less-loader": "^2.2.3",
|
||||
"mocha": "^2.5.3",
|
||||
"purifycss-webpack-plugin": "^2.0.3",
|
||||
"style-loader": "^0.13.1",
|
||||
"url-loader": "^0.5.7",
|
||||
@ -56,28 +58,30 @@
|
||||
"classnames": "^2.2.3",
|
||||
"expect": "^1.20.2",
|
||||
"font-awesome": "^4.7.0",
|
||||
"history": "^1.17.0",
|
||||
"history": "^4.7.2",
|
||||
"humanize": "0.0.9",
|
||||
"json-loader": "^0.5.4",
|
||||
"local-storage-fallback": "^1.3.0",
|
||||
"local-storage-fallback": "^4.0.2",
|
||||
"material-design-iconic-font": "^2.2.0",
|
||||
"mime-db": "^1.25.0",
|
||||
"mime-types": "^2.1.13",
|
||||
"moment": "^2.15.1",
|
||||
"react": "^0.14.8",
|
||||
"react": "^16.2.0",
|
||||
"react-addons-test-utils": "^0.14.8",
|
||||
"react-bootstrap": "^0.28.5",
|
||||
"react-copy-to-clipboard": "^4.2.3",
|
||||
"react-custom-scrollbars": "^2.2.2",
|
||||
"react-dom": "^0.14.6",
|
||||
"react-dropzone": "^3.5.3",
|
||||
"react-bootstrap": "^0.32.0",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-dropzone": "^4.2.3",
|
||||
"react-infinite-scroller": "^1.0.6",
|
||||
"react-onclickout": "2.0.4",
|
||||
"react-redux": "^4.4.5",
|
||||
"react-router": "^2.8.1",
|
||||
"redux": "^3.6.0",
|
||||
"redux-thunk": "^1.0.3",
|
||||
"superagent": "^1.8.4",
|
||||
"react-redux": "^5.0.6",
|
||||
"react-router-dom": "^4.2.0",
|
||||
"redux": "^3.7.2",
|
||||
"redux-mock-store": "^1.5.1",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"reselect": "^3.0.1",
|
||||
"superagent": "^3.8.2",
|
||||
"superagent-es6-promise": "^1.0.0",
|
||||
"webpack": "^3.10.0"
|
||||
}
|
||||
|
2504
browser/yarn.lock
2504
browser/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user