- upgraded react from v16.2.0 - upgraded react-router to v4.2.0 and re-writen the routes - using prettier to format the code - added jest to unit test components/reducers/selectors This provides a skeleton to start of with. Only basic unit test cases are added, remaining needs to be added. In terms of functionality, it provides login, listing and searching buckets. Remaining functionalities will be added in upcoming patches.master
parent
76be54551f
commit
9ab6a8035f
@ -0,0 +1,3 @@ |
||||
{ |
||||
"semi": false |
||||
} |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.9 KiB |
@ -1,695 +0,0 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import Moment from 'moment' |
||||
import browserHistory from 'react-router/lib/browserHistory' |
||||
import storage from 'local-storage-fallback' |
||||
import { minioBrowserPrefix } from './constants' |
||||
|
||||
export const SET_WEB = 'SET_WEB' |
||||
export const SET_CURRENT_BUCKET = 'SET_CURRENT_BUCKET' |
||||
export const SET_CURRENT_PATH = 'SET_CURRENT_PATH' |
||||
export const SET_BUCKETS = 'SET_BUCKETS' |
||||
export const ADD_BUCKET = 'ADD_BUCKET' |
||||
export const REMOVE_BUCKET = 'REMOVE_BUCKET' |
||||
export const SHOW_BUCKET_DROPDOWN = 'SHOW_BUCKET_DROPDOWN' |
||||
export const SET_VISIBLE_BUCKETS = 'SET_VISIBLE_BUCKETS' |
||||
export const SET_OBJECTS = 'SET_OBJECTS' |
||||
export const APPEND_OBJECTS = 'APPEND_OBJECTS' |
||||
export const RESET_OBJECTS = 'RESET_OBJECTS' |
||||
export const SET_STORAGE_INFO = 'SET_STORAGE_INFO' |
||||
export const SET_SERVER_INFO = 'SET_SERVER_INFO' |
||||
export const SHOW_MAKEBUCKET_MODAL = 'SHOW_MAKEBUCKET_MODAL' |
||||
export const ADD_UPLOAD = 'ADD_UPLOAD' |
||||
export const STOP_UPLOAD = 'STOP_UPLOAD' |
||||
export const UPLOAD_PROGRESS = 'UPLOAD_PROGRESS' |
||||
export const SET_ALERT = 'SET_ALERT' |
||||
export const SET_LOGIN_ERROR = 'SET_LOGIN_ERROR' |
||||
export const SET_SHOW_ABORT_MODAL = 'SET_SHOW_ABORT_MODAL' |
||||
export const SHOW_ABOUT = 'SHOW_ABOUT' |
||||
export const SET_SORT_NAME_ORDER = 'SET_SORT_NAME_ORDER' |
||||
export const SET_SORT_SIZE_ORDER = 'SET_SORT_SIZE_ORDER' |
||||
export const SET_SORT_DATE_ORDER = 'SET_SORT_DATE_ORDER' |
||||
export const SET_LATEST_UI_VERSION = 'SET_LATEST_UI_VERSION' |
||||
export const SET_SIDEBAR_STATUS = 'SET_SIDEBAR_STATUS' |
||||
export const SET_LOGIN_REDIRECT_PATH = 'SET_LOGIN_REDIRECT_PATH' |
||||
export const SET_LOAD_BUCKET = 'SET_LOAD_BUCKET' |
||||
export const SET_LOAD_PATH = 'SET_LOAD_PATH' |
||||
export const SHOW_SETTINGS = 'SHOW_SETTINGS' |
||||
export const SET_SETTINGS = 'SET_SETTINGS' |
||||
export const SHOW_BUCKET_POLICY = 'SHOW_BUCKET_POLICY' |
||||
export const SET_POLICIES = 'SET_POLICIES' |
||||
export const SET_SHARE_OBJECT = 'SET_SHARE_OBJECT' |
||||
export const DELETE_CONFIRMATION = 'DELETE_CONFIRMATION' |
||||
export const SET_PREFIX_WRITABLE = 'SET_PREFIX_WRITABLE' |
||||
export const REMOVE_OBJECT = 'REMOVE_OBJECT' |
||||
export const CHECKED_OBJECTS_ADD = 'CHECKED_OBJECTS_ADD' |
||||
export const CHECKED_OBJECTS_REMOVE = 'CHECKED_OBJECTS_REMOVE' |
||||
export const CHECKED_OBJECTS_RESET = 'CHECKED_OBJECTS_RESET' |
||||
|
||||
export const showDeleteConfirmation = (object) => { |
||||
return { |
||||
type: DELETE_CONFIRMATION, |
||||
payload: { |
||||
object, |
||||
show: true |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const hideDeleteConfirmation = () => { |
||||
return { |
||||
type: DELETE_CONFIRMATION, |
||||
payload: { |
||||
object: '', |
||||
show: false |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const showShareObject = (object, url) => { |
||||
return { |
||||
type: SET_SHARE_OBJECT, |
||||
shareObject: { |
||||
object, |
||||
url, |
||||
show: true |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const hideShareObject = () => { |
||||
return { |
||||
type: SET_SHARE_OBJECT, |
||||
shareObject: { |
||||
url: '', |
||||
show: false |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const shareObject = (object, days, hours, minutes) => (dispatch, getState) => { |
||||
const {currentBucket, web} = getState() |
||||
let host = location.host |
||||
let bucket = currentBucket |
||||
|
||||
if (!web.LoggedIn()) { |
||||
dispatch(showShareObject(object, `${host}/${bucket}/${object}`)) |
||||
return |
||||
} |
||||
|
||||
let expiry = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 |
||||
web.PresignedGet({ |
||||
host, |
||||
bucket, |
||||
object, |
||||
expiry |
||||
}) |
||||
.then(obj => { |
||||
dispatch(showShareObject(object, obj.url)) |
||||
dispatch(showAlert({ |
||||
type: 'success', |
||||
message: `Object shared. Expires in ${days} days ${hours} hours ${minutes} minutes.` |
||||
})) |
||||
}) |
||||
.catch(err => { |
||||
dispatch(showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
})) |
||||
}) |
||||
} |
||||
|
||||
export const setLoginRedirectPath = (path) => { |
||||
return { |
||||
type: SET_LOGIN_REDIRECT_PATH, |
||||
path |
||||
} |
||||
} |
||||
|
||||
export const setLoadPath = (loadPath) => { |
||||
return { |
||||
type: SET_LOAD_PATH, |
||||
loadPath |
||||
} |
||||
} |
||||
|
||||
export const setLoadBucket = (loadBucket) => { |
||||
return { |
||||
type: SET_LOAD_BUCKET, |
||||
loadBucket |
||||
} |
||||
} |
||||
|
||||
export const setWeb = web => { |
||||
return { |
||||
type: SET_WEB, |
||||
web |
||||
} |
||||
} |
||||
|
||||
export const setBuckets = buckets => { |
||||
return { |
||||
type: SET_BUCKETS, |
||||
buckets |
||||
} |
||||
} |
||||
|
||||
export const addBucket = bucket => { |
||||
return { |
||||
type: ADD_BUCKET, |
||||
bucket |
||||
} |
||||
} |
||||
|
||||
export const removeBucket = bucket => { |
||||
return { |
||||
type: REMOVE_BUCKET, |
||||
bucket |
||||
} |
||||
} |
||||
|
||||
export const showBucketDropdown = bucket => { |
||||
return { |
||||
type: SHOW_BUCKET_DROPDOWN, |
||||
showBucketDropdown: true |
||||
} |
||||
} |
||||
|
||||
export const hideBucketDropdown = bucket => { |
||||
return { |
||||
type: SHOW_BUCKET_DROPDOWN, |
||||
showBucketDropdown: false |
||||
} |
||||
} |
||||
|
||||
export const showMakeBucketModal = () => { |
||||
return { |
||||
type: SHOW_MAKEBUCKET_MODAL, |
||||
showMakeBucketModal: true |
||||
} |
||||
} |
||||
|
||||
export const hideAlert = () => { |
||||
return { |
||||
type: SET_ALERT, |
||||
alert: { |
||||
show: false, |
||||
message: '', |
||||
type: '' |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const showAlert = alert => { |
||||
return (dispatch, getState) => { |
||||
let alertTimeout = null |
||||
if (alert.type !== 'danger') { |
||||
alertTimeout = setTimeout(() => { |
||||
dispatch({ |
||||
type: SET_ALERT, |
||||
alert: { |
||||
show: false |
||||
} |
||||
}) |
||||
}, 5000) |
||||
} |
||||
dispatch({ |
||||
type: SET_ALERT, |
||||
alert: Object.assign({}, alert, { |
||||
show: true, |
||||
alertTimeout |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
export const removeObject = object => { |
||||
return { |
||||
type: REMOVE_OBJECT, |
||||
object |
||||
} |
||||
} |
||||
|
||||
export const setSidebarStatus = (status) => { |
||||
return { |
||||
type: SET_SIDEBAR_STATUS, |
||||
sidebarStatus: status |
||||
} |
||||
} |
||||
|
||||
export const hideMakeBucketModal = () => { |
||||
return { |
||||
type: SHOW_MAKEBUCKET_MODAL, |
||||
showMakeBucketModal: false |
||||
} |
||||
} |
||||
|
||||
export const setVisibleBuckets = visibleBuckets => { |
||||
return { |
||||
type: SET_VISIBLE_BUCKETS, |
||||
visibleBuckets |
||||
} |
||||
} |
||||
|
||||
const appendObjects = (objects, marker, istruncated) => { |
||||
return { |
||||
type: APPEND_OBJECTS, |
||||
objects, |
||||
marker, |
||||
istruncated |
||||
} |
||||
} |
||||
|
||||
export const setObjects = (objects) => { |
||||
return { |
||||
type: SET_OBJECTS, |
||||
objects, |
||||
} |
||||
} |
||||
|
||||
export const resetObjects = () => { |
||||
return { |
||||
type: RESET_OBJECTS |
||||
} |
||||
} |
||||
|
||||
export const setCurrentBucket = currentBucket => { |
||||
return { |
||||
type: SET_CURRENT_BUCKET, |
||||
currentBucket |
||||
} |
||||
} |
||||
|
||||
export const setCurrentPath = currentPath => { |
||||
return { |
||||
type: SET_CURRENT_PATH, |
||||
currentPath |
||||
} |
||||
} |
||||
|
||||
export const setStorageInfo = storageInfo => { |
||||
return { |
||||
type: SET_STORAGE_INFO, |
||||
storageInfo |
||||
} |
||||
} |
||||
|
||||
export const setServerInfo = serverInfo => { |
||||
return { |
||||
type: SET_SERVER_INFO, |
||||
serverInfo |
||||
} |
||||
} |
||||
|
||||
const setPrefixWritable = prefixWritable => { |
||||
return { |
||||
type: SET_PREFIX_WRITABLE, |
||||
prefixWritable, |
||||
} |
||||
} |
||||
|
||||
export const selectBucket = (newCurrentBucket, prefix) => { |
||||
if (!prefix) |
||||
prefix = '' |
||||
return (dispatch, getState) => { |
||||
let web = getState().web |
||||
let currentBucket = getState().currentBucket |
||||
|
||||
if (currentBucket !== newCurrentBucket) dispatch(setLoadBucket(newCurrentBucket)) |
||||
|
||||
dispatch(setCurrentBucket(newCurrentBucket)) |
||||
dispatch(selectPrefix(prefix)) |
||||
return |
||||
} |
||||
} |
||||
|
||||
export const deleteBucket = (bucket) => { |
||||
return (dispatch, getState) => { |
||||
// DeleteBucket() RPC call will ONLY delete a bucket if it is empty of
|
||||
// objects. This means a call can just be sent, as it is entirely reversable
|
||||
// and won't do any permanent damage.
|
||||
web.DeleteBucket({ |
||||
bucketName: bucket |
||||
}) |
||||
.then(() => { |
||||
dispatch(showAlert({ |
||||
type: 'info', |
||||
message: `Bucket '${bucket}' has been deleted.` |
||||
})) |
||||
dispatch(removeBucket(bucket)) |
||||
}) |
||||
.catch(err => { |
||||
let message = err.message |
||||
dispatch(showAlert({ |
||||
type: 'danger', |
||||
message: message |
||||
})) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
export const listObjects = () => { |
||||
return (dispatch, getState) => { |
||||
const {buckets, currentBucket, currentPath, marker, objects, istruncated, web} = getState() |
||||
if (!istruncated || buckets.length === 0) return |
||||
web.ListObjects({ |
||||
bucketName: currentBucket, |
||||
prefix: currentPath, |
||||
marker: marker |
||||
}) |
||||
.then(res => { |
||||
let objects = res.objects |
||||
if (!objects) |
||||
objects = [] |
||||
objects = objects.map(object => { |
||||
object.name = object.name.replace(`${currentPath}`, ''); |
||||
return object |
||||
}) |
||||
dispatch(appendObjects(objects, res.nextmarker, res.istruncated)) |
||||
dispatch(setPrefixWritable(res.writable)) |
||||
dispatch(setLoadBucket('')) |
||||
dispatch(setLoadPath('')) |
||||
}) |
||||
.catch(err => { |
||||
dispatch(showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
})) |
||||
dispatch(setLoadBucket('')) |
||||
dispatch(setLoadPath('')) |
||||
// Use browserHistory.replace instead of push so that browser back button works fine.
|
||||
browserHistory.replace(`${minioBrowserPrefix}/login`) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
export const selectPrefix = prefix => { |
||||
return (dispatch, getState) => { |
||||
const {currentBucket, web} = getState() |
||||
dispatch(setLoadPath(prefix)) |
||||
web.ListObjects({ |
||||
bucketName: currentBucket, |
||||
prefix, |
||||
marker: "" |
||||
}) |
||||
.then(res => { |
||||
let objects = res.objects |
||||
if (!objects) |
||||
objects = [] |
||||
objects = objects.map(object => { |
||||
object.name = object.name.replace(`${prefix}`, ''); |
||||
return object |
||||
}) |
||||
dispatch(resetObjects()) |
||||
dispatch(appendObjects( |
||||
objects, |
||||
res.nextmarker, |
||||
res.istruncated |
||||
)) |
||||
dispatch(setPrefixWritable(res.writable)) |
||||
dispatch(setSortNameOrder(false)) |
||||
dispatch(setCurrentPath(prefix)) |
||||
dispatch(setLoadBucket('')) |
||||
dispatch(setLoadPath('')) |
||||
}) |
||||
.catch(err => { |
||||
dispatch(showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
})) |
||||
dispatch(setLoadBucket('')) |
||||
dispatch(setLoadPath('')) |
||||
// Use browserHistory.replace instead of push so that browser back button works fine.
|
||||
browserHistory.replace(`${minioBrowserPrefix}/login`) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
export const addUpload = options => { |
||||
return { |
||||
type: ADD_UPLOAD, |
||||
slug: options.slug, |
||||
size: options.size, |
||||
xhr: options.xhr, |
||||
name: options.name |
||||
} |
||||
} |
||||
|
||||
export const stopUpload = options => { |
||||
return { |
||||
type: STOP_UPLOAD, |
||||
slug: options.slug |
||||
} |
||||
} |
||||
|
||||
export const uploadProgress = options => { |
||||
return { |
||||
type: UPLOAD_PROGRESS, |
||||
slug: options.slug, |
||||
loaded: options.loaded |
||||
} |
||||
} |
||||
|
||||
export const setShowAbortModal = showAbortModal => { |
||||
return { |
||||
type: SET_SHOW_ABORT_MODAL, |
||||
showAbortModal |
||||
} |
||||
} |
||||
|
||||
export const setLoginError = () => { |
||||
return { |
||||
type: SET_LOGIN_ERROR, |
||||
loginError: true |
||||
} |
||||
} |
||||
|
||||
export const downloadSelected = (url, req, xhr) => { |
||||
return (dispatch) => { |
||||
var anchor = document.createElement('a') |
||||
document.body.appendChild(anchor); |
||||
xhr.open('POST', url, true) |
||||
xhr.responseType = 'blob' |
||||
|
||||
xhr.onload = function(e) { |
||||
if (this.status == 200) { |
||||
dispatch(checkedObjectsReset()) |
||||
var blob = new Blob([this.response], { |
||||
type: 'octet/stream' |
||||
}) |
||||
var blobUrl = window.URL.createObjectURL(blob); |
||||
var separator = req.prefix.length > 1 ? '-' : '' |
||||
|
||||
anchor.href = blobUrl |
||||
anchor.download = req.bucketName+separator+req.prefix.slice(0, -1)+'.zip'; |
||||
|
||||
|
||||
|
||||
|
||||
anchor.click() |
||||
window.URL.revokeObjectURL(blobUrl) |
||||
anchor.remove() |
||||
} |
||||
}; |
||||
xhr.send(JSON.stringify(req)); |
||||
} |
||||
} |
||||
|
||||
export const uploadFile = (file, xhr) => { |
||||
return (dispatch, getState) => { |
||||
const {currentBucket, currentPath} = getState() |
||||
const objectName = `${currentPath}${file.name}` |
||||
const uploadUrl = `${window.location.origin}${minioBrowserPrefix}/upload/${currentBucket}/${objectName}` |
||||
// The slug is a unique identifer for the file upload.
|
||||
const slug = `${currentBucket}-${currentPath}-${file.name}` |
||||
|
||||
xhr.open('PUT', uploadUrl, true) |
||||
xhr.withCredentials = false |
||||
const token = storage.getItem('token') |
||||
if (token) xhr.setRequestHeader("Authorization", 'Bearer ' + storage.getItem('token')) |
||||
xhr.setRequestHeader('x-amz-date', Moment().utc().format('YYYYMMDDTHHmmss') + 'Z') |
||||
dispatch(addUpload({ |
||||
slug, |
||||
xhr, |
||||
size: file.size, |
||||
name: file.name |
||||
})) |
||||
|
||||
xhr.onload = function(event) { |
||||
if (xhr.status == 401 || xhr.status == 403) { |
||||
setShowAbortModal(false) |
||||
dispatch(stopUpload({ |
||||
slug |
||||
})) |
||||
dispatch(showAlert({ |
||||
type: 'danger', |
||||
message: 'Unauthorized request.' |
||||
})) |
||||
} |
||||
if (xhr.status == 500) { |
||||
setShowAbortModal(false) |
||||
dispatch(stopUpload({ |
||||
slug |
||||
})) |
||||
dispatch(showAlert({ |
||||
type: 'danger', |
||||
message: xhr.responseText |
||||
})) |
||||
} |
||||
if (xhr.status == 200) { |
||||
setShowAbortModal(false) |
||||
dispatch(stopUpload({ |
||||
slug |
||||
})) |
||||
dispatch(showAlert({ |
||||
type: 'success', |
||||
message: 'File \'' + file.name + '\' uploaded successfully.' |
||||
})) |
||||
dispatch(selectPrefix(currentPath)) |
||||
} |
||||
} |
||||
|
||||
xhr.upload.addEventListener('error', event => { |
||||
dispatch(showAlert({ |
||||
type: 'danger', |
||||
message: 'Error occurred uploading \'' + file.name + '\'.' |
||||
})) |
||||
dispatch(stopUpload({ |
||||
slug |
||||
})) |
||||
}) |
||||
|
||||
xhr.upload.addEventListener('progress', event => { |
||||
if (event.lengthComputable) { |
||||
let loaded = event.loaded |
||||
let total = event.total |
||||
|
||||
// Update the counter.
|
||||
dispatch(uploadProgress({ |
||||
slug, |
||||
loaded |
||||
})) |
||||
} |
||||
}) |
||||
xhr.send(file) |
||||
} |
||||
} |
||||
|
||||
export const showAbout = () => { |
||||
return { |
||||
type: SHOW_ABOUT, |
||||
showAbout: true |
||||
} |
||||
} |
||||
|
||||
export const hideAbout = () => { |
||||
return { |
||||
type: SHOW_ABOUT, |
||||
showAbout: false |
||||
} |
||||
} |
||||
|
||||
export const setSortNameOrder = (sortNameOrder) => { |
||||
return { |
||||
type: SET_SORT_NAME_ORDER, |
||||
sortNameOrder |
||||
} |
||||
} |
||||
|
||||
export const setSortSizeOrder = (sortSizeOrder) => { |
||||
return { |
||||
type: SET_SORT_SIZE_ORDER, |
||||
sortSizeOrder |
||||
} |
||||
} |
||||
|
||||
export const setSortDateOrder = (sortDateOrder) => { |
||||
return { |
||||
type: SET_SORT_DATE_ORDER, |
||||
sortDateOrder |
||||
} |
||||
} |
||||
|
||||
export const setLatestUIVersion = (latestUiVersion) => { |
||||
return { |
||||
type: SET_LATEST_UI_VERSION, |
||||
latestUiVersion |
||||
} |
||||
} |
||||
|
||||
export const showSettings = () => { |
||||
return { |
||||
type: SHOW_SETTINGS, |
||||
showSettings: true |
||||
} |
||||
} |
||||
|
||||
export const hideSettings = () => { |
||||
return { |
||||
type: SHOW_SETTINGS, |
||||
showSettings: false |
||||
} |
||||
} |
||||
|
||||
export const setSettings = (settings) => { |
||||
return { |
||||
type: SET_SETTINGS, |
||||
settings |
||||
} |
||||
} |
||||
|
||||
export const showBucketPolicy = () => { |
||||
return { |
||||
type: SHOW_BUCKET_POLICY, |
||||
showBucketPolicy: true |
||||
} |
||||
} |
||||
|
||||
export const hideBucketPolicy = () => { |
||||
return { |
||||
type: SHOW_BUCKET_POLICY, |
||||
showBucketPolicy: false |
||||
} |
||||
} |
||||
|
||||
export const setPolicies = (policies) => { |
||||
return { |
||||
type: SET_POLICIES, |
||||
policies |
||||
} |
||||
} |
||||
|
||||
export const checkedObjectsAdd = (objectName) => { |
||||
return { |
||||
type: CHECKED_OBJECTS_ADD, |
||||
objectName |
||||
} |
||||
} |
||||
|
||||
export const checkedObjectsRemove = (objectName) => { |
||||
return { |
||||
type: CHECKED_OBJECTS_REMOVE, |
||||
objectName |
||||
} |
||||
} |
||||
|
||||
export const checkedObjectsReset = (objectName) => { |
||||
return { |
||||
type: CHECKED_OBJECTS_RESET, |
||||
objectName |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import configureStore from "redux-mock-store" |
||||
import thunk from "redux-thunk" |
||||
import * as actionsAlert from "../alert" |
||||
|
||||
const middlewares = [thunk] |
||||
const mockStore = configureStore(middlewares) |
||||
|
||||
jest.useFakeTimers() |
||||
|
||||
describe("Alert actions", () => { |
||||
it("creates alert/SET action", () => { |
||||
const store = mockStore() |
||||
const expectedActions = [ |
||||
{ |
||||
type: "alert/SET", |
||||
alert: { id: 0, message: "Test alert", type: "danger" } |
||||
} |
||||
] |
||||
store.dispatch(actionsAlert.set({ message: "Test alert", type: "danger" })) |
||||
const actions = store.getActions() |
||||
expect(actions).toEqual(expectedActions) |
||||
}) |
||||
|
||||
it("creates alert/CLEAR action for non danger alerts", () => { |
||||
const store = mockStore() |
||||
const expectedActions = [ |
||||
{ |
||||
type: "alert/SET", |
||||
alert: { id: 1, message: "Test alert" } |
||||
}, |
||||
{ |
||||
type: "alert/CLEAR", |
||||
alert: { id: 1 } |
||||
} |
||||
] |
||||
store.dispatch(actionsAlert.set({ message: "Test alert" })) |
||||
jest.runAllTimers() |
||||
const actions = store.getActions() |
||||
expect(actions).toEqual(expectedActions) |
||||
}) |
||||
}) |
@ -0,0 +1,41 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import configureStore from "redux-mock-store" |
||||
import thunk from "redux-thunk" |
||||
import * as actionsBuckets from "../buckets" |
||||
|
||||
jest.mock("../../web", () => ({ |
||||
ListBuckets: jest.fn(() => { |
||||
return Promise.resolve({ buckets: [{ name: "test1" }, { name: "test2" }] }) |
||||
}) |
||||
})) |
||||
|
||||
const middlewares = [thunk] |
||||
const mockStore = configureStore(middlewares) |
||||
|
||||
describe("Buckets actions", () => { |
||||
it("creates buckets/SET_LIST after fetching the buckets", () => { |
||||
const store = mockStore() |
||||
const expectedActions = [ |
||||
{ type: "buckets/SET_LIST", buckets: ["test1", "test2"] } |
||||
] |
||||
return store.dispatch(actionsBuckets.fetchBuckets()).then(() => { |
||||
const actions = store.getActions() |
||||
expect(actions).toEqual(expectedActions) |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1,46 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
export const SET = "alert/SET" |
||||
export const CLEAR = "alert/CLEAR" |
||||
|
||||
let alertId = 0 |
||||
|
||||
export const set = alert => { |
||||
const id = alertId++ |
||||
return (dispatch, getState) => { |
||||
if (alert.type !== "danger") { |
||||
setTimeout(() => { |
||||
dispatch({ |
||||
type: CLEAR, |
||||
alert: { |
||||
id |
||||
} |
||||
}) |
||||
}, 5000) |
||||
} |
||||
dispatch({ |
||||
type: SET, |
||||
alert: Object.assign({}, alert, { |
||||
id |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
export const clear = () => { |
||||
return { type: CLEAR } |
||||
} |
@ -0,0 +1,51 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import web from "../web" |
||||
|
||||
export const SET_LIST = "buckets/SET_LIST" |
||||
export const SET_FILTER = "buckets/SET_FILTER" |
||||
export const SET_CURRENT_BUCKET = "buckets/SET_CURRENT_BUCKET" |
||||
|
||||
export const fetchBuckets = () => { |
||||
return function(dispatch) { |
||||
return web.ListBuckets().then(res => { |
||||
const buckets = res.buckets ? res.buckets.map(bucket => bucket.name) : [] |
||||
dispatch(setList(buckets)) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
export const setList = buckets => { |
||||
return { |
||||
type: SET_LIST, |
||||
buckets |
||||
} |
||||
} |
||||
|
||||
export const setFilter = filter => { |
||||
return { |
||||
type: SET_FILTER, |
||||
filter |
||||
} |
||||
} |
||||
|
||||
export const setCurrentBucket = bucket => { |
||||
return { |
||||
type: SET_CURRENT_BUCKET, |
||||
bucket |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from "react" |
||||
import AlertComponent from "react-bootstrap/lib/Alert" |
||||
|
||||
const Alert = ({ show, type, message, onDismiss }) => ( |
||||
<AlertComponent |
||||
className={"alert animated " + (show ? "fadeInDown" : "fadeOutUp")} |
||||
bsStyle={type} |
||||
onDismiss={onDismiss} |
||||
> |
||||
<div className="text-center">{message}</div> |
||||
</AlertComponent> |
||||
) |
||||
|
||||
export default Alert |
@ -0,0 +1,46 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from "react" |
||||
import { Route, Switch, Redirect } from "react-router-dom" |
||||
import Browser from "../components/Browser" |
||||
import Login from "../components/Login" |
||||
import web from "../web" |
||||
import { minioBrowserPrefix } from "../constants" |
||||
|
||||
const AuthorizedRoute = ({ component: Component, ...rest }) => ( |
||||
<Route |
||||
{...rest} |
||||
render={props => |
||||
web.LoggedIn() ? ( |
||||
<Component {...props} /> |
||||
) : ( |
||||
<Redirect to={`${minioBrowserPrefix}/login`} /> |
||||
) |
||||
} |
||||
/> |
||||
) |
||||
|
||||
export const App = ({ match }) => ( |
||||
<Switch> |
||||
<AuthorizedRoute exact path={match.url} component={Browser} /> |
||||
<Route path={`${match.url}/login`} component={Login} /> |
||||
<AuthorizedRoute path={`${match.url}/:bucket/*`} component={Browser} /> |
||||
<AuthorizedRoute path={`${match.url}/:bucket`} component={Browser} /> |
||||
</Switch> |
||||
) |
||||
|
||||
export default App |
@ -1,869 +0,0 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from 'react' |
||||
import classNames from 'classnames' |
||||
import browserHistory from 'react-router/lib/browserHistory' |
||||
import humanize from 'humanize' |
||||
import Moment from 'moment' |
||||
import Modal from 'react-bootstrap/lib/Modal' |
||||
import ModalBody from 'react-bootstrap/lib/ModalBody' |
||||
import ModalHeader from 'react-bootstrap/lib/ModalHeader' |
||||
import Alert from 'react-bootstrap/lib/Alert' |
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger' |
||||
import Tooltip from 'react-bootstrap/lib/Tooltip' |
||||
import Dropdown from 'react-bootstrap/lib/Dropdown' |
||||
import MenuItem from 'react-bootstrap/lib/MenuItem' |
||||
import InputGroup from '../components/InputGroup' |
||||
import Dropzone from '../components/Dropzone' |
||||
import ObjectsList from '../components/ObjectsList' |
||||
import SideBar from '../components/SideBar' |
||||
import Path from '../components/Path' |
||||
import BrowserUpdate from '../components/BrowserUpdate' |
||||
import UploadModal from '../components/UploadModal' |
||||
import SettingsModal from '../components/SettingsModal' |
||||
import PolicyInput from '../components/PolicyInput' |
||||
import Policy from '../components/Policy' |
||||
import BrowserDropdown from '../components/BrowserDropdown' |
||||
import ConfirmModal from './ConfirmModal' |
||||
import logo from '../../img/logo.svg' |
||||
import * as actions from '../actions' |
||||
import * as utils from '../utils' |
||||
import * as mime from '../mime' |
||||
import { minioBrowserPrefix } from '../constants' |
||||
import CopyToClipboard from 'react-copy-to-clipboard' |
||||
import storage from 'local-storage-fallback' |
||||
import InfiniteScroll from 'react-infinite-scroller'; |
||||
|
||||
export default class Browse extends React.Component { |
||||
componentDidMount() { |
||||
const {web, dispatch, currentBucket} = this.props |
||||
if (!web.LoggedIn()) return |
||||
web.StorageInfo() |
||||
.then(res => { |
||||
let storageInfo = Object.assign({}, { |
||||
total: res.storageInfo.Total, |
||||
free: res.storageInfo.Free |
||||
}) |
||||
storageInfo.used = storageInfo.total - storageInfo.free |
||||
dispatch(actions.setStorageInfo(storageInfo)) |
||||
return web.ServerInfo() |
||||
}) |
||||
.then(res => { |
||||
let serverInfo = Object.assign({}, { |
||||
version: res.MinioVersion, |
||||
memory: res.MinioMemory, |
||||
platform: res.MinioPlatform, |
||||
runtime: res.MinioRuntime, |
||||
info: res.MinioGlobalInfo |
||||
}) |
||||
dispatch(actions.setServerInfo(serverInfo)) |
||||
}) |
||||
.catch(err => { |
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
})) |
||||
}) |
||||
} |
||||
|
||||
componentWillMount() { |
||||
const {dispatch} = this.props |
||||
// Clear out any stale message in the alert of Login page
|
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: '' |
||||
})) |
||||
if (web.LoggedIn()) { |
||||
web.ListBuckets() |
||||
.then(res => { |
||||
let buckets |
||||
if (!res.buckets) |
||||
buckets = [] |
||||
else |
||||
buckets = res.buckets.map(bucket => bucket.name) |
||||
if (buckets.length) { |
||||
dispatch(actions.setBuckets(buckets)) |
||||
dispatch(actions.setVisibleBuckets(buckets)) |
||||
if (location.pathname === minioBrowserPrefix || location.pathname === minioBrowserPrefix + '/') { |
||||
browserHistory.push(utils.pathJoin(buckets[0])) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
this.history = browserHistory.listen(({pathname}) => { |
||||
let decPathname = decodeURI(pathname) |
||||
if (decPathname === `${minioBrowserPrefix}/login`) return // FIXME: better organize routes and remove this
|
||||
if (!decPathname.endsWith('/')) |
||||
decPathname += '/' |
||||
if (decPathname === minioBrowserPrefix + '/') { |
||||
return |
||||
} |
||||
let obj = utils.pathSlice(decPathname) |
||||
if (!web.LoggedIn()) { |
||||
dispatch(actions.setBuckets([obj.bucket])) |
||||
dispatch(actions.setVisibleBuckets([obj.bucket])) |
||||
} |
||||
dispatch(actions.selectBucket(obj.bucket, obj.prefix)) |
||||
}) |
||||
} |
||||
|
||||
componentWillUnmount() { |
||||
this.history() |
||||
} |
||||
|
||||
selectBucket(e, bucket) { |
||||
e.preventDefault() |
||||
if (bucket === this.props.currentBucket) return |
||||
browserHistory.push(utils.pathJoin(bucket)) |
||||
} |
||||
|
||||
searchBuckets(e) { |
||||
e.preventDefault() |
||||
let {buckets} = this.props |
||||
this.props.dispatch(actions.setVisibleBuckets(buckets.filter(bucket => bucket.indexOf(e.target.value) > -1))) |
||||
} |
||||
|
||||
listObjects() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.listObjects()) |
||||
} |
||||
|
||||
selectPrefix(e, prefix) { |
||||
e.preventDefault() |
||||
const {dispatch, currentPath, web, currentBucket} = this.props |
||||
const encPrefix = encodeURI(prefix) |
||||
if (prefix.endsWith('/') || prefix === '') { |
||||
if (prefix === currentPath) return |
||||
browserHistory.push(utils.pathJoin(currentBucket, encPrefix)) |
||||
} else { |
||||
if (!web.LoggedIn()) { |
||||
let url = `${window.location.origin}/minio/download/${currentBucket}/${encPrefix}?token=''` |
||||
window.location = url |
||||
} else { |
||||
// Download the selected file.
|
||||
web.CreateURLToken() |
||||
.then(res => { |
||||
let url = `${window.location.origin}${minioBrowserPrefix}/download/${currentBucket}/${encPrefix}?token=${res.token}` |
||||
window.location = url |
||||
}) |
||||
.catch(err => dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
}))) |
||||
} |
||||
} |
||||
} |
||||
|
||||
makeBucket(e) { |
||||
e.preventDefault() |
||||
const bucketName = this.refs.makeBucketRef.value |
||||
this.refs.makeBucketRef.value = '' |
||||
const {web, dispatch} = this.props |
||||
this.hideMakeBucketModal() |
||||
web.MakeBucket({ |
||||
bucketName |
||||
}) |
||||
.then(() => { |
||||
dispatch(actions.addBucket(bucketName)) |
||||
dispatch(actions.selectBucket(bucketName)) |
||||
}) |
||||
.catch(err => dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
}))) |
||||
} |
||||
|
||||
hideMakeBucketModal() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideMakeBucketModal()) |
||||
} |
||||
|
||||
showMakeBucketModal(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.showMakeBucketModal()) |
||||
} |
||||
|
||||
showAbout(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.showAbout()) |
||||
} |
||||
|
||||
hideAbout(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideAbout()) |
||||
} |
||||
|
||||
toggleBucketDropdown(e) { |
||||
const {dispatch, showBucketDropdown} = this.props |
||||
if (showBucketDropdown) { |
||||
dispatch(actions.hideBucketDropdown()) |
||||
} else { |
||||
dispatch(actions.showBucketDropdown()) |
||||
} |
||||
} |
||||
|
||||
showBucketPolicy(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
this.toggleBucketDropdown(e) |
||||
dispatch(actions.showBucketPolicy()) |
||||
} |
||||
|
||||
hideBucketPolicy(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideBucketPolicy()) |
||||
} |
||||
|
||||
deleteBucket(e, bucket) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
this.toggleBucketDropdown(e) |
||||
dispatch(actions.deleteBucket(bucket)) |
||||
browserHistory.push(`${minioBrowserPrefix}/`) |
||||
} |
||||
|
||||
uploadFile(e) { |
||||
e.preventDefault() |
||||
const {dispatch, buckets, currentBucket} = this.props |
||||
if (buckets.length === 0) { |
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: "Bucket needs to be created before trying to upload files." |
||||
})) |
||||
return |
||||
} |
||||
if (currentBucket === '') { |
||||
dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: "Please choose a bucket before trying to upload files." |
||||
})) |
||||
return |
||||
} |
||||
let file = e.target.files[0] |
||||
e.target.value = null |
||||
this.xhr = new XMLHttpRequest() |
||||
dispatch(actions.uploadFile(file, this.xhr)) |
||||
} |
||||
|
||||
removeObject() { |
||||
const {web, dispatch, currentPath, currentBucket, deleteConfirmation, checkedObjects} = this.props |
||||
let objects = [] |
||||
if (checkedObjects.length > 0) { |
||||
objects = checkedObjects.map(obj => `${currentPath}${obj}`) |
||||
} else { |
||||
objects = [deleteConfirmation.object] |
||||
} |
||||
|
||||
web.RemoveObject({ |
||||
bucketname: currentBucket, |
||||
objects: objects |
||||
}) |
||||
.then(() => { |
||||
this.hideDeleteConfirmation() |
||||
if (checkedObjects.length > 0) { |
||||
for (let i = 0; i < checkedObjects.length; i++) { |
||||
dispatch(actions.removeObject(checkedObjects[i].replace(currentPath, ''))) |
||||
} |
||||
dispatch(actions.checkedObjectsReset()) |
||||
} else { |
||||
let delObject = deleteConfirmation.object.replace(currentPath, '') |
||||
dispatch(actions.removeObject(delObject)) |
||||
} |
||||
}) |
||||
.catch(e => dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: e.message |
||||
}))) |
||||
} |
||||
|
||||
hideAlert(e) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideAlert()) |
||||
} |
||||
|
||||
showDeleteConfirmation(e, object) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
dispatch(actions.showDeleteConfirmation(object)) |
||||
} |
||||
|
||||
hideDeleteConfirmation() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideDeleteConfirmation()) |
||||
} |
||||
|
||||
shareObject(e, object) { |
||||
e.preventDefault() |
||||
const {dispatch} = this.props |
||||
// let expiry = 5 * 24 * 60 * 60 // 5 days expiry by default
|
||||
dispatch(actions.shareObject(object, 5, 0, 0)) |
||||
} |
||||
|
||||
hideShareObjectModal() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.hideShareObject()) |
||||
} |
||||
|
||||
dataType(name, contentType) { |
||||
return mime.getDataType(name, contentType) |
||||
} |
||||
|
||||
sortObjectsByName(e) { |
||||
const {dispatch, objects, sortNameOrder} = this.props |
||||
dispatch(actions.setObjects(utils.sortObjectsByName(objects, !sortNameOrder))) |
||||
dispatch(actions.setSortNameOrder(!sortNameOrder)) |
||||
} |
||||
|
||||
sortObjectsBySize() { |
||||
const {dispatch, objects, sortSizeOrder} = this.props |
||||
dispatch(actions.setObjects(utils.sortObjectsBySize(objects, !sortSizeOrder))) |
||||
dispatch(actions.setSortSizeOrder(!sortSizeOrder)) |
||||
} |
||||
|
||||
sortObjectsByDate() { |
||||
const {dispatch, objects, sortDateOrder} = this.props |
||||
dispatch(actions.setObjects(utils.sortObjectsByDate(objects, !sortDateOrder))) |
||||
dispatch(actions.setSortDateOrder(!sortDateOrder)) |
||||
} |
||||
|
||||
logout(e) { |
||||
const {web} = this.props |
||||
e.preventDefault() |
||||
web.Logout() |
||||
browserHistory.push(`${minioBrowserPrefix}/login`) |
||||
} |
||||
|
||||
fullScreen(e) { |
||||
e.preventDefault() |
||||
let el = document.documentElement |
||||
if (el.requestFullscreen) { |
||||
el.requestFullscreen() |
||||
} |
||||
if (el.mozRequestFullScreen) { |
||||
el.mozRequestFullScreen() |
||||
} |
||||
if (el.webkitRequestFullscreen) { |
||||
el.webkitRequestFullscreen() |
||||
} |
||||
if (el.msRequestFullscreen) { |
||||
el.msRequestFullscreen() |
||||
} |
||||
} |
||||
|
||||
toggleSidebar(status) { |
||||
this.props.dispatch(actions.setSidebarStatus(status)) |
||||
} |
||||
|
||||
hideSidebar(event) { |
||||
let e = event || window.event; |
||||
|
||||
// Support all browsers.
|
||||
let target = e.srcElement || e.target; |
||||
if (target.nodeType === 3) // Safari support.
|
||||
target = target.parentNode; |
||||
|
||||
let targetID = target.id; |
||||
if (!(targetID === 'feh-trigger')) { |
||||
this.props.dispatch(actions.setSidebarStatus(false)) |
||||
} |
||||
} |
||||
|
||||
showSettings(e) { |
||||
e.preventDefault() |
||||
|
||||
const {dispatch} = this.props |
||||
dispatch(actions.showSettings()) |
||||
} |
||||
|
||||
showMessage() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.showAlert({ |
||||
type: 'success', |
||||
message: 'Link copied to clipboard!' |
||||
})) |
||||
this.hideShareObjectModal() |
||||
} |
||||
|
||||
selectTexts() { |
||||
this.refs.copyTextInput.select() |
||||
} |
||||
|
||||
handleExpireValue(targetInput, inc, object) { |
||||
let value = this.refs[targetInput].value |
||||
let maxValue = (targetInput == 'expireHours') ? 23 : (targetInput == 'expireMins') ? 59 : (targetInput == 'expireDays') ? 7 : 0 |
||||
value = isNaN(value) ? 0 : value |
||||
|
||||
// Use custom step count to support browser Edge
|
||||
if((inc === -1)) { |
||||
if(value != 0) { |
||||
value-- |
||||
} |
||||
} |
||||
else { |
||||
if(value != maxValue) { |
||||
value++ |
||||
} |
||||
} |
||||
this.refs[targetInput].value = value |
||||
|
||||
// Reset hours and mins when days reaches it's max value
|
||||
if (this.refs.expireDays.value == 7) { |
||||
this.refs.expireHours.value = 0 |
||||
this.refs.expireMins.value = 0 |
||||
} |
||||
if (this.refs.expireDays.value + this.refs.expireHours.value + this.refs.expireMins.value == 0) { |
||||
this.refs.expireDays.value = 7 |
||||
} |
||||
|
||||
const {dispatch} = this.props |
||||
dispatch(actions.shareObject(object, this.refs.expireDays.value, this.refs.expireHours.value, this.refs.expireMins.value)) |
||||
} |
||||
|
||||
checkObject(e, objectName) { |
||||
const {dispatch} = this.props |
||||
e.target.checked ? dispatch(actions.checkedObjectsAdd(objectName)) : dispatch(actions.checkedObjectsRemove(objectName)) |
||||
} |
||||
|
||||
downloadSelected() { |
||||
const {dispatch, web} = this.props |
||||
let req = { |
||||
bucketName: this.props.currentBucket, |
||||
objects: this.props.checkedObjects, |
||||
prefix: this.props.currentPath |
||||
} |
||||
if (!web.LoggedIn()) { |
||||
let requestUrl = location.origin + "/minio/zip?token=''" |
||||
this.xhr = new XMLHttpRequest() |
||||
dispatch(actions.downloadSelected(requestUrl, req, this.xhr)) |
||||
} else { |
||||
web.CreateURLToken() |
||||
.then(res => { |
||||
let requestUrl = location.origin + minioBrowserPrefix + "/zip?token=" + res.token |
||||
this.xhr = new XMLHttpRequest() |
||||
dispatch(actions.downloadSelected(requestUrl, req, this.xhr)) |
||||
}) |
||||
.catch(err => dispatch(actions.showAlert({ |
||||
type: 'danger', |
||||
message: err.message |
||||
}))) |
||||
} |
||||
} |
||||
|
||||
clearSelected() { |
||||
const {dispatch} = this.props |
||||
dispatch(actions.checkedObjectsReset()) |
||||
} |
||||
|
||||
render() { |
||||
const {total, free} = this.props.storageInfo |
||||
const {showMakeBucketModal, alert, sortNameOrder, sortSizeOrder, sortDateOrder, showAbout, showBucketPolicy, checkedObjects} = this.props |
||||
const {version, memory, platform, runtime} = this.props.serverInfo |
||||
const {sidebarStatus} = this.props |
||||
const {showSettings} = this.props |
||||
const {policies, currentBucket, currentPath} = this.props |
||||
const {deleteConfirmation} = this.props |
||||
const {shareObject} = this.props |
||||
const {web, prefixWritable, istruncated} = this.props |
||||
|
||||
// Don't always show the SettingsModal. This is done here instead of in
|
||||
// SettingsModal.js so as to allow for #componentWillMount to handle
|
||||
// the loading of the settings.
|
||||
let settingsModal = showSettings ? <SettingsModal /> : <noscript></noscript> |
||||
|
||||
let alertBox = <Alert className={ classNames({ |
||||
'alert': true, |
||||
'animated': true, |
||||
'fadeInDown': alert.show, |
||||
'fadeOutUp': !alert.show |
||||
}) } bsStyle={ alert.type } onDismiss={ this.hideAlert.bind(this) }> |
||||
<div className='text-center'> |
||||
{ alert.message } |
||||
</div> |
||||
</Alert> |
||||
// Make sure you don't show a fading out alert box on the initial web-page load.
|
||||
if (!alert.message) |
||||
alertBox = '' |
||||
|
||||
let signoutTooltip = <Tooltip id="tt-sign-out"> |
||||
Sign out |
||||
</Tooltip> |
||||
let uploadTooltip = <Tooltip id="tt-upload-file"> |
||||
Upload file |
||||
</Tooltip> |
||||
let makeBucketTooltip = <Tooltip id="tt-create-bucket"> |
||||
Create bucket |
||||
</Tooltip> |
||||
let loginButton = '' |
||||
let browserDropdownButton = '' |
||||
let storageUsageDetails = '' |
||||
|
||||
let used = total - free |
||||
let usedPercent = (used / total) * 100 + '%' |
||||
let freePercent = free * 100 / total |
||||
|
||||
if (web.LoggedIn()) { |
||||
browserDropdownButton = <BrowserDropdown fullScreenFunc={ this.fullScreen.bind(this) } |
||||
aboutFunc={ this.showAbout.bind(this) } |
||||
settingsFunc={ this.showSettings.bind(this) } |
||||
logoutFunc={ this.logout.bind(this) } /> |
||||
} else { |
||||
loginButton = <a className='btn btn-danger' href={minioBrowserPrefix+'/login'}>Login</a> |
||||
} |
||||
|
||||
if (web.LoggedIn()) { |
||||
if (!(used === 0 && free === 0)) { |
||||
storageUsageDetails = <div className="feh-usage"> |
||||
<div className="fehu-chart"> |
||||
<div style={ { width: usedPercent } }></div> |
||||
</div> |
||||
<ul> |
||||
<li> |
||||
<span>Used: </span> |
||||
{ humanize.filesize(total - free) } |
||||
</li> |
||||
<li className="pull-right"> |
||||
<span>Free: </span> |
||||
{ humanize.filesize(total - used) } |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
let createButton = '' |
||||
if (web.LoggedIn()) { |
||||
createButton = <Dropdown dropup className="feb-actions" id="fe-action-toggle"> |
||||
<Dropdown.Toggle noCaret className="feba-toggle"> |
||||
<span><i className="fa fa-plus"></i></span> |
||||
</Dropdown.Toggle> |
||||
<Dropdown.Menu> |
||||
<OverlayTrigger placement="left" overlay={ uploadTooltip }> |
||||
<a href="#" className="feba-btn feba-upload"> |
||||
<input type="file" |
||||
onChange={ this.uploadFile.bind(this) } |
||||
style={ { display: 'none' } } |
||||
id="file-input"></input> |
||||
<label htmlFor="file-input"> <i className="fa fa-cloud-upload"></i> </label> |
||||
</a> |
||||
</OverlayTrigger> |
||||
<OverlayTrigger placement="left" overlay={ makeBucketTooltip }> |
||||
<a href="#" className="feba-btn feba-bucket" onClick={ this.showMakeBucketModal.bind(this) }><i className="fa fa-hdd-o"></i></a> |
||||
</OverlayTrigger> |
||||
</Dropdown.Menu> |
||||
</Dropdown> |
||||
|
||||
} else { |
||||
if (prefixWritable) |
||||
createButton = <Dropdown dropup className="feb-actions" id="fe-action-toggle"> |
||||
<Dropdown.Toggle noCaret className="feba-toggle"> |
||||
<span><i className="fa fa-plus"></i></span> |
||||
</Dropdown.Toggle> |
||||
<Dropdown.Menu> |
||||
<OverlayTrigger placement="left" overlay={ uploadTooltip }> |
||||
<a href="#" className="feba-btn feba-upload"> |
||||
<input type="file" |
||||
onChange={ this.uploadFile.bind(this) } |
||||
style={ { display: 'none' } } |
||||
id="file-input"></input> |
||||
<label htmlFor="file-input"> <i className="fa fa-cloud-upload"></i> </label> |
||||
</a> |
||||
</OverlayTrigger> |
||||
</Dropdown.Menu> |
||||
</Dropdown> |
||||
} |
||||
|
||||
return ( |
||||
<div className={ classNames({ |
||||
'file-explorer': true, |
||||
'toggled': sidebarStatus |
||||
}) }> |
||||
<SideBar searchBuckets={ this.searchBuckets.bind(this) } |
||||
selectBucket={ this.selectBucket.bind(this) } |
||||
clickOutside={ this.hideSidebar.bind(this) } |
||||
showPolicy={ this.showBucketPolicy.bind(this) } |
||||
deleteBucket={ this.deleteBucket.bind(this) } |
||||
toggleBucketDropdown={ this.toggleBucketDropdown.bind(this) } /> |
||||
<div className="fe-body"> |
||||
<div className={ 'list-actions' + (classNames({ |
||||
' list-actions-toggled': checkedObjects.length > 0 |
||||
})) }> |
||||
<span className="la-label"><i className="fa fa-check-circle" /> { checkedObjects.length } Objects selected</span> |
||||
<span className="la-actions pull-right"><button onClick={ this.downloadSelected.bind(this) }> Download all as zip </button></span> |
||||
<span className="la-actions pull-right"><button onClick={ this.showDeleteConfirmation.bind(this) }> Delete selected </button></span> |
||||
<i className="la-close fa fa-times" onClick={ this.clearSelected.bind(this) }></i> |
||||
</div> |
||||
<Dropzone> |
||||
{ alertBox } |
||||
<header className="fe-header-mobile hidden-lg hidden-md"> |
||||
<div id="feh-trigger" className={ 'feh-trigger ' + (classNames({ |
||||
'feht-toggled': sidebarStatus |
||||
})) } onClick={ this.toggleSidebar.bind(this, !sidebarStatus) }> |
||||
<div className="feht-lines"> |
||||
<div className="top"></div> |
||||
<div className="center"></div> |
||||
<div className="bottom"></div> |
||||
</div> |
||||
</div> |
||||
<img className="mh-logo" src={ logo } alt="" /> |
||||
</header> |
||||
<header className="fe-header"> |
||||
<Path selectPrefix={ this.selectPrefix.bind(this) } /> |
||||
{ storageUsageDetails } |
||||
<ul className="feh-actions"> |
||||
<BrowserUpdate /> |
||||
{ loginButton } |
||||
{ browserDropdownButton } |
||||
</ul> |
||||
</header> |
||||
<div className="feb-container"> |
||||
<header className="fesl-row" data-type="folder"> |
||||
<div className="fesl-item fesl-item-icon"></div> |
||||
<div className="fesl-item fesl-item-name" onClick={ this.sortObjectsByName.bind(this) } data-sort="name"> |
||||
Name |
||||
<i className={ classNames({ |
||||
'fesli-sort': true, |
||||
'fa': true, |
||||
'fa-sort-alpha-desc': sortNameOrder, |
||||
'fa-sort-alpha-asc': !sortNameOrder |
||||
}) } /> |
||||
</div> |
||||
<div className="fesl-item fesl-item-size" onClick={ this.sortObjectsBySize.bind(this) } data-sort="size"> |
||||
Size |
||||
<i className={ classNames({ |
||||
'fesli-sort': true, |
||||
'fa': true, |
||||
'fa-sort-amount-desc': sortSizeOrder, |
||||
'fa-sort-amount-asc': !sortSizeOrder |
||||
}) } /> |
||||
</div> |
||||
<div className="fesl-item fesl-item-modified" onClick={ this.sortObjectsByDate.bind(this) } data-sort="last-modified"> |
||||
Last Modified |
||||
<i className={ classNames({ |
||||
'fesli-sort': true, |
||||
'fa': true, |
||||
'fa-sort-numeric-desc': sortDateOrder, |
||||
'fa-sort-numeric-asc': !sortDateOrder |
||||
}) } /> |
||||
</div> |
||||
<div className="fesl-item fesl-item-actions"></div> |
||||
</header> |
||||
</div> |
||||
<div className="feb-container"> |
||||
<InfiniteScroll loadMore={ this.listObjects.bind(this) } |
||||
hasMore={ istruncated } |
||||
useWindow={ true } |
||||
initialLoad={ false }> |
||||
<ObjectsList dataType={ this.dataType.bind(this) } |
||||
selectPrefix={ this.selectPrefix.bind(this) } |
||||
showDeleteConfirmation={ this.showDeleteConfirmation.bind(this) } |
||||
shareObject={ this.shareObject.bind(this) } |
||||
checkObject={ this.checkObject.bind(this) } |
||||
checkedObjectsArray={ checkedObjects } /> |
||||
</InfiniteScroll> |
||||
<div className="text-center" style={ { display: (istruncated && currentBucket) ? 'block' : 'none' } }> |
||||
<span>Loading...</span> |
||||
</div> |
||||
</div> |
||||
<UploadModal /> |
||||
{ createButton } |
||||
<Modal className="modal-create-bucket" |
||||
bsSize="small" |
||||
animation={ false } |
||||
show={ showMakeBucketModal } |
||||
onHide={ this.hideMakeBucketModal.bind(this) }> |
||||
<button className="close close-alt" onClick={ this.hideMakeBucketModal.bind(this) }> |
||||
<span>×</span> |
||||
</button> |
||||
<ModalBody> |
||||
<form onSubmit={ this.makeBucket.bind(this) }> |
||||
<div className="input-group"> |
||||
<input className="ig-text" |
||||
type="text" |
||||
ref="makeBucketRef" |
||||
placeholder="Bucket Name" |
||||
autoFocus/> |
||||
<i className="ig-helpers"></i> |
||||
</div> |
||||
</form> |
||||
</ModalBody> |
||||
</Modal> |
||||
<Modal className="modal-about modal-dark" |
||||
animation={ false } |
||||
show={ showAbout } |
||||
onHide={ this.hideAbout.bind(this) }> |
||||
<button className="close" onClick={ this.hideAbout.bind(this) }> |
||||
<span>×</span> |
||||
</button> |
||||
<div className="ma-inner"> |
||||
<div className="mai-item hidden-xs"> |
||||
<a href="https://minio.io" target="_blank"><img className="maii-logo" src={ logo } alt="" /></a> |
||||
</div> |
||||
<div className="mai-item"> |
||||
<ul className="maii-list"> |
||||
<li> |
||||
<div> |
||||
Version |
||||
</div> |
||||
<small>{ version }</small> |
||||
</li> |
||||
<li> |
||||
<div> |
||||
Memory |
||||
</div> |
||||
<small>{ memory }</small> |
||||
</li> |
||||
<li> |
||||
<div> |
||||
Platform |
||||
</div> |
||||
<small>{ platform }</small> |
||||
</li> |
||||
<li> |
||||
<div> |
||||
Runtime |
||||
</div> |
||||
<small>{ runtime }</small> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
</Modal> |
||||
<Modal className="modal-policy" |
||||
animation={ false } |
||||
show={ showBucketPolicy } |
||||
onHide={ this.hideBucketPolicy.bind(this) }> |
||||
<ModalHeader> |
||||
Bucket Policy ( |
||||
{ currentBucket }) |
||||
<button className="close close-alt" onClick={ this.hideBucketPolicy.bind(this) }> |
||||
<span>×</span> |
||||
</button> |
||||
</ModalHeader> |
||||
<div className="pm-body"> |
||||
<PolicyInput bucket={ currentBucket } /> |
||||
{ policies.map((policy, i) => <Policy key={ i } prefix={ policy.prefix } policy={ policy.policy } /> |
||||
) } |
||||
</div> |
||||
</Modal> |
||||
<ConfirmModal show={ deleteConfirmation.show } |
||||
icon='fa fa-exclamation-triangle mci-red' |
||||
text='Are you sure you want to delete?' |
||||
sub='This cannot be undone!' |
||||
okText='Delete' |
||||
cancelText='Cancel' |
||||
okHandler={ this.removeObject.bind(this) } |
||||
cancelHandler={ this.hideDeleteConfirmation.bind(this) }> |
||||
</ConfirmModal> |
||||
<Modal show={ shareObject.show } |
||||
animation={ false } |
||||
onHide={ this.hideShareObjectModal.bind(this) } |
||||
bsSize="small"> |
||||
<ModalHeader> |
||||
Share Object |
||||
</ModalHeader> |
||||
<ModalBody> |
||||
<div className="input-group copy-text"> |
||||
<label> |
||||
Shareable Link |
||||
</label> |
||||
<input type="text" |
||||
ref="copyTextInput" |
||||
readOnly="readOnly" |
||||
value={ window.location.protocol + '//' + shareObject.url } |
||||
onClick={ this.selectTexts.bind(this) } /> |
||||
</div> |
||||
<div className="input-group" style={ { display: web.LoggedIn() ? 'block' : 'none' } }> |
||||
<label> |
||||
Expires in (Max 7 days) |
||||
</label> |
||||
<div className="set-expire"> |
||||
<div className="set-expire-item"> |
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireDays', 1, shareObject.object) } /> |
||||
<div className="set-expire-title"> |
||||
Days |
||||
</div> |
||||
<div className="set-expire-value"> |
||||
<input ref="expireDays" |
||||
type="number" |
||||
min={ 0 } |
||||
max={ 7 } |
||||
defaultValue={ 5 } |
||||
readOnly="readOnly" |
||||
/> |
||||
</div> |
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireDays', -1, shareObject.object) } /> |
||||
</div> |
||||
<div className="set-expire-item"> |
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireHours', 1, shareObject.object) } /> |
||||
<div className="set-expire-title"> |
||||
Hours |
||||
</div> |
||||
<div className="set-expire-value"> |
||||
<input ref="expireHours" |
||||
type="number" |
||||
min={ 0 } |
||||
max={ 23 } |
||||
defaultValue={ 0 } |
||||
readOnly="readOnly" |
||||
/> |
||||
</div> |
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireHours', -1, shareObject.object) } /> |
||||
</div> |
||||
<div className="set-expire-item"> |
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireMins', 1, shareObject.object) } /> |
||||
<div className="set-expire-title"> |
||||
Minutes |
||||
</div> |
||||
<div className="set-expire-value"> |
||||
<input ref="expireMins" |
||||
type="number" |
||||
min={ 0 } |
||||
max={ 59 } |
||||
defaultValue={ 0 } |
||||
readOnly="readOnly" |
||||
/> |
||||
</div> |
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireMins', -1, shareObject.object) } /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</ModalBody> |
||||
<div className="modal-footer"> |
||||
<CopyToClipboard text={ window.location.protocol + '//' + shareObject.url } onCopy={ this.showMessage.bind(this) }> |
||||
<button className="btn btn-success"> |
||||
Copy Link |
||||
</button> |
||||
</CopyToClipboard> |
||||
<button className="btn btn-link" onClick={ this.hideShareObjectModal.bind(this) }> |
||||
Cancel |
||||
</button> |
||||
</div> |
||||
</Modal> |
||||
{ settingsModal } |
||||
</Dropzone> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,36 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from "react" |
||||
import classNames from "classnames" |
||||
import { connect } from "react-redux" |
||||
import SideBar from "./SideBar" |
||||
|
||||
class Browser extends React.Component { |
||||
render() { |
||||
return ( |
||||
<div |
||||
className={classNames({ |
||||
"file-explorer": true |
||||
})} |
||||
> |
||||
<SideBar /> |
||||
</div> |
||||
) |
||||
} |
||||
} |
||||
|
||||
export default connect(state => state)(Browser) |
@ -0,0 +1,43 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from "react" |
||||
import classNames from "classnames" |
||||
|
||||
export const Bucket = ({ bucket, isActive, selectBucket }) => { |
||||
return ( |
||||
<li |
||||
className={classNames({ |
||||
active: isActive |
||||
})} |
||||
onClick={e => { |
||||
e.preventDefault() |
||||
selectBucket(bucket) |
||||
}} |
||||
> |
||||
<a |
||||
href="" |
||||
className={classNames({ |
||||
"fesli-loading": false |
||||
})} |
||||
> |
||||
{bucket} |
||||
</a> |
||||
</li> |
||||
) |
||||
} |
||||
|
||||
export default Bucket |
@ -0,0 +1,35 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from "react" |
||||
import { connect } from "react-redux" |
||||
import * as actionsBuckets from "../actions/buckets" |
||||
import { getCurrentBucket } from "../selectors/buckets" |
||||
import Bucket from "./Bucket" |
||||
|
||||
const mapStateToProps = (state, ownProps) => { |
||||
return { |
||||
isActive: getCurrentBucket(state) === ownProps.bucket |
||||
} |
||||
} |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
selectBucket: bucket => dispatch(actionsBuckets.setCurrentBucket(bucket)) |
||||
} |
||||
} |
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Bucket) |
@ -0,0 +1,59 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from "react" |
||||
import { connect } from "react-redux" |
||||
import { Scrollbars } from "react-custom-scrollbars" |
||||
import * as actionsBuckets from "../actions/buckets" |
||||
import { getVisibleBuckets } from "../selectors/buckets" |
||||
import BucketContainer from "./BucketContainer" |
||||
|
||||
export class BucketList extends React.Component { |
||||
componentWillMount() { |
||||
const { fetchBuckets } = this.props |
||||
fetchBuckets() |
||||
} |
||||
render() { |
||||
const { visibleBuckets } = this.props |
||||
return ( |
||||
<div className="fesl-inner"> |
||||
<Scrollbars |
||||
renderTrackVertical={props => <div className="scrollbar-vertical" />} |
||||
> |
||||
<ul> |
||||
{visibleBuckets.map(bucket => ( |
||||
<BucketContainer key={bucket} bucket={bucket} /> |
||||
))} |
||||
</ul> |
||||
</Scrollbars> |
||||
</div> |
||||
) |
||||
} |
||||
} |
||||
|
||||
const mapStateToProps = state => { |
||||
return { |
||||
visibleBuckets: getVisibleBuckets(state) |
||||
} |
||||
} |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
fetchBuckets: () => dispatch(actionsBuckets.fetchBuckets()) |
||||
} |
||||
} |
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BucketList) |
@ -0,0 +1,44 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from "react" |
||||
import { connect } from "react-redux" |
||||
import * as actionsBuckets from "../actions/buckets" |
||||
|
||||
export const BucketSearch = ({ onChange }) => ( |
||||
<div |
||||
className="input-group ig-dark ig-left ig-search" |
||||
style={{ display: "block" }} |
||||
> |
||||
<input |
||||
className="ig-text" |
||||
type="text" |
||||
onChange={e => onChange(e.target.value)} |
||||
placeholder="Search Buckets..." |
||||
/> |
||||
<i className="ig-helpers" /> |
||||
</div> |
||||
) |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
onChange: filter => { |
||||
dispatch(actionsBuckets.setFilter(filter)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
export default connect(undefined, mapDispatchToProps)(BucketSearch) |
@ -0,0 +1,26 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2016, 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from "react" |
||||
|
||||
export const Host = () => ( |
||||
<div className="fes-host"> |
||||
<i className="fa fa-globe" /> |
||||
<a href="/">{window.location.host}</a> |
||||
</div> |
||||
) |
||||
|
||||
export default Host |
@ -0,0 +1,39 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from "react" |
||||
import { shallow } from "enzyme" |
||||
import { Bucket } from "../Bucket" |
||||
|
||||
describe("Bucket", () => { |
||||
it("should render without crashing", () => { |
||||
shallow(<Bucket />) |
||||
}) |
||||
|
||||
it("should call selectBucket when clicked", () => { |
||||
const selectBucket = jest.fn() |
||||
const wrapper = shallow( |
||||
<Bucket bucket={"test"} selectBucket={selectBucket} /> |
||||
) |
||||
wrapper.find("li").simulate("click", { preventDefault: jest.fn() }) |
||||
expect(selectBucket).toHaveBeenCalledWith("test") |
||||
}) |
||||
|
||||
it("should highlight the selected bucket", () => { |
||||
const wrapper = shallow(<Bucket bucket={"test"} isActive={true} />) |
||||
expect(wrapper.find("li").hasClass("active")).toBeTruthy() |
||||
}) |
||||
}) |
@ -0,0 +1,29 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from "react" |
||||
import { shallow } from "enzyme" |
||||
import { BucketList } from "../BucketList" |
||||
|
||||
describe("Bucket", () => { |
||||
it("should call fetchBuckets before component is mounted", () => { |
||||
const fetchBuckets = jest.fn() |
||||
const wrapper = shallow( |
||||
<BucketList visibleBuckets={[]} fetchBuckets={fetchBuckets} /> |
||||
) |
||||
expect(fetchBuckets).toHaveBeenCalled() |
||||
}) |
||||
}) |
@ -0,0 +1,32 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import React from "react" |
||||
import { shallow } from "enzyme" |
||||
import { BucketSearch } from "../BucketSearch" |
||||
|
||||
describe("BucketSearch", () => { |
||||
it("should render without crashing", () => { |
||||
shallow(<BucketSearch />) |
||||
}) |
||||
|
||||
it("should call onChange with search text", () => { |
||||
const onChange = jest.fn() |
||||
const wrapper = shallow(<BucketSearch onChange={onChange} />) |
||||
wrapper.find("input").simulate("change", { target: { value: "test" } }) |
||||
expect(onChange).toHaveBeenCalledWith("test") |
||||
}) |
||||
}) |
@ -1,54 +0,0 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
/* |
||||
import React from 'react' |
||||
import ReactTestUtils, {renderIntoDocument} from 'react-addons-test-utils' |
||||
|
||||
import expect from 'expect' |
||||
import Login from '../Login' |
||||
|
||||
describe('Login', () => { |
||||
it('it should have empty credentials', () => { |
||||
const alert = { |
||||
show: false |
||||
} |
||||
const dispatch = () => {} |
||||
let loginComponent = renderIntoDocument(<Login alert={alert} dispatch={dispatch} />) |
||||
const accessKey = document.getElementById('accessKey') |
||||
const secretKey = document.getElementById('secretKey') |
||||
// Validate default value.
|
||||
expect(accessKey.value).toEqual('') |
||||
expect(secretKey.value).toEqual('') |
||||
}) |
||||
it('it should set accessKey and secretKey', () => { |
||||
const alert = { |
||||
show: false |
||||
} |
||||
const dispatch = () => {} |
||||
let loginComponent = renderIntoDocument(<Login alert={alert} dispatch={dispatch} />) |
||||
let accessKey = loginComponent.refs.accessKey |
||||
let secretKey = loginComponent.refs.secretKey |
||||
accessKey.value = 'demo-username' |
||||
secretKey.value = 'demo-password' |
||||
ReactTestUtils.Simulate.change(accessKey) |
||||
ReactTestUtils.Simulate.change(secretKey) |
||||
// Validate if the change has occurred.
|
||||
expect(loginComponent.refs.accessKey.value).toEqual('demo-username') |
||||
expect(loginComponent.refs.secretKey.value).toEqual('demo-password') |
||||
}) |
||||
}); |
||||
*/ |
@ -0,0 +1,21 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import "jest-enzyme" |
||||
import { configure } from "enzyme" |
||||
import Adapter from "enzyme-adapter-react-16" |
||||
|
||||
configure({ adapter: new Adapter() }) |
@ -0,0 +1,30 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
let delay = [0, 400] |
||||
|
||||
function handleLoader(i) { |
||||
if (i < 2) { |
||||
setTimeout(function() { |
||||
document.querySelector(".page-load").classList.add("pl-" + i) |
||||
handleLoader(i + 1) |
||||
}, delay[i]) |
||||
} |
||||
} |
||||
|
||||
const hideLoader = () => handleLoader(0) |
||||
|
||||
export default hideLoader |
@ -1,215 +0,0 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2016 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import * as actions from './actions' |
||||
import { minioBrowserPrefix } from './constants' |
||||
|
||||
export default (state = { |
||||
buckets: [], |
||||
visibleBuckets: [], |
||||
objects: [], |
||||
istruncated: true, |
||||
storageInfo: {}, |
||||
serverInfo: {}, |
||||
currentBucket: '', |
||||
showBucketDropdown: false, |
||||
currentPath: '', |
||||
showMakeBucketModal: false, |
||||
uploads: {}, |
||||
alert: { |
||||
show: false, |
||||
type: 'danger', |
||||
message: '' |
||||
}, |
||||
loginError: false, |
||||
sortNameOrder: false, |
||||
sortSizeOrder: false, |
||||
sortDateOrder: false, |
||||
latestUiVersion: currentUiVersion, |
||||
sideBarActive: false, |
||||
loginRedirectPath: minioBrowserPrefix, |
||||
settings: { |
||||
accessKey: '', |
||||
secretKey: '', |
||||
secretKeyVisible: false |
||||
}, |
||||
showSettings: false, |
||||
policies: [], |
||||
deleteConfirmation: { |
||||
object: '', |
||||
show: false |
||||
}, |
||||
shareObject: { |
||||
show: false, |
||||
url: '', |
||||
object: '' |
||||
}, |
||||
prefixWritable: false, |
||||
checkedObjects: [] |
||||
}, action) => { |
||||
let newState = Object.assign({}, state) |
||||
switch (action.type) { |
||||
case actions.SET_WEB: |
||||
newState.web = action.web |
||||
break |
||||
case actions.SET_BUCKETS: |
||||
newState.buckets = action.buckets |
||||
break |
||||
case actions.ADD_BUCKET: |
||||
newState.buckets = [action.bucket, ...newState.buckets] |
||||
newState.visibleBuckets = [action.bucket, ...newState.visibleBuckets] |
||||
break |
||||
case actions.REMOVE_BUCKET: |
||||
newState.buckets = newState.buckets.filter(bucket => bucket != action.bucket) |
||||
newState.visibleBuckets = newState.visibleBuckets.filter(bucket => bucket != action.bucket) |
||||
newState.currentBucket = "" |
||||
break |
||||
case actions.SHOW_BUCKET_DROPDOWN: |
||||
newState.showBucketDropdown = action.showBucketDropdown |
||||
break |
||||
case actions.SET_VISIBLE_BUCKETS: |
||||
newState.visibleBuckets = action.visibleBuckets |
||||
break |
||||
case actions.SET_CURRENT_BUCKET: |
||||
newState.currentBucket = action.currentBucket |
||||
break |
||||
case actions.APPEND_OBJECTS: |
||||
newState.objects = [...newState.objects, ...action.objects] |
||||
newState.marker = action.marker |
||||
newState.istruncated = action.istruncated |
||||
break |
||||
case actions.SET_OBJECTS: |
||||
newState.objects = [...action.objects] |
||||
break |
||||
case actions.RESET_OBJECTS: |
||||
newState.objects = [] |
||||
newState.marker = "" |
||||
newState.istruncated = false |
||||
break |
||||
case actions.SET_CURRENT_PATH: |
||||
newState.currentPath = action.currentPath |
||||
break |
||||
case actions.SET_STORAGE_INFO: |
||||
newState.storageInfo = action.storageInfo |
||||
break |
||||
case actions.SET_SERVER_INFO: |
||||
newState.serverInfo = action.serverInfo |
||||
break |
||||
case actions.SHOW_MAKEBUCKET_MODAL: |
||||
newState.showMakeBucketModal = action.showMakeBucketModal |
||||
break |
||||
case actions.UPLOAD_PROGRESS: |
||||
newState.uploads = Object.assign({}, newState.uploads) |
||||
newState.uploads[action.slug].loaded = action.loaded |
||||
break |
||||
case actions.ADD_UPLOAD: |
||||
newState.uploads = Object.assign({}, newState.uploads, { |
||||
[action.slug]: { |
||||
loaded: 0, |
||||
size: action.size, |
||||
xhr: action.xhr, |
||||
name: action.name |
||||
} |
||||
}) |
||||
break |
||||
case actions.STOP_UPLOAD: |
||||
newState.uploads = Object.assign({}, newState.uploads) |
||||
delete newState.uploads[action.slug] |
||||
break |
||||
case actions.SET_ALERT: |
||||
if (newState.alert.alertTimeout) clearTimeout(newState.alert.alertTimeout) |
||||
if (!action.alert.show) { |
||||
newState.alert = Object.assign({}, newState.alert, { |
||||
show: false |
||||
}) |
||||
} else { |
||||
newState.alert = action.alert |
||||
} |
||||
break |
||||
case actions.SET_LOGIN_ERROR: |
||||
newState.loginError = true |
||||
break |
||||
case actions.SET_SHOW_ABORT_MODAL: |
||||
newState.showAbortModal = action.showAbortModal |
||||
break |
||||
case actions.SHOW_ABOUT: |
||||
newState.showAbout = action.showAbout |
||||
break |
||||
case actions.SET_SORT_NAME_ORDER: |
||||
newState.sortNameOrder = action.sortNameOrder |
||||
break |
||||
case actions.SET_SORT_SIZE_ORDER: |
||||
newState.sortSizeOrder = action.sortSizeOrder |
||||
break |
||||
case actions.SET_SORT_DATE_ORDER: |
||||
newState.sortDateOrder = action.sortDateOrder |
||||
break |
||||
case actions.SET_LATEST_UI_VERSION: |
||||
newState.latestUiVersion = action.latestUiVersion |
||||
break |
||||
case actions.SET_SIDEBAR_STATUS: |
||||
newState.sidebarStatus = action.sidebarStatus |
||||
break |
||||
case actions.SET_LOGIN_REDIRECT_PATH: |
||||
newState.loginRedirectPath = action.path |
||||
case actions.SET_LOAD_BUCKET: |
||||
newState.loadBucket = action.loadBucket |
||||
break |
||||
case actions.SET_LOAD_PATH: |
||||
newState.loadPath = action.loadPath |
||||
break |
||||
case actions.SHOW_SETTINGS: |
||||
newState.showSettings = action.showSettings |
||||
break |
||||
case actions.SET_SETTINGS: |
||||
newState.settings = Object.assign({}, newState.settings, action.settings) |
||||
break |
||||
case actions.SHOW_BUCKET_POLICY: |
||||
newState.showBucketPolicy = action.showBucketPolicy |
||||
break |
||||
case actions.SET_POLICIES: |
||||
newState.policies = action.policies |
||||
break |
||||
case actions.DELETE_CONFIRMATION: |
||||
newState.deleteConfirmation = Object.assign({}, action.payload) |
||||
break |
||||
case actions.SET_SHARE_OBJECT: |
||||
newState.shareObject = Object.assign({}, action.shareObject) |
||||
break |
||||
case actions.SET_PREFIX_WRITABLE: |
||||
newState.prefixWritable = action.prefixWritable |
||||
break |
||||
case actions.REMOVE_OBJECT: |
||||
let idx = newState.objects.findIndex(object => object.name === action.object) |
||||
if (idx == -1) break |
||||
newState.objects = [...newState.objects.slice(0, idx), ...newState.objects.slice(idx + 1)] |
||||
break |
||||
|
||||
case actions.CHECKED_OBJECTS_ADD: |
||||
newState.checkedObjects = [...newState.checkedObjects, action.objectName] |
||||
break |
||||
case actions.CHECKED_OBJECTS_REMOVE: |
||||
let index = newState.checkedObjects.indexOf(action.objectName) |
||||
if (index == -1) break |
||||
newState.checkedObjects = [...newState.checkedObjects.slice(0, index), ...newState.checkedObjects.slice(index + 1)] |
||||
break |
||||
case actions.CHECKED_OBJECTS_RESET: |
||||
newState.checkedObjects = [] |
||||
break |
||||
} |
||||
|
||||
return newState |
||||
} |
@ -0,0 +1,87 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import reducer from "../alert" |
||||
import * as actionsAlert from "../../actions/alert" |
||||
|
||||
describe("buckets reducer", () => { |
||||
it("should return the initial state", () => { |
||||
expect(reducer(undefined, {})).toEqual({ |
||||
show: false, |
||||
type: "danger" |
||||
}) |
||||
}) |
||||
|
||||
it("should handle SET_ALERT", () => { |
||||
expect( |
||||
reducer(undefined, { |
||||
type: actionsAlert.SET, |
||||
alert: { id: 1, type: "danger", message: "Test message" } |
||||
}) |
||||
).toEqual({ |
||||
show: true, |
||||
id: 1, |
||||
type: "danger", |
||||
message: "Test message" |
||||
}) |
||||
}) |
||||
|
||||
it("should clear alert if id not passed", () => { |
||||
expect( |
||||
reducer( |
||||
{ show: true, type: "danger", message: "Test message" }, |
||||
{ |
||||
type: actionsAlert.CLEAR |
||||
} |
||||
) |
||||
).toEqual({ |
||||
show: false, |
||||
type: "danger" |
||||
}) |
||||
}) |
||||
|
||||
it("should clear alert if id is matching", () => { |
||||
expect( |
||||
reducer( |
||||
{ show: true, id: 1, type: "danger", message: "Test message" }, |
||||
{ |
||||
type: actionsAlert.CLEAR, |
||||
alert: { id: 1 } |
||||
} |
||||
) |
||||
).toEqual({ |
||||
show: false, |
||||
type: "danger" |
||||
}) |
||||
}) |
||||
|
||||
it("should not clear alert if id is not matching", () => { |
||||
expect( |
||||
reducer( |
||||
{ show: true, id: 1, type: "danger", message: "Test message" }, |
||||
{ |
||||
type: actionsAlert.CLEAR, |
||||
alert: { id: 2 } |
||||
} |
||||
) |
||||
).toEqual({ |
||||
show: true, |
||||
id: 1, |
||||
type: "danger", |
||||
message: "Test message" |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1,64 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import reducer from "../buckets" |
||||
import * as actions from "../../actions/buckets" |
||||
|
||||
describe("buckets reducer", () => { |
||||
it("should return the initial state", () => { |
||||
expect(reducer(undefined, {})).toEqual({ |
||||
list: [], |
||||
filter: "", |
||||
currentBucket: "" |
||||
}) |
||||
}) |
||||
|
||||
it("should handle SET_BUCKETS", () => { |
||||
expect( |
||||
reducer(undefined, { type: actions.SET_LIST, buckets: ["bk1", "bk2"] }) |
||||
).toEqual({ |
||||
list: ["bk1", "bk2"], |
||||
filter: "", |
||||
currentBucket: "" |
||||
}) |
||||
}) |
||||
|
||||
it("should handle SET_BUCKETS_FILTER", () => { |
||||
expect( |
||||
reducer(undefined, { |
||||
type: actions.SET_FILTER, |
||||
filter: "test" |
||||
}) |
||||
).toEqual({ |
||||
list: [], |
||||
filter: "test", |
||||
currentBucket: "" |
||||
}) |
||||
}) |
||||
|
||||
it("should handle SELECT_BUCKET", () => { |
||||
expect( |
||||
reducer(undefined, { |
||||
type: actions.SET_CURRENT_BUCKET, |
||||
bucket: "test" |
||||
}) |
||||
).toEqual({ |
||||
list: [], |
||||
filter: "", |
||||
currentBucket: "test" |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1,41 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import * as actionsAlert from "../actions/alert" |
||||
|
||||
const initialState = { |
||||
show: false, |
||||
type: "danger" |
||||
} |
||||
export default (state = initialState, action) => { |
||||
switch (action.type) { |
||||
case actionsAlert.SET: |
||||
return { |
||||
show: true, |
||||
id: action.alert.id, |
||||
type: action.alert.type, |
||||
message: action.alert.message |
||||
} |
||||
case actionsAlert.CLEAR: |
||||
if (action.alert && action.alert.id != state.id) { |
||||
return state |
||||
} else { |
||||
return initialState |
||||
} |
||||
default: |
||||
return state |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import * as actionsBuckets from "../actions/buckets" |
||||
|
||||
export default ( |
||||
state = { list: [], filter: "", currentBucket: "" }, |
||||
action |
||||
) => { |
||||
switch (action.type) { |
||||
case actionsBuckets.SET_LIST: |
||||
return { |
||||
...state, |
||||
list: action.buckets |
||||
} |
||||
case actionsBuckets.SET_FILTER: |
||||
return { |
||||
...state, |
||||
filter: action.filter |
||||
} |
||||
case actionsBuckets.SET_CURRENT_BUCKET: |
||||
return { |
||||
...state, |
||||
currentBucket: action.bucket |
||||
} |
||||
default: |
||||
return state |
||||
} |
||||
} |
@ -0,0 +1,26 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { combineReducers } from "redux" |
||||
import alert from "./alert" |
||||
import buckets from "./buckets" |
||||
|
||||
const rootReducer = combineReducers({ |
||||
alert, |
||||
buckets |
||||
}) |
||||
|
||||
export default rootReducer |
@ -0,0 +1,38 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { getVisibleBuckets, getCurrentBucket } from "../buckets" |
||||
|
||||
describe("getVisibleBuckets", () => { |
||||
let state |
||||
beforeEach(() => { |
||||
state = { |
||||
buckets: { |
||||
list: ["test1", "test11", "test2"] |
||||
} |
||||
} |
||||
}) |
||||
|
||||
it("should return all buckets if no filter specified", () => { |
||||
state.buckets.filter = "" |
||||
expect(getVisibleBuckets(state)).toEqual(["test1", "test11", "test2"]) |
||||
}) |
||||
|
||||
it("should return all matching buckets if filter is specified", () => { |
||||
state.buckets.filter = "test1" |
||||
expect(getVisibleBuckets(state)).toEqual(["test1", "test11"]) |
||||
}) |
||||
}) |
@ -0,0 +1,28 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { createSelector } from "reselect" |
||||
|
||||
const bucketsSelector = state => state.buckets.list |
||||
const bucketsFilterSelector = state => state.buckets.filter |
||||
|
||||
export const getVisibleBuckets = createSelector( |
||||
bucketsSelector, |
||||
bucketsFilterSelector, |
||||
(buckets, filter) => buckets.filter(bucket => bucket.indexOf(filter) > -1) |
||||
) |
||||
|
||||
export const getCurrentBucket = state => state.buckets.currentBucket |
@ -0,0 +1,26 @@ |
||||
/* |
||||
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { createStore, applyMiddleware } from "redux" |
||||
import thunkMiddleware from "redux-thunk" |
||||
import reducers from "../reducers/index" |
||||
|
||||
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore) |
||||
|
||||
export default function configureStore(initialState) { |
||||
const store = createStoreWithMiddleware(reducers, initialState) |
||||
return store |
||||
} |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue