miniobrowser: Bring Minio browser source into minio repo. (#3617)
@ -0,0 +1,8 @@ |
||||
{ |
||||
"presets": [ |
||||
"es2015", |
||||
"react" |
||||
], |
||||
|
||||
"plugins": ["transform-object-rest-spread"] |
||||
} |
@ -0,0 +1,16 @@ |
||||
# editorconfig.org |
||||
root = true |
||||
|
||||
[*] |
||||
indent_style = space |
||||
indent_size = 4 |
||||
end_of_line = lf |
||||
charset = utf-8 |
||||
trim_trailing_whitespace = true |
||||
insert_final_newline = true |
||||
|
||||
[*.json] |
||||
indent_size = 2 |
||||
|
||||
[*.md] |
||||
trim_trailing_whitespace = false |
@ -0,0 +1,23 @@ |
||||
{ |
||||
"plugins": [ |
||||
"esformatter-jsx" |
||||
], |
||||
// Copied from https://github.com/royriojas/esformatter-jsx |
||||
"jsx": { |
||||
"formatJSX": true, //Duh! that's the default |
||||
"attrsOnSameLineAsTag": false, // move each attribute to its own line |
||||
"maxAttrsOnTag": 3, // if lower or equal than 3 attributes, they will be kept on a single line |
||||
"firstAttributeOnSameLine": true, // keep the first attribute in the same line as the tag |
||||
"formatJSXExpressions": true, // default true, if false jsxExpressions won't be recursively formatted |
||||
"JSXExpressionsSingleLine": true, // default true, if false the JSXExpressions might span several lines |
||||
"alignWithFirstAttribute": false, // do not align attributes with the first tag |
||||
"spaceInJSXExpressionContainers": " ", // default to one space. Make it empty if you don't like spaces between JSXExpressionContainers |
||||
"removeSpaceBeforeClosingJSX": false, // default false. if true <React.Something /> => <React.Something/> |
||||
"closingTagOnNewLine": false, // default false. if true attributes on multiple lines will close the tag on a new line |
||||
"JSXAttributeQuotes": "", // possible values "single" or "double". Leave it as empty string if you don't want to modify the attributes' quotes |
||||
"htmlOptions": { |
||||
// put here the options for js-beautify.html |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,37 @@ |
||||
# Minio File Browser |
||||
|
||||
``Minio Browser`` provides minimal set of UI to manage buckets and objects on ``minio`` server. ``Minio Browser`` is written in javascript and released under [Apache 2.0 License](./LICENSE). |
||||
|
||||
## Installation |
||||
|
||||
### Install yarn: |
||||
```sh |
||||
$ curl -o- -L https://yarnpkg.com/install.sh | bash |
||||
$ yarn |
||||
``` |
||||
|
||||
### Install `go-bindata` and `go-bindata-assetfs`. |
||||
|
||||
If you do not have a working Golang environment, please follow [Install Golang](https://docs.minio.io/docs/how-to-install-golang) |
||||
|
||||
```sh |
||||
$ go get github.com/jteeuwen/go-bindata/... |
||||
$ go get github.com/elazarl/go-bindata-assetfs/... |
||||
``` |
||||
|
||||
## Generating Assets. |
||||
|
||||
### Generate ui-assets.go |
||||
|
||||
```sh |
||||
$ yarn release |
||||
``` |
||||
This generates ui-assets.go in the current direcotry. Now do `make` in the parent directory to build the minio binary with the newly generated ui-assets.go |
||||
|
||||
### Run Minio Browser with live reload. |
||||
|
||||
```sh |
||||
$ yarn dev |
||||
``` |
||||
|
||||
Open [http://localhost:8080/minio/](http://localhost:8080/minio/) in your browser to play with the application |
@ -0,0 +1,98 @@ |
||||
.page-load { |
||||
position: fixed; |
||||
width: 100%; |
||||
height: 100%; |
||||
top: 0; |
||||
left: 0; |
||||
background: #32393F; |
||||
z-index: 100; |
||||
transition: opacity 200ms; |
||||
-webkit-transition: opacity 200ms; |
||||
} |
||||
|
||||
.pl-0{ |
||||
opacity: 0; |
||||
} |
||||
|
||||
.pl-1 { |
||||
display: none; |
||||
} |
||||
|
||||
.pl-inner { |
||||
position: absolute; |
||||
width: 100px; |
||||
height: 100px; |
||||
left: 50%; |
||||
margin-left: -50px; |
||||
top: 50%; |
||||
margin-top: -50px; |
||||
text-align: center; |
||||
-webkit-animation: fade-in 500ms; |
||||
animation: fade-in 500ms; |
||||
-webkit-animation-fill-mode: both; |
||||
animation-fill-mode: both; |
||||
animation-delay: 350ms; |
||||
-webkit-animation-delay: 350ms; |
||||
-webkit-backface-visibility: visible; |
||||
backface-visibility: visible; |
||||
} |
||||
|
||||
.pl-inner:before { |
||||
content: ''; |
||||
position: absolute; |
||||
width: 100%; |
||||
height: 100%; |
||||
left: 0; |
||||
top: 0; |
||||
display: block; |
||||
-webkit-animation: spin 1000ms infinite linear; |
||||
animation: spin 1000ms infinite linear; |
||||
border: 1px solid rgba(255, 255, 255, 0.2);; |
||||
border-left-color: #fff; |
||||
border-radius: 50%; |
||||
} |
||||
|
||||
.pl-inner > img { |
||||
width: 30px; |
||||
margin-top: 28px; |
||||
} |
||||
|
||||
@-webkit-keyframes fade-in { |
||||
0% { |
||||
opacity: 0; |
||||
} |
||||
100% { |
||||
opacity: 1; |
||||
} |
||||
} |
||||
|
||||
@keyframes fade-in { |
||||
0% { |
||||
opacity: 0; |
||||
} |
||||
100% { |
||||
opacity: 1; |
||||
} |
||||
} |
||||
|
||||
@-webkit-keyframes spin { |
||||
0% { |
||||
-webkit-transform: rotate(0deg); |
||||
transform: rotate(0deg); |
||||
} |
||||
100% { |
||||
-webkit-transform: rotate(360deg); |
||||
transform: rotate(360deg); |
||||
} |
||||
} |
||||
|
||||
@keyframes spin { |
||||
0% { |
||||
-webkit-transform: rotate(0deg); |
||||
transform: rotate(0deg); |
||||
} |
||||
100% { |
||||
-webkit-transform: rotate(360deg); |
||||
transform: rotate(360deg); |
||||
} |
||||
} |
After Width: | Height: | Size: 797 B |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 894 B |
After Width: | Height: | Size: 261 B |
After Width: | Height: | Size: 586 B |
@ -0,0 +1,56 @@ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
|
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
<title>Minio Browser</title> |
||||
<link rel="stylesheet" href="/minio/loader.css" type="text/css"> |
||||
</head> |
||||
|
||||
<body> |
||||
<div class="page-load"> |
||||
<div class="pl-inner"> |
||||
<img src="/minio/logo.svg" alt=""> |
||||
</div> |
||||
</div> |
||||
<div id="root"></div> |
||||
|
||||
<!--[if lt IE 11]> |
||||
<div class="ie-warning"> |
||||
<div class="iw-inner"> |
||||
<i class="iwi-icon fa fa-warning"></i> |
||||
|
||||
You are using Internet Explorer version 12.0 or lower. Due to security issues and lack of support for Web Standards it is highly recommended that you upgrade to a modern browser |
||||
|
||||
|
||||
<ul> |
||||
<li> |
||||
<a href="http://www.google.com/chrome/"> |
||||
<img src="/minio/chrome.png" alt=""> |
||||
<div>Chrome</div> |
||||
</a> |
||||
</li> |
||||
<li> |
||||
<a href="https://www.mozilla.org/en-US/firefox/new/"> |
||||
<img src="/minio/firefox.png" alt=""> |
||||
<div>Firefox</div> |
||||
</a> |
||||
</li> |
||||
<li> |
||||
<a href="https://www.apple.com/safari/"> |
||||
<img src="/minio/safari.png" alt=""> |
||||
<div>Safari</div> |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
|
||||
<div class="iwi-skip">Skip & Continue</div> |
||||
</div> |
||||
</div> |
||||
<![endif]--> |
||||
|
||||
<script>currentUiVersion = 'MINIO_UI_VERSION'</script> |
||||
<script src="/minio/index_bundle.js"></script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,116 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import './less/main.less' |
||||
|
||||
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 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 Provider from 'react-redux/lib/components/Provider' |
||||
import connect from 'react-redux/lib/components/connect' |
||||
|
||||
import Moment from 'moment' |
||||
|
||||
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 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='minio' 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') |
||||
} |
@ -0,0 +1,43 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import expect from 'expect'; |
||||
import JSONrpc from '../jsonrpc'; |
||||
|
||||
describe('jsonrpc', () => { |
||||
it('should fail with invalid endpoint', (done) => { |
||||
try { |
||||
let jsonRPC = new JSONrpc({ |
||||
endpoint: 'htt://localhost:9000', |
||||
namespace: 'Test' |
||||
}); |
||||
} catch (e) { |
||||
done(); |
||||
} |
||||
}); |
||||
it('should succeed with valid endpoint', () => { |
||||
let jsonRPC = new JSONrpc({ |
||||
endpoint: 'http://localhost:9000/webrpc', |
||||
namespace: 'Test' |
||||
}); |
||||
expect(jsonRPC.version).toEqual('2.0'); |
||||
expect(jsonRPC.host).toEqual('localhost'); |
||||
expect(jsonRPC.port).toEqual('9000'); |
||||
expect(jsonRPC.path).toEqual('/webrpc'); |
||||
expect(jsonRPC.scheme).toEqual('http'); |
||||
}); |
||||
}); |
||||
|
@ -0,0 +1,509 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import url from 'url' |
||||
import Moment from 'moment' |
||||
import web from './web' |
||||
import * as utils from './utils' |
||||
import storage from 'local-storage-fallback' |
||||
|
||||
export const SET_WEB = 'SET_WEB' |
||||
export const SET_CURRENT_BUCKET = 'SET_CURRENT_BUCKET' |
||||
export const SET_CURRENT_PATH = 'SET_CURRENT_PATH' |
||||
export const SET_BUCKETS = 'SET_BUCKETS' |
||||
export const ADD_BUCKET = 'ADD_BUCKET' |
||||
export const ADD_OBJECT = 'ADD_OBJECT' |
||||
export const SET_VISIBLE_BUCKETS = 'SET_VISIBLE_BUCKETS' |
||||
export const SET_OBJECTS = 'SET_OBJECTS' |
||||
export const SET_STORAGE_INFO = 'SET_STORAGE_INFO' |
||||
export const SET_SERVER_INFO = 'SET_SERVER_INFO' |
||||
export const SHOW_MAKEBUCKET_MODAL = 'SHOW_MAKEBUCKET_MODAL' |
||||
export const ADD_UPLOAD = 'ADD_UPLOAD' |
||||
export const STOP_UPLOAD = 'STOP_UPLOAD' |
||||
export const UPLOAD_PROGRESS = 'UPLOAD_PROGRESS' |
||||
export const SET_ALERT = 'SET_ALERT' |
||||
export const SET_LOGIN_ERROR = 'SET_LOGIN_ERROR' |
||||
export const SET_SHOW_ABORT_MODAL = 'SET_SHOW_ABORT_MODAL' |
||||
export const SHOW_ABOUT = 'SHOW_ABOUT' |
||||
export const SET_SORT_NAME_ORDER = 'SET_SORT_NAME_ORDER' |
||||
export const SET_SORT_SIZE_ORDER = 'SET_SORT_SIZE_ORDER' |
||||
export const SET_SORT_DATE_ORDER = 'SET_SORT_DATE_ORDER' |
||||
export const SET_LATEST_UI_VERSION = 'SET_LATEST_UI_VERSION' |
||||
export const SET_SIDEBAR_STATUS = 'SET_SIDEBAR_STATUS' |
||||
export const SET_LOGIN_REDIRECT_PATH = 'SET_LOGIN_REDIRECT_PATH' |
||||
export const SET_LOAD_BUCKET = 'SET_LOAD_BUCKET' |
||||
export const SET_LOAD_PATH = 'SET_LOAD_PATH' |
||||
export const SHOW_SETTINGS = 'SHOW_SETTINGS' |
||||
export const SET_SETTINGS = 'SET_SETTINGS' |
||||
export const SHOW_BUCKET_POLICY = 'SHOW_BUCKET_POLICY' |
||||
export const SET_POLICIES = 'SET_POLICIES' |
||||
export const SET_SHARE_OBJECT = 'SET_SHARE_OBJECT' |
||||
export const DELETE_CONFIRMATION = 'DELETE_CONFIRMATION' |
||||
export const SET_PREFIX_WRITABLE = 'SET_PREFIX_WRITABLE' |
||||
|
||||
export const showDeleteConfirmation = (object) => { |
||||
return { |
||||
type: DELETE_CONFIRMATION, |
||||
payload: { |
||||
object, |
||||
show: true |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const hideDeleteConfirmation = () => { |
||||
return { |
||||
type: DELETE_CONFIRMATION, |
||||
payload: { |
||||
object: '', |
||||
show: false |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const showShareObject = url => { |
||||
return { |
||||
type: SET_SHARE_OBJECT, |
||||
shareObject: { |
||||
url: url, |
||||
show: true |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const hideShareObject = () => { |
||||
return { |
||||
type: SET_SHARE_OBJECT, |
||||
shareObject: { |
||||
url: '', |
||||
show: false |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const shareObject = (object, expiry) => (dispatch, getState) => { |
||||
const {currentBucket, web} = getState() |
||||
let host = location.host |
||||
let bucket = currentBucket |
||||
|
||||
if (!web.LoggedIn()) { |
||||
dispatch(showShareObject(`${host}/${bucket}/${object}`)) |
||||
return |
||||
} |
||||
web.PresignedGet({ |
||||
host, |
||||
bucket, |
||||
object, |
||||
expiry |
||||
}) |
||||
.then(obj => { |
||||
dispatch(showShareObject(obj.url)) |
||||
}) |
||||
.catch(err => { |
||||
dispatch(showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
})) |
||||
}) |
||||
} |
||||
|
||||
export const setLoginRedirectPath = (path) => { |
||||
return { |
||||
type: SET_LOGIN_REDIRECT_PATH, |
||||
path |
||||
} |
||||
} |
||||
|
||||
export const setLoadPath = (loadPath) => { |
||||
return { |
||||
type: SET_LOAD_PATH, |
||||
loadPath |
||||
} |
||||
} |
||||
|
||||
export const setLoadBucket = (loadBucket) => { |
||||
return { |
||||
type: SET_LOAD_BUCKET, |
||||
loadBucket |
||||
} |
||||
} |
||||
|
||||
export const setWeb = web => { |
||||
return { |
||||
type: SET_WEB, |
||||
web |
||||
} |
||||
} |
||||
|
||||
export const setBuckets = buckets => { |
||||
return { |
||||
type: SET_BUCKETS, |
||||
buckets |
||||
} |
||||
} |
||||
|
||||
export const addBucket = bucket => { |
||||
return { |
||||
type: ADD_BUCKET, |
||||
bucket |
||||
} |
||||
} |
||||
|
||||
export const showMakeBucketModal = () => { |
||||
return { |
||||
type: SHOW_MAKEBUCKET_MODAL, |
||||
showMakeBucketModal: true |
||||
} |
||||
} |
||||
|
||||
export const hideAlert = () => { |
||||
return { |
||||
type: SET_ALERT, |
||||
alert: { |
||||
show: false, |
||||
message: '', |
||||
type: '' |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const showAlert = alert => { |
||||
return (dispatch, getState) => { |
||||
let alertTimeout = null |
||||
if (alert.type !== 'danger') { |
||||
alertTimeout = setTimeout(() => { |
||||
dispatch({ |
||||
type: SET_ALERT, |
||||
alert: { |
||||
show: false |
||||
} |
||||
}) |
||||
}, 5000) |
||||
} |
||||
dispatch({ |
||||
type: SET_ALERT, |
||||
alert: Object.assign({}, alert, { |
||||
show: true, |
||||
alertTimeout |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
export const setSidebarStatus = (status) => { |
||||
return { |
||||
type: SET_SIDEBAR_STATUS, |
||||
sidebarStatus: status |
||||
} |
||||
} |
||||
|
||||
export const hideMakeBucketModal = () => { |
||||
return { |
||||
type: SHOW_MAKEBUCKET_MODAL, |
||||
showMakeBucketModal: false |
||||
} |
||||
} |
||||
|
||||
export const setVisibleBuckets = visibleBuckets => { |
||||
return { |
||||
type: SET_VISIBLE_BUCKETS, |
||||
visibleBuckets |
||||
} |
||||
} |
||||
|
||||
export const setObjects = (objects) => { |
||||
return { |
||||
type: SET_OBJECTS, |
||||
objects |
||||
} |
||||
} |
||||
|
||||
export const setCurrentBucket = currentBucket => { |
||||
return { |
||||
type: SET_CURRENT_BUCKET, |
||||
currentBucket |
||||
} |
||||
} |
||||
|
||||
export const setCurrentPath = currentPath => { |
||||
return { |
||||
type: SET_CURRENT_PATH, |
||||
currentPath |
||||
} |
||||
} |
||||
|
||||
export const setStorageInfo = storageInfo => { |
||||
return { |
||||
type: SET_STORAGE_INFO, |
||||
storageInfo |
||||
} |
||||
} |
||||
|
||||
export const setServerInfo = serverInfo => { |
||||
return { |
||||
type: SET_SERVER_INFO, |
||||
serverInfo |
||||
} |
||||
} |
||||
|
||||
const setPrefixWritable = prefixWritable => { |
||||
return { |
||||
type: SET_PREFIX_WRITABLE, |
||||
prefixWritable, |
||||
} |
||||
} |
||||
|
||||
export const selectBucket = (newCurrentBucket, prefix) => { |
||||
if (!prefix) |
||||
prefix = '' |
||||
return (dispatch, getState) => { |
||||
let web = getState().web |
||||
let currentBucket = getState().currentBucket |
||||
|
||||
if (currentBucket !== newCurrentBucket) dispatch(setLoadBucket(newCurrentBucket)) |
||||
|
||||
dispatch(setCurrentBucket(newCurrentBucket)) |
||||
dispatch(selectPrefix(prefix)) |
||||
return |
||||
} |
||||
} |
||||
|
||||
export const selectPrefix = prefix => { |
||||
return (dispatch, getState) => { |
||||
const {currentBucket, web} = getState() |
||||
dispatch(setLoadPath(prefix)) |
||||
web.ListObjects({ |
||||
bucketName: currentBucket, |
||||
prefix |
||||
}) |
||||
.then(res => { |
||||
let objects = res.objects |
||||
if (!objects) |
||||
objects = [] |
||||
dispatch(setObjects( |
||||
utils.sortObjectsByName(objects.map(object => { |
||||
object.name = object.name.replace(`${prefix}`, ''); return object |
||||
})) |
||||
)) |
||||
dispatch(setPrefixWritable(res.writable)) |
||||
dispatch(setSortNameOrder(false)) |
||||
dispatch(setCurrentPath(prefix)) |
||||
dispatch(setLoadBucket('')) |
||||
dispatch(setLoadPath('')) |
||||
}) |
||||
.catch(err => { |
||||
dispatch(showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
})) |
||||
dispatch(setLoadBucket('')) |
||||
dispatch(setLoadPath('')) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
export const addUpload = options => { |
||||
return { |
||||
type: ADD_UPLOAD, |
||||
slug: options.slug, |
||||
size: options.size, |
||||
xhr: options.xhr, |
||||
name: options.name |
||||
} |
||||
} |
||||
|
||||
export const stopUpload = options => { |
||||
return { |
||||
type: STOP_UPLOAD, |
||||
slug: options.slug |
||||
} |
||||
} |
||||
|
||||
export const uploadProgress = options => { |
||||
return { |
||||
type: UPLOAD_PROGRESS, |
||||
slug: options.slug, |
||||
loaded: options.loaded |
||||
} |
||||
} |
||||
|
||||
export const setShowAbortModal = showAbortModal => { |
||||
return { |
||||
type: SET_SHOW_ABORT_MODAL, |
||||
showAbortModal |
||||
} |
||||
} |
||||
|
||||
export const setLoginError = () => { |
||||
return { |
||||
type: SET_LOGIN_ERROR, |
||||
loginError: true |
||||
} |
||||
} |
||||
|
||||
export const uploadFile = (file, xhr) => { |
||||
return (dispatch, getState) => { |
||||
const {currentBucket, currentPath} = getState() |
||||
const objectName = `${currentPath}${file.name}` |
||||
const uploadUrl = `${window.location.origin}/minio/upload/${currentBucket}/${objectName}` |
||||
// The slug is a unique identifer for the file upload.
|
||||
const slug = `${currentBucket}-${currentPath}-${file.name}` |
||||
|
||||
xhr.open('PUT', uploadUrl, true) |
||||
xhr.withCredentials = false |
||||
const token = storage.getItem('token') |
||||
if (token) xhr.setRequestHeader("Authorization", 'Bearer ' + storage.getItem('token')) |
||||
xhr.setRequestHeader('x-amz-date', Moment().utc().format('YYYYMMDDTHHmmss') + 'Z') |
||||
dispatch(addUpload({ |
||||
slug, |
||||
xhr, |
||||
size: file.size, |
||||
name: file.name |
||||
})) |
||||
|
||||
xhr.onload = function(event) { |
||||
if (xhr.status == 401 || xhr.status == 403 || xhr.status == 500) { |
||||
setShowAbortModal(false) |
||||
dispatch(stopUpload({ |
||||
slug |
||||
})) |
||||
dispatch(showAlert({ |
||||
type: 'danger', |
||||
message: 'Unauthorized request.' |
||||
})) |
||||
} |
||||
if (xhr.status == 200) { |
||||
setShowAbortModal(false) |
||||
dispatch(stopUpload({ |
||||
slug |
||||
})) |
||||
dispatch(showAlert({ |
||||
type: 'success', |
||||
message: 'File \'' + file.name + '\' uploaded successfully.' |
||||
})) |
||||
dispatch(selectPrefix(currentPath)) |
||||
} |
||||
} |
||||
|
||||
xhr.upload.addEventListener('error', event => { |
||||
dispatch(showAlert({ |
||||
type: 'danger', |
||||
message: 'Error occurred uploading \'' + file.name + '\'.' |
||||
})) |
||||
dispatch(stopUpload({ |
||||
slug |
||||
})) |
||||
}) |
||||
|
||||
xhr.upload.addEventListener('progress', event => { |
||||
if (event.lengthComputable) { |
||||
let loaded = event.loaded |
||||
let total = event.total |
||||
|
||||
// Update the counter.
|
||||
dispatch(uploadProgress({ |
||||
slug, |
||||
loaded |
||||
})) |
||||
} |
||||
}) |
||||
xhr.send(file) |
||||
} |
||||
} |
||||
|
||||
export const showAbout = () => { |
||||
return { |
||||
type: SHOW_ABOUT, |
||||
showAbout: true |
||||
} |
||||
} |
||||
|
||||
export const hideAbout = () => { |
||||
return { |
||||
type: SHOW_ABOUT, |
||||
showAbout: false |
||||
} |
||||
} |
||||
|
||||
export const setSortNameOrder = (sortNameOrder) => { |
||||
return { |
||||
type: SET_SORT_NAME_ORDER, |
||||
sortNameOrder |
||||
} |
||||
} |
||||
|
||||
export const setSortSizeOrder = (sortSizeOrder) => { |
||||
return { |
||||
type: SET_SORT_SIZE_ORDER, |
||||
sortSizeOrder |
||||
} |
||||
} |
||||
|
||||
export const setSortDateOrder = (sortDateOrder) => { |
||||
return { |
||||
type: SET_SORT_DATE_ORDER, |
||||
sortDateOrder |
||||
} |
||||
} |
||||
|
||||
export const setLatestUIVersion = (latestUiVersion) => { |
||||
return { |
||||
type: SET_LATEST_UI_VERSION, |
||||
latestUiVersion |
||||
} |
||||
} |
||||
|
||||
export const showSettings = () => { |
||||
return { |
||||
type: SHOW_SETTINGS, |
||||
showSettings: true |
||||
} |
||||
} |
||||
|
||||
export const hideSettings = () => { |
||||
return { |
||||
type: SHOW_SETTINGS, |
||||
showSettings: false |
||||
} |
||||
} |
||||
|
||||
export const setSettings = (settings) => { |
||||
return { |
||||
type: SET_SETTINGS, |
||||
settings |
||||
} |
||||
} |
||||
|
||||
export const showBucketPolicy = () => { |
||||
return { |
||||
type: SHOW_BUCKET_POLICY, |
||||
showBucketPolicy: true |
||||
} |
||||
} |
||||
|
||||
export const hideBucketPolicy = () => { |
||||
return { |
||||
type: SHOW_BUCKET_POLICY, |
||||
showBucketPolicy: false |
||||
} |
||||
} |
||||
|
||||
export const setPolicies = (policies) => { |
||||
return { |
||||
type: SET_POLICIES, |
||||
policies |
||||
} |
||||
} |
@ -0,0 +1,734 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import classNames from 'classnames' |
||||
import browserHistory from 'react-router/lib/browserHistory' |
||||
import humanize from 'humanize' |
||||
import Moment from 'moment' |
||||
import Modal from 'react-bootstrap/lib/Modal' |
||||
import ModalBody from 'react-bootstrap/lib/ModalBody' |
||||
import ModalHeader from 'react-bootstrap/lib/ModalHeader' |
||||
import Alert from 'react-bootstrap/lib/Alert' |
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger' |
||||
import Tooltip from 'react-bootstrap/lib/Tooltip' |
||||
import Dropdown from 'react-bootstrap/lib/Dropdown' |
||||
import MenuItem from 'react-bootstrap/lib/MenuItem' |
||||
|
||||
import InputGroup from '../components/InputGroup' |
||||
import Dropzone from '../components/Dropzone' |
||||
import ObjectsList from '../components/ObjectsList' |
||||
import SideBar from '../components/SideBar' |
||||
import Path from '../components/Path' |
||||
import BrowserUpdate from '../components/BrowserUpdate' |
||||
import UploadModal from '../components/UploadModal' |
||||
import SettingsModal from '../components/SettingsModal' |
||||
import PolicyInput from '../components/PolicyInput' |
||||
import Policy from '../components/Policy' |
||||
import BrowserDropdown from '../components/BrowserDropdown' |
||||
import ConfirmModal from './ConfirmModal' |
||||
import logo from '../../img/logo.svg' |
||||
import * as actions from '../actions' |
||||
import * as utils from '../utils' |
||||
import * as mime from '../mime' |
||||
import { minioBrowserPrefix } from '../constants' |
||||
import CopyToClipboard from 'react-copy-to-clipboard' |
||||
import storage from 'local-storage-fallback' |
||||
|
||||
export default class Browse extends React.Component { |
||||
componentDidMount() { |
||||
const {web, dispatch, currentBucket} = this.props |
||||
if (!web.LoggedIn()) return |
||||
web.StorageInfo() |
||||
.then(res => { |
||||
let storageInfo = Object.assign({}, { |
||||
total: res.storageInfo.Total, |
||||
free: res.storageInfo.Free |
||||
}) |
||||
storageInfo.used = storageInfo.total - storageInfo.free |
||||
dispatch(actions.setStorageInfo(storageInfo)) |
||||
return web.ServerInfo() |
||||
}) |
||||
.then(res => { |
||||
let serverInfo = Object.assign({}, { |
||||
version: res.MinioVersion, |
||||
memory: res.MinioMemory, |
||||
platform: res.MinioPlatform, |
||||
runtime: res.MinioRuntime, |
||||
envVars: res.MinioEnvVars |
||||
}) |
||||
dispatch(actions.setServerInfo(serverInfo)) |
||||
}) |
||||
.catch(err => { |
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
})) |
||||
}) |
||||
} |
||||
|
||||
componentWillMount() { |
||||
const {dispatch} = this.props |
||||
// Clear out any stale message in the alert of Login page
|
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: '' |
||||
})) |
||||
if (web.LoggedIn()) { |
||||
web.ListBuckets() |
||||
.then(res => { |
||||
let buckets |
||||
if (!res.buckets) |
||||
buckets = [] |
||||
else |
||||
buckets = res.buckets.map(bucket => bucket.name) |
||||
if (buckets.length) { |
||||
dispatch(actions.setBuckets(buckets)) |
||||
dispatch(actions.setVisibleBuckets(buckets)) |
||||
if (location.pathname === minioBrowserPrefix || location.pathname === minioBrowserPrefix + '/') { |
||||
browserHistory.push(utils.pathJoin(buckets[0])) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
this.history = browserHistory.listen(({pathname}) => { |
||||
let decPathname = decodeURI(pathname) |
||||
if (decPathname === `${minioBrowserPrefix}/login`) return // FIXME: better organize routes and remove this
|
||||
if (!decPathname.endsWith('/')) |
||||
decPathname += '/' |
||||
if (decPathname === minioBrowserPrefix + '/') { |
||||
dispatch(actions.setCurrentBucket('')) |
||||
dispatch(actions.setCurrentPath('')) |
||||
dispatch(actions.setObjects([])) |
||||
return |
||||
} |
||||
let obj = utils.pathSlice(decPathname) |
||||
if (!web.LoggedIn()) { |
||||
dispatch(actions.setBuckets([obj.bucket])) |
||||
dispatch(actions.setVisibleBuckets([obj.bucket])) |
||||
} |
||||
dispatch(actions.selectBucket(obj.bucket, obj.prefix)) |
||||
}) |
||||
} |
||||
|
||||
componentWillUnmount() { |
||||
this.history() |
||||
} |
||||
|
||||
selectBucket(e, bucket) { |
||||
e.preventDefault() |
||||
if (bucket === this.props.currentBucket) return |
||||
browserHistory.push(utils.pathJoin(bucket)) |
||||
} |
||||
|
||||
searchBuckets(e) { |
||||
e.preventDefault() |
||||
let {buckets} = this.props |
||||
this.props.dispatch(actions.setVisibleBuckets(buckets.filter(bucket => bucket.indexOf(e.target.value) > -1))) |
||||
} |
||||
|
||||
selectPrefix(e, prefix) { |
||||
e.preventDefault() |
||||
const {dispatch, currentPath, web, currentBucket} = this.props |
||||
const encPrefix = encodeURI(prefix) |
||||
if (prefix.endsWith('/') || prefix === '') { |
||||
if (prefix === currentPath) return |
||||
browserHistory.push(utils.pathJoin(currentBucket, encPrefix)) |
||||
} else { |
||||
window.location = `${window.location.origin}/minio/download/${currentBucket}/${encPrefix}?token=${storage.getItem('token')}` |
||||
} |
||||
} |
||||
|
||||
makeBucket(e) { |
||||
e.preventDefault() |
||||
const bucketName = this.refs.makeBucketRef.value |
||||
this.refs.makeBucketRef.value = '' |
||||
const {web, dispatch} = this.props |
||||
this.hideMakeBucketModal() |
||||
web.MakeBucket({ |
||||
bucketName |
||||
}) |
||||
.then(() => { |
||||
dispatch(actions.addBucket(bucketName)) |
||||
dispatch(actions.selectBucket(bucketName)) |
||||
}) |
||||
.catch(err => dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
}))) |
||||
} |
||||
|
||||
hideMakeBucketModal() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideMakeBucketModal()) |
||||
} |
||||
|
||||
showMakeBucketModal(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.showMakeBucketModal()) |
||||
} |
||||
|
||||
showAbout(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.showAbout()) |
||||
} |
||||
|
||||
hideAbout(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideAbout()) |
||||
} |
||||
|
||||
showBucketPolicy(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.showBucketPolicy()) |
||||
} |
||||
|
||||
hideBucketPolicy(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideBucketPolicy()) |
||||
} |
||||
|
||||
uploadFile(e) { |
||||
e.preventDefault() |
||||
const {dispatch, buckets} = this.props |
||||
|
||||
if (buckets.length === 0) { |
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: "Bucket needs to be created before trying to upload files." |
||||
})) |
||||
return |
||||
} |
||||
let file = e.target.files[0] |
||||
e.target.value = null |
||||
this.xhr = new XMLHttpRequest() |
||||
dispatch(actions.uploadFile(file, this.xhr)) |
||||
} |
||||
|
||||
removeObject() { |
||||
const {web, dispatch, currentPath, currentBucket, deleteConfirmation} = this.props |
||||
web.RemoveObject({ |
||||
bucketName: currentBucket, |
||||
objectName: deleteConfirmation.object |
||||
}) |
||||
.then(() => { |
||||
this.hideDeleteConfirmation() |
||||
dispatch(actions.selectPrefix(currentPath)) |
||||
}) |
||||
.catch(e => dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: e.message |
||||
}))) |
||||
} |
||||
|
||||
hideAlert(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideAlert()) |
||||
} |
||||
|
||||
showDeleteConfirmation(e, object) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.showDeleteConfirmation(object)) |
||||
} |
||||
|
||||
hideDeleteConfirmation() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideDeleteConfirmation()) |
||||
} |
||||
|
||||
shareObject(e, object) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.shareObject(object)) |
||||
} |
||||
|
||||
hideShareObjectModal() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideShareObject()) |
||||
} |
||||
|
||||
dataType(name, contentType) { |
||||
return mime.getDataType(name, contentType) |
||||
} |
||||
|
||||
sortObjectsByName(e) { |
||||
const {dispatch, objects, sortNameOrder} = this.props |
||||
dispatch(actions.setObjects(utils.sortObjectsByName(objects, !sortNameOrder))) |
||||
dispatch(actions.setSortNameOrder(!sortNameOrder)) |
||||
} |
||||
|
||||
sortObjectsBySize() { |
||||
const {dispatch, objects, sortSizeOrder} = this.props |
||||
dispatch(actions.setObjects(utils.sortObjectsBySize(objects, !sortSizeOrder))) |
||||
dispatch(actions.setSortSizeOrder(!sortSizeOrder)) |
||||
} |
||||
|
||||
sortObjectsByDate() { |
||||
const {dispatch, objects, sortDateOrder} = this.props |
||||
dispatch(actions.setObjects(utils.sortObjectsByDate(objects, !sortDateOrder))) |
||||
dispatch(actions.setSortDateOrder(!sortDateOrder)) |
||||
} |
||||
|
||||
logout(e) { |
||||
const {web} = this.props |
||||
e.preventDefault() |
||||
web.Logout() |
||||
browserHistory.push(`${minioBrowserPrefix}/login`) |
||||
} |
||||
|
||||
landingPage(e) { |
||||
e.preventDefault() |
||||
this.props.dispatch(actions.selectBucket(this.props.buckets[0])) |
||||
} |
||||
|
||||
fullScreen(e) { |
||||
e.preventDefault() |
||||
let el = document.documentElement |
||||
if (el.requestFullscreen) { |
||||
el.requestFullscreen() |
||||
} |
||||
if (el.mozRequestFullScreen) { |
||||
el.mozRequestFullScreen() |
||||
} |
||||
if (el.webkitRequestFullscreen) { |
||||
el.webkitRequestFullscreen() |
||||
} |
||||
if (el.msRequestFullscreen) { |
||||
el.msRequestFullscreen() |
||||
} |
||||
} |
||||
|
||||
toggleSidebar(status) { |
||||
this.props.dispatch(actions.setSidebarStatus(status)) |
||||
} |
||||
|
||||
hideSidebar(event) { |
||||
let e = event || window.event; |
||||
|
||||
// Support all browsers.
|
||||
let target = e.srcElement || e.target; |
||||
if (target.nodeType === 3) // Safari support.
|
||||
target = target.parentNode; |
||||
|
||||
let targetID = target.id; |
||||
if (!(targetID === 'feh-trigger')) { |
||||
this.props.dispatch(actions.setSidebarStatus(false)) |
||||
} |
||||
} |
||||
|
||||
showSettings(e) { |
||||
e.preventDefault() |
||||
|
||||
const {dispatch} = this.props |
||||
dispatch(actions.showSettings()) |
||||
} |
||||
|
||||
showMessage() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.showAlert({ |
||||
type: 'success', |
||||
message: 'Link copied to clipboard!' |
||||
})) |
||||
this.hideShareObjectModal() |
||||
} |
||||
|
||||
selectTexts() { |
||||
this.refs.copyTextInput.select() |
||||
} |
||||
|
||||
handleExpireValue(targetInput, inc) { |
||||
inc === -1 ? this.refs[targetInput].stepDown(1) : this.refs[targetInput].stepUp(1) |
||||
|
||||
if (this.refs.expireDays.value == 7) { |
||||
this.refs.expireHours.value = 0 |
||||
this.refs.expireMins.value = 0 |
||||
} |
||||
} |
||||
|
||||
|
||||
render() { |
||||
const {total, free} = this.props.storageInfo |
||||
const {showMakeBucketModal, alert, sortNameOrder, sortSizeOrder, sortDateOrder, showAbout, showBucketPolicy} = this.props |
||||
const {version, memory, platform, runtime} = this.props.serverInfo |
||||
const {sidebarStatus} = this.props |
||||
const {showSettings} = this.props |
||||
const {policies, currentBucket, currentPath} = this.props |
||||
const {deleteConfirmation} = this.props |
||||
const {shareObject} = this.props |
||||
const {web, prefixWritable} = this.props |
||||
|
||||
// Don't always show the SettingsModal. This is done here instead of in
|
||||
// SettingsModal.js so as to allow for #componentWillMount to handle
|
||||
// the loading of the settings.
|
||||
let settingsModal = showSettings ? <SettingsModal /> : <noscript></noscript> |
||||
|
||||
let alertBox = <Alert className={ classNames({ |
||||
'alert': true, |
||||
'animated': true, |
||||
'fadeInDown': alert.show, |
||||
'fadeOutUp': !alert.show |
||||
}) } bsStyle={ alert.type } onDismiss={ this.hideAlert.bind(this) }> |
||||
<div className='text-center'> |
||||
{ alert.message } |
||||
</div> |
||||
</Alert> |
||||
// Make sure you don't show a fading out alert box on the initial web-page load.
|
||||
if (!alert.message) |
||||
alertBox = '' |
||||
|
||||
let signoutTooltip = <Tooltip id="tt-sign-out"> |
||||
Sign out |
||||
</Tooltip> |
||||
let uploadTooltip = <Tooltip id="tt-upload-file"> |
||||
Upload file |
||||
</Tooltip> |
||||
let makeBucketTooltip = <Tooltip id="tt-create-bucket"> |
||||
Create bucket |
||||
</Tooltip> |
||||
let loginButton = '' |
||||
let browserDropdownButton = '' |
||||
let storageUsageDetails = '' |
||||
|
||||
let used = total - free |
||||
let usedPercent = (used / total) * 100 + '%' |
||||
let freePercent = free * 100 / total |
||||
|
||||
if (web.LoggedIn()) { |
||||
browserDropdownButton = <BrowserDropdown fullScreen={ this.fullScreen.bind(this) } |
||||
showAbout={ this.showAbout.bind(this) } |
||||
showSettings={ this.showSettings.bind(this) } |
||||
logout={ this.logout.bind(this) } /> |
||||
} else { |
||||
loginButton = <a className='btn btn-danger' href='/minio/login'>Login</a> |
||||
} |
||||
|
||||
if (web.LoggedIn()) { |
||||
storageUsageDetails = <div className="feh-usage"> |
||||
<div className="fehu-chart"> |
||||
<div style={ { width: usedPercent } }></div> |
||||
</div> |
||||
<ul> |
||||
<li> |
||||
Used: |
||||
{ humanize.filesize(total - free) } |
||||
</li> |
||||
<li className="pull-right"> |
||||
Free: |
||||
{ humanize.filesize(total - used) } |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
|
||||
} |
||||
|
||||
let createButton = '' |
||||
if (web.LoggedIn()) { |
||||
createButton = <Dropdown dropup className="feb-actions" id="fe-action-toggle"> |
||||
<Dropdown.Toggle noCaret className="feba-toggle"> |
||||
<span><i className="fa fa-plus"></i></span> |
||||
</Dropdown.Toggle> |
||||
<Dropdown.Menu> |
||||
<OverlayTrigger placement="left" overlay={ uploadTooltip }> |
||||
<a href="#" className="feba-btn feba-upload"> |
||||
<input type="file" |
||||
onChange={ this.uploadFile.bind(this) } |
||||
style={ { display: 'none' } } |
||||
id="file-input"></input> |
||||
<label htmlFor="file-input"> <i className="fa fa-cloud-upload"></i> </label> |
||||
</a> |
||||
</OverlayTrigger> |
||||
<OverlayTrigger placement="left" overlay={ makeBucketTooltip }> |
||||
<a href="#" className="feba-btn feba-bucket" onClick={ this.showMakeBucketModal.bind(this) }><i className="fa fa-hdd-o"></i></a> |
||||
</OverlayTrigger> |
||||
</Dropdown.Menu> |
||||
</Dropdown> |
||||
|
||||
} else { |
||||
if (prefixWritable) |
||||
createButton = <Dropdown dropup className="feb-actions" id="fe-action-toggle"> |
||||
<Dropdown.Toggle noCaret className="feba-toggle"> |
||||
<span><i className="fa fa-plus"></i></span> |
||||
</Dropdown.Toggle> |
||||
<Dropdown.Menu> |
||||
<OverlayTrigger placement="left" overlay={ uploadTooltip }> |
||||
<a href="#" className="feba-btn feba-upload"> |
||||
<input type="file" |
||||
onChange={ this.uploadFile.bind(this) } |
||||
style={ { display: 'none' } } |
||||
id="file-input"></input> |
||||
<label htmlFor="file-input"> <i className="fa fa-cloud-upload"></i> </label> |
||||
</a> |
||||
</OverlayTrigger> |
||||
</Dropdown.Menu> |
||||
</Dropdown> |
||||
|
||||
} |
||||
|
||||
return ( |
||||
<div className={ classNames({ |
||||
'file-explorer': true, |
||||
'toggled': sidebarStatus |
||||
}) }> |
||||
<SideBar landingPage={ this.landingPage.bind(this) } |
||||
searchBuckets={ this.searchBuckets.bind(this) } |
||||
selectBucket={ this.selectBucket.bind(this) } |
||||
clickOutside={ this.hideSidebar.bind(this) } |
||||
showPolicy={ this.showBucketPolicy.bind(this) } /> |
||||
<div className="fe-body"> |
||||
<Dropzone> |
||||
{ alertBox } |
||||
<header className="fe-header-mobile hidden-lg hidden-md"> |
||||
<div id="feh-trigger" className={ 'feh-trigger ' + (classNames({ |
||||
'feht-toggled': sidebarStatus |
||||
})) } onClick={ this.toggleSidebar.bind(this, !sidebarStatus) }> |
||||
<div className="feht-lines"> |
||||
<div className="top"></div> |
||||
<div className="center"></div> |
||||
<div className="bottom"></div> |
||||
</div> |
||||
</div> |
||||
<img className="mh-logo" src={ logo } alt="" /> |
||||
</header> |
||||
<header className="fe-header"> |
||||
<Path selectPrefix={ this.selectPrefix.bind(this) } /> |
||||
{ storageUsageDetails } |
||||
<ul className="feh-actions"> |
||||
<BrowserUpdate /> |
||||
{ loginButton } |
||||
{ browserDropdownButton } |
||||
</ul> |
||||
</header> |
||||
<div className="feb-container"> |
||||
<header className="fesl-row" data-type="folder"> |
||||
<div className="fesl-item fi-name" onClick={ this.sortObjectsByName.bind(this) } data-sort="name"> |
||||
Name |
||||
<i className={ classNames({ |
||||
'fesli-sort': true, |
||||
'fa': true, |
||||
'fa-sort-alpha-desc': sortNameOrder, |
||||
'fa-sort-alpha-asc': !sortNameOrder |
||||
}) } /> |
||||
</div> |
||||
<div className="fesl-item fi-size" onClick={ this.sortObjectsBySize.bind(this) } data-sort="size"> |
||||
Size |
||||
<i className={ classNames({ |
||||
'fesli-sort': true, |
||||
'fa': true, |
||||
'fa-sort-amount-desc': sortSizeOrder, |
||||
'fa-sort-amount-asc': !sortSizeOrder |
||||
}) } /> |
||||
</div> |
||||
<div className="fesl-item fi-modified" onClick={ this.sortObjectsByDate.bind(this) } data-sort="last-modified"> |
||||
Last Modified |
||||
<i className={ classNames({ |
||||
'fesli-sort': true, |
||||
'fa': true, |
||||
'fa-sort-numeric-desc': sortDateOrder, |
||||
'fa-sort-numeric-asc': !sortDateOrder |
||||
}) } /> |
||||
</div> |
||||
<div className="fesl-item fi-actions"></div> |
||||
</header> |
||||
</div> |
||||
<div className="feb-container"> |
||||
<ObjectsList dataType={ this.dataType.bind(this) } |
||||
selectPrefix={ this.selectPrefix.bind(this) } |
||||
showDeleteConfirmation={ this.showDeleteConfirmation.bind(this) } |
||||
shareObject={ this.shareObject.bind(this) } /> |
||||
</div> |
||||
<UploadModal /> |
||||
{ createButton } |
||||
<Modal className="modal-create-bucket" |
||||
bsSize="small" |
||||
animation={ false } |
||||
show={ showMakeBucketModal } |
||||
onHide={ this.hideMakeBucketModal.bind(this) }> |
||||
<button className="close close-alt" onClick={ this.hideMakeBucketModal.bind(this) }> |
||||
<span>×</span> |
||||
</button> |
||||
<ModalBody> |
||||
<form onSubmit={ this.makeBucket.bind(this) }> |
||||
<div className="input-group"> |
||||
<input className="ig-text" |
||||
type="text" |
||||
ref="makeBucketRef" |
||||
placeholder="Bucket Name" |
||||
autoFocus/> |
||||
<i className="ig-helpers"></i> |
||||
</div> |
||||
</form> |
||||
</ModalBody> |
||||
</Modal> |
||||
<Modal className="modal-about modal-dark" |
||||
animation={ false } |
||||
show={ showAbout } |
||||
onHide={ this.hideAbout.bind(this) }> |
||||
<button className="close" onClick={ this.hideAbout.bind(this) }> |
||||
<span>×</span> |
||||
</button> |
||||
<div className="ma-inner"> |
||||
<div className="mai-item hidden-xs"> |
||||
<a href="https://minio.io" target="_blank"><img className="maii-logo" src={ logo } alt="" /></a> |
||||
</div> |
||||
<div className="mai-item"> |
||||
<ul className="maii-list"> |
||||
<li> |
||||
<div> |
||||
Version |
||||
</div> |
||||
<small>{ version }</small> |
||||
</li> |
||||
<li> |
||||
<div> |
||||
Memory |
||||
</div> |
||||
<small>{ memory }</small> |
||||
</li> |
||||
<li> |
||||
<div> |
||||
Platform |
||||
</div> |
||||
<small>{ platform }</small> |
||||
</li> |
||||
<li> |
||||
<div> |
||||
Runtime |
||||
</div> |
||||
<small>{ runtime }</small> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
</Modal> |
||||
<Modal className="modal-policy" |
||||
animation={ false } |
||||
show={ showBucketPolicy } |
||||
onHide={ this.hideBucketPolicy.bind(this) }> |
||||
<ModalHeader> |
||||
Bucket Policy ( |
||||
{ currentBucket }) |
||||
<button className="close close-alt" onClick={ this.hideBucketPolicy.bind(this) }> |
||||
<span>×</span> |
||||
</button> |
||||
</ModalHeader> |
||||
<div className="pm-body"> |
||||
<PolicyInput bucket={ currentBucket } /> |
||||
{ policies.map((policy, i) => <Policy key={ i } prefix={ policy.prefix } policy={ policy.policy } /> |
||||
) } |
||||
</div> |
||||
</Modal> |
||||
<ConfirmModal show={ deleteConfirmation.show } |
||||
icon='fa fa-exclamation-triangle mci-red' |
||||
text='Are you sure you want to delete?' |
||||
sub='This cannot be undone!' |
||||
okText='Delete' |
||||
cancelText='Cancel' |
||||
okHandler={ this.removeObject.bind(this) } |
||||
cancelHandler={ this.hideDeleteConfirmation.bind(this) }> |
||||
</ConfirmModal> |
||||
<Modal show={ shareObject.show } |
||||
animation={ false } |
||||
onHide={ this.hideShareObjectModal.bind(this) } |
||||
bsSize="small"> |
||||
<ModalHeader> |
||||
Share Object |
||||
</ModalHeader> |
||||
<ModalBody> |
||||
<div className="input-group copy-text"> |
||||
<label> |
||||
Shareable Link |
||||
</label> |
||||
<input type="text" |
||||
ref="copyTextInput" |
||||
readOnly="readOnly" |
||||
value={ window.location.protocol + '//' + shareObject.url } |
||||
onClick={ this.selectTexts.bind(this) } /> |
||||
</div> |
||||
<div className="input-group" style={ { display: web.LoggedIn() ? 'block' : 'none' } }> |
||||
<label> |
||||
Expires in |
||||
</label> |
||||
<div className="set-expire"> |
||||
<div className="set-expire-item"> |
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireDays', 1) }></i> |
||||
<div className="set-expire-title"> |
||||
Days |
||||
</div> |
||||
<div className="set-expire-value"> |
||||
<input ref="expireDays" |
||||
type="number" |
||||
min={ 0 } |
||||
max={ 7 } |
||||
defaultValue={ 0 } /> |
||||
</div> |
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireDays', -1) }></i> |
||||
</div> |
||||
<div className="set-expire-item"> |
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireHours', 1) }></i> |
||||
<div className="set-expire-title"> |
||||
Hours |
||||
</div> |
||||
<div className="set-expire-value"> |
||||
<input ref="expireHours" |
||||
type="number" |
||||
min={ 0 } |
||||
max={ 24 } |
||||
defaultValue={ 0 } /> |
||||
</div> |
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireHours', -1) }></i> |
||||
</div> |
||||
<div className="set-expire-item"> |
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireMins', 1) }></i> |
||||
<div className="set-expire-title"> |
||||
Minutes |
||||
</div> |
||||
<div className="set-expire-value"> |
||||
<input ref="expireMins" |
||||
type="number" |
||||
min={ 1 } |
||||
max={ 60 } |
||||
defaultValue={ 45 } /> |
||||
</div> |
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireMins', -1) }></i> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</ModalBody> |
||||
<div className="modal-footer"> |
||||
<CopyToClipboard text={ shareObject.url } onCopy={ this.showMessage.bind(this) }> |
||||
<button className="btn btn-success"> |
||||
Copy Link |
||||
</button> |
||||
</CopyToClipboard> |
||||
<button className="btn btn-link" onClick={ this.hideShareObjectModal.bind(this) }> |
||||
Cancel |
||||
</button> |
||||
</div> |
||||
</Modal> |
||||
{ settingsModal } |
||||
</Dropzone> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,56 @@ |
||||
/* |
||||
* Minio Browser (C) 2016, 2017 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import connect from 'react-redux/lib/components/connect' |
||||
import Dropdown from 'react-bootstrap/lib/Dropdown' |
||||
|
||||
let BrowserDropdown = ({fullScreen, showAbout, showSettings, logout}) => { |
||||
return ( |
||||
<li> |
||||
<Dropdown pullRight id="top-right-menu"> |
||||
<Dropdown.Toggle noCaret> |
||||
<i className="fa fa-reorder"></i> |
||||
</Dropdown.Toggle> |
||||
<Dropdown.Menu className="dropdown-menu-right"> |
||||
<li> |
||||
<a target="_blank" href="https://github.com/minio/miniobrowser">Github <i className="fa fa-github"></i></a> |
||||
</li> |
||||
<li> |
||||
<a href="" onClick={ fullScreen }>Fullscreen <i className="fa fa-expand"></i></a> |
||||
</li> |
||||
<li> |
||||
<a target="_blank" href="https://docs.minio.io/">Documentation <i className="fa fa-book"></i></a> |
||||
</li> |
||||
<li> |
||||
<a target="_blank" href="https://slack.minio.io">Ask for help <i className="fa fa-question-circle"></i></a> |
||||
</li> |
||||
<li> |
||||
<a href="" onClick={ showAbout }>About <i className="fa fa-info-circle"></i></a> |
||||
</li> |
||||
<li> |
||||
<a href="" onClick={ showSettings }>Settings <i className="fa fa-cog"></i></a> |
||||
</li> |
||||
<li> |
||||
<a href="" onClick={ logout }>Sign Out <i className="fa fa-sign-out"></i></a> |
||||
</li> |
||||
</Dropdown.Menu> |
||||
</Dropdown> |
||||
</li> |
||||
) |
||||
} |
||||
|
||||
export default connect(state => state)(BrowserDropdown) |
@ -0,0 +1,42 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import connect from 'react-redux/lib/components/connect' |
||||
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip' |
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger' |
||||
|
||||
let BrowserUpdate = ({latestUiVersion}) => { |
||||
// Don't show an update if we're already updated!
|
||||
if (latestUiVersion === currentUiVersion) return ( <noscript></noscript> ) |
||||
|
||||
return ( |
||||
<li className="hidden-xs hidden-sm"> |
||||
<a href=""> |
||||
<OverlayTrigger placement="left" overlay={ <Tooltip id="tt-version-update"> |
||||
New update available. Click to refresh. |
||||
</Tooltip> }> <i className="fa fa-refresh"></i> </OverlayTrigger> |
||||
</a> |
||||
</li> |
||||
) |
||||
} |
||||
|
||||
export default connect(state => { |
||||
return { |
||||
latestUiVersion: state.latestUiVersion |
||||
} |
||||
})(BrowserUpdate) |
@ -0,0 +1,50 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import Modal from 'react-bootstrap/lib/Modal' |
||||
import ModalBody from 'react-bootstrap/lib/ModalBody' |
||||
|
||||
let ConfirmModal = ({baseClass, icon, text, sub, okText, cancelText, okHandler, cancelHandler, show}) => { |
||||
return ( |
||||
<Modal bsSize="small" |
||||
animation={ false } |
||||
show={ show } |
||||
className={ "modal-confirm " + (baseClass || '') }> |
||||
<ModalBody> |
||||
<div className="mc-icon"> |
||||
<i className={ icon }></i> |
||||
</div> |
||||
<div className="mc-text"> |
||||
{ text } |
||||
</div> |
||||
<div className="mc-sub"> |
||||
{ sub } |
||||
</div> |
||||
</ModalBody> |
||||
<div className="modal-footer"> |
||||
<button className="btn btn-danger" onClick={ okHandler }> |
||||
{ okText } |
||||
</button> |
||||
<button className="btn btn-link" onClick={ cancelHandler }> |
||||
{ cancelText } |
||||
</button> |
||||
</div> |
||||
</Modal> |
||||
) |
||||
} |
||||
|
||||
export default ConfirmModal |
@ -0,0 +1,65 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import ReactDropzone from 'react-dropzone' |
||||
import * as actions from '../actions' |
||||
|
||||
// Dropzone is a drag-and-drop element for uploading files. It will create a
|
||||
// landing zone of sorts that automatically receives the files.
|
||||
export default class Dropzone extends React.Component { |
||||
|
||||
onDrop(files) { |
||||
// FIXME: Currently you can upload multiple files, but only one abort
|
||||
// modal will be shown, and progress updates will only occur for one
|
||||
// file at a time. See #171.
|
||||
files.forEach(file => { |
||||
let req = new XMLHttpRequest() |
||||
|
||||
// Dispatch the upload.
|
||||
web.dispatch(actions.uploadFile(file, req)) |
||||
}) |
||||
} |
||||
|
||||
render() { |
||||
// Overwrite the default styling from react-dropzone; otherwise it
|
||||
// won't handle child elements correctly.
|
||||
const style = { |
||||
height: '100%', |
||||
borderWidth: '2px', |
||||
borderStyle: 'dashed', |
||||
borderColor: '#fff' |
||||
} |
||||
const activeStyle = { |
||||
borderColor: '#777' |
||||
} |
||||
const rejectStyle = { |
||||
backgroundColor: '#ffdddd' |
||||
} |
||||
|
||||
// disableClick means that it won't trigger a file upload box when
|
||||
// the user clicks on a file.
|
||||
return ( |
||||
<ReactDropzone style={ style } |
||||
activeStyle={ activeStyle } |
||||
rejectStyle={ rejectStyle } |
||||
disableClick={ true } |
||||
onDrop={ this.onDrop }> |
||||
{ this.props.children } |
||||
</ReactDropzone> |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,49 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
|
||||
let InputGroup = ({label, id, name, value, onChange, type, spellCheck, required, readonly, autoComplete, align, className}) => { |
||||
var input = <input id={ id } |
||||
name={ name } |
||||
value={ value } |
||||
onChange={ onChange } |
||||
className="ig-text" |
||||
type={ type } |
||||
spellCheck={ spellCheck } |
||||
required={ required } |
||||
autoComplete={ autoComplete } /> |
||||
if (readonly) |
||||
input = <input id={ id } |
||||
name={ name } |
||||
value={ value } |
||||
onChange={ onChange } |
||||
className="ig-text" |
||||
type={ type } |
||||
spellCheck={ spellCheck } |
||||
required={ required } |
||||
autoComplete={ autoComplete } |
||||
disabled /> |
||||
return <div className={ "input-group " + align + ' ' + className }> |
||||
{ input } |
||||
<i className="ig-helpers"></i> |
||||
<label className="ig-label"> |
||||
{ label } |
||||
</label> |
||||
</div> |
||||
} |
||||
|
||||
export default InputGroup |
@ -0,0 +1,133 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import classNames from 'classnames' |
||||
import logo from '../../img/logo.svg' |
||||
import Alert from 'react-bootstrap/lib/Alert' |
||||
import * as actions from '../actions' |
||||
import InputGroup from '../components/InputGroup' |
||||
|
||||
export default class Login extends React.Component { |
||||
handleSubmit(event) { |
||||
event.preventDefault() |
||||
const {web, dispatch, loginRedirectPath} = this.props |
||||
let message = '' |
||||
if (!document.getElementById('accessKey').value) { |
||||
message = 'Secret Key cannot be empty' |
||||
} |
||||
if (!document.getElementById('secretKey').value) { |
||||
message = 'Access Key cannot be empty' |
||||
} |
||||
if (message) { |
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message |
||||
})) |
||||
return |
||||
} |
||||
web.Login({ |
||||
username: document.getElementById('accessKey').value, |
||||
password: document.getElementById('secretKey').value |
||||
}) |
||||
.then((res) => { |
||||
this.context.router.push(loginRedirectPath) |
||||
}) |
||||
.catch(e => { |
||||
dispatch(actions.setLoginError()) |
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: e.message |
||||
})) |
||||
}) |
||||
} |
||||
|
||||
componentWillMount() { |
||||
const {dispatch} = this.props |
||||
// Clear out any stale message in the alert of previous page
|
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: '' |
||||
})) |
||||
document.body.classList.add('is-guest') |
||||
} |
||||
|
||||
componentWillUnmount() { |
||||
document.body.classList.remove('is-guest') |
||||
} |
||||
|
||||
hideAlert() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideAlert()) |
||||
} |
||||
|
||||
render() { |
||||
const {alert} = this.props |
||||
let alertBox = <Alert className={ 'alert animated ' + (alert.show ? 'fadeInDown' : 'fadeOutUp') } bsStyle={ alert.type } onDismiss={ this.hideAlert.bind(this) }> |
||||
<div className='text-center'> |
||||
{ alert.message } |
||||
</div> |
||||
</Alert> |
||||
// Make sure you don't show a fading out alert box on the initial web-page load.
|
||||
if (!alert.message) |
||||
alertBox = '' |
||||
return ( |
||||
<div className="login"> |
||||
{ alertBox } |
||||
<div className="l-wrap"> |
||||
<form onSubmit={ this.handleSubmit.bind(this) }> |
||||
<input name="fixBrowser" |
||||
autoComplete="username" |
||||
type="text" |
||||
style={ { display: 'none' } } /> |
||||
<InputGroup className="ig-dark" |
||||
label="Access Key" |
||||
id="accessKey" |
||||
name="username" |
||||
type="text" |
||||
spellCheck="false" |
||||
required="required" |
||||
autoComplete="username"> |
||||
</InputGroup> |
||||
<input type="text" autoComplete="new-password" style={ { display: 'none' } } /> |
||||
<InputGroup className="ig-dark" |
||||
label="Secret Key" |
||||
id="secretKey" |
||||
name="password" |
||||
type="password" |
||||
spellCheck="false" |
||||
required="required" |
||||
autoComplete="new-password"> |
||||
</InputGroup> |
||||
<button className="lw-btn" type="submit"> |
||||
<i className="fa fa-sign-in"></i> |
||||
</button> |
||||
</form> |
||||
</div> |
||||
<div className="l-footer"> |
||||
<a className="lf-logo" href=""><img src={ logo } alt="" /></a> |
||||
<div className="lf-server"> |
||||
{ window.location.host } |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
} |
||||
|
||||
Login.contextTypes = { |
||||
router: React.PropTypes.object.isRequired |
||||
} |
@ -0,0 +1,75 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import Moment from 'moment' |
||||
import humanize from 'humanize' |
||||
import connect from 'react-redux/lib/components/connect' |
||||
import Dropdown from 'react-bootstrap/lib/Dropdown' |
||||
|
||||
|
||||
let ObjectsList = ({objects, currentPath, selectPrefix, dataType, showDeleteConfirmation, shareObject, loadPath}) => { |
||||
const list = objects.map((object, i) => { |
||||
let size = object.name.endsWith('/') ? '-' : humanize.filesize(object.size) |
||||
let lastModified = object.name.endsWith('/') ? '-' : Moment(object.lastModified).format('lll') |
||||
let loadingClass = loadPath === `${currentPath}${object.name}` ? 'fesl-loading' : '' |
||||
let actionButtons = '' |
||||
let deleteButton = '' |
||||
if (web.LoggedIn()) |
||||
deleteButton = <a href="" className="fiad-action" onClick={ (e) => showDeleteConfirmation(e, `${currentPath}${object.name}`) }><i className="fa fa-trash"></i></a> |
||||
if (!object.name.endsWith('/')) { |
||||
actionButtons = <Dropdown id="fia-dropdown"> |
||||
<Dropdown.Toggle noCaret className="fia-toggle"></Dropdown.Toggle> |
||||
<Dropdown.Menu> |
||||
<a href="" className="fiad-action" onClick={ (e) => shareObject(e, `${currentPath}${object.name}`) }><i className="fa fa-copy"></i></a> |
||||
{ deleteButton } |
||||
</Dropdown.Menu> |
||||
</Dropdown> |
||||
} |
||||
return ( |
||||
<div key={ i } className={ "fesl-row " + loadingClass } data-type={ dataType(object.name, object.contentType) }> |
||||
<div className="fesl-item fi-name"> |
||||
<a href="" onClick={ (e) => selectPrefix(e, `${currentPath}${object.name}`) }> |
||||
{ object.name } |
||||
</a> |
||||
</div> |
||||
<div className="fesl-item fi-size"> |
||||
{ size } |
||||
</div> |
||||
<div className="fesl-item fi-modified"> |
||||
{ lastModified } |
||||
</div> |
||||
<div className="fesl-item fi-actions"> |
||||
{ actionButtons } |
||||
</div> |
||||
</div> |
||||
) |
||||
}) |
||||
return ( |
||||
<div> |
||||
{ list } |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
// Subscribe it to state changes.
|
||||
export default connect(state => { |
||||
return { |
||||
objects: state.objects, |
||||
currentPath: state.currentPath, |
||||
loadPath: state.loadPath |
||||
} |
||||
})(ObjectsList) |
@ -0,0 +1,41 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import connect from 'react-redux/lib/components/connect' |
||||
|
||||
let Path = ({currentBucket, currentPath, selectPrefix}) => { |
||||
let dirPath = [] |
||||
let path = '' |
||||
if (currentPath) { |
||||
path = currentPath.split('/').map((dir, i) => { |
||||
dirPath.push(dir) |
||||
let dirPath_ = dirPath.join('/') + '/' |
||||
return <span key={ i }><a href="" onClick={ (e) => selectPrefix(e, dirPath_) }>{ dir }</a></span> |
||||
}) |
||||
} |
||||
|
||||
return ( |
||||
<h2><span className="main"><a onClick={ (e) => selectPrefix(e, '') } href="">{ currentBucket }</a></span>{ path }</h2> |
||||
) |
||||
} |
||||
|
||||
export default connect(state => { |
||||
return { |
||||
currentBucket: state.currentBucket, |
||||
currentPath: state.currentPath |
||||
} |
||||
})(Path) |
@ -0,0 +1,80 @@ |
||||
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants' |
||||
|
||||
import React, { Component, PropTypes } from 'react' |
||||
import connect from 'react-redux/lib/components/connect' |
||||
import classnames from 'classnames' |
||||
import * as actions from '../actions' |
||||
|
||||
class Policy extends Component { |
||||
constructor(props, context) { |
||||
super(props, context) |
||||
this.state = {} |
||||
} |
||||
|
||||
handlePolicyChange(e) { |
||||
this.setState({ |
||||
policy: { |
||||
policy: e.target.value |
||||
} |
||||
}) |
||||
} |
||||
|
||||
removePolicy(e) { |
||||
e.preventDefault() |
||||
const {dispatch, currentBucket, prefix} = this.props |
||||
let newPrefix = prefix.replace(currentBucket + '/', '') |
||||
newPrefix = newPrefix.replace('*', '') |
||||
web.SetBucketPolicy({ |
||||
bucketName: currentBucket, |
||||
prefix: newPrefix, |
||||
policy: 'none' |
||||
}) |
||||
.then(() => { |
||||
dispatch(actions.setPolicies(this.props.policies.filter(policy => policy.prefix != prefix))) |
||||
}) |
||||
.catch(e => dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: e.message, |
||||
}))) |
||||
} |
||||
|
||||
render() { |
||||
const {policy, prefix, currentBucket} = this.props |
||||
let newPrefix = prefix.replace(currentBucket + '/', '') |
||||
newPrefix = newPrefix.replace('*', '') |
||||
|
||||
if (!newPrefix) |
||||
newPrefix = '*' |
||||
|
||||
return ( |
||||
<div className="pmb-list"> |
||||
<div className="pmbl-item"> |
||||
{ newPrefix } |
||||
</div> |
||||
<div className="pmbl-item"> |
||||
<select className="form-control" |
||||
disabled |
||||
value={ policy } |
||||
onChange={ this.handlePolicyChange.bind(this) }> |
||||
<option value={ READ_ONLY }> |
||||
Read Only |
||||
</option> |
||||
<option value={ WRITE_ONLY }> |
||||
Write Only |
||||
</option> |
||||
<option value={ READ_WRITE }> |
||||
Read and Write |
||||
</option> |
||||
</select> |
||||
</div> |
||||
<div className="pmbl-item"> |
||||
<button className="btn btn-block btn-danger" onClick={ this.removePolicy.bind(this) }> |
||||
Remove |
||||
</button> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
} |
||||
|
||||
export default connect(state => state)(Policy) |
@ -0,0 +1,83 @@ |
||||
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants' |
||||
import React, { Component, PropTypes } from 'react' |
||||
import connect from 'react-redux/lib/components/connect' |
||||
import classnames from 'classnames' |
||||
import * as actions from '../actions' |
||||
|
||||
class PolicyInput extends Component { |
||||
componentDidMount() { |
||||
const {web, dispatch} = this.props |
||||
web.ListAllBucketPolicies({ |
||||
bucketName: this.props.currentBucket |
||||
}).then(res => { |
||||
let policies = res.policies |
||||
if (policies) dispatch(actions.setPolicies(policies)) |
||||
}).catch(err => { |
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
})) |
||||
}) |
||||
} |
||||
|
||||
componentWillUnmount() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.setPolicies([])) |
||||
} |
||||
|
||||
handlePolicySubmit(e) { |
||||
e.preventDefault() |
||||
const {web, dispatch} = this.props |
||||
|
||||
web.SetBucketPolicy({ |
||||
bucketName: this.props.currentBucket, |
||||
prefix: this.prefix.value, |
||||
policy: this.policy.value |
||||
}) |
||||
.then(() => { |
||||
dispatch(actions.setPolicies([{ |
||||
policy: this.policy.value, |
||||
prefix: this.prefix.value + '*', |
||||
}, ...this.props.policies])) |
||||
this.prefix.value = '' |
||||
}) |
||||
.catch(e => dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: e.message, |
||||
}))) |
||||
} |
||||
|
||||
render() { |
||||
return ( |
||||
<header className="pmb-list"> |
||||
<div className="pmbl-item"> |
||||
<input type="text" |
||||
ref={ prefix => this.prefix = prefix } |
||||
className="form-control" |
||||
placeholder="Prefix" |
||||
editable={ true } /> |
||||
</div> |
||||
<div className="pmbl-item"> |
||||
<select ref={ policy => this.policy = policy } className="form-control"> |
||||
<option value={ READ_ONLY }> |
||||
Read Only |
||||
</option> |
||||
<option value={ WRITE_ONLY }> |
||||
Write Only |
||||
</option> |
||||
<option value={ READ_WRITE }> |
||||
Read and Write |
||||
</option> |
||||
</select> |
||||
</div> |
||||
<div className="pmbl-item"> |
||||
<button className="btn btn-block btn-primary" onClick={ this.handlePolicySubmit.bind(this) }> |
||||
Add |
||||
</button> |
||||
</div> |
||||
</header> |
||||
) |
||||
} |
||||
} |
||||
|
||||
export default connect(state => state)(PolicyInput) |
@ -0,0 +1,215 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import connect from 'react-redux/lib/components/connect' |
||||
import * as actions from '../actions' |
||||
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip' |
||||
import Modal from 'react-bootstrap/lib/Modal' |
||||
import ModalBody from 'react-bootstrap/lib/ModalBody' |
||||
import ModalHeader from 'react-bootstrap/lib/ModalHeader' |
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger' |
||||
import InputGroup from './InputGroup' |
||||
|
||||
class SettingsModal extends React.Component { |
||||
|
||||
// When the settings are shown, it loads the access key and secret key.
|
||||
componentWillMount() { |
||||
const {web, dispatch} = this.props |
||||
const {serverInfo} = this.props |
||||
|
||||
let accessKeyEnv = '' |
||||
let secretKeyEnv = '' |
||||
// Check environment variables first. They may or may not have been
|
||||
// loaded already; they load in Browse#componentDidMount.
|
||||
if (serverInfo.envVars) { |
||||
serverInfo.envVars.forEach(envVar => { |
||||
let keyVal = envVar.split('=') |
||||
if (keyVal[0] == 'MINIO_ACCESS_KEY') { |
||||
accessKeyEnv = keyVal[1] |
||||
} else if (keyVal[0] == 'MINIO_SECRET_KEY') { |
||||
secretKeyEnv = keyVal[1] |
||||
} |
||||
}) |
||||
} |
||||
if (accessKeyEnv != '' || secretKeyEnv != '') { |
||||
dispatch(actions.setSettings({ |
||||
accessKey: accessKeyEnv, |
||||
secretKey: secretKeyEnv, |
||||
keysReadOnly: true |
||||
})) |
||||
} else { |
||||
web.GetAuth() |
||||
.then(data => { |
||||
dispatch(actions.setSettings({ |
||||
accessKey: data.accessKey, |
||||
secretKey: data.secretKey |
||||
})) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
// When they are re-hidden, the keys are unloaded from memory.
|
||||
componentWillUnmount() { |
||||
const {dispatch} = this.props |
||||
|
||||
dispatch(actions.setSettings({ |
||||
accessKey: '', |
||||
secretKey: '', |
||||
secretKeyVisible: false |
||||
})) |
||||
dispatch(actions.hideSettings()) |
||||
} |
||||
|
||||
// Handle field changes from inside the modal.
|
||||
accessKeyChange(e) { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.setSettings({ |
||||
accessKey: e.target.value |
||||
})) |
||||
} |
||||
|
||||
secretKeyChange(e) { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.setSettings({ |
||||
secretKey: e.target.value |
||||
})) |
||||
} |
||||
|
||||
secretKeyVisible(secretKeyVisible) { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.setSettings({ |
||||
secretKeyVisible |
||||
})) |
||||
} |
||||
|
||||
// Save the auth params and set them.
|
||||
setAuth(e) { |
||||
e.preventDefault() |
||||
const {web, dispatch} = this.props |
||||
|
||||
let accessKey = document.getElementById('accessKey').value |
||||
let secretKey = document.getElementById('secretKey').value |
||||
web.SetAuth({ |
||||
accessKey, |
||||
secretKey |
||||
}) |
||||
.then(data => { |
||||
dispatch(actions.setSettings({ |
||||
accessKey: '', |
||||
secretKey: '', |
||||
secretKeyVisible: false |
||||
})) |
||||
dispatch(actions.hideSettings()) |
||||
dispatch(actions.showAlert({ |
||||
type: 'success', |
||||
message: 'Changed credentials' |
||||
})) |
||||
}) |
||||
.catch(err => { |
||||
dispatch(actions.setSettings({ |
||||
accessKey: '', |
||||
secretKey: '', |
||||
secretKeyVisible: false |
||||
})) |
||||
dispatch(actions.hideSettings()) |
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
})) |
||||
}) |
||||
} |
||||
|
||||
generateAuth(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
|
||||
web.GenerateAuth() |
||||
.then(data => { |
||||
dispatch(actions.setSettings({ |
||||
secretKeyVisible: true |
||||
})) |
||||
dispatch(actions.setSettings({ |
||||
accessKey: data.accessKey, |
||||
secretKey: data.secretKey |
||||
})) |
||||
}) |
||||
} |
||||
|
||||
hideSettings(e) { |
||||
e.preventDefault() |
||||
|
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideSettings()) |
||||
} |
||||
|
||||
render() { |
||||
let {settings} = this.props |
||||
|
||||
return ( |
||||
<Modal bsSize="sm" animation={ false } show={ true }> |
||||
<ModalHeader> |
||||
Change Password |
||||
</ModalHeader> |
||||
<ModalBody className="m-t-20"> |
||||
<InputGroup value={ settings.accessKey } |
||||
onChange={ this.accessKeyChange.bind(this) } |
||||
id="accessKey" |
||||
label="Access Key" |
||||
name="accesskey" |
||||
type="text" |
||||
spellCheck="false" |
||||
required="required" |
||||
autoComplete="false" |
||||
align="ig-left" |
||||
readonly={ settings.keysReadOnly }></InputGroup> |
||||
<i onClick={ this.secretKeyVisible.bind(this, !settings.secretKeyVisible) } className={ "toggle-password fa fa-eye " + (settings.secretKeyVisible ? "toggled" : "") } /> |
||||
<InputGroup value={ settings.secretKey } |
||||
onChange={ this.secretKeyChange.bind(this) } |
||||
id="secretKey" |
||||
label="Secret Key" |
||||
name="accesskey" |
||||
type={ settings.secretKeyVisible ? "text" : "password" } |
||||
spellCheck="false" |
||||
required="required" |
||||
autoComplete="false" |
||||
align="ig-left" |
||||
readonly={ settings.keysReadOnly }></InputGroup> |
||||
</ModalBody> |
||||
<div className="modal-footer"> |
||||
<button className={ "btn btn-primary " + (settings.keysReadOnly ? "hidden" : "") } onClick={ this.generateAuth.bind(this) }> |
||||
Generate |
||||
</button> |
||||
<button href="" className={ "btn btn-success " + (settings.keysReadOnly ? "hidden" : "") } onClick={ this.setAuth.bind(this) }> |
||||
Update |
||||
</button> |
||||
<button href="" className="btn btn-link" onClick={ this.hideSettings.bind(this) }> |
||||
Cancel |
||||
</button> |
||||
</div> |
||||
</Modal> |
||||
) |
||||
} |
||||
} |
||||
|
||||
export default connect(state => { |
||||
return { |
||||
web: state.web, |
||||
settings: state.settings, |
||||
serverInfo: state.serverInfo |
||||
} |
||||
})(SettingsModal) |
@ -0,0 +1,85 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import classNames from 'classnames' |
||||
import ClickOutHandler from 'react-onclickout' |
||||
import Scrollbars from 'react-custom-scrollbars/lib/Scrollbars' |
||||
import connect from 'react-redux/lib/components/connect' |
||||
|
||||
import logo from '../../img/logo.svg' |
||||
|
||||
let SideBar = ({visibleBuckets, loadBucket, currentBucket, selectBucket, searchBuckets, landingPage, sidebarStatus, clickOutside, showPolicy}) => { |
||||
|
||||
const list = visibleBuckets.map((bucket, i) => { |
||||
return <li className={ classNames({ |
||||
'active': bucket === currentBucket |
||||
}) } key={ i } onClick={ (e) => selectBucket(e, bucket) }> |
||||
<a href="" className={ classNames({ |
||||
'fesli-loading': bucket === loadBucket |
||||
}) }> |
||||
{ bucket } |
||||
</a> |
||||
<i className="fesli-trigger" onClick={ showPolicy }></i> |
||||
</li> |
||||
}) |
||||
|
||||
return ( |
||||
<ClickOutHandler onClickOut={ clickOutside }> |
||||
<div className={ classNames({ |
||||
'fe-sidebar': true, |
||||
'toggled': sidebarStatus |
||||
}) }> |
||||
<div className="fes-header clearfix hidden-sm hidden-xs"> |
||||
<a href="" onClick={ landingPage }><img src={ logo } alt="" /> |
||||
<h2>Minio Browser</h2></a> |
||||
</div> |
||||
<div className="fes-list"> |
||||
<div className="input-group ig-dark ig-left ig-search" style={ { display: web.LoggedIn() ? 'block' : 'none' } }> |
||||
<input className="ig-text" |
||||
type="text" |
||||
onChange={ searchBuckets } |
||||
placeholder="Search Buckets..." /> |
||||
<i className="ig-helpers"></i> |
||||
</div> |
||||
<div className="fesl-inner"> |
||||
<Scrollbars renderScrollbarVertical={ props => <div className="scrollbar-vertical" /> }> |
||||
<ul> |
||||
{ list } |
||||
</ul> |
||||
</Scrollbars> |
||||
</div> |
||||
</div> |
||||
<div className="fes-host"> |
||||
<i className="fa fa-globe"></i> |
||||
<a href="/"> |
||||
{ window.location.host } |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</ClickOutHandler> |
||||
) |
||||
} |
||||
|
||||
// Subscribe it to state changes that affect only the sidebar.
|
||||
export default connect(state => { |
||||
return { |
||||
visibleBuckets: state.visibleBuckets, |
||||
loadBucket: state.loadBucket, |
||||
currentBucket: state.currentBucket, |
||||
sidebarStatus: state.sidebarStatus |
||||
} |
||||
})(SideBar) |
@ -0,0 +1,141 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import humanize from 'humanize' |
||||
import classNames from 'classnames' |
||||
import connect from 'react-redux/lib/components/connect' |
||||
|
||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar' |
||||
import ConfirmModal from './ConfirmModal' |
||||
|
||||
import * as actions from '../actions' |
||||
|
||||
// UploadModal is a modal that handles multiple file uploads.
|
||||
// During the upload, it displays a progress bar, and can transform into an
|
||||
// abort modal if the user decides to abort the uploads.
|
||||
class UploadModal extends React.Component { |
||||
|
||||
// Abort all the current uploads.
|
||||
abortUploads(e) { |
||||
e.preventDefault() |
||||
const {dispatch, uploads} = this.props |
||||
|
||||
for (var slug in uploads) { |
||||
let upload = uploads[slug] |
||||
upload.xhr.abort() |
||||
dispatch(actions.stopUpload({ |
||||
slug |
||||
})) |
||||
} |
||||
|
||||
this.hideAbort(e) |
||||
} |
||||
|
||||
// Show the abort modal instead of the progress modal.
|
||||
showAbort(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
|
||||
dispatch(actions.setShowAbortModal(true)) |
||||
} |
||||
|
||||
// Show the progress modal instead of the abort modal.
|
||||
hideAbort(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
|
||||
dispatch(actions.setShowAbortModal(false)) |
||||
} |
||||
|
||||
render() { |
||||
const {uploads, showAbortModal} = this.props |
||||
|
||||
// Show the abort modal.
|
||||
if (showAbortModal) { |
||||
let baseClass = classNames({ |
||||
'abort-upload': true |
||||
}) |
||||
let okIcon = classNames({ |
||||
'fa': true, |
||||
'fa-times': true |
||||
}) |
||||
let cancelIcon = classNames({ |
||||
'fa': true, |
||||
'fa-cloud-upload': true |
||||
}) |
||||
|
||||
return ( |
||||
<ConfirmModal show={ true } |
||||
baseClass={ baseClass } |
||||
text='Abort uploads in progress?' |
||||
icon='fa fa-info-circle mci-amber' |
||||
sub='This cannot be undone!' |
||||
okText='Abort' |
||||
okIcon={ okIcon } |
||||
cancelText='Upload' |
||||
cancelIcon={ cancelIcon } |
||||
okHandler={ this.abortUploads.bind(this) } |
||||
cancelHandler={ this.hideAbort.bind(this) }> |
||||
</ConfirmModal> |
||||
) |
||||
} |
||||
|
||||
// If we don't have any files uploading, don't show anything.
|
||||
let numberUploading = Object.keys(uploads).length |
||||
if (numberUploading == 0) |
||||
return ( <noscript></noscript> ) |
||||
|
||||
let totalLoaded = 0 |
||||
let totalSize = 0 |
||||
|
||||
// Iterate over each upload, adding together the total size and that
|
||||
// which has been uploaded.
|
||||
for (var slug in uploads) { |
||||
let upload = uploads[slug] |
||||
totalLoaded += upload.loaded |
||||
totalSize += upload.size |
||||
} |
||||
|
||||
let percent = (totalLoaded / totalSize) * 100 |
||||
|
||||
// If more than one: "Uploading files (5)..."
|
||||
// If only one: "Uploading myfile.txt..."
|
||||
let text = 'Uploading ' + (numberUploading == 1 ? `'${uploads[Object.keys(uploads)[0]].name}'` : `files (${numberUploading})`) + '...' |
||||
|
||||
return ( |
||||
<div className="alert alert-info progress animated fadeInUp "> |
||||
<button type="button" className="close" onClick={ this.showAbort.bind(this) }> |
||||
<span>×</span> |
||||
</button> |
||||
<div className="text-center"> |
||||
<small>{ text }</small> |
||||
</div> |
||||
<ProgressBar now={ percent } /> |
||||
<div className="text-center"> |
||||
<small>{ humanize.filesize(totalLoaded) } ({ percent.toFixed(2) } %)</small> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
} |
||||
|
||||
export default connect(state => { |
||||
return { |
||||
uploads: state.uploads, |
||||
showAbortModal: state.showAbortModal |
||||
} |
||||
})(UploadModal) |
@ -0,0 +1,54 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
/* |
||||
import React from 'react' |
||||
import ReactTestUtils, {renderIntoDocument} from 'react-addons-test-utils' |
||||
|
||||
import expect from 'expect' |
||||
import Login from '../Login' |
||||
|
||||
describe('Login', () => { |
||||
it('it should have empty credentials', () => { |
||||
const alert = { |
||||
show: false |
||||
} |
||||
const dispatch = () => {} |
||||
let loginComponent = renderIntoDocument(<Login alert={alert} dispatch={dispatch} />) |
||||
const accessKey = document.getElementById('accessKey') |
||||
const secretKey = document.getElementById('secretKey') |
||||
// Validate default value.
|
||||
expect(accessKey.value).toEqual('') |
||||
expect(secretKey.value).toEqual('') |
||||
}) |
||||
it('it should set accessKey and secretKey', () => { |
||||
const alert = { |
||||
show: false |
||||
} |
||||
const dispatch = () => {} |
||||
let loginComponent = renderIntoDocument(<Login alert={alert} dispatch={dispatch} />) |
||||
let accessKey = loginComponent.refs.accessKey |
||||
let secretKey = loginComponent.refs.secretKey |
||||
accessKey.value = 'demo-username' |
||||
secretKey.value = 'demo-password' |
||||
ReactTestUtils.Simulate.change(accessKey) |
||||
ReactTestUtils.Simulate.change(secretKey) |
||||
// Validate if the change has occurred.
|
||||
expect(loginComponent.refs.accessKey.value).toEqual('demo-username') |
||||
expect(loginComponent.refs.secretKey.value).toEqual('demo-password') |
||||
}) |
||||
}); |
||||
*/ |
@ -0,0 +1,23 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
// File for all the browser constants.
|
||||
|
||||
// minioBrowserPrefix absolute path.
|
||||
export const minioBrowserPrefix = '/minio' |
||||
export const READ_ONLY = 'readonly' |
||||
export const WRITE_ONLY = 'writeonly' |
||||
export const READ_WRITE = 'readwrite' |
@ -0,0 +1,91 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import SuperAgent from 'superagent-es6-promise'; |
||||
import url from 'url' |
||||
import Moment from 'moment' |
||||
|
||||
export default class JSONrpc { |
||||
constructor(params) { |
||||
this.endpoint = params.endpoint |
||||
this.namespace = params.namespace |
||||
this.version = '2.0'; |
||||
const parsedUrl = url.parse(this.endpoint) |
||||
this.host = parsedUrl.hostname |
||||
this.path = parsedUrl.path |
||||
this.port = parsedUrl.port |
||||
|
||||
switch (parsedUrl.protocol) { |
||||
case 'http:': { |
||||
this.scheme = 'http' |
||||
if (parsedUrl.port === 0) { |
||||
this.port = 80 |
||||
} |
||||
break |
||||
} |
||||
case 'https:': { |
||||
this.scheme = 'https' |
||||
if (parsedUrl.port === 0) { |
||||
this.port = 443 |
||||
} |
||||
break |
||||
} |
||||
default: { |
||||
throw new Error('Unknown protocol: ' + parsedUrl.protocol) |
||||
} |
||||
} |
||||
} |
||||
// call('Get', {id: NN, params: [...]}, function() {})
|
||||
call(method, options, token) { |
||||
if (!options) { |
||||
options = {} |
||||
} |
||||
if (!options.id) { |
||||
options.id = 1; |
||||
} |
||||
if (!options.params) { |
||||
options.params = {}; |
||||
} |
||||
const dataObj = { |
||||
id: options.id, |
||||
jsonrpc: this.version, |
||||
params: options.params ? options.params : {}, |
||||
method: this.namespace ? this.namespace + '.' + method : method |
||||
} |
||||
let requestParams = { |
||||
host: this.host, |
||||
port: this.port, |
||||
path: this.path, |
||||
scheme: this.scheme, |
||||
method: 'POST', |
||||
headers: { |
||||
'Content-Type': 'application/json', |
||||
'x-amz-date': Moment().utc().format('YYYYMMDDTHHmmss') + 'Z' |
||||
} |
||||
} |
||||
|
||||
if (token) { |
||||
requestParams.headers.Authorization = 'Bearer ' + token |
||||
} |
||||
|
||||
let req = SuperAgent.post(this.endpoint) |
||||
for (let key in requestParams.headers) { |
||||
req.set(key, requestParams.headers[key]) |
||||
} |
||||
// req.set('Access-Control-Allow-Origin', 'http://localhost:8080')
|
||||
return req.send(JSON.stringify(dataObj)).then(res => res) |
||||
} |
||||
} |
@ -0,0 +1,106 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import mimedb from 'mime-types' |
||||
|
||||
const isFolder = (name, contentType) => { |
||||
if (name.endsWith('/')) return true |
||||
return false |
||||
} |
||||
|
||||
const isPdf = (name, contentType) => { |
||||
if (contentType === 'application/pdf') return true |
||||
return false |
||||
} |
||||
|
||||
const isZip = (name, contentType) => { |
||||
if (!contentType || !contentType.includes('/')) return false |
||||
if (contentType.split('/')[1].includes('zip')) return true |
||||
return false |
||||
} |
||||
|
||||
const isCode = (name, contentType) => { |
||||
const codeExt = ['c', 'cpp', 'go', 'py', 'java', 'rb', 'js', 'pl', 'fs', |
||||
'php', 'css', 'less', 'scss', 'coffee', 'net', 'html', |
||||
'rs', 'exs', 'scala', 'hs', 'clj', 'el', 'scm', 'lisp', |
||||
'asp', 'aspx'] |
||||
const ext = name.split('.').reverse()[0] |
||||
for (var i in codeExt) { |
||||
if (ext === codeExt[i]) return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
const isExcel = (name, contentType) => { |
||||
if (!contentType || !contentType.includes('/')) return false |
||||
const types = ['excel', 'spreadsheet'] |
||||
const subType = contentType.split('/')[1] |
||||
for (var i in types) { |
||||
if (subType.includes(types[i])) return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
const isDoc = (name, contentType) => { |
||||
if (!contentType || !contentType.includes('/')) return false |
||||
const types = ['word', '.document'] |
||||
const subType = contentType.split('/')[1] |
||||
for (var i in types) { |
||||
if (subType.includes(types[i])) return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
const isPresentation = (name, contentType) => { |
||||
if (!contentType || !contentType.includes('/')) return false |
||||
var types = ['powerpoint', 'presentation'] |
||||
const subType = contentType.split('/')[1] |
||||
for (var i in types) { |
||||
if (subType.includes(types[i])) return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
const typeToIcon = (type) => { |
||||
return (name, contentType) => { |
||||
if (!contentType || !contentType.includes('/')) return false |
||||
if (contentType.split('/')[0] === type) return true |
||||
return false |
||||
} |
||||
} |
||||
|
||||
export const getDataType = (name, contentType) => { |
||||
if (contentType === "") { |
||||
contentType = mimedb.lookup(name) || 'application/octet-stream' |
||||
} |
||||
const check = [ |
||||
['folder', isFolder], |
||||
['code', isCode], |
||||
['audio', typeToIcon('audio')], |
||||
['image', typeToIcon('image')], |
||||
['video', typeToIcon('video')], |
||||
['text', typeToIcon('text')], |
||||
['pdf', isPdf], |
||||
['zip', isZip], |
||||
['excel', isExcel], |
||||
['doc', isDoc], |
||||
['presentation', isPresentation] |
||||
] |
||||
for (var i in check) { |
||||
if (check[i][1](name, contentType)) return check[i][0] |
||||
} |
||||
return 'other' |
||||
} |
@ -0,0 +1,176 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import * as actions from './actions' |
||||
import { minioBrowserPrefix } from './constants' |
||||
|
||||
export default (state = { |
||||
buckets: [], |
||||
visibleBuckets: [], |
||||
objects: [], |
||||
storageInfo: {}, |
||||
serverInfo: {}, |
||||
currentBucket: '', |
||||
currentPath: '', |
||||
showMakeBucketModal: false, |
||||
uploads: {}, |
||||
alert: { |
||||
show: false, |
||||
type: 'danger', |
||||
message: '' |
||||
}, |
||||
loginError: false, |
||||
sortNameOrder: false, |
||||
sortSizeOrder: false, |
||||
sortDateOrder: false, |
||||
latestUiVersion: currentUiVersion, |
||||
sideBarActive: false, |
||||
loginRedirectPath: minioBrowserPrefix, |
||||
settings: { |
||||
accessKey: '', |
||||
secretKey: '', |
||||
secretKeyVisible: false |
||||
}, |
||||
showSettings: false, |
||||
policies: [], |
||||
deleteConfirmation: { |
||||
object: '', |
||||
show: false |
||||
}, |
||||
shareObject: { |
||||
show: false, |
||||
url: '', |
||||
expiry: 604800 |
||||
}, |
||||
prefixWritable: false |
||||
}, action) => { |
||||
let newState = Object.assign({}, state) |
||||
switch (action.type) { |
||||
case actions.SET_WEB: |
||||
newState.web = action.web |
||||
break |
||||
case actions.SET_BUCKETS: |
||||
newState.buckets = action.buckets |
||||
break |
||||
case actions.ADD_BUCKET: |
||||
newState.buckets = [action.bucket, ...newState.buckets] |
||||
newState.visibleBuckets = [action.bucket, ...newState.visibleBuckets] |
||||
break |
||||
case actions.SET_VISIBLE_BUCKETS: |
||||
newState.visibleBuckets = action.visibleBuckets |
||||
break |
||||
case actions.SET_CURRENT_BUCKET: |
||||
newState.currentBucket = action.currentBucket |
||||
break |
||||
case actions.SET_OBJECTS: |
||||
newState.objects = action.objects |
||||
break |
||||
case actions.SET_CURRENT_PATH: |
||||
newState.currentPath = action.currentPath |
||||
break |
||||
case actions.SET_STORAGE_INFO: |
||||
newState.storageInfo = action.storageInfo |
||||
break |
||||
case actions.SET_SERVER_INFO: |
||||
newState.serverInfo = action.serverInfo |
||||
break |
||||
case actions.SHOW_MAKEBUCKET_MODAL: |
||||
newState.showMakeBucketModal = action.showMakeBucketModal |
||||
break |
||||
case actions.UPLOAD_PROGRESS: |
||||
newState.uploads = Object.assign({}, newState.uploads) |
||||
newState.uploads[action.slug].loaded = action.loaded |
||||
break |
||||
case actions.ADD_UPLOAD: |
||||
newState.uploads = Object.assign({}, newState.uploads, { |
||||
[action.slug]: { |
||||
loaded: 0, |
||||
size: action.size, |
||||
xhr: action.xhr, |
||||
name: action.name |
||||
} |
||||
}) |
||||
break |
||||
case actions.STOP_UPLOAD: |
||||
newState.uploads = Object.assign({}, newState.uploads) |
||||
delete newState.uploads[action.slug] |
||||
break |
||||
case actions.SET_ALERT: |
||||
if (newState.alert.alertTimeout) clearTimeout(newState.alert.alertTimeout) |
||||
if (!action.alert.show) { |
||||
newState.alert = Object.assign({}, newState.alert, { |
||||
show: false |
||||
}) |
||||
} else { |
||||
newState.alert = action.alert |
||||
} |
||||
break |
||||
case actions.SET_LOGIN_ERROR: |
||||
newState.loginError = true |
||||
break |
||||
case actions.SET_SHOW_ABORT_MODAL: |
||||
newState.showAbortModal = action.showAbortModal |
||||
break |
||||
case actions.SHOW_ABOUT: |
||||
newState.showAbout = action.showAbout |
||||
break |
||||
case actions.SET_SORT_NAME_ORDER: |
||||
newState.sortNameOrder = action.sortNameOrder |
||||
break |
||||
case actions.SET_SORT_SIZE_ORDER: |
||||
newState.sortSizeOrder = action.sortSizeOrder |
||||
break |
||||
case actions.SET_SORT_DATE_ORDER: |
||||
newState.sortDateOrder = action.sortDateOrder |
||||
break |
||||
case actions.SET_LATEST_UI_VERSION: |
||||
newState.latestUiVersion = action.latestUiVersion |
||||
break |
||||
case actions.SET_SIDEBAR_STATUS: |
||||
newState.sidebarStatus = action.sidebarStatus |
||||
break |
||||
case actions.SET_LOGIN_REDIRECT_PATH: |
||||
newState.loginRedirectPath = action.path |
||||
case actions.SET_LOAD_BUCKET: |
||||
newState.loadBucket = action.loadBucket |
||||
break |
||||
case actions.SET_LOAD_PATH: |
||||
newState.loadPath = action.loadPath |
||||
break |
||||
case actions.SHOW_SETTINGS: |
||||
newState.showSettings = action.showSettings |
||||
break |
||||
case actions.SET_SETTINGS: |
||||
newState.settings = Object.assign({}, newState.settings, action.settings) |
||||
break |
||||
case actions.SHOW_BUCKET_POLICY: |
||||
newState.showBucketPolicy = action.showBucketPolicy |
||||
break |
||||
case actions.SET_POLICIES: |
||||
newState.policies = action.policies |
||||
break |
||||
case actions.DELETE_CONFIRMATION: |
||||
newState.deleteConfirmation = Object.assign({}, action.payload) |
||||
break |
||||
case actions.SET_SHARE_OBJECT: |
||||
newState.shareObject = Object.assign({}, action.shareObject) |
||||
break |
||||
case actions.SET_PREFIX_WRITABLE: |
||||
newState.prefixWritable = action.prefixWritable |
||||
break |
||||
} |
||||
return newState |
||||
} |
@ -0,0 +1,85 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { minioBrowserPrefix } from './constants.js' |
||||
|
||||
export const sortObjectsByName = (objects, order) => { |
||||
let folders = objects.filter(object => object.name.endsWith('/')) |
||||
let files = objects.filter(object => !object.name.endsWith('/')) |
||||
folders = folders.sort((a, b) => { |
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1 |
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1 |
||||
return 0 |
||||
}) |
||||
files = files.sort((a, b) => { |
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1 |
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1 |
||||
return 0 |
||||
}) |
||||
if (order) { |
||||
folders = folders.reverse() |
||||
files = files.reverse() |
||||
} |
||||
return [...folders, ...files] |
||||
} |
||||
|
||||
export const sortObjectsBySize = (objects, order) => { |
||||
let folders = objects.filter(object => object.name.endsWith('/')) |
||||
let files = objects.filter(object => !object.name.endsWith('/')) |
||||
files = files.sort((a, b) => a.size - b.size) |
||||
if (order) |
||||
files = files.reverse() |
||||
return [...folders, ...files] |
||||
} |
||||
|
||||
export const sortObjectsByDate = (objects, order) => { |
||||
let folders = objects.filter(object => object.name.endsWith('/')) |
||||
let files = objects.filter(object => !object.name.endsWith('/')) |
||||
files = files.sort((a, b) => new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime()) |
||||
if (order) |
||||
files = files.reverse() |
||||
return [...folders, ...files] |
||||
} |
||||
|
||||
export const pathSlice = (path) => { |
||||
path = path.replace(minioBrowserPrefix, '') |
||||
let prefix = '' |
||||
let bucket = '' |
||||
if (!path) return { |
||||
bucket, |
||||
prefix |
||||
} |
||||
let objectIndex = path.indexOf('/', 1) |
||||
if (objectIndex == -1) { |
||||
bucket = path.slice(1) |
||||
return { |
||||
bucket, |
||||
prefix |
||||
} |
||||
} |
||||
bucket = path.slice(1, objectIndex) |
||||
prefix = path.slice(objectIndex + 1) |
||||
return { |
||||
bucket, |
||||
prefix |
||||
} |
||||
} |
||||
|
||||
export const pathJoin = (bucket, prefix) => { |
||||
if (!prefix) |
||||
prefix = '' |
||||
return minioBrowserPrefix + '/' + bucket + '/' + prefix |
||||
} |
@ -0,0 +1,124 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { browserHistory } from 'react-router' |
||||
import JSONrpc from './jsonrpc' |
||||
import * as actions from './actions' |
||||
import { minioBrowserPrefix } from './constants.js' |
||||
import Moment from 'moment' |
||||
import storage from 'local-storage-fallback' |
||||
|
||||
export default class Web { |
||||
constructor(endpoint, dispatch) { |
||||
const namespace = 'Web' |
||||
this.dispatch = dispatch |
||||
this.JSONrpc = new JSONrpc({ |
||||
endpoint, |
||||
namespace |
||||
}) |
||||
} |
||||
makeCall(method, options) { |
||||
return this.JSONrpc.call(method, { |
||||
params: options |
||||
}, storage.getItem('token')) |
||||
.catch(err => { |
||||
if (err.status === 401) { |
||||
storage.removeItem('token') |
||||
browserHistory.push(`${minioBrowserPrefix}/login`) |
||||
throw new Error('Please re-login.') |
||||
} |
||||
if (err.status) |
||||
throw new Error(`Server returned error [${err.status}]`) |
||||
throw new Error('Minio server is unreachable') |
||||
}) |
||||
.then(res => { |
||||
let json = JSON.parse(res.text) |
||||
let result = json.result |
||||
let error = json.error |
||||
if (error) { |
||||
throw new Error(error.message) |
||||
} |
||||
if (!Moment(result.uiVersion).isValid()) { |
||||
throw new Error("Invalid UI version in the JSON-RPC response") |
||||
} |
||||
if (result.uiVersion !== currentUiVersion |
||||
&& currentUiVersion !== 'MINIO_UI_VERSION') { |
||||
storage.setItem('newlyUpdated', true) |
||||
location.reload() |
||||
} |
||||
return result |
||||
}) |
||||
} |
||||
LoggedIn() { |
||||
return !!storage.getItem('token') |
||||
} |
||||
Login(args) { |
||||
return this.makeCall('Login', args) |
||||
.then(res => { |
||||
storage.setItem('token', `${res.token}`) |
||||
return res |
||||
}) |
||||
} |
||||
Logout() { |
||||
storage.removeItem('token') |
||||
} |
||||
ServerInfo() { |
||||
return this.makeCall('ServerInfo') |
||||
} |
||||
StorageInfo() { |
||||
return this.makeCall('StorageInfo') |
||||
} |
||||
ListBuckets() { |
||||
return this.makeCall('ListBuckets') |
||||
} |
||||
MakeBucket(args) { |
||||
return this.makeCall('MakeBucket', args) |
||||
} |
||||
ListObjects(args) { |
||||
return this.makeCall('ListObjects', args) |
||||
} |
||||
PresignedGet(args) { |
||||
return this.makeCall('PresignedGet', args) |
||||
} |
||||
PutObjectURL(args) { |
||||
return this.makeCall('PutObjectURL', args) |
||||
} |
||||
RemoveObject(args) { |
||||
return this.makeCall('RemoveObject', args) |
||||
} |
||||
GetAuth() { |
||||
return this.makeCall('GetAuth') |
||||
} |
||||
GenerateAuth() { |
||||
return this.makeCall('GenerateAuth') |
||||
} |
||||
SetAuth(args) { |
||||
return this.makeCall('SetAuth', args) |
||||
.then(res => { |
||||
storage.setItem('token', `${res.token}`) |
||||
return res |
||||
}) |
||||
} |
||||
GetBucketPolicy(args) { |
||||
return this.makeCall('GetBucketPolicy', args) |
||||
} |
||||
SetBucketPolicy(args) { |
||||
return this.makeCall('SetBucketPolicy', args) |
||||
} |
||||
ListAllBucketPolicies(args) { |
||||
return this.makeCall('ListAllBucketPolicies', args) |
||||
} |
||||
} |
@ -0,0 +1,68 @@ |
||||
.alert { |
||||
border: 0; |
||||
position: fixed; |
||||
max-width: 500px; |
||||
margin: 0; |
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1); |
||||
color: @white; |
||||
width: 100%; |
||||
right: 20px; |
||||
border-radius: 3px; |
||||
padding: 17px 50px 17px 17px; |
||||
z-index: 10010; |
||||
.animation-duration(800ms); |
||||
.animation-fill-mode(both); |
||||
|
||||
&:not(.progress) { |
||||
top: 20px; |
||||
|
||||
@media(min-width: (@screen-sm-min)) { |
||||
left: 50%; |
||||
margin-left: -250px; |
||||
} |
||||
} |
||||
|
||||
&.progress { |
||||
bottom: 20px; |
||||
right: 20px; |
||||
} |
||||
|
||||
&.alert-danger { |
||||
background: @red; |
||||
} |
||||
|
||||
&.alert-success { |
||||
background: @green; |
||||
} |
||||
|
||||
&.alert-info { |
||||
background: @blue; |
||||
} |
||||
|
||||
@media(max-width: (@screen-xs-max)) { |
||||
left: 20px; |
||||
width: ~"calc(100% - 40px)"; |
||||
max-width: 100%; |
||||
} |
||||
|
||||
.progress { |
||||
margin: 10px 10px 8px 0; |
||||
height: 5px; |
||||
box-shadow: none; |
||||
border-radius: 1px; |
||||
background-color: @blue; |
||||
border-radius: 2px; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.progress-bar { |
||||
box-shadow: none; |
||||
background-color: @white; |
||||
height: 100%; |
||||
} |
||||
|
||||
.close { |
||||
position: absolute; |
||||
top: 15px; |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
.animated{ |
||||
&.infinite { |
||||
.animation-iteration-count(infinite); |
||||
} |
||||
} |
||||
|
||||
@import 'fadeIn'; |
||||
@import 'fadeInDown'; |
||||
@import 'fadeInUp'; |
||||
@import 'fadeOut'; |
||||
@import 'fadeOutDown'; |
||||
@import 'fadeOutUp'; |
||||
@import 'zoomIn'; |
@ -0,0 +1,26 @@ |
||||
@-webkit-keyframes fadeIn { |
||||
0% {opacity: 0;} |
||||
100% {opacity: 1;} |
||||
} |
||||
|
||||
@-moz-keyframes fadeIn { |
||||
0% {opacity: 0;} |
||||
100% {opacity: 1;} |
||||
} |
||||
|
||||
@-o-keyframes fadeIn { |
||||
0% {opacity: 0;} |
||||
100% {opacity: 1;} |
||||
} |
||||
|
||||
@keyframes fadeIn { |
||||
0% {opacity: 0;} |
||||
100% {opacity: 1;} |
||||
} |
||||
|
||||
.fadeIn { |
||||
-webkit-animation-name: fadeIn; |
||||
-moz-animation-name: fadeIn; |
||||
-o-animation-name: fadeIn; |
||||
animation-name: fadeIn; |
||||
} |
@ -0,0 +1,54 @@ |
||||
@-webkit-keyframes fadeInDown { |
||||
0% { |
||||
opacity: 0; |
||||
-webkit-transform: translateY(-20px); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 1; |
||||
-webkit-transform: translateY(0); |
||||
} |
||||
} |
||||
|
||||
@-moz-keyframes fadeInDown { |
||||
0% { |
||||
opacity: 0; |
||||
-moz-transform: translateY(-20px); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 1; |
||||
-moz-transform: translateY(0); |
||||
} |
||||
} |
||||
|
||||
@-o-keyframes fadeInDown { |
||||
0% { |
||||
opacity: 0; |
||||
-ms-transform: translateY(-20px); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 1; |
||||
-ms-transform: translateY(0); |
||||
} |
||||
} |
||||
|
||||
@keyframes fadeInDown { |
||||
0% { |
||||
opacity: 0; |
||||
transform: translateY(-20px); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 1; |
||||
transform: translateY(0); |
||||
} |
||||
} |
||||
|
||||
.fadeInDown { |
||||
-webkit-animation-name: fadeInDown; |
||||
-moz-animation-name: fadeInDown; |
||||
-o-animation-name: fadeInDown; |
||||
animation-name: fadeInDown; |
||||
} |
@ -0,0 +1,54 @@ |
||||
@-webkit-keyframes fadeInUp { |
||||
0% { |
||||
opacity: 0; |
||||
-webkit-transform: translateY(20px); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 1; |
||||
-webkit-transform: translateY(0); |
||||
} |
||||
} |
||||
|
||||
@-moz-keyframes fadeInUp { |
||||
0% { |
||||
opacity: 0; |
||||
-moz-transform: translateY(20px); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 1; |
||||
-moz-transform: translateY(0); |
||||
} |
||||
} |
||||
|
||||
@-o-keyframes fadeInUp { |
||||
0% { |
||||
opacity: 0; |
||||
-o-transform: translateY(20px); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 1; |
||||
-o-transform: translateY(0); |
||||
} |
||||
} |
||||
|
||||
@keyframes fadeInUp { |
||||
0% { |
||||
opacity: 0; |
||||
transform: translateY(20px); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 1; |
||||
transform: translateY(0); |
||||
} |
||||
} |
||||
|
||||
.fadeInUp { |
||||
-webkit-animation-name: fadeInUp; |
||||
-moz-animation-name: fadeInUp; |
||||
-o-animation-name: fadeInUp; |
||||
animation-name: fadeInUp; |
||||
} |
@ -0,0 +1,26 @@ |
||||
@-webkit-keyframes fadeOut { |
||||
0% {opacity: 1;} |
||||
100% {opacity: 0;} |
||||
} |
||||
|
||||
@-moz-keyframes fadeOut { |
||||
0% {opacity: 1;} |
||||
100% {opacity: 0;} |
||||
} |
||||
|
||||
@-o-keyframes fadeOut { |
||||
0% {opacity: 1;} |
||||
100% {opacity: 0;} |
||||
} |
||||
|
||||
@keyframes fadeOut { |
||||
0% {opacity: 1;} |
||||
100% {opacity: 0;} |
||||
} |
||||
|
||||
.fadeOut { |
||||
-webkit-animation-name: fadeOut; |
||||
-moz-animation-name: fadeOut; |
||||
-o-animation-name: fadeOut; |
||||
animation-name: fadeOut; |
||||
} |
@ -0,0 +1,54 @@ |
||||
@-webkit-keyframes fadeOutDown { |
||||
0% { |
||||
opacity: 1; |
||||
-webkit-transform: translateY(0); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 0; |
||||
-webkit-transform: translateY(20px); |
||||
} |
||||
} |
||||
|
||||
@-moz-keyframes fadeOutDown { |
||||
0% { |
||||
opacity: 1; |
||||
-moz-transform: translateY(0); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 0; |
||||
-moz-transform: translateY(20px); |
||||
} |
||||
} |
||||
|
||||
@-o-keyframes fadeOutDown { |
||||
0% { |
||||
opacity: 1; |
||||
-o-transform: translateY(0); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 0; |
||||
-o-transform: translateY(20px); |
||||
} |
||||
} |
||||
|
||||
@keyframes fadeOutDown { |
||||
0% { |
||||
opacity: 1; |
||||
transform: translateY(0); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 0; |
||||
transform: translateY(20px); |
||||
} |
||||
} |
||||
|
||||
.fadeOutDown { |
||||
-webkit-animation-name: fadeOutDown; |
||||
-moz-animation-name: fadeOutDown; |
||||
-o-animation-name: fadeOutDown; |
||||
animation-name: fadeOutDown; |
||||
} |
@ -0,0 +1,51 @@ |
||||
@-webkit-keyframes fadeOutUp { |
||||
0% { |
||||
opacity: 1; |
||||
-webkit-transform: translateY(0); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 0; |
||||
-webkit-transform: translateY(-20px); |
||||
} |
||||
} |
||||
@-moz-keyframes fadeOutUp { |
||||
0% { |
||||
opacity: 1; |
||||
-moz-transform: translateY(0); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 0; |
||||
-moz-transform: translateY(-20px); |
||||
} |
||||
} |
||||
@-o-keyframes fadeOutUp { |
||||
0% { |
||||
opacity: 1; |
||||
-o-transform: translateY(0); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 0; |
||||
-o-transform: translateY(-20px); |
||||
} |
||||
} |
||||
@keyframes fadeOutUp { |
||||
0% { |
||||
opacity: 1; |
||||
transform: translateY(0); |
||||
} |
||||
|
||||
100% { |
||||
opacity: 0; |
||||
transform: translateY(-20px); |
||||
} |
||||
} |
||||
|
||||
.fadeOutUp { |
||||
-webkit-animation-name: fadeOutUp; |
||||
-moz-animation-name: fadeOutUp; |
||||
-o-animation-name: fadeOutUp; |
||||
animation-name: fadeOutUp; |
||||
} |
@ -0,0 +1,23 @@ |
||||
@-webkit-keyframes zoomIn { |
||||
from { |
||||
opacity: 0; |
||||
-webkit-transform: scale3d(.3, .3, .3); |
||||
transform: scale3d(.3, .3, .3); |
||||
} |
||||
|
||||
50% { |
||||
opacity: 1; |
||||
} |
||||
} |
||||
|
||||
@keyframes zoomIn { |
||||
from { |
||||
opacity: 0; |
||||
-webkit-transform: scale3d(.3, .3, .3); |
||||
transform: scale3d(.3, .3, .3); |
||||
} |
||||
|
||||
50% { |
||||
opacity: 1; |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
* { |
||||
-webkit-font-smoothing: antialiased; |
||||
-moz-osx-font-smoothing: grayscale; |
||||
|
||||
&:focus, |
||||
&:active { |
||||
outline: 0; |
||||
} |
||||
} |
||||
|
||||
html { |
||||
font-size: 10px; |
||||
-webkit-tap-highlight-color: rgba(0,0,0,0); |
||||
} |
||||
|
||||
html, |
||||
body { |
||||
min-height: 100%; |
||||
} |
||||
|
||||
a { |
||||
.transition(color); |
||||
.transition-duration(300ms); |
||||
|
||||
} |
||||
|
||||
button { |
||||
border: 0; |
||||
} |
||||
|
||||
|
@ -0,0 +1,53 @@ |
||||
.btn { |
||||
border: 0; |
||||
padding: 5px 10px; |
||||
font-size: 12px; |
||||
line-height: 1.5; |
||||
border-radius: 2px; |
||||
text-align: center; |
||||
.transition(all); |
||||
.transition-duration(300ms); |
||||
|
||||
&:hover, |
||||
&:focus { |
||||
.opacity(0.9); |
||||
} |
||||
} |
||||
|
||||
/*----------------------------------- |
||||
Button Variants |
||||
------------------------------------*/ |
||||
.btn-variant(@bg-color, @color) { |
||||
color: @color; |
||||
background-color: @bg-color; |
||||
|
||||
&:hover, |
||||
&:focus { |
||||
color: @color; |
||||
background-color: darken(@bg-color, 6%); |
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
.btn-block { |
||||
display: block; |
||||
width: 100%; |
||||
} |
||||
|
||||
.btn-link { |
||||
.btn-variant(#eee, #545454); |
||||
} |
||||
|
||||
.btn-danger { |
||||
.btn-variant(@red, @white); |
||||
} |
||||
|
||||
.btn-primary { |
||||
.btn-variant(@blue, @white); |
||||
} |
||||
|
||||
.btn-success { |
||||
.btn-variant(@green, @white); |
||||
} |
||||
//----------------------------------- |
@ -0,0 +1,26 @@ |
||||
.dropdown-menu { |
||||
padding: 15px 0; |
||||
top: 0; |
||||
margin-top: -1px; |
||||
|
||||
& > li { |
||||
& > a { |
||||
padding: 8px 20px; |
||||
font-size: 15px; |
||||
|
||||
& > i { |
||||
width: 20px; |
||||
position: relative; |
||||
top: 1px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.dropdown-menu-right { |
||||
& > li { |
||||
& > a { |
||||
text-align: right; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,160 @@ |
||||
/*------------------------------ |
||||
Layout |
||||
--------------------------------*/ |
||||
.file-explorer { |
||||
background-color: @white; |
||||
position: relative; |
||||
height: 100%; |
||||
|
||||
&.toggled { |
||||
height: 100vh; |
||||
overflow: hidden; |
||||
} |
||||
} |
||||
|
||||
.fe-body { |
||||
@media(min-width: @screen-md-min) { |
||||
padding: 0 0 40px @fe-sidebar-width; |
||||
} |
||||
|
||||
@media(max-width: @screen-sm-max) { |
||||
padding: 75px 0 80px; |
||||
} |
||||
|
||||
min-height:100vh; |
||||
overflow: auto; |
||||
} |
||||
|
||||
|
||||
/*------------------------------ |
||||
Create and Upload Button |
||||
--------------------------------*/ |
||||
.feb-actions { |
||||
position: fixed; |
||||
bottom: 30px; |
||||
right: 30px; |
||||
|
||||
.dropdown-menu { |
||||
min-width: 55px; |
||||
width: 55px; |
||||
text-align: center; |
||||
background: transparent; |
||||
box-shadow: none; |
||||
margin: 0; |
||||
} |
||||
|
||||
&.open { |
||||
.feba-btn { |
||||
.scale(1); |
||||
|
||||
&:first-child { |
||||
.animation-name(feba-btn-anim); |
||||
.animation-duration(300ms); |
||||
} |
||||
|
||||
&:last-child { |
||||
.animation-name(feba-btn-anim); |
||||
.animation-duration(100ms); |
||||
} |
||||
} |
||||
|
||||
.feba-toggle { |
||||
background: darken(@red, 10%); |
||||
|
||||
& > span { |
||||
.rotate(135deg); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.feba-toggle { |
||||
width: 55px; |
||||
height: 55px; |
||||
line-height: 55px; |
||||
border-radius: 50%; |
||||
background: @red; |
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.15); |
||||
display: inline-block; |
||||
text-align: center; |
||||
border: 0; |
||||
padding: 0; |
||||
|
||||
span { |
||||
display: inline-block; |
||||
height: 100%; |
||||
width: 100%; |
||||
} |
||||
|
||||
i { |
||||
color: @white; |
||||
font-size: 17px; |
||||
line-height: 58px; |
||||
} |
||||
} |
||||
|
||||
.feba-toggle, |
||||
.feba-toggle > span { |
||||
.transition(all); |
||||
.transition-duration(250ms); |
||||
.backface-visibility(hidden); |
||||
} |
||||
|
||||
.feba-btn { |
||||
width: 40px; |
||||
margin-top: 10px; |
||||
height: 40px; |
||||
border-radius: 50%; |
||||
text-align: center; |
||||
display: inline-block; |
||||
color: @white; |
||||
line-height: 40px; |
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.15); |
||||
-webkit-transform: scale(0); |
||||
transform: scale(0); |
||||
position: relative; |
||||
|
||||
&:hover, |
||||
&:focus { |
||||
color: @white; |
||||
} |
||||
|
||||
label { |
||||
width: 100%; |
||||
height: 100%; |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
|
||||
.feba-bucket { |
||||
background: @orange; |
||||
} |
||||
|
||||
.feba-upload { |
||||
background: @yellow; |
||||
} |
||||
|
||||
@-webkit-keyframes feba-btn-anim { |
||||
from { |
||||
.scale(0); |
||||
.opacity(0); |
||||
} |
||||
to { |
||||
.scale(1); |
||||
.opacity(1); |
||||
} |
||||
} |
||||
|
||||
@keyframes feba-btn-anim { |
||||
from { |
||||
.scale(0); |
||||
.opacity(0); |
||||
} |
||||
to { |
||||
.scale(1); |
||||
.opacity(1); |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
@font-face { |
||||
font-family: Lato; |
||||
src: url('../../fonts/lato/lato-normal.woff2') format('woff2'), |
||||
url('../../fonts/lato/lato-normal.woff') format('woff'); |
||||
font-weight: normal; |
||||
font-style: normal; |
||||
} |
@ -0,0 +1,249 @@ |
||||
.form-control { |
||||
border: 0; |
||||
border-bottom: 1px solid @input-border; |
||||
color: #32393F; |
||||
padding: 5px; |
||||
width: 100%; |
||||
font-size: 13px; |
||||
background-color: transparent; |
||||
} |
||||
|
||||
select.form-control { |
||||
-webkit-appearance: none; |
||||
-moz-appearance: none; |
||||
border-radius: 0; |
||||
background: url(../../img/select-caret.svg) no-repeat bottom 7px right; |
||||
|
||||
} |
||||
|
||||
|
||||
/*-------------------------- |
||||
Input Group |
||||
----------------------------*/ |
||||
.input-group { |
||||
position: relative; |
||||
&:not(:last-child) { |
||||
margin-bottom: 25px; |
||||
} |
||||
|
||||
label:not(.ig-label) { |
||||
font-size: 13px; |
||||
display: block; |
||||
margin-bottom: 10px; |
||||
} |
||||
} |
||||
|
||||
.ig-label { |
||||
position: absolute; |
||||
text-align: center; |
||||
bottom: 7px; |
||||
left: 0; |
||||
width: 100%; |
||||
.transition(all); |
||||
.transition-duration(250ms); |
||||
padding: 2px 0 3px; |
||||
border-radius: 2px; |
||||
font-weight: 400; |
||||
} |
||||
|
||||
.ig-helpers { |
||||
z-index: 1; |
||||
width: 100%; |
||||
left: 0; |
||||
|
||||
&, |
||||
&:before, |
||||
&:after { |
||||
position: absolute; |
||||
height: 2px; |
||||
bottom: 0; |
||||
} |
||||
|
||||
&:before, |
||||
&:after { |
||||
content: ''; |
||||
width: 0; |
||||
.transition(all); |
||||
.transition-duration(250ms); |
||||
background-color: #03A9F4; |
||||
} |
||||
|
||||
&:before { |
||||
left: 50%; |
||||
} |
||||
|
||||
&:after { |
||||
right: 50%; |
||||
} |
||||
} |
||||
|
||||
.ig-text { |
||||
width: 100%; |
||||
height: 40px; |
||||
border: 0; |
||||
background: transparent; |
||||
text-align: center; |
||||
position: relative; |
||||
z-index: 1; |
||||
border-bottom: 1px solid #eee; |
||||
color: #32393F; |
||||
font-size: 13px; |
||||
|
||||
|
||||
&:focus + .ig-helpers { |
||||
&:before, |
||||
&:after { |
||||
width: 50%; |
||||
} |
||||
} |
||||
|
||||
&:valid, |
||||
&:disabled, |
||||
&:focus { |
||||
& ~ .ig-label { |
||||
bottom: 35px; |
||||
font-size: 13px; |
||||
z-index: 1; |
||||
} |
||||
} |
||||
|
||||
&:disabled { |
||||
.opacity(0.5); |
||||
} |
||||
} |
||||
|
||||
.ig-dark { |
||||
.ig-text { |
||||
color: @white; |
||||
border-color: rgba(255,255,255,0.1); |
||||
} |
||||
|
||||
.ig-helpers { |
||||
&:before, |
||||
&:after { |
||||
background-color: #dfdfdf; |
||||
height: 1px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.ig-left { |
||||
.ig-label, |
||||
.ig-text { |
||||
text-align: left; |
||||
} |
||||
} |
||||
|
||||
.ig-error { |
||||
.ig-label { |
||||
color: #E23F3F; |
||||
} |
||||
.ig-helpers i { |
||||
&:first-child, |
||||
&:first-child:before, |
||||
&:first-child:after { |
||||
background: rgba(226, 63, 63, 0.43); |
||||
} |
||||
&:last-child, |
||||
&:last-child:before, |
||||
&:last-child:after { |
||||
background: #E23F3F !important; |
||||
} |
||||
} |
||||
&:after { |
||||
content: "\f05a"; |
||||
font-family: FontAwesome; |
||||
position: absolute; |
||||
top: 17px; |
||||
right: 9px; |
||||
font-size: 20px; |
||||
color: #D33D3E; |
||||
} |
||||
} |
||||
|
||||
.ig-search { |
||||
&:before { |
||||
font-family: @font-family-icon; |
||||
content: '\f002'; |
||||
font-size: 15px; |
||||
position: absolute; |
||||
left: 2px; |
||||
top: 8px; |
||||
} |
||||
|
||||
.ig-text { |
||||
padding-left: 25px; |
||||
} |
||||
} |
||||
|
||||
|
||||
/*-------------------------- |
||||
Share Spinners |
||||
----------------------------*/ |
||||
.set-expire { |
||||
border: 1px solid @input-border; |
||||
margin: 35px 0 30px; |
||||
} |
||||
|
||||
.set-expire-item { |
||||
padding: 9px 5px 3px; |
||||
position: relative; |
||||
display: table-cell; |
||||
width: 1%; |
||||
text-align: center; |
||||
|
||||
&:not(:last-child) { |
||||
border-right: 1px solid @input-border; |
||||
} |
||||
} |
||||
|
||||
.set-expire-title { |
||||
font-size: 10px; |
||||
text-transform: uppercase; |
||||
} |
||||
|
||||
.set-expire-value { |
||||
display: inline-block; |
||||
overflow: hidden; |
||||
position: relative; |
||||
left: -8px; |
||||
|
||||
input { |
||||
font-size: 20px; |
||||
text-align: center; |
||||
position: relative; |
||||
right: -15px; |
||||
border: 0; |
||||
color: @text-strong-color; |
||||
padding: 0; |
||||
height: 25px; |
||||
width: 100%; |
||||
font-weight: normal; |
||||
} |
||||
} |
||||
|
||||
.set-expire-decrease, |
||||
.set-expire-increase { |
||||
position: absolute; |
||||
width: 20px; |
||||
height: 20px; |
||||
background: url(../../img/arrow.svg) no-repeat center; |
||||
background-size: 85%; |
||||
left: 50%; |
||||
margin-left: -10px; |
||||
.opacity(0.2); |
||||
cursor: pointer; |
||||
|
||||
&:hover { |
||||
.opacity(0.5); |
||||
} |
||||
} |
||||
|
||||
.set-expire-increase { |
||||
top: -25px; |
||||
} |
||||
|
||||
.set-expire-decrease { |
||||
bottom: -27px; |
||||
.rotate(-180deg); |
||||
} |
@ -0,0 +1,83 @@ |
||||
/*---------------------------- |
||||
Text Alignment |
||||
-----------------------------*/ |
||||
.text-center { text-align: center !important; } |
||||
.text-left { text-align: left !important; } |
||||
.text-right { text-align: right !important; } |
||||
|
||||
|
||||
/*---------------------------- |
||||
Float |
||||
-----------------------------*/ |
||||
.clearfix { .clearfix(); } |
||||
.pull-right { float: right !important; } |
||||
.pull-left { float: left !important; } |
||||
|
||||
|
||||
/*---------------------------- |
||||
Position |
||||
-----------------------------*/ |
||||
.p-relative { position: relative; } |
||||
|
||||
|
||||
/*--------------------------------------------------------------------------- |
||||
Generate Margin Class |
||||
margin, margin-top, margin-bottom, margin-left, margin-right |
||||
----------------------------------------------------------------------------*/ |
||||
|
||||
.margin (@label, @size: 1, @key:1) when (@size =< 30){ |
||||
.m-@{key} { |
||||
margin: @size !important; |
||||
} |
||||
|
||||
.m-t-@{key} { |
||||
margin-top: @size !important; |
||||
} |
||||
|
||||
.m-b-@{key} { |
||||
margin-bottom: @size !important; |
||||
} |
||||
|
||||
.m-l-@{key} { |
||||
margin-left: @size !important; |
||||
} |
||||
|
||||
.m-r-@{key} { |
||||
margin-right: @size !important; |
||||
} |
||||
|
||||
.margin(@label - 5; @size + 5; @key + 5); |
||||
} |
||||
|
||||
.margin(25, 0px, 0); |
||||
|
||||
|
||||
/*--------------------------------------------------------------------------- |
||||
Generate Padding Class |
||||
padding, padding-top, padding-bottom, padding-left, padding-right |
||||
----------------------------------------------------------------------------*/ |
||||
.padding (@label, @size: 1, @key:1) when (@size =< 30){ |
||||
.p-@{key} { |
||||
padding: @size !important; |
||||
} |
||||
|
||||
.p-t-@{key} { |
||||
padding-top: @size !important; |
||||
} |
||||
|
||||
.p-b-@{key} { |
||||
padding-bottom: @size !important; |
||||
} |
||||
|
||||
.p-l-@{key} { |
||||
padding-left: @size !important; |
||||
} |
||||
|
||||
.p-r-@{key} { |
||||
padding-right: @size !important; |
||||
} |
||||
|
||||
.padding(@label - 5; @size + 5; @key + 5); |
||||
} |
||||
|
||||
.padding(25, 0px, 0); |
@ -0,0 +1,242 @@ |
||||
/*-------------------------- |
||||
Header |
||||
----------------------------*/ |
||||
.fe-header { |
||||
padding: 45px 55px 20px; |
||||
|
||||
@media(min-width: @screen-md-min) { |
||||
position: relative; |
||||
} |
||||
|
||||
@media(max-width: (@screen-xs-max - 100)) { |
||||
padding: 25px 25px 20px; |
||||
} |
||||
|
||||
h2 { |
||||
font-size: 16px; |
||||
font-weight: normal; |
||||
margin: 0; |
||||
|
||||
& > span { |
||||
margin-bottom: 7px; |
||||
display: inline-block; |
||||
|
||||
&:not(:first-child) { |
||||
&:before { |
||||
content: '/'; |
||||
margin: 0 4px; |
||||
color: @text-color; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
p { |
||||
margin-top: 7px; |
||||
} |
||||
} |
||||
|
||||
|
||||
/*-------------------------- |
||||
Disk usage |
||||
----------------------------*/ |
||||
.feh-usage { |
||||
margin-top: 12px; |
||||
max-width: 285px; |
||||
|
||||
@media(max-width: (@screen-xs-max - 100px)) { |
||||
max-width: 100%; |
||||
font-size: 12px; |
||||
} |
||||
|
||||
& > ul { |
||||
margin-top: 7px; |
||||
list-style: none; |
||||
padding: 0; |
||||
|
||||
& > li { |
||||
padding-right: 0; |
||||
display: inline-block; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.fehu-chart { |
||||
height: 5px; |
||||
background: #eee; |
||||
position: relative; |
||||
border-radius: 2px; |
||||
overflow: hidden; |
||||
|
||||
& > div { |
||||
position: absolute; |
||||
left: 0; |
||||
height: 100%; |
||||
background: @link-color; |
||||
} |
||||
} |
||||
|
||||
/*-------------------------- |
||||
Header Actions |
||||
----------------------------*/ |
||||
.feh-actions { |
||||
list-style: none; |
||||
padding: 0; |
||||
margin: 0; |
||||
position: absolute; |
||||
right: 35px; |
||||
top: 30px; |
||||
z-index: 11; |
||||
|
||||
@media(max-width: (@screen-sm-max)) { |
||||
top: 7px; |
||||
right: 10px; |
||||
position: fixed; |
||||
} |
||||
|
||||
& > li { |
||||
display: inline-block; |
||||
text-align: right; |
||||
vertical-align: top; |
||||
line-height: 100%; |
||||
|
||||
& > a, |
||||
& > .btn-group > button { |
||||
display: block; |
||||
height: 45px; |
||||
min-width: 45px; |
||||
text-align: center; |
||||
border-radius: 50%; |
||||
padding: 0; |
||||
border: 0; |
||||
background: none; |
||||
|
||||
@media(min-width: @screen-md-min) { |
||||
color: #7B7B7B; |
||||
font-size: 21px; |
||||
line-height: 45px; |
||||
.transition(all); |
||||
.transition-duration(300ms); |
||||
|
||||
&:hover { |
||||
background: rgba(0,0,0,0.09); |
||||
} |
||||
} |
||||
|
||||
@media(max-width: (@screen-sm-max)) { |
||||
background: url(../../img/more-h-light.svg) no-repeat center; |
||||
|
||||
.fa-reorder { |
||||
display: none; |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
/*-------------------------- |
||||
Mobile Header |
||||
----------------------------*/ |
||||
@media(max-width: @screen-sm-max) { |
||||
.fe-header-mobile { |
||||
background-color: @dark-gray; |
||||
padding: 10px 50px 9px 12px; |
||||
text-align: center; |
||||
position: fixed; |
||||
z-index: 10; |
||||
box-shadow: 0 0 10px rgba(0,0,0,0.3); |
||||
left: 0; |
||||
top: 0; |
||||
width: 100%; |
||||
|
||||
.mh-logo { |
||||
height: 35px; |
||||
position: relative; |
||||
top: 4px; |
||||
} |
||||
} |
||||
|
||||
.feh-trigger { |
||||
width: 41px; |
||||
height: 41px; |
||||
cursor: pointer; |
||||
float: left; |
||||
position: relative; |
||||
text-align: center; |
||||
|
||||
&:before, |
||||
&:after { |
||||
content: ""; |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
border-radius: 50%; |
||||
|
||||
} |
||||
|
||||
&:after { |
||||
z-index: 1; |
||||
} |
||||
|
||||
&:before { |
||||
background: rgba(255, 255, 255, 0.1); |
||||
.transition(all); |
||||
.transition-duration(300ms); |
||||
.scale(0); |
||||
} |
||||
} |
||||
|
||||
.feht-toggled { |
||||
&:before { |
||||
.scale(1); |
||||
} |
||||
|
||||
.feht-lines { |
||||
.rotate(180deg); |
||||
|
||||
& > div { |
||||
&.top { |
||||
width: 12px; |
||||
transform: translateX(8px) translateY(1px) rotate(45deg); |
||||
-webkit-transform: translateX(8px) translateY(1px) rotate(45deg); |
||||
} |
||||
|
||||
&.bottom { |
||||
width: 12px; |
||||
transform: translateX(8px) translateY(-1px) rotate(-45deg); |
||||
-webkit-transform: translateX(8px) translateY(-1px) rotate(-45deg); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.feht-lines, |
||||
.feht-lines > div { |
||||
.transition(all); |
||||
.transition-duration(300ms); |
||||
} |
||||
|
||||
.feht-lines { |
||||
width: 18px; |
||||
height: 12px; |
||||
display: inline-block; |
||||
margin-top: 14px; |
||||
|
||||
& > div { |
||||
background-color: #EAEAEA; |
||||
width: 18px; |
||||
height: 2px; |
||||
|
||||
&.center { |
||||
margin: 3px 0; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
|
@ -0,0 +1,81 @@ |
||||
.ie-warning { |
||||
background-color: #ff5252; |
||||
width: 100%; |
||||
height: 100%; |
||||
position: fixed; |
||||
left: 0; |
||||
top: 0; |
||||
text-align: center; |
||||
|
||||
&:before { |
||||
width: 1px; |
||||
content: ''; |
||||
height: 100%; |
||||
} |
||||
|
||||
&:before, |
||||
.iw-inner { |
||||
display: inline-block; |
||||
vertical-align: middle; |
||||
} |
||||
} |
||||
|
||||
.iw-inner { |
||||
width: 470px; |
||||
height: 300px; |
||||
background-color: @white; |
||||
border-radius: 5px; |
||||
padding: 40px; |
||||
position: relative; |
||||
|
||||
ul { |
||||
list-style: none; |
||||
padding: 0; |
||||
margin: 0; |
||||
width: 230px; |
||||
margin-left: 80px; |
||||
margin-top: 16px; |
||||
|
||||
& > li { |
||||
float: left; |
||||
|
||||
& > a { |
||||
display: block; |
||||
padding: 10px 15px 7px; |
||||
font-size: 14px; |
||||
margin: 0 1px; |
||||
border-radius: 3px; |
||||
|
||||
&:hover { |
||||
background: #eee; |
||||
} |
||||
|
||||
img { |
||||
height: 40px; |
||||
margin-bottom: 5px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.iwi-icon { |
||||
color: #ff5252; |
||||
font-size: 40px; |
||||
display: block; |
||||
line-height: 100%; |
||||
margin-bottom: 15px; |
||||
} |
||||
|
||||
.iwi-skip { |
||||
position: absolute; |
||||
left: 0; |
||||
bottom: -35px; |
||||
width: 100%; |
||||
color: rgba(255, 255, 255, 0.6); |
||||
cursor: pointer; |
||||
|
||||
&:hover { |
||||
color: @white; |
||||
} |
||||
} |
@ -0,0 +1,352 @@ |
||||
/*-------------------------- |
||||
Row |
||||
----------------------------*/ |
||||
.fesl-row { |
||||
padding-right: 40px; |
||||
padding-top: 5px; |
||||
padding-bottom: 5px; |
||||
position: relative; |
||||
|
||||
@media (min-width: (@screen-sm-min - 100px)) { |
||||
display: flex; |
||||
flex-flow: row nowrap; |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
.clearfix(); |
||||
} |
||||
|
||||
header.fesl-row { |
||||
@media (min-width:(@screen-sm-min - 100px)) { |
||||
margin-bottom: 20px; |
||||
border-bottom: 1px solid lighten(@text-muted-color, 20%); |
||||
padding-left: 40px; |
||||
|
||||
.fesl-item, |
||||
.fesli-sort { |
||||
.transition(all); |
||||
.transition-duration(300ms); |
||||
} |
||||
|
||||
.fesl-item { |
||||
cursor: pointer; |
||||
color: @text-color; |
||||
font-weight: 500; |
||||
margin-bottom: -5px; |
||||
|
||||
& > .fesli-sort { |
||||
float: right; |
||||
margin: 4px 0 0; |
||||
.opacity(0); |
||||
color: @dark-gray; |
||||
font-size: 14px; |
||||
} |
||||
|
||||
&:hover:not(.fi-actions) { |
||||
background: lighten(@text-muted-color, 22%); |
||||
color: @dark-gray; |
||||
|
||||
& > .fesli-sort { |
||||
.opacity(0.5); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@media (max-width:(@screen-xs-max - 100px)) { |
||||
display: none; |
||||
} |
||||
} |
||||
|
||||
div.fesl-row { |
||||
padding-left: 85px; |
||||
border-bottom: 1px solid transparent; |
||||
cursor: default; |
||||
|
||||
@media (max-width: (@screen-xs-max - 100px)) { |
||||
padding-left: 70px; |
||||
padding-right: 45px; |
||||
} |
||||
|
||||
&:nth-child(even) { |
||||
background-color: #fafafa; |
||||
} |
||||
|
||||
&:hover { |
||||
background-color: #fbf7dc; |
||||
} |
||||
|
||||
&[data-type]:before { |
||||
font-family: @font-family-icon; |
||||
width: 35px; |
||||
height: 35px; |
||||
text-align: center; |
||||
line-height: 35px; |
||||
position: absolute; |
||||
border-radius: 50%; |
||||
font-size: 16px; |
||||
left: 50px; |
||||
top: 9px; |
||||
color: @white; |
||||
|
||||
@media (max-width: (@screen-xs-max - 100px)) { |
||||
left: 20px; |
||||
} |
||||
} |
||||
|
||||
&[data-type="folder"] { |
||||
@media (max-width: (@screen-xs-max - 100px)) { |
||||
.fesl-item { |
||||
&.fi-name { |
||||
padding-top: 10px; |
||||
padding-bottom: 7px; |
||||
} |
||||
|
||||
&.fi-size, |
||||
&.fi-modified { |
||||
display: none; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/*-------------------------- |
||||
Icons |
||||
----------------------------*/ |
||||
&[data-type=folder]:before { |
||||
content: '\f114'; |
||||
background-color: #a1d6dd; |
||||
} |
||||
&[data-type=pdf]:before { |
||||
content: "\f1c1"; |
||||
background-color: #fa7775; |
||||
} |
||||
&[data-type=zip]:before { |
||||
content: "\f1c6"; |
||||
background-color: #427089; |
||||
} |
||||
&[data-type=audio]:before { |
||||
content: "\f1c7"; |
||||
background-color: #009688 |
||||
} |
||||
&[data-type=code]:before { |
||||
content: "\f1c9"; |
||||
background-color: #997867; |
||||
} |
||||
&[data-type=excel]:before { |
||||
content: "\f1c3"; |
||||
background-color: #64c866; |
||||
} |
||||
&[data-type=image]:before { |
||||
content: "\f1c5"; |
||||
background-color: #f06292; |
||||
} |
||||
&[data-type=video]:before { |
||||
content: "\f1c8"; |
||||
background-color: #f8c363; |
||||
} |
||||
&[data-type=other]:before { |
||||
content: "\f016"; |
||||
background-color: #afafaf; |
||||
} |
||||
&[data-type=text]:before { |
||||
content: "\f0f6"; |
||||
background-color: #8a8a8a; |
||||
} |
||||
&[data-type=doc]:before { |
||||
content: "\f1c2"; |
||||
background-color: #2196f5; |
||||
} |
||||
&[data-type=presentation]:before { |
||||
content: "\f1c4"; |
||||
background-color: #896ea6; |
||||
} |
||||
|
||||
&.fesl-loading{ |
||||
&:before { |
||||
content: ''; |
||||
} |
||||
|
||||
&:after { |
||||
.list-loader(20px, 20px, rgba(255, 255, 255, 0.5), @white); |
||||
left: 57px; |
||||
top: 17px; |
||||
|
||||
@media (max-width: (@screen-xs-max - 100px)) { |
||||
left: 27px; |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
|
||||
/*-------------------------- |
||||
Files and Folders |
||||
----------------------------*/ |
||||
.fesl-item { |
||||
display: block; |
||||
|
||||
a { |
||||
color: darken(@text-color, 5%); |
||||
} |
||||
|
||||
@media(min-width: (@screen-sm-min - 100px)) { |
||||
&:not(.fi-actions) { |
||||
text-overflow: ellipsis; |
||||
padding: 10px 15px; |
||||
white-space: nowrap; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
&.fi-name { |
||||
flex: 3; |
||||
} |
||||
|
||||
&.fi-size { |
||||
width: 140px; |
||||
} |
||||
|
||||
&.fi-modified { |
||||
width: 190px; |
||||
} |
||||
|
||||
&.fi-actions { |
||||
width: 40px; |
||||
} |
||||
} |
||||
|
||||
@media(max-width: (@screen-xs-max - 100px)) { |
||||
padding: 0; |
||||
|
||||
&.fi-name { |
||||
width: 100%; |
||||
margin-bottom: 3px; |
||||
} |
||||
|
||||
&.fi-size, |
||||
&.fi-modified { |
||||
font-size: 12px; |
||||
color: #B5B5B5; |
||||
float: left; |
||||
} |
||||
|
||||
&.fi-modified { |
||||
max-width: 72px; |
||||
white-space: nowrap; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
&.fi-size { |
||||
margin-right: 10px; |
||||
} |
||||
|
||||
&.fi-actions { |
||||
position: absolute; |
||||
top: 5px; |
||||
right: 10px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
/*-------------------------- |
||||
Action buttons |
||||
----------------------------*/ |
||||
.fia-toggle { |
||||
height: 36px; |
||||
width: 36px; |
||||
background: transparent url(../../img/more-h.svg) no-repeat center; |
||||
position: relative; |
||||
top: 3px; |
||||
.opacity(0.4); |
||||
|
||||
&:hover { |
||||
.opacity(0.7); |
||||
} |
||||
} |
||||
|
||||
.fi-actions { |
||||
.dropdown-menu { |
||||
background-color: transparent; |
||||
box-shadow: none; |
||||
padding: 0; |
||||
right: 38px; |
||||
left: auto; |
||||
margin: 0; |
||||
height: 100%; |
||||
text-align: right; |
||||
} |
||||
|
||||
.dropdown { |
||||
&.open { |
||||
.dropdown-menu { |
||||
.fiad-action { |
||||
right: 0; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.fiad-action { |
||||
height: 35px; |
||||
width: 35px; |
||||
background: @amber; |
||||
display: inline-block; |
||||
border-radius: 50%; |
||||
text-align: center; |
||||
line-height: 35px; |
||||
font-weight: normal; |
||||
position: relative; |
||||
top: 4px; |
||||
margin-left: 5px; |
||||
.animation-name(fiad-action-anim); |
||||
.transform-origin(center center); |
||||
.backface-visibility(none); |
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
||||
|
||||
&:nth-child(2) { |
||||
.animation-duration(100ms); |
||||
} |
||||
|
||||
&:nth-child(1) { |
||||
.animation-duration(250ms); |
||||
} |
||||
|
||||
& > i { |
||||
font-size: 14px; |
||||
color: @white; |
||||
} |
||||
|
||||
&:hover { |
||||
background-color: darken(@amber, 3%); |
||||
} |
||||
} |
||||
|
||||
|
||||
@-webkit-keyframes fiad-action-anim { |
||||
from { |
||||
.scale(0); |
||||
.opacity(0); |
||||
right: -20px; |
||||
} |
||||
to { |
||||
.scale(1); |
||||
.opacity(1); |
||||
right: 0; |
||||
} |
||||
} |
||||
|
||||
@keyframes fiad-action-anim { |
||||
from { |
||||
.scale(0); |
||||
.opacity(0); |
||||
right: -20px; |
||||
} |
||||
to { |
||||
.scale(1); |
||||
.opacity(1); |
||||
right: 0; |
||||
} |
||||
} |
@ -0,0 +1,104 @@ |
||||
.login { |
||||
height: 100vh; |
||||
min-height: 500px; |
||||
background: @dark-gray; |
||||
|
||||
text-align: center; |
||||
&:before { |
||||
height: ~"calc(100% - 110px)"; |
||||
width: 1px; |
||||
content: ""; |
||||
} |
||||
} |
||||
|
||||
.l-wrap, |
||||
.login:before { |
||||
display: inline-block; |
||||
vertical-align: middle; |
||||
} |
||||
|
||||
.l-wrap { |
||||
width: 80%; |
||||
max-width: 500px; |
||||
margin-top: -50px; |
||||
&.toggled { |
||||
display: inline-block; |
||||
} |
||||
|
||||
.input-group:not(:last-child) { |
||||
margin-bottom: 40px; |
||||
} |
||||
} |
||||
|
||||
.l-footer { |
||||
height: 110px; |
||||
padding: 0 50px; |
||||
} |
||||
|
||||
.lf-logo { |
||||
float: right; |
||||
img { |
||||
width: 40px; |
||||
} |
||||
} |
||||
|
||||
.lf-server { |
||||
float: left; |
||||
color: rgba(255, 255, 255, 0.4); |
||||
font-size: 20px; |
||||
font-weight: 400; |
||||
padding-top: 40px; |
||||
} |
||||
|
||||
@media (max-width: @screen-sm-min) { |
||||
.lf-logo, |
||||
.lf-server { |
||||
float: none; |
||||
display: block; |
||||
text-align: center; |
||||
width: 100%; |
||||
} |
||||
|
||||
.lf-logo { |
||||
margin-bottom: 5px; |
||||
} |
||||
|
||||
.lf-server { |
||||
font-size: 15px; |
||||
} |
||||
} |
||||
|
||||
.lw-btn { |
||||
width: 50px; |
||||
height: 50px; |
||||
border: 1px solid @white; |
||||
display: inline-block; |
||||
border-radius: 50%; |
||||
font-size: 22px; |
||||
color: @white; |
||||
.transition(all); |
||||
.transition-duration(300ms); |
||||
opacity: 0.3; |
||||
background-color: transparent; |
||||
line-height: 45px; |
||||
padding: 0; |
||||
&:hover { |
||||
color: @white; |
||||
opacity: 0.8; |
||||
border-color: @white; |
||||
} |
||||
|
||||
i { |
||||
display: block; |
||||
width: 100%; |
||||
padding-left: 3px; |
||||
} |
||||
} |
||||
|
||||
/*------------------------------ |
||||
Chrome autofill fix |
||||
-------------------------------*/ |
||||
input:-webkit-autofill { |
||||
-webkit-box-shadow:0 0 0 50px @dark-gray inset !important; |
||||
-webkit-text-fill-color: @white !important; |
||||
} |
@ -0,0 +1,102 @@ |
||||
/*-------------------------- |
||||
Close |
||||
----------------------------*/ |
||||
.close-variant(@color, @bg-color, @color-hover, @bg-color-hover) { |
||||
span { |
||||
background-color: @bg-color; |
||||
color: @color; |
||||
} |
||||
|
||||
&:hover, |
||||
&:focus { |
||||
span { |
||||
background-color: @bg-color-hover; |
||||
color: @color-hover; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.close { |
||||
right: 15px; |
||||
font-weight: normal; |
||||
opacity: 1; |
||||
font-size: 18px; |
||||
position: absolute; |
||||
text-align: center; |
||||
top: 16px; |
||||
z-index: 1; |
||||
padding: 0; |
||||
border: 0; |
||||
background-color: transparent; |
||||
|
||||
span { |
||||
width: 25px; |
||||
height: 25px; |
||||
display: block; |
||||
border-radius: 50%; |
||||
line-height: 24px; |
||||
text-shadow: none; |
||||
} |
||||
|
||||
&:not(.close-alt) { |
||||
.close-variant(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1), @white, rgba(255, 255, 255, 0.2)); |
||||
} |
||||
} |
||||
|
||||
.close-alt { |
||||
.close-variant(#989898, #efefef, #7b7b7b, #e8e8e8); |
||||
} |
||||
|
||||
|
||||
/*-------------------------- |
||||
Hidden |
||||
----------------------------*/ |
||||
.hidden { |
||||
display: none !important; |
||||
} |
||||
|
||||
|
||||
/*-------------------------- |
||||
Copy text |
||||
----------------------------*/ |
||||
.copy-text { |
||||
input { |
||||
width: 100%; |
||||
border-radius: 1px; |
||||
border: 1px solid @input-border; |
||||
padding: 7px 12px; |
||||
font-size: 13px; |
||||
line-height: 100%; |
||||
cursor: text; |
||||
.transition(border-color); |
||||
.transition-duration(300ms); |
||||
|
||||
&:hover { |
||||
border-color: darken(@input-border, 5%); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/*-------------------------- |
||||
Sharing |
||||
----------------------------*/ |
||||
.share-availability { |
||||
margin-bottom: 40px; |
||||
|
||||
&:before, |
||||
&:after { |
||||
position: absolute; |
||||
bottom: -30px; |
||||
font-size: 10px; |
||||
} |
||||
|
||||
&:before { |
||||
content: '01 Sec'; |
||||
left: 0; |
||||
} |
||||
|
||||
&:after { |
||||
content: '7 days'; |
||||
right: 0; |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
/*-------------------------- |
||||
User Select |
||||
----------------------------*/ |
||||
.user-select(@value) { |
||||
-webkit-user-select: @value; |
||||
-moz-user-select: @value; |
||||
-ms-user-select: @value; |
||||
user-select: @value; |
||||
} |
||||
|
||||
|
||||
/*---------------------------------------- |
||||
CSS Animations based on animate.css |
||||
-----------------------------------------*/ |
||||
.animated(@name, @duration) { |
||||
-webkit-animation-name: @name; |
||||
animation-name: @name; |
||||
-webkit-animation-duration: @duration; |
||||
animation-duration: @duration; |
||||
-webkit-animation-fill-mode: both; |
||||
animation-fill-mode: both; |
||||
} |
||||
|
||||
/*------------------------------------------------- |
||||
For loop mixin for generate custom classes |
||||
--------------------------------------------------*/ |
||||
.for(@i, @n) {.-each(@i)} |
||||
.for(@n) when (isnumber(@n)) {.for(1, @n)} |
||||
.for(@i, @n) when not (@i = @n) { |
||||
.for((@i + (@n - @i) / abs(@n - @i)), @n); |
||||
} |
||||
|
||||
.for(@array) when (default()) {.for-impl_(length(@array))} |
||||
.for-impl_(@i) when (@i > 1) {.for-impl_((@i - 1))} |
||||
.for-impl_(@i) when (@i > 0) {.-each(extract(@array, @i))} |
||||
|
||||
/*---------------------------------------- |
||||
List Loader |
||||
-----------------------------------------*/ |
||||
.list-loader(@width, @height, @borderColor, @borderColorBottom) { |
||||
content: ''; |
||||
width: @width; |
||||
height: @height; |
||||
border-radius: 50%; |
||||
.animated(zoomIn, 500ms); |
||||
border: 2px solid @borderColor; |
||||
border-bottom-color: @borderColorBottom; |
||||
position: absolute; |
||||
z-index: 1; |
||||
-webkit-animation: zoomIn 250ms, spin 700ms 250ms infinite linear; |
||||
animation: zoomIn 250ms, spin 700ms 250ms infinite linear; |
||||
} |
@ -0,0 +1,294 @@ |
||||
/*-------------------------- |
||||
Modal |
||||
----------------------------*/ |
||||
.modal { |
||||
@media(min-width: @screen-sm-min) { |
||||
text-align: center; |
||||
|
||||
&:before { |
||||
content: ''; |
||||
height: 100%; |
||||
width: 1px; |
||||
display: inline-block; |
||||
vertical-align: middle; |
||||
} |
||||
|
||||
.modal-dialog { |
||||
text-align: left; |
||||
margin: 10px auto; |
||||
display: inline-block; |
||||
vertical-align: middle; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.modal-dark { |
||||
.modal-header { |
||||
color: rgba(255, 255, 255, 0.4); |
||||
|
||||
small { |
||||
color: rgba(255, 255, 255, 0.2); |
||||
} |
||||
} |
||||
|
||||
.modal-content { |
||||
background-color: @dark-gray; |
||||
} |
||||
} |
||||
|
||||
.modal-backdrop { |
||||
.animated(fadeIn, 200ms); |
||||
} |
||||
|
||||
.modal-dialog { |
||||
.animated(zoomIn, 200ms); |
||||
} |
||||
|
||||
.modal-header { |
||||
color: @text-strong-color; |
||||
position: relative; |
||||
|
||||
small { |
||||
display: block; |
||||
text-transform: none; |
||||
font-size: 12px; |
||||
margin-top: 5px; |
||||
color: #a8a8a8; |
||||
} |
||||
} |
||||
|
||||
.modal-content { |
||||
border-radius: 3px; |
||||
box-shadow: none; |
||||
} |
||||
|
||||
.modal-footer { |
||||
padding: 0 30px 30px; |
||||
text-align: center; |
||||
} |
||||
|
||||
|
||||
/*-------------------------- |
||||
Dialog |
||||
----------------------------*/ |
||||
.modal-confirm { |
||||
.modal-dialog { |
||||
text-align: center; |
||||
} |
||||
} |
||||
|
||||
.mc-icon { |
||||
margin: 0 0 10px; |
||||
|
||||
& > i { |
||||
font-size: 60px; |
||||
} |
||||
} |
||||
|
||||
.mci-red { |
||||
color: #ff8f8f; |
||||
} |
||||
|
||||
.mci-amber { |
||||
color: @amber; |
||||
} |
||||
|
||||
.mci-green { |
||||
color: #64e096; |
||||
} |
||||
|
||||
.mc-text { |
||||
color: @text-strong-color; |
||||
} |
||||
|
||||
.mc-sub { |
||||
color: @text-muted-color; |
||||
margin-top: 5px; |
||||
font-size: 13px; |
||||
} |
||||
//-------------------------- |
||||
|
||||
|
||||
/*-------------------------- |
||||
About |
||||
----------------------------*/ |
||||
.modal-about { |
||||
@media (max-width: @screen-xs-max) { |
||||
text-align: center; |
||||
|
||||
.modal-dialog { |
||||
max-width: 400px; |
||||
width: 90%; |
||||
margin: 20px auto 0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.ma-inner { |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
min-height: 350px; |
||||
position: relative; |
||||
|
||||
@media (min-width: @screen-sm-min) { |
||||
&:before { |
||||
content: ''; |
||||
width: 150px; |
||||
height: 100%; |
||||
top: 0; |
||||
left: 0; |
||||
position: absolute; |
||||
border-radius: 3px 0 0px 3px; |
||||
background-color: #23282C; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.mai-item { |
||||
&:first-child { |
||||
width: 150px; |
||||
text-align: center; |
||||
} |
||||
|
||||
&:last-child { |
||||
flex: 4; |
||||
padding: 30px; |
||||
} |
||||
} |
||||
|
||||
.maii-logo { |
||||
width: 70px; |
||||
position: relative; |
||||
|
||||
} |
||||
|
||||
.maii-list { |
||||
list-style: none; |
||||
padding: 0; |
||||
|
||||
& > li { |
||||
margin-bottom: 15px; |
||||
|
||||
div { |
||||
color: rgba(255, 255, 255, 0.8); |
||||
text-transform: uppercase; |
||||
font-size: 14px; |
||||
} |
||||
|
||||
small { |
||||
font-size: 13px; |
||||
color: rgba(255, 255, 255, 0.4); |
||||
} |
||||
} |
||||
} |
||||
//-------------------------- |
||||
|
||||
|
||||
/*-------------------------- |
||||
Preferences |
||||
----------------------------*/ |
||||
.toggle-password { |
||||
position: absolute; |
||||
bottom: 30px; |
||||
right: 35px; |
||||
width: 30px; |
||||
height: 30px; |
||||
border: 1px solid #eee; |
||||
border-radius: 0; |
||||
text-align: center; |
||||
cursor: pointer; |
||||
z-index: 10; |
||||
background-color: @white; |
||||
padding-top: 5px; |
||||
|
||||
&.toggled { |
||||
background: #eee; |
||||
} |
||||
} |
||||
//-------------------------- |
||||
|
||||
|
||||
/*-------------------------- |
||||
Policy |
||||
----------------------------*/ |
||||
.pm-body { |
||||
padding-bottom: 30px; |
||||
} |
||||
|
||||
.pmb-header { |
||||
margin-bottom: 35px; |
||||
} |
||||
|
||||
.pmb-list { |
||||
display: flex; |
||||
flex-flow: row nowrap; |
||||
align-items: center; |
||||
justify-content: center; |
||||
padding: 10px 35px; |
||||
|
||||
&:nth-child(even) { |
||||
background-color: #F7F7F7; |
||||
} |
||||
|
||||
.form-control { |
||||
padding-left: 0; |
||||
padding-right: 0; |
||||
} |
||||
} |
||||
|
||||
header.pmb-list { |
||||
margin: 20px 0 10px; |
||||
} |
||||
|
||||
.pmbl-item { |
||||
display: block; |
||||
font-size: 13px; |
||||
|
||||
&:nth-child(1) { |
||||
flex: 2; |
||||
} |
||||
|
||||
&:nth-child(2) { |
||||
margin: 0 25px; |
||||
width: 150px; |
||||
} |
||||
|
||||
&:nth-child(3) { |
||||
width: 70px; |
||||
} |
||||
} |
||||
|
||||
div.pmb-list { |
||||
select { |
||||
border: 0; |
||||
} |
||||
|
||||
.pml-item { |
||||
&:not(:last-child) { |
||||
padding: 0 5px; |
||||
} |
||||
} |
||||
} |
||||
//-------------------------- |
||||
|
||||
|
||||
/*-------------------------- |
||||
Create Bucket |
||||
----------------------------*/ |
||||
.modal-create-bucket { |
||||
.modal-dialog { |
||||
position: fixed; |
||||
right: 25px; |
||||
bottom: 95px; |
||||
margin: 0; |
||||
height: 110px; |
||||
} |
||||
|
||||
.modal-content { |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
} |
||||
//-------------------------- |
||||
|
@ -0,0 +1,187 @@ |
||||
/*-------------------------- |
||||
Sidebar |
||||
----------------------------*/ |
||||
.fe-sidebar { |
||||
width: @fe-sidebar-width; |
||||
background-color: @dark-gray; |
||||
position: fixed; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
padding: 35px; |
||||
|
||||
@media(min-width: @screen-md-min) { |
||||
.translate3d(0, 0, 0); |
||||
} |
||||
|
||||
@media(max-width: @screen-sm-max) { |
||||
padding-top: 85px; |
||||
z-index: 9; |
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.65); |
||||
.transition(all); |
||||
.transition-duration(300ms); |
||||
.translate3d((-@fe-sidebar-width - 15px), 0, 0); |
||||
|
||||
&.toggled { |
||||
.translate3d(0, 0, 0); |
||||
} |
||||
} |
||||
|
||||
a { |
||||
color: rgba(255, 255, 255, 0.58); |
||||
|
||||
&:hover { |
||||
color: @white; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/*-------------------------- |
||||
Header |
||||
----------------------------*/ |
||||
.fes-header { |
||||
margin-bottom: 40px; |
||||
|
||||
img, |
||||
h2 { |
||||
float: left; |
||||
} |
||||
|
||||
h2 { |
||||
margin: 13px 0 0 10px; |
||||
font-weight: normal; |
||||
} |
||||
|
||||
img { |
||||
width: 32px; |
||||
} |
||||
} |
||||
|
||||
/*-------------------------- |
||||
List |
||||
----------------------------*/ |
||||
.fesl-inner { |
||||
height: ~"calc(100vh - 260px)"; |
||||
overflow: auto; |
||||
padding: 0; |
||||
margin: 0 -35px; |
||||
|
||||
& li { |
||||
position: relative; |
||||
|
||||
& > a { |
||||
display: block; |
||||
padding: 10px 40px 12px 65px; |
||||
.text-overflow(); |
||||
|
||||
&:before { |
||||
font-family: FontAwesome; |
||||
content: '\f0a0'; |
||||
font-size: 17px; |
||||
position: absolute; |
||||
top: 10px; |
||||
left: 35px; |
||||
.opacity(0.8); |
||||
} |
||||
|
||||
&.fesli-loading { |
||||
&:before { |
||||
.list-loader(20px, 20px, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.5)); |
||||
left: 32px; |
||||
top: 0; |
||||
bottom: 0; |
||||
margin: auto; |
||||
} |
||||
} |
||||
} |
||||
|
||||
&.active { |
||||
background-color: rgba(0, 0, 0, 0.2); |
||||
|
||||
& > a { |
||||
color: @white; |
||||
} |
||||
} |
||||
|
||||
&:not(.active):hover { |
||||
background-color: rgba(0, 0, 0, 0.1); |
||||
|
||||
& > a { |
||||
color: @white; |
||||
} |
||||
} |
||||
|
||||
&:hover { |
||||
.fesli-trigger { |
||||
.opacity(0.6); |
||||
|
||||
&:hover { |
||||
.opacity(1); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
ul { |
||||
list-style: none; |
||||
padding: 0; |
||||
margin: 0; |
||||
} |
||||
|
||||
&:hover .scrollbar-vertical { |
||||
opacity: 1; |
||||
} |
||||
} |
||||
|
||||
.fesli-trigger { |
||||
.opacity(0); |
||||
.transition(all); |
||||
.transition-duration(200ms); |
||||
position: absolute; |
||||
top: 0; |
||||
right: 0; |
||||
width: 40px; |
||||
height: 100%; |
||||
cursor: pointer; |
||||
background: url(../../img/more-h-light.svg) no-repeat left; |
||||
} |
||||
|
||||
/* Scrollbar */ |
||||
.scrollbar-vertical { |
||||
position: absolute; |
||||
right: 5px; |
||||
width: 4px; |
||||
height: 100%; |
||||
opacity: 0; |
||||
.transition(opacity); |
||||
.transition-duration(300ms); |
||||
|
||||
div { |
||||
border-radius: 1px !important; |
||||
background-color: #6a6a6a !important; |
||||
} |
||||
} |
||||
|
||||
/*-------------------------- |
||||
Host |
||||
----------------------------*/ |
||||
.fes-host { |
||||
position: fixed; |
||||
left: 0; |
||||
bottom: 0; |
||||
z-index: 1; |
||||
background: @dark-gray; |
||||
color: rgba(255, 255, 255, 0.4); |
||||
font-size: 15px; |
||||
font-weight: 400; |
||||
width: @fe-sidebar-width; |
||||
padding: 20px; |
||||
.text-overflow(); |
||||
|
||||
& > i { |
||||
margin-right: 10px; |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,94 @@ |
||||
/*-------------------------- |
||||
Base |
||||
----------------------------*/ |
||||
@font-family-sans-serif : 'Lato', sans-serif; |
||||
@font-family-icon : 'fontAwesome'; |
||||
@body-bg : #edecec; |
||||
@text-color : #8e8e8e; |
||||
@font-size-base : 15px; |
||||
@link-color : #46a5e0; |
||||
@link-hover-decoration : none; |
||||
|
||||
|
||||
/*-------------------------- |
||||
File Explorer |
||||
----------------------------*/ |
||||
@fe-sidebar-width : 300px; |
||||
@text-muted-color : #BDBDBD; |
||||
@text-strong-color : #333; |
||||
|
||||
/*-------------------------- |
||||
Colors |
||||
----------------------------*/ |
||||
@cyan : #2ED2FF; |
||||
@amber : #ffc107; |
||||
@red : #ff726f; |
||||
@grey : #f5f5f5; |
||||
@dark-blue : #0084d3; |
||||
@blue : #00a6f7; |
||||
@white : #ffffff; |
||||
@black : #1b1e25; |
||||
@blue : #50b2ff; |
||||
@light-blue : #c1d1e8; |
||||
@green : #33d46f; |
||||
@yellow : #FFC107; |
||||
@orange : #ffc155; |
||||
@purple : #9C27B0; |
||||
@teal : #009688; |
||||
@brown : #795548; |
||||
@blue-gray : #374952; |
||||
@dark-gray : #32393F; |
||||
|
||||
|
||||
/*-------------------------- |
||||
Dropdown |
||||
----------------------------*/ |
||||
@dropdown-fallback-border : transparent; |
||||
@dropdown-border : transparent; |
||||
@dropdown-divider-bg : ''; |
||||
@dropdown-link-hover-bg : rgba(0,0,0,0.05); |
||||
@dropdown-link-color : @text-color; |
||||
@dropdown-link-hover-color : #333; |
||||
@dropdown-link-disabled-color : #e4e4e4; |
||||
@dropdown-divider-bg : rgba(0,0,0,0.08); |
||||
@dropdown-link-active-color : #333; |
||||
@dropdown-link-active-bg : rgba(0, 0, 0, 0.075); |
||||
@dropdown-shadow : 0 2px 10px rgba(0, 0, 0, 0.2); |
||||
|
||||
|
||||
/*-------------------------- |
||||
Modal |
||||
----------------------------*/ |
||||
@modal-content-fallback-border-color: transparent; |
||||
@modal-content-border-color: transparent; |
||||
@modal-backdrop-bg: rgba(0,0,0,0.1); |
||||
@modal-header-border-color: transparent; |
||||
@modal-title-line-height: transparent; |
||||
@modal-footer-border-color: transparent; |
||||
@modal-inner-padding: 30px 35px; |
||||
@modal-title-padding: 30px 35px 0px; |
||||
@modal-sm: 400px; |
||||
|
||||
|
||||
/*------------------------- |
||||
Buttons |
||||
--------------------------*/ |
||||
@btn-border-radius-large: 2px; |
||||
@btn-border-radius-small: 2px; |
||||
@btn-border-radius-base: 2px; |
||||
|
||||
|
||||
/*------------------------- |
||||
Colors |
||||
--------------------------*/ |
||||
@brand-primary: #2196F3; |
||||
@brand-success: #4CAF50; |
||||
@brand-info: #00BCD4; |
||||
@brand-warning: #FF9800; |
||||
@brand-danger: #FF5722; |
||||
|
||||
|
||||
/*------------------------- |
||||
Form |
||||
--------------------------*/ |
||||
@input-border: #eee; |
@ -0,0 +1,39 @@ |
||||
/*---------------------------- |
||||
Bootstrap |
||||
-----------------------------*/ |
||||
@import "../../node_modules/bootstrap/less/scaffolding.less"; |
||||
@import "../../node_modules/bootstrap/less/variables.less"; |
||||
@import "../../node_modules/bootstrap/less/grid.less"; |
||||
@import "../../node_modules/bootstrap/less/mixins.less"; |
||||
@import "../../node_modules/bootstrap/less/normalize.less"; |
||||
@import "../../node_modules/bootstrap/less/dropdowns.less"; |
||||
@import "../../node_modules/bootstrap/less/modals.less"; |
||||
@import "../../node_modules/bootstrap/less/tooltip.less"; |
||||
@import "../../node_modules/bootstrap/less/responsive-utilities.less"; |
||||
|
||||
|
||||
/*---------------------------- |
||||
App |
||||
-----------------------------*/ |
||||
@import 'inc/mixin'; |
||||
@import 'inc/variables'; |
||||
@import 'inc/base'; |
||||
@import 'inc/animate/animate'; |
||||
@import 'inc/generics'; |
||||
@import 'inc/font'; |
||||
@import 'inc/form'; |
||||
@import 'inc/buttons'; |
||||
@import 'inc/misc'; |
||||
@import 'inc/login'; |
||||
@import 'inc/header'; |
||||
@import 'inc/sidebar'; |
||||
@import 'inc/list'; |
||||
@import 'inc/file-explorer'; |
||||
@import 'inc/ie-warning'; |
||||
|
||||
/*---------------------------- |
||||
Boostrap |
||||
-----------------------------*/ |
||||
@import 'inc/dropdown'; |
||||
@import 'inc/alert'; |
||||
@import 'inc/modal'; |
@ -0,0 +1,126 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
var moment = require('moment') |
||||
var async = require('async') |
||||
var exec = require('child_process').exec |
||||
var fs = require('fs') |
||||
|
||||
var isProduction = process.env.NODE_ENV == 'production' ? true : false |
||||
var assetsFileName = '' |
||||
var commitId = '' |
||||
var date = moment.utc() |
||||
var version = date.format('YYYY-MM-DDTHH:mm:ss') + 'Z' |
||||
var releaseTag = date.format('YYYY-MM-DDTHH-mm-ss') + 'Z' |
||||
var buildType = 'DEVELOPMENT' |
||||
if (process.env.MINIO_UI_BUILD) buildType = process.env.MINIO_UI_BUILD |
||||
|
||||
rmDir = function(dirPath) { |
||||
try { var files = fs.readdirSync(dirPath); } |
||||
catch(e) { return; } |
||||
if (files.length > 0) |
||||
for (var i = 0; i < files.length; i++) { |
||||
var filePath = dirPath + '/' + files[i]; |
||||
if (fs.statSync(filePath).isFile()) |
||||
fs.unlinkSync(filePath); |
||||
else |
||||
rmDir(filePath); |
||||
} |
||||
fs.rmdirSync(dirPath); |
||||
}; |
||||
|
||||
async.waterfall([ |
||||
function(cb) { |
||||
rmDir('production'); |
||||
rmDir('dev'); |
||||
var cmd = 'webpack -p --config webpack.production.config.js' |
||||
if (!isProduction) { |
||||
cmd = 'webpack'; |
||||
} |
||||
console.log('Running', cmd) |
||||
exec(cmd, cb) |
||||
}, |
||||
function(stdout, stderr, cb) { |
||||
if (isProduction) { |
||||
fs.renameSync('production/index_bundle.js', |
||||
'production/index_bundle-' + releaseTag + '.js') |
||||
} else { |
||||
fs.renameSync('dev/index_bundle.js', |
||||
'dev/index_bundle-' + releaseTag + '.js') |
||||
} |
||||
var cmd = 'git log --format="%H" -n1' |
||||
console.log('Running', cmd) |
||||
exec(cmd, cb) |
||||
}, |
||||
function(stdout, stderr, cb) { |
||||
if (!stdout) throw new Error('commitId is empty') |
||||
commitId = stdout.replace('\n', '') |
||||
if (commitId.length !== 40) throw new Error('commitId invalid : ' + commitId) |
||||
assetsFileName = 'ui-assets.go'; |
||||
var cmd = 'go-bindata-assetfs -pkg miniobrowser -nocompress=true production/...' |
||||
if (!isProduction) { |
||||
cmd = 'go-bindata-assetfs -pkg miniobrowser -nocompress=true dev/...' |
||||
} |
||||
console.log('Running', cmd) |
||||
exec(cmd, cb) |
||||
}, |
||||
function(stdout, stderr, cb) { |
||||
var cmd = 'gofmt -s -w -l bindata_assetfs.go' |
||||
console.log('Running', cmd) |
||||
exec(cmd, cb) |
||||
}, |
||||
function(stdout, stderr, cb) { |
||||
fs.renameSync('bindata_assetfs.go', assetsFileName) |
||||
fs.appendFileSync(assetsFileName, '\n') |
||||
fs.appendFileSync(assetsFileName, 'var UIReleaseTag = "' + buildType + '.' + |
||||
releaseTag + '"\n') |
||||
fs.appendFileSync(assetsFileName, 'var UICommitID = "' + commitId + '"\n') |
||||
fs.appendFileSync(assetsFileName, 'var UIVersion = "' + version + '"') |
||||
fs.appendFileSync(assetsFileName, '\n') |
||||
var contents; |
||||
if (isProduction) { |
||||
contents = fs.readFileSync(assetsFileName, 'utf8') |
||||
.replace(/_productionIndexHtml/g, '_productionIndexHTML') |
||||
.replace(/productionIndexHtmlBytes/g, 'productionIndexHTMLBytes') |
||||
.replace(/productionIndexHtml/g, 'productionIndexHTML') |
||||
.replace(/_productionIndex_bundleJs/g, '_productionIndexBundleJs') |
||||
.replace(/productionIndex_bundleJsBytes/g, 'productionIndexBundleJsBytes') |
||||
.replace(/productionIndex_bundleJs/g, 'productionIndexBundleJs') |
||||
.replace(/_productionJqueryUiMinJs/g, '_productionJqueryUIMinJs') |
||||
.replace(/productionJqueryUiMinJsBytes/g, 'productionJqueryUIMinJsBytes') |
||||
.replace(/productionJqueryUiMinJs/g, 'productionJqueryUIMinJs'); |
||||
} else { |
||||
contents = fs.readFileSync(assetsFileName, 'utf8') |
||||
.replace(/_devIndexHtml/g, '_devIndexHTML') |
||||
.replace(/devIndexHtmlBytes/g, 'devIndexHTMLBytes') |
||||
.replace(/devIndexHtml/g, 'devIndexHTML') |
||||
.replace(/_devIndex_bundleJs/g, '_devIndexBundleJs') |
||||
.replace(/devIndex_bundleJsBytes/g, 'devIndexBundleJsBytes') |
||||
.replace(/devIndex_bundleJs/g, 'devIndexBundleJs') |
||||
.replace(/_devJqueryUiMinJs/g, '_devJqueryUIMinJs') |
||||
.replace(/devJqueryUiMinJsBytes/g, 'devJqueryUIMinJsBytes') |
||||
.replace(/devJqueryUiMinJs/g, 'devJqueryUIMinJs'); |
||||
} |
||||
contents = contents.replace(/MINIO_UI_VERSION/g, version) |
||||
contents = contents.replace(/index_bundle.js/g, 'index_bundle-' + releaseTag + '.js') |
||||
|
||||
fs.writeFileSync(assetsFileName, contents, 'utf8') |
||||
console.log('UI assets file :', assetsFileName) |
||||
cb() |
||||
} |
||||
], function(err) { |
||||
if (err) return console.log(err) |
||||
}) |
@ -0,0 +1,40 @@ |
||||
var webpack = require('webpack'); |
||||
|
||||
module.exports = function (config) { |
||||
config.set({ |
||||
browsers: [ process.env.CONTINUOUS_INTEGRATION ? 'Firefox' : 'Chrome' ], |
||||
singleRun: true, |
||||
frameworks: [ 'mocha' ], |
||||
files: [ |
||||
'tests.webpack.js' |
||||
], |
||||
preprocessors: { |
||||
'tests.webpack.js': [ 'webpack' ] |
||||
}, |
||||
reporters: [ 'dots' ], |
||||
webpack: { |
||||
module: { |
||||
loaders: [{ |
||||
test: /\.js$/, |
||||
exclude: /(node_modules|bower_components)/, |
||||
loader: 'babel', |
||||
query: { |
||||
presets: ['react', 'es2015'] |
||||
} |
||||
}, { |
||||
test: /\.less$/, |
||||
loader: 'style!css!less' |
||||
}, { |
||||
test: /\.css$/, |
||||
loader: 'style!css' |
||||
}, { |
||||
test: /\.(eot|woff|woff2|ttf|svg|png)/, |
||||
loader: 'url' |
||||
}] |
||||
} |
||||
}, |
||||
webpackServer: { |
||||
noInfo: true |
||||
} |
||||
}); |
||||
}; |
@ -0,0 +1,82 @@ |
||||
{ |
||||
"name": "minio-browser", |
||||
"version": "0.0.1", |
||||
"description": "Minio Browser", |
||||
"scripts": { |
||||
"test": "karma start", |
||||
"dev": "NODE_ENV=dev webpack-dev-server --devtool eval --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'" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "https://github.com/minio/miniobrowser" |
||||
}, |
||||
"author": "Minio Inc", |
||||
"license": "Apache-2.0", |
||||
"bugs": { |
||||
"url": "https://github.com/minio/miniobrowser/issues" |
||||
}, |
||||
"homepage": "https://github.com/minio/miniobrowser", |
||||
"devDependencies": { |
||||
"async": "^1.5.2", |
||||
"babel-cli": "^6.14.0", |
||||
"babel-core": "^6.14.0", |
||||
"babel-loader": "^6.2.5", |
||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0", |
||||
"babel-plugin-transform-object-rest-spread": "^6.8.0", |
||||
"babel-preset-es2015": "^6.14.0", |
||||
"babel-preset-react": "^6.11.1", |
||||
"babel-register": "^6.14.0", |
||||
"copy-webpack-plugin": "^0.3.3", |
||||
"css-loader": "^0.23.1", |
||||
"esformatter": "^0.10.0", |
||||
"esformatter-jsx-ignore": "^1.0.6", |
||||
"expect": "^1.20.2", |
||||
"history": "^1.17.0", |
||||
"html-webpack-plugin": "^2.22.0", |
||||
"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": "^1.7.0", |
||||
"less": "^2.7.1", |
||||
"less-loader": "^2.2.3", |
||||
"mocha": "^2.5.3", |
||||
"moment": "^2.15.1", |
||||
"purifycss-webpack-plugin": "^2.0.3", |
||||
"react": "^0.14.8", |
||||
"react-addons-test-utils": "^0.14.8", |
||||
"react-bootstrap": "^0.28.5", |
||||
"react-custom-scrollbars": "^2.3.0", |
||||
"react-redux": "^4.4.5", |
||||
"react-router": "^2.8.1", |
||||
"redux": "^3.6.0", |
||||
"redux-thunk": "^1.0.3", |
||||
"style-loader": "^0.13.1", |
||||
"superagent": "^1.8.4", |
||||
"superagent-es6-promise": "^1.0.0", |
||||
"url-loader": "^0.5.7", |
||||
"webpack": "^1.12.11", |
||||
"webpack-dev-server": "^1.14.1" |
||||
}, |
||||
"dependencies": { |
||||
"bootstrap": "^3.3.6", |
||||
"classnames": "^2.2.3", |
||||
"font-awesome": "^4.7.0", |
||||
"humanize": "0.0.9", |
||||
"json-loader": "^0.5.4", |
||||
"local-storage-fallback": "^1.3.0", |
||||
"mime-db": "^1.25.0", |
||||
"mime-types": "^2.1.13", |
||||
"react": "^0.14.8", |
||||
"react-copy-to-clipboard": "^4.2.3", |
||||
"react-custom-scrollbars": "^2.2.2", |
||||
"react-dom": "^0.14.6", |
||||
"react-dropzone": "^3.5.3", |
||||
"react-onclickout": "2.0.4" |
||||
} |
||||
} |
@ -0,0 +1,2 @@ |
||||
var context = require.context('./app', true, /-test\.js$/); |
||||
context.keys().forEach(context); |
@ -0,0 +1,105 @@ |
||||
/* |
||||
* Minio Browser (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
var webpack = require('webpack') |
||||
var path = require('path') |
||||
var CopyWebpackPlugin = require('copy-webpack-plugin') |
||||
var purify = require("purifycss-webpack-plugin") |
||||
|
||||
var exports = { |
||||
context: __dirname, |
||||
entry: [ |
||||
path.resolve(__dirname, 'app/index.js') |
||||
], |
||||
output: { |
||||
path: path.resolve(__dirname, 'dev'), |
||||
filename: 'index_bundle.js', |
||||
publicPath: '/minio/' |
||||
}, |
||||
module: { |
||||
loaders: [{ |
||||
test: /\.js$/, |
||||
exclude: /(node_modules|bower_components)/, |
||||
loader: 'babel', |
||||
query: { |
||||
presets: ['react', 'es2015'] |
||||
} |
||||
}, { |
||||
test: /\.less$/, |
||||
loader: 'style!css!less' |
||||
}, { |
||||
test: /\.json$/, |
||||
loader: 'json-loader' |
||||
},{ |
||||
test: /\.css$/, |
||||
loader: 'style!css' |
||||
}, { |
||||
test: /\.(eot|woff|woff2|ttf|svg|png)/, |
||||
loader: 'url' |
||||
}] |
||||
}, |
||||
node:{ |
||||
fs:'empty' |
||||
}, |
||||
devServer: { |
||||
historyApiFallback: { |
||||
index: '/minio/' |
||||
}, |
||||
proxy: { |
||||
'/minio/webrpc': { |
||||
target: 'http://localhost:9000', |
||||
secure: false |
||||
}, |
||||
'/minio/upload/*': { |
||||
target: 'http://localhost:9000', |
||||
secure: false |
||||
}, |
||||
'/minio/download/*': { |
||||
target: 'http://localhost:9000', |
||||
secure: false |
||||
}, |
||||
} |
||||
}, |
||||
plugins: [ |
||||
new CopyWebpackPlugin([ |
||||
{from: 'app/css/loader.css'}, |
||||
{from: 'app/img/favicon.ico'}, |
||||
{from: 'app/img/browsers/chrome.png'}, |
||||
{from: 'app/img/browsers/firefox.png'}, |
||||
{from: 'app/img/browsers/safari.png'}, |
||||
{from: 'app/img/logo.svg'}, |
||||
{from: 'app/index.html'} |
||||
]), |
||||
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en)$/), |
||||
new purify({ |
||||
basePath: __dirname, |
||||
paths: [ |
||||
"app/index.html", |
||||
"app/js/*.js" |
||||
] |
||||
}) |
||||
] |
||||
} |
||||
|
||||
if (process.env.NODE_ENV === 'dev') { |
||||
exports.entry = [ |
||||
'webpack/hot/dev-server', |
||||
'webpack-dev-server/client?http://localhost:8080', |
||||
path.resolve(__dirname, 'app/index.js') |
||||
] |
||||
} |
||||
|
||||
module.exports = exports |
@ -0,0 +1,88 @@ |
||||
/* |
||||
* Isomorphic Javascript library for Minio Browser JSON-RPC API, (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. |
||||
*/ |
||||
|
||||
var webpack = require('webpack') |
||||
var path = require('path') |
||||
var CopyWebpackPlugin = require('copy-webpack-plugin') |
||||
var purify = require("purifycss-webpack-plugin") |
||||
|
||||
var exports = { |
||||
context: __dirname, |
||||
entry: [ |
||||
path.resolve(__dirname, 'app/index.js') |
||||
], |
||||
output: { |
||||
path: path.resolve(__dirname, 'production'), |
||||
filename: 'index_bundle.js' |
||||
}, |
||||
module: { |
||||
loaders: [{ |
||||
test: /\.js$/, |
||||
exclude: /(node_modules|bower_components)/, |
||||
loader: 'babel', |
||||
query: { |
||||
presets: ['react', 'es2015'] |
||||
} |
||||
}, { |
||||
test: /\.less$/, |
||||
loader: 'style!css!less' |
||||
}, { |
||||
test: /\.json$/, |
||||
loader: 'json-loader' |
||||
}, { |
||||
test: /\.css$/, |
||||
loader: 'style!css' |
||||
}, { |
||||
test: /\.(eot|woff|woff2|ttf|svg|png)/, |
||||
loader: 'url' |
||||
}] |
||||
}, |
||||
node:{ |
||||
fs:'empty' |
||||
}, |
||||
plugins: [ |
||||
new CopyWebpackPlugin([ |
||||
{from: 'app/css/loader.css'}, |
||||
{from: 'app/img/favicon.ico'}, |
||||
{from: 'app/img/browsers/chrome.png'}, |
||||
{from: 'app/img/browsers/firefox.png'}, |
||||
{from: 'app/img/browsers/safari.png'}, |
||||
{from: 'app/img/logo.svg'}, |
||||
{from: 'app/index.html'} |
||||
]), |
||||
new webpack.DefinePlugin({ |
||||
'process.env.NODE_ENV': '"production"' |
||||
}), |
||||
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en)$/), |
||||
new purify({ |
||||
basePath: __dirname, |
||||
paths: [ |
||||
"app/index.html", |
||||
"app/js/*.js" |
||||
] |
||||
}) |
||||
] |
||||
} |
||||
|
||||
if (process.env.NODE_ENV === 'dev') { |
||||
exports.entry = [ |
||||
'webpack/hot/dev-server', |
||||
'webpack-dev-server/client?http://localhost:8080', |
||||
path.resolve(__dirname, 'app/index.js') |
||||
] |
||||
} |
||||
|
||||
module.exports = exports |
@ -1,202 +0,0 @@ |
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "{}" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright {yyyy} {name of copyright owner} |
||||
|
||||
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. |
||||
|