Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 048af5e5cd | |||
| b367365daa | |||
| d1331ecc5c | |||
| 774e9c8e3a | |||
| 614a8cf7ad | |||
| e32e8d1922 | |||
| 8c59a41668 | |||
| 29a2ffef5d | |||
| c836c454cc | |||
| 0f7300058b | |||
| c5300ec279 | |||
| 3d8d12ba1b |
@@ -1,33 +1,13 @@
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
## Expected behaviour
|
||||
|
||||
## Expected Behavior
|
||||
<!--- If you're describing a bug, tell us what should happen -->
|
||||
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||
## Actual behaviour
|
||||
|
||||
## Current Behavior
|
||||
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||
## Steps to reproduce the behaviour
|
||||
|
||||
## Possible Solution
|
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
<!--- or ideas how to implement the addition or change -->
|
||||
## Minio version
|
||||
- (paste output of `minio version`)
|
||||
|
||||
## System information
|
||||
|
||||
## Steps to Reproduce (for bugs)
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
## Context
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||
|
||||
## Your Environment
|
||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||
* Version used (`minio version`):
|
||||
* Environment name and version (e.g. nginx 1.9.1):
|
||||
* Server type and version:
|
||||
* Operating System and version (`uname -a`):
|
||||
* Link to your project:
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Motivation and Context
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
|
||||
## How Has This Been Tested?
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
## Types of changes
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
## Checklist:
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have added unit tests to cover my changes.
|
||||
- [ ] I have added/updated functional tests in [mint](https://github.com/minio/mint). (If yes, add `mint` PR # here: )
|
||||
- [ ] All new and existing tests passed.
|
||||
@@ -9,19 +9,10 @@ site/
|
||||
/.idea/
|
||||
/Minio.iml
|
||||
**/access.log
|
||||
build/
|
||||
build
|
||||
vendor/**/*.js
|
||||
vendor/**/*.json
|
||||
release
|
||||
.DS_Store
|
||||
*.syso
|
||||
coverage.txt
|
||||
.vscode/
|
||||
.snap
|
||||
*.tar.bz2
|
||||
parts/
|
||||
prime/
|
||||
snap/.snapcraft/
|
||||
stage/
|
||||
.sia_temp/
|
||||
buildcoveragecoverage.txt
|
||||
@@ -1,29 +1,26 @@
|
||||
go_import_path: github.com/minio/minio
|
||||
sudo: required
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
dist: trusty
|
||||
|
||||
language: go
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
osx_image: xcode7.2
|
||||
|
||||
env:
|
||||
- ARCH=x86_64
|
||||
- ARCH=i686
|
||||
|
||||
script:
|
||||
## Run all the tests
|
||||
- make
|
||||
- diff -au <(gofmt -s -d cmd) <(printf "")
|
||||
- diff -au <(gofmt -s -d pkg) <(printf "")
|
||||
- make test GOFLAGS="-timeout 15m -race -v"
|
||||
- make test GOFLAGS="-race"
|
||||
- make coverage
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
go:
|
||||
- 1.9.4
|
||||
- 1.7.1
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
# Minio Contribution Guide [](https://slack.minio.io) [](https://goreportcard.com/report/minio/minio) [](https://hub.docker.com/r/minio/minio/) [](https://codecov.io/gh/minio/minio)
|
||||
### Install Golang
|
||||
|
||||
``Minio`` community welcomes your contribution. To make the process as seamless as possible, we recommend you read this contribution guide.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
Start by forking the Minio GitHub repository, make changes in a branch and then send a pull request. We encourage pull requests to discuss code changes. Here are the steps in details:
|
||||
If you do not have a working Golang environment setup please follow [Golang Installation Guide](./INSTALLGO.md).
|
||||
|
||||
### Setup your Minio Github Repository
|
||||
Fork [Minio upstream](https://github.com/minio/minio/fork) source repository to your own personal repository. Copy the URL of your Minio fork (you will need it for the `git clone` command below).
|
||||
|
||||
Fork [Minio upstream](https://github.com/minio/minio/fork) source repository to your own personal repository. Copy the URL for minio from your personal github repo (you will need it for the `git clone` command below).
|
||||
```sh
|
||||
$ mkdir -p $GOPATH/src/github.com/minio
|
||||
$ cd $GOPATH/src/github.com/minio
|
||||
@@ -16,56 +11,61 @@ $ git clone <paste saved URL for personal forked minio repo>
|
||||
$ cd minio
|
||||
```
|
||||
|
||||
### Set up git remote as ``upstream``
|
||||
### Compiling Minio from source
|
||||
Minio uses ``Makefile`` to wrap around some of redundant checks done through command line.
|
||||
|
||||
```sh
|
||||
$ make
|
||||
Checking if proper environment variables are set.. Done
|
||||
...
|
||||
Checking dependencies for Minio.. Done
|
||||
Installed govet
|
||||
Building Libraries
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
### Setting up git remote as ``upstream``
|
||||
```sh
|
||||
$ cd $GOPATH/src/github.com/minio/minio
|
||||
$ git remote add upstream https://github.com/minio/minio
|
||||
$ git fetch upstream
|
||||
$ git merge upstream/master
|
||||
...
|
||||
...
|
||||
$ make
|
||||
Checking if proper environment variables are set.. Done
|
||||
...
|
||||
Checking dependencies for Minio.. Done
|
||||
Installed govet
|
||||
Building Libraries
|
||||
...
|
||||
```
|
||||
|
||||
### Create your feature branch
|
||||
Before making code changes, make sure you create a separate branch for these changes
|
||||
### Developer Guidelines
|
||||
``Minio`` community welcomes your contribution. To make the process as seamless as possible, we ask for the following:
|
||||
* Go ahead and fork the project and make your changes. We encourage pull requests to discuss code changes.
|
||||
- Fork it
|
||||
- Create your feature branch (git checkout -b my-new-feature)
|
||||
- Commit your changes (git commit -am 'Add some feature')
|
||||
- Push to the branch (git push origin my-new-feature)
|
||||
- Create new Pull Request
|
||||
|
||||
```
|
||||
$ git checkout -b my-new-feature
|
||||
```
|
||||
* If you have additional dependencies for ``Minio``, ``Minio`` manages its dependencies using [govendor](https://github.com/kardianos/govendor)
|
||||
- Run `go get foo/bar`
|
||||
- Edit your code to import foo/bar
|
||||
- Run `make pkg-add PKG=foo/bar` from top-level directory
|
||||
|
||||
### Test Minio server changes
|
||||
After your code changes, make sure
|
||||
* If you have dependencies for ``Minio`` which needs to be removed
|
||||
- Edit your code to not import foo/bar
|
||||
- Run `make pkg-remove PKG=foo/bar` from top-level directory
|
||||
|
||||
- To add test cases for the new code. If you have questions about how to do it, please ask on our [Slack](slack.minio.io) channel.
|
||||
- To run `make verifiers`
|
||||
- To squash your commits into a single commit. `git rebase -i`. It's okay to force update your pull request.
|
||||
- To run `go test -race ./...` and `go build` completes.
|
||||
* When you're ready to create a pull request, be sure to:
|
||||
- Have test cases for the new code. If you have questions about how to do it, please ask in your pull request.
|
||||
- Run `make verifiers`
|
||||
- Squash your commits into a single commit. `git rebase -i`. It's okay to force update your pull request.
|
||||
- Make sure `go test -race ./...` and `go build` completes.
|
||||
|
||||
### Commit changes
|
||||
After verification, commit your changes. This is a [great post](https://chris.beams.io/posts/git-commit/) on how to write useful commit messages
|
||||
|
||||
```
|
||||
$ git commit -am 'Add some feature'
|
||||
```
|
||||
|
||||
### Push to the branch
|
||||
Push your locally committed changes to the remote origin (your fork)
|
||||
```
|
||||
$ git push origin my-new-feature
|
||||
```
|
||||
|
||||
### Create a Pull Request
|
||||
Pull requests can be created via GitHub. Refer to [this document](https://help.github.com/articles/creating-a-pull-request/) for detailed steps on how to create a pull request. After a Pull Request gets peer reviewed and approved, it will be merged.
|
||||
|
||||
## FAQs
|
||||
### How does ``Minio`` manages dependencies?
|
||||
``Minio`` manages its dependencies using [govendor](https://github.com/kardianos/govendor). To add a dependency
|
||||
- Run `go get foo/bar`
|
||||
- Edit your code to import foo/bar
|
||||
- Run `make pkg-add PKG=foo/bar` from top-level directory
|
||||
|
||||
To remove a dependency
|
||||
- Edit your code to not import foo/bar
|
||||
- Run `make pkg-remove PKG=foo/bar` from top-level directory
|
||||
|
||||
### What are the coding guidelines for Minio?
|
||||
``Minio`` is fully conformant with Golang style. Refer: [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project. If you observe offending code, please feel free to send a pull request or ping us on [Slack](slack.minio.io).
|
||||
* Read [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project
|
||||
- `Minio` project is fully conformant with Golang style
|
||||
- if you happen to observe offending code, please feel free to send a pull request
|
||||
|
||||
@@ -1,34 +1,17 @@
|
||||
FROM golang:1.9.4-alpine3.6
|
||||
FROM golang:1.7-alpine
|
||||
|
||||
MAINTAINER Minio Inc <dev@minio.io>
|
||||
WORKDIR /go/src/app
|
||||
|
||||
ENV GOPATH /go
|
||||
ENV PATH $PATH:$GOPATH/bin
|
||||
ENV CGO_ENABLED 0
|
||||
ENV MINIO_UPDATE off
|
||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
MINIO_SECRET_KEY_FILE=secret_key
|
||||
|
||||
WORKDIR /go/src/github.com/minio/
|
||||
|
||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck.sh /usr/bin/
|
||||
|
||||
RUN \
|
||||
apk add --no-cache ca-certificates curl && \
|
||||
apk add --no-cache --virtual .build-deps git && \
|
||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
||||
go get -v -d github.com/minio/minio && \
|
||||
cd /go/src/github.com/minio/minio && \
|
||||
go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" && \
|
||||
rm -rf /go/pkg /go/src /usr/local/go && apk del .build-deps
|
||||
COPY . /go/src/app
|
||||
RUN \
|
||||
apk add --no-cache git && \
|
||||
go-wrapper download && \
|
||||
go-wrapper install -ldflags "-X github.com/minio/minio/cmd.Version=2016-10-22T00:50:41Z -X github.com/minio/minio/cmd.ReleaseTag=RELEASE.2016-10-22T00-50-41Z -X github.com/minio/minio/cmd.CommitID=d1331ecc5cc4abc7304157cc26be64618821a77c" && \
|
||||
mkdir -p /export/docker && \
|
||||
cp /go/src/app/docs/Docker.md /export/docker/ && \
|
||||
rm -rf /go/pkg /go/src && \
|
||||
apk del git
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s \
|
||||
CMD /usr/bin/healthcheck.sh
|
||||
|
||||
CMD ["minio"]
|
||||
ENTRYPOINT ["go-wrapper", "run"]
|
||||
VOLUME ["/export"]
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
FROM golang:1.9.4-alpine3.6
|
||||
|
||||
MAINTAINER Minio Inc <dev@minio.io>
|
||||
|
||||
ENV GOPATH /go
|
||||
ENV PATH $PATH:$GOPATH/bin
|
||||
ENV CGO_ENABLED 0
|
||||
ENV MINIO_UPDATE off
|
||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
MINIO_SECRET_KEY_FILE=secret_key
|
||||
|
||||
WORKDIR /go/src/github.com/minio/
|
||||
|
||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck.sh /usr/bin/
|
||||
|
||||
COPY . /go/src/github.com/minio/minio
|
||||
|
||||
RUN \
|
||||
apk add --no-cache ca-certificates curl && \
|
||||
apk add --no-cache --virtual .build-deps git && \
|
||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
||||
cd /go/src/github.com/minio/minio && \
|
||||
go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" && \
|
||||
rm -rf /go/pkg /go/src /usr/local/go && apk del .build-deps
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s \
|
||||
CMD /usr/bin/healthcheck.sh
|
||||
|
||||
CMD ["minio"]
|
||||
@@ -1,30 +0,0 @@
|
||||
FROM alpine:3.6
|
||||
|
||||
MAINTAINER Minio Inc <dev@minio.io>
|
||||
|
||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck.sh /usr/bin/
|
||||
|
||||
ENV MINIO_UPDATE off
|
||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
MINIO_SECRET_KEY_FILE=secret_key
|
||||
|
||||
RUN \
|
||||
apk add --no-cache ca-certificates && \
|
||||
apk add --no-cache --virtual .build-deps curl && \
|
||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
||||
curl https://dl.minio.io/server/minio/release/linux-amd64/minio > /usr/bin/minio && \
|
||||
chmod +x /usr/bin/minio && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/bin/healthcheck.sh
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s \
|
||||
CMD /usr/bin/healthcheck.sh
|
||||
|
||||
CMD ["minio"]
|
||||
|
||||
@@ -18,7 +18,7 @@ $
|
||||
|
||||
- Run `go get foo/bar`
|
||||
- Edit your code to import foo/bar
|
||||
- Run `govendor add foo/bar` from top-level directory
|
||||
- Run `govendor add foo/bar` from top-level folder
|
||||
|
||||
#### Remove dependencies
|
||||
|
||||
|
||||
@@ -1,100 +1,144 @@
|
||||
LDFLAGS := $(shell go run buildscripts/gen-ldflags.go)
|
||||
PWD := $(shell pwd)
|
||||
GOPATH := $(shell go env GOPATH)
|
||||
LDFLAGS := $(shell go run buildscripts/gen-ldflags.go)
|
||||
|
||||
BUILD_LDFLAGS := '$(LDFLAGS)'
|
||||
TAG := latest
|
||||
|
||||
all: build
|
||||
HOST ?= $(shell uname)
|
||||
CPU ?= $(shell uname -m)
|
||||
|
||||
# if no host is identifed (no uname tool)
|
||||
# we assume a Linux-64bit build
|
||||
ifeq ($(HOST),)
|
||||
HOST = Linux
|
||||
endif
|
||||
|
||||
# identify CPU
|
||||
ifeq ($(CPU), x86_64)
|
||||
HOST := $(HOST)64
|
||||
else
|
||||
ifeq ($(CPU), amd64)
|
||||
HOST := $(HOST)64
|
||||
else
|
||||
ifeq ($(CPU), i686)
|
||||
HOST := $(HOST)32
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
#############################################
|
||||
# now we find out the target OS for
|
||||
# which we are going to compile in case
|
||||
# the caller didn't yet define OS himself
|
||||
ifndef (OS)
|
||||
ifeq ($(HOST), Linux64)
|
||||
arch = gcc
|
||||
else
|
||||
ifeq ($(HOST), Linux32)
|
||||
arch = 32
|
||||
else
|
||||
ifeq ($(HOST), Darwin64)
|
||||
arch = clang
|
||||
else
|
||||
ifeq ($(HOST), Darwin32)
|
||||
arch = clang
|
||||
else
|
||||
ifeq ($(HOST), FreeBSD64)
|
||||
arch = gcc
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
all: install
|
||||
|
||||
checks:
|
||||
@echo "Checking dependencies"
|
||||
@echo "Checking deps:"
|
||||
@(env bash $(PWD)/buildscripts/checkdeps.sh)
|
||||
@echo "Checking for project in GOPATH"
|
||||
@(env bash $(PWD)/buildscripts/checkgopath.sh)
|
||||
|
||||
getdeps:
|
||||
@echo "Installing golint" && go get -u github.com/golang/lint/golint
|
||||
@echo "Installing gocyclo" && go get -u github.com/fzipp/gocyclo
|
||||
@echo "Installing deadcode" && go get -u github.com/remyoudompheng/go-misc/deadcode
|
||||
@echo "Installing misspell" && go get -u github.com/client9/misspell/cmd/misspell
|
||||
@echo "Installing ineffassign" && go get -u github.com/gordonklaus/ineffassign
|
||||
getdeps: checks
|
||||
@go get -u github.com/golang/lint/golint && echo "Installed golint:"
|
||||
@go get -u github.com/fzipp/gocyclo && echo "Installed gocyclo:"
|
||||
@go get -u github.com/remyoudompheng/go-misc/deadcode && echo "Installed deadcode:"
|
||||
@go get -u github.com/client9/misspell/cmd/misspell && echo "Installed misspell:"
|
||||
@go get -u github.com/gordonklaus/ineffassign && echo "Installed ineffassign:"
|
||||
|
||||
verifiers: getdeps vet fmt lint cyclo deadcode spelling
|
||||
verifiers: vet fmt lint cyclo spelling
|
||||
|
||||
vet:
|
||||
@echo "Running $@"
|
||||
@go tool vet -atomic -bool -copylocks -nilfunc -printf -shadow -rangeloops -unreachable -unsafeptr -unusedresult cmd
|
||||
@go tool vet -atomic -bool -copylocks -nilfunc -printf -shadow -rangeloops -unreachable -unsafeptr -unusedresult pkg
|
||||
@echo "Running $@:"
|
||||
@GO15VENDOREXPERIMENT=1 go tool vet -all ./cmd
|
||||
@GO15VENDOREXPERIMENT=1 go tool vet -all ./pkg
|
||||
@GO15VENDOREXPERIMENT=1 go tool vet -shadow=true ./cmd
|
||||
@GO15VENDOREXPERIMENT=1 go tool vet -shadow=true ./pkg
|
||||
|
||||
fmt:
|
||||
@echo "Running $@"
|
||||
@gofmt -d cmd
|
||||
@gofmt -d pkg
|
||||
@echo "Running $@:"
|
||||
@GO15VENDOREXPERIMENT=1 gofmt -s -l cmd
|
||||
@GO15VENDOREXPERIMENT=1 gofmt -s -l pkg
|
||||
|
||||
lint:
|
||||
@echo "Running $@"
|
||||
@${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/cmd...
|
||||
@${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/pkg...
|
||||
@echo "Running $@:"
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/golint github.com/minio/minio/cmd...
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/golint github.com/minio/minio/pkg...
|
||||
|
||||
ineffassign:
|
||||
@echo "Running $@"
|
||||
@${GOPATH}/bin/ineffassign .
|
||||
@echo "Running $@:"
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/ineffassign .
|
||||
|
||||
cyclo:
|
||||
@echo "Running $@"
|
||||
@${GOPATH}/bin/gocyclo -over 100 cmd
|
||||
@${GOPATH}/bin/gocyclo -over 100 pkg
|
||||
@echo "Running $@:"
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/gocyclo -over 65 cmd
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/gocyclo -over 65 pkg
|
||||
|
||||
build: getdeps verifiers $(UI_ASSETS)
|
||||
|
||||
deadcode:
|
||||
@echo "Running $@"
|
||||
@${GOPATH}/bin/deadcode -test $(shell go list ./...) || true
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/deadcode
|
||||
|
||||
spelling:
|
||||
@${GOPATH}/bin/misspell -error `find cmd/`
|
||||
@${GOPATH}/bin/misspell -error `find pkg/`
|
||||
@${GOPATH}/bin/misspell -error `find docs/`
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/misspell -error cmd/**/*
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/misspell -error pkg/**/*
|
||||
|
||||
# Builds minio, runs the verifiers then runs the tests.
|
||||
check: test
|
||||
test: verifiers build
|
||||
@echo "Running unit tests"
|
||||
@go test $(GOFLAGS) ./...
|
||||
@echo "Verifying build"
|
||||
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
||||
test: build
|
||||
@echo "Running all minio testing:"
|
||||
@GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) github.com/minio/minio/cmd...
|
||||
@GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) github.com/minio/minio/pkg...
|
||||
|
||||
coverage: build
|
||||
@echo "Running all coverage for minio"
|
||||
@(env bash $(PWD)/buildscripts/go-coverage.sh)
|
||||
@echo "Running all coverage for minio:"
|
||||
@GO15VENDOREXPERIMENT=1 ./buildscripts/go-coverage.sh
|
||||
|
||||
# Builds minio locally.
|
||||
build: checks
|
||||
@echo "Building minio binary to './minio'"
|
||||
@CGO_ENABLED=0 go build --ldflags $(BUILD_LDFLAGS) -o $(PWD)/minio
|
||||
gomake-all: build
|
||||
@echo "Installing minio:"
|
||||
@GO15VENDOREXPERIMENT=1 go build --ldflags $(BUILD_LDFLAGS) -o $(GOPATH)/bin/minio
|
||||
|
||||
pkg-add:
|
||||
@echo "Adding new package $(PKG)"
|
||||
@${GOPATH}/bin/govendor add $(PKG)
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/govendor add $(PKG)
|
||||
|
||||
pkg-update:
|
||||
@echo "Updating new package $(PKG)"
|
||||
@${GOPATH}/bin/govendor update $(PKG)
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/govendor update $(PKG)
|
||||
|
||||
pkg-remove:
|
||||
@echo "Remove new package $(PKG)"
|
||||
@${GOPATH}/bin/govendor remove $(PKG)
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/govendor remove $(PKG)
|
||||
|
||||
pkg-list:
|
||||
@$(GOPATH)/bin/govendor list
|
||||
@GO15VENDOREXPERIMENT=1 $(GOPATH)/bin/govendor list
|
||||
|
||||
# Builds minio and installs it to $GOPATH/bin.
|
||||
install: build
|
||||
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
||||
@cp $(PWD)/minio $(GOPATH)/bin/minio
|
||||
@echo "Installation successful. To learn more, try \"minio --help\"."
|
||||
install: gomake-all
|
||||
|
||||
release: verifiers
|
||||
@MINIO_RELEASE=RELEASE ./buildscripts/build.sh
|
||||
|
||||
experimental: verifiers
|
||||
@MINIO_RELEASE=EXPERIMENTAL ./buildscripts/build.sh
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up all the generated files"
|
||||
@echo "Cleaning up all the generated files:"
|
||||
@find . -name '*.test' | xargs rm -fv
|
||||
@rm -rvf minio
|
||||
@rm -rvf build
|
||||
@rm -rvf release
|
||||
@rm -rf build
|
||||
@rm -rf release
|
||||
|
||||
@@ -1,121 +1,108 @@
|
||||
# Minio Quickstart Guide
|
||||
[](https://slack.minio.io) [](https://goreportcard.com/report/minio/minio) [](https://hub.docker.com/r/minio/minio/) [](https://codecov.io/gh/minio/minio)
|
||||
# Minio Quickstart Guide [](https://gitter.im/minio/minio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://goreportcard.com/report/minio/minio) [](https://hub.docker.com/r/minio/minio/) [](https://codecov.io/gh/minio/minio)
|
||||
|
||||
Minio is an object storage server released under Apache License v2.0. It is compatible with Amazon S3 cloud storage service. It is best suited for storing unstructured data such as photos, videos, log files, backups and container / VM images. Size of an object can range from a few KBs to a maximum of 5TB.
|
||||
|
||||
## 1. Download
|
||||
Minio server is light enough to be bundled with the application stack, similar to NodeJS, Redis and MySQL.
|
||||
|
||||
## Docker Container
|
||||
### Stable
|
||||
```
|
||||
docker pull minio/minio
|
||||
docker run -p 9000:9000 minio/minio server /data
|
||||
```
|
||||
| Platform| Architecture | URL|
|
||||
| ----------| -------- | ------|
|
||||
|GNU/Linux|64-bit Intel|https://dl.minio.io/server/minio/release/linux-amd64/minio|
|
||||
||32-bit Intel|https://dl.minio.io/server/minio/release/linux-386/minio|
|
||||
||32-bit ARM|https://dl.minio.io/server/minio/release/linux-arm/minio|
|
||||
|Apple OS X|64-bit Intel|https://dl.minio.io/server/minio/release/darwin-amd64/minio|
|
||||
|Microsoft Windows|64-bit|https://dl.minio.io/server/minio/release/windows-amd64/minio.exe|
|
||||
||32-bit|https://dl.minio.io/server/minio/release/windows-386/minio.exe|
|
||||
|FreeBSD|64-bit|https://dl.minio.io/server/minio/release/freebsd-amd64/minio|
|
||||
|
||||
### Edge
|
||||
```
|
||||
docker pull minio/minio:edge
|
||||
docker run -p 9000:9000 minio/minio:edge server /data
|
||||
```
|
||||
Please visit Minio Docker quickstart guide for more [here](https://docs.minio.io/docs/minio-docker-quickstart-guide)
|
||||
|
||||
## macOS
|
||||
### Homebrew
|
||||
### Install from Homebrew
|
||||
Install minio packages using [Homebrew](http://brew.sh/)
|
||||
|
||||
```sh
|
||||
brew install minio/stable/minio
|
||||
minio server /data
|
||||
$ brew install minio
|
||||
$ minio --help
|
||||
```
|
||||
|
||||
> NOTE: If you previously installed minio using `brew install minio` then it is recommended that you reinstall minio from `minio/stable/minio` official repo instead.
|
||||
```sh
|
||||
brew uninstall minio
|
||||
brew install minio/stable/minio
|
||||
```
|
||||
|
||||
### Binary Download
|
||||
| Platform| Architecture | URL|
|
||||
| ----------| -------- | ------|
|
||||
|Apple macOS|64-bit Intel|https://dl.minio.io/server/minio/release/darwin-amd64/minio |
|
||||
```sh
|
||||
chmod 755 minio
|
||||
./minio server /data
|
||||
```
|
||||
|
||||
## GNU/Linux
|
||||
### Binary Download
|
||||
| Platform| Architecture | URL|
|
||||
| ----------| -------- | ------|
|
||||
|GNU/Linux|64-bit Intel|https://dl.minio.io/server/minio/release/linux-amd64/minio |
|
||||
```sh
|
||||
wget https://dl.minio.io/server/minio/release/linux-amd64/minio
|
||||
chmod +x minio
|
||||
./minio server /data
|
||||
```
|
||||
|
||||
### Snap
|
||||
Install minio using [Snap](https://snapcraft.io)
|
||||
```sh
|
||||
sudo snap install minio --edge
|
||||
```
|
||||
|
||||
Start minio using `snap run` command
|
||||
```sh
|
||||
sudo snap connect minio:mount-observe
|
||||
sudo snap run minio server /data
|
||||
```
|
||||
|
||||
## Microsoft Windows
|
||||
### Binary Download
|
||||
| Platform| Architecture | URL|
|
||||
| ----------| -------- | ------|
|
||||
|Microsoft Windows|64-bit|https://dl.minio.io/server/minio/release/windows-amd64/minio.exe |
|
||||
```sh
|
||||
minio.exe server D:\Photos
|
||||
```
|
||||
|
||||
## FreeBSD
|
||||
### Port
|
||||
Install minio packages using [pkg](https://github.com/freebsd/pkg)
|
||||
|
||||
```sh
|
||||
pkg install minio
|
||||
sysrc minio_enable=yes
|
||||
sysrc minio_disks=/home/user/Photos
|
||||
service minio start
|
||||
```
|
||||
|
||||
## Install from Source
|
||||
### Install from Source
|
||||
Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://docs.minio.io/docs/how-to-install-golang).
|
||||
|
||||
|
||||
```sh
|
||||
go get -u github.com/minio/minio
|
||||
$ go get -u github.com/minio/minio
|
||||
```
|
||||
|
||||
## Test using Minio Browser
|
||||
Minio Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 ensure your server has started successfully.
|
||||
## 2. Run Minio Server
|
||||
In the examples below, Minio serves the contents of the ``Photos`` directory as an object store.
|
||||
|
||||
### Docker Container
|
||||
|
||||
```sh
|
||||
$ docker pull minio/minio
|
||||
$ docker run -p 9000:9000 minio/minio server /export
|
||||
```
|
||||
|
||||
Please visit Minio Docker quickstart guide for more [here](https://docs.minio.io/docs/minio-docker-quickstart-guide)
|
||||
|
||||
### GNU/Linux
|
||||
|
||||
```sh
|
||||
$ chmod +x minio
|
||||
$ ./minio --help
|
||||
$ ./minio server ~/Photos
|
||||
...
|
||||
```
|
||||
|
||||
### OS X
|
||||
|
||||
```sh
|
||||
$ chmod 755 minio
|
||||
$ ./minio --help
|
||||
$ ./minio server ~/Photos
|
||||
...
|
||||
```
|
||||
|
||||
### Microsoft Windows
|
||||
|
||||
```sh
|
||||
C:\Users\Username\Downloads> minio.exe --help
|
||||
C:\Users\Username\Downloads> minio.exe server D:\Photos
|
||||
...
|
||||
```
|
||||
|
||||
### FreeBSD
|
||||
|
||||
```sh
|
||||
$ chmod 755 minio
|
||||
$ ./minio --help
|
||||
$ ./minio server ~/Photos
|
||||
...
|
||||
```
|
||||
Please visit official zfs FreeBSD guide for more details [here](https://www.freebsd.org/doc/handbook/zfs-quickstart.html)
|
||||
|
||||
## 3. Test Minio Server using Minio Browser
|
||||
Open a web browser and navigate to http://127.0.0.1:9000 to view your buckets on minio server.
|
||||
|
||||

|
||||
|
||||
## Test using Minio Client `mc`
|
||||
`mc` provides a modern alternative to UNIX commands like ls, cat, cp, mirror, diff etc. It supports filesystems and Amazon S3 compatible cloud storage services. Follow the Minio Client [Quickstart Guide](https://docs.minio.io/docs/minio-client-quickstart-guide) for further instructions.
|
||||
## 4. Test Minio Server using `mc`
|
||||
Install mc from [here](https://docs.minio.io/docs/minio-client-quickstart-guide). Use `mc ls` command to list all the buckets on your minio server.
|
||||
|
||||
## Pre-existing data
|
||||
When deployed on a single drive, Minio server lets clients access any pre-existing data in the data directory. For example, if Minio is started with the command `minio server /mnt/data`, any pre-existing data in the `/mnt/data` directory would be accessible to the clients.
|
||||
```sh
|
||||
$ mc config host add myminio http://10.0.0.10:9000 USWUXHGYZQYFYFFIT3RE MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
$ mc mb myminio/mybucket
|
||||
$ mc ls myminio/
|
||||
[2015-08-05 08:13:11 IST] 0B mybucket/
|
||||
```
|
||||
|
||||
The above statement is also valid for all gateway backends.
|
||||
For more examples please navigate to [Minio Client Complete Guide](https://docs.minio.io/docs/minio-client-complete-guide).
|
||||
|
||||
## Explore Further
|
||||
## 5. Explore Further
|
||||
- [Minio Erasure Code QuickStart Guide](https://docs.minio.io/docs/minio-erasure-code-quickstart-guide)
|
||||
- [Minio Docker Quickstart Guide](https://docs.minio.io/docs/minio-docker-quickstart-guide)
|
||||
- [Use `mc` with Minio Server](https://docs.minio.io/docs/minio-client-quickstart-guide)
|
||||
- [Use `aws-cli` with Minio Server](https://docs.minio.io/docs/aws-cli-with-minio)
|
||||
- [Use `s3cmd` with Minio Server](https://docs.minio.io/docs/s3cmd-with-minio)
|
||||
- [Use `minio-go` SDK with Minio Server](https://docs.minio.io/docs/golang-client-quickstart-guide)
|
||||
- [The Minio documentation website](https://docs.minio.io)
|
||||
|
||||
## Contribute to Minio Project
|
||||
## 6. Contribute to Minio Project
|
||||
Please follow Minio [Contributor's Guide](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)
|
||||
|
||||
|
||||
## License
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fminio%2Fminio?ref=badge_large)
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
# Minio Quickstart Guide [](https://slack.minio.io) [](https://goreportcard.com/report/minio/minio) [](https://hub.docker.com/r/minio/minio/) [](https://codecov.io/gh/minio/minio)
|
||||
|
||||
Minio 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
|
||||
|
||||
Minio是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
|
||||
|
||||
## Docker 容器
|
||||
### 稳定版
|
||||
```
|
||||
docker pull minio/minio
|
||||
docker run -p 9000:9000 minio/minio server /data
|
||||
```
|
||||
|
||||
### 尝鲜版
|
||||
```
|
||||
docker pull minio/minio:edge
|
||||
docker run -p 9000:9000 minio/minio:edge server /data
|
||||
```
|
||||
更多Docker部署信息请访问 [这里](https://docs.minio.io/docs/minio-docker-quickstart-guide)
|
||||
|
||||
## macOS
|
||||
### Homebrew
|
||||
使用 [Homebrew](http://brew.sh/)安装minio
|
||||
|
||||
```sh
|
||||
brew install minio/stable/minio
|
||||
minio server /data
|
||||
```
|
||||
#### Note
|
||||
如果你之前使用 `brew install minio`安装过minio, 可以用 `minio/stable/minio` 官方镜像进行重装. 由于golang 1.8的bug,homebrew版本不太稳定。
|
||||
|
||||
```
|
||||
brew uninstall minio
|
||||
brew install minio/stable/minio
|
||||
```
|
||||
|
||||
### 下载二进制文件
|
||||
| 操作系统| CPU架构 | 地址|
|
||||
| ----------| -------- | ------|
|
||||
|Apple macOS|64-bit Intel|https://dl.minio.io/server/minio/release/darwin-amd64/minio |
|
||||
```sh
|
||||
chmod 755 minio
|
||||
./minio server /data
|
||||
```
|
||||
|
||||
## GNU/Linux
|
||||
### 下载二进制文件
|
||||
| 操作系统| CPU架构 | 地址|
|
||||
| ----------| -------- | ------|
|
||||
|GNU/Linux|64-bit Intel|https://dl.minio.io/server/minio/release/linux-amd64/minio |
|
||||
```sh
|
||||
chmod +x minio
|
||||
./minio server /data
|
||||
```
|
||||
|
||||
### 快照版
|
||||
你可以下载最新版 `minio` [快照](https://snapcraft.io), 并且帮助我们一起验证master分支上[所有支持的Linux发行版](https://snapcraft.io/docs/core/install) 的一些最新修改:
|
||||
|
||||
```sh
|
||||
sudo snap install minio --edge
|
||||
```
|
||||
|
||||
每次有最新的 `minio` 被推送到服务器,你都会自动更新下来.
|
||||
|
||||
你需要允许minio snap来观察其安装:
|
||||
|
||||
```sh
|
||||
sudo snap connect minio:mount-observe
|
||||
```
|
||||
|
||||
## 微软Windows系统
|
||||
### 下载二进制文件
|
||||
| 操作系统| CPU架构 | 地址|
|
||||
| ----------| -------- | ------|
|
||||
|微软Windows系统|64位|https://dl.minio.io/server/minio/release/windows-amd64/minio.exe |
|
||||
```sh
|
||||
minio.exe server D:\Photos
|
||||
```
|
||||
|
||||
## FreeBSD
|
||||
### Port
|
||||
使用 [pkg](https://github.com/freebsd/pkg)进行安装。
|
||||
|
||||
```sh
|
||||
pkg install minio
|
||||
sysrc minio_enable=yes
|
||||
sysrc minio_disks=/home/user/Photos
|
||||
service minio start
|
||||
```
|
||||
|
||||
## 使用源码安装
|
||||
|
||||
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://docs.minio.io/docs/how-to-install-golang).
|
||||
|
||||
```sh
|
||||
go get -u github.com/minio/minio
|
||||
```
|
||||
|
||||
## 使用Minio浏览器进行验证
|
||||
安装后使用浏览器访问[http://127.0.0.1:9000](http://127.0.0.1:9000),如果可以访问,则表示minio已经安装成功。
|
||||
|
||||

|
||||
|
||||
## 使用Minio客户端 `mc`进行验证
|
||||
`mc` 提供了一些UNIX常用命令的替代品,像ls, cat, cp, mirror, diff这些。 它支持文件系统和亚马逊S3云存储服务。 更多信息请参考 [mc快速入门](https://docs.minio.io/docs/minio-client-quickstart-guide) 。
|
||||
|
||||
## 已经存在的数据
|
||||
当在单块磁盘上部署Minio server,Minio server允许客户端访问数据目录下已经存在的数据。比如,如果Minio使用`minio server /mnt/data`启动,那么所有已经在`/mnt/data`目录下的数据都可以被客户端访问到。
|
||||
|
||||
上述描述对所有网关后端同样有效。
|
||||
|
||||
## 了解更多
|
||||
- [Minio纠删码入门](https://docs.minio.io/docs/minio-erasure-code-quickstart-guide)
|
||||
- [`mc`快速入门](https://docs.minio.io/docs/minio-client-quickstart-guide)
|
||||
- [使用 `aws-cli`](https://docs.minio.io/docs/aws-cli-with-minio)
|
||||
- [使用 `s3cmd`](https://docs.minio.io/docs/s3cmd-with-minio)
|
||||
- [使用 `minio-go` SDK](https://docs.minio.io/docs/golang-client-quickstart-guide)
|
||||
- [Minio文档](https://docs.minio.io)
|
||||
|
||||
## 如何参与到Minio项目
|
||||
请参考 [贡献者指南](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)。欢迎各位中国程序员加到Minio项目中。
|
||||
@@ -12,11 +12,11 @@ clone_folder: c:\gopath\src\github.com\minio\minio
|
||||
# Environment variables
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
GOROOT: c:\go19
|
||||
GO15VENDOREXPERIMENT: 1
|
||||
|
||||
# scripts that run after cloning repository
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH%
|
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||
- go version
|
||||
- go env
|
||||
- python --version
|
||||
@@ -24,8 +24,6 @@ install:
|
||||
# To run your custom scripts instead of automatic MSBuild
|
||||
build_script:
|
||||
# Compile
|
||||
# We need to disable firewall - https://github.com/appveyor/ci/issues/1579#issuecomment-309830648
|
||||
- ps: Disable-NetFirewallRule -DisplayName 'File and Printer Sharing (SMB-Out)'
|
||||
- appveyor AddCompilationMessage "Starting Compile"
|
||||
- cd c:\gopath\src\github.com\minio\minio
|
||||
- go run buildscripts/gen-ldflags.go > temp.txt
|
||||
@@ -38,8 +36,9 @@ test_script:
|
||||
# Unit tests
|
||||
- ps: Add-AppveyorTest "Unit Tests" -Outcome Running
|
||||
- mkdir build\coverage
|
||||
- for /f "" %%G in ('go list github.com/minio/minio/... ^| find /i /v "browser/"') do ( go test -v -timeout 20m -race %%G )
|
||||
- go test -v -timeout 20m -coverprofile=build\coverage\coverage.txt -covermode=atomic github.com/minio/minio/cmd
|
||||
- go test -race github.com/minio/minio/cmd...
|
||||
- go test -race github.com/minio/minio/pkg...
|
||||
- go test -coverprofile=build\coverage\coverage.txt -covermode=atomic github.com/minio/minio/cmd
|
||||
- ps: Update-AppveyorTest "Unit Tests" -Outcome Passed
|
||||
|
||||
after_test:
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015",
|
||||
"react"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-object-rest-spread"
|
||||
]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
"esformatter-jsx"
|
||||
],
|
||||
// Copied from https://github.com/royriojas/esformatter-jsx
|
||||
"jsx": {
|
||||
"formatJSX": true, //Duh! that's the default
|
||||
"attrsOnSameLineAsTag": false, // move each attribute to its own line
|
||||
"maxAttrsOnTag": 3, // if lower or equal than 3 attributes, they will be kept on a single line
|
||||
"firstAttributeOnSameLine": true, // keep the first attribute in the same line as the tag
|
||||
"formatJSXExpressions": true, // default true, if false jsxExpressions won't be recursively formatted
|
||||
"JSXExpressionsSingleLine": true, // default true, if false the JSXExpressions might span several lines
|
||||
"alignWithFirstAttribute": false, // do not align attributes with the first tag
|
||||
"spaceInJSXExpressionContainers": " ", // default to one space. Make it empty if you don't like spaces between JSXExpressionContainers
|
||||
"removeSpaceBeforeClosingJSX": false, // default false. if true <React.Something /> => <React.Something/>
|
||||
"closingTagOnNewLine": false, // default false. if true attributes on multiple lines will close the tag on a new line
|
||||
"JSXAttributeQuotes": "", // possible values "single" or "double". Leave it as empty string if you don't want to modify the attributes' quotes
|
||||
"htmlOptions": {
|
||||
// put here the options for js-beautify.html
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
**/*.swp
|
||||
cover.out
|
||||
*~
|
||||
minio
|
||||
!*/
|
||||
site/
|
||||
**/*.test
|
||||
**/*.sublime-workspace
|
||||
/.idea/
|
||||
/Minio.iml
|
||||
**/access.log
|
||||
build
|
||||
vendor/**/*.js
|
||||
vendor/**/*.json
|
||||
release
|
||||
.DS_Store
|
||||
*.syso
|
||||
coverage.txt
|
||||
node_modules
|
||||
production
|
||||
@@ -1,72 +0,0 @@
|
||||
# Minio File Browser
|
||||
|
||||
``Minio Browser`` provides minimal set of UI to manage buckets and objects on ``minio`` server. ``Minio Browser`` is written in javascript and released under [Apache 2.0 License](./LICENSE).
|
||||
|
||||
## Installation
|
||||
|
||||
### Install yarn
|
||||
```sh
|
||||
curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
yarn
|
||||
```
|
||||
|
||||
### Install `go-bindata` and `go-bindata-assetfs`
|
||||
|
||||
If you do not have a working Golang environment, please follow [Install Golang](https://docs.minio.io/docs/how-to-install-golang)
|
||||
|
||||
```sh
|
||||
go get github.com/jteeuwen/go-bindata/...
|
||||
go get github.com/elazarl/go-bindata-assetfs/...
|
||||
```
|
||||
|
||||
## Generating Assets
|
||||
|
||||
### Generate ui-assets.go
|
||||
|
||||
```sh
|
||||
yarn release
|
||||
```
|
||||
|
||||
This generates ui-assets.go in the current directory. Now do `make` in the parent directory to build the minio binary with the newly generated ``ui-assets.go``
|
||||
|
||||
### Run Minio Browser with live reload
|
||||
|
||||
```sh
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:8080/minio/](http://localhost:8080/minio/) in your browser to play with the application
|
||||
|
||||
### Run Minio Browser with live reload on custom port
|
||||
|
||||
Edit `browser/webpack.config.js`
|
||||
|
||||
```diff
|
||||
diff --git a/browser/webpack.config.js b/browser/webpack.config.js
|
||||
index 3ccdaba..9496c56 100644
|
||||
--- a/browser/webpack.config.js
|
||||
+++ b/browser/webpack.config.js
|
||||
@@ -58,6 +58,7 @@ var exports = {
|
||||
historyApiFallback: {
|
||||
index: '/minio/'
|
||||
},
|
||||
+ port: 8888,
|
||||
proxy: {
|
||||
'/minio/webrpc': {
|
||||
target: 'http://localhost:9000',
|
||||
@@ -97,7 +98,7 @@ var exports = {
|
||||
if (process.env.NODE_ENV === 'dev') {
|
||||
exports.entry = [
|
||||
'webpack/hot/dev-server',
|
||||
- 'webpack-dev-server/client?http://localhost:8080',
|
||||
+ 'webpack-dev-server/client?http://localhost:8888',
|
||||
path.resolve(__dirname, 'app/index.js')
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:8888/minio/](http://localhost:8888/minio/) in your browser to play with the application
|
||||
@@ -1,98 +0,0 @@
|
||||
.page-load {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #32393F;
|
||||
z-index: 100;
|
||||
transition: opacity 200ms;
|
||||
-webkit-transition: opacity 200ms;
|
||||
}
|
||||
|
||||
.pl-0{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.pl-1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pl-inner {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
left: 50%;
|
||||
margin-left: -50px;
|
||||
top: 50%;
|
||||
margin-top: -50px;
|
||||
text-align: center;
|
||||
-webkit-animation: fade-in 500ms;
|
||||
animation: fade-in 500ms;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
animation-delay: 350ms;
|
||||
-webkit-animation-delay: 350ms;
|
||||
-webkit-backface-visibility: visible;
|
||||
backface-visibility: visible;
|
||||
}
|
||||
|
||||
.pl-inner:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: block;
|
||||
-webkit-animation: spin 1000ms infinite linear;
|
||||
animation: spin 1000ms infinite linear;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);;
|
||||
border-left-color: #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.pl-inner > img {
|
||||
width: 30px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
@-webkit-keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="139.0389584668397 284.78404581828653 12.617622649141168 6.417622649141265"><defs><path d="M139.04 290.7L144.95 284.78L145.46 285.29L139.54 291.2L139.04 290.7Z" id="NsdmgIWbGe"></path><path d="M145.24 285.29L151.15 291.2L151.66 290.7L145.74 284.78L145.24 285.29Z" id="VqPWmhvQEo"></path></defs><g visibility="inherit"><g><use xlink:href="#NsdmgIWbGe" opacity="1" fill="#000000" fill-opacity="1"></use></g><g><use xlink:href="#VqPWmhvQEo" opacity="1" fill="#000000" fill-opacity="1"></use></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 797 B |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,57 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewbox="0 0 160 256"
|
||||
version="1.1"
|
||||
id="svg3092"
|
||||
height="218.14844"
|
||||
width="137">
|
||||
<defs
|
||||
id="defs3094" />
|
||||
<metadata
|
||||
id="metadata3097">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0.99999967,-982.85149)"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="matrix(1.0112586,0,0,1.0112586,5.4603732,-13.223714)"
|
||||
id="g4144">
|
||||
<g
|
||||
id="g4140">
|
||||
<g
|
||||
style="image-rendering:auto"
|
||||
id="minio"
|
||||
transform="matrix(1.0000023,0,0,0.99999799,-739.31646,295.2269)">
|
||||
<title
|
||||
id="title3337">Minio Logo</title>
|
||||
<path
|
||||
d="m 803.42903,801.80813 c 12.40802,4.17067 27.0499,9.11665 37.95186,12.80906 -12.01295,-21.20683 -27.84305,-34.11687 -37.95186,-40.78578 l 0,27.97672 z m 0,93.72113 -14.22303,8.96217 0,-92.45864 c -1.52985,-0.5139 -2.97948,-0.9981 -4.33405,-1.45259 -19.98593,-6.67189 -32.7207,-17.95703 -35.85168,-31.77904 -2.54577,-11.21386 1.55064,-23.02184 11.24654,-32.39691 8.84929,-8.55225 21.22761,-18.39964 31.17304,-26.31619 3.60329,-2.86658 6.73129,-5.3173 9.2028,-7.39669 2.31406,-1.93977 1.61598,-4.95488 0.57033,-6.21441 -1.74073,-2.09127 -4.61921,-1.74669 -6.56195,-0.32379 -0.10398,0.0802 -5.65595,4.40832 -5.65595,4.40832 l -8.58195,-11.57033 c 0,0 5.60843,-4.14096 5.8223,-4.30137 8.39777,-6.155 19.54034,-4.98758 25.92406,2.71509 3.19039,3.84093 4.68459,8.69779 4.20929,13.67051 -0.47232,4.9549 -2.84579,9.43153 -6.68078,12.5922 -2.58439,2.12988 -5.73912,4.64298 -9.39291,7.54522 -9.70779,7.72641 -21.78905,17.33915 -30.14226,25.41907 -6.1253,5.9233 -8.70671,12.67834 -7.26896,19.02345 1.9873,8.75424 11.33268,16.34105 26.32213,21.37911 l 0,-46.22486 c 40.29563,13.62298 68.76248,61.22321 78.20589,87.64039 0,0 -41.76308,-14.15768 -63.98286,-21.64051 l 0,78.7198"
|
||||
style="fill:#f8f8f8;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path48" />
|
||||
<path
|
||||
d="m 803.42903,826.12513 -14.22303,-4.78261 0,-9.30973 14.22303,4.77667 0,9.31567"
|
||||
style="fill:#cdccca;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path50" />
|
||||
<path
|
||||
d="m 734.75566,745.06155 c -0.23469,0.16636 -0.54956,0.14853 -0.73077,-0.0743 -0.17823,-0.22576 -0.13063,-0.53766 0.0802,-0.73373 4.93113,-4.51525 24.45661,-23.86844 46.30805,-45.2624 l 8.58193,11.57033 c 0,0 -53.54135,34.01288 -54.23942,34.50007"
|
||||
style="fill:#f14621;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path52" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" width="16" height="4" viewBox="4 10 16 4"><defs><path d="M4 12C4 13.1 4.9 14 6 14C7.1 14 8 13.1 8 12C8 10.9 7.1 10 6 10C4.9 10 4 10.9 4 12ZM16 12C16 13.1 16.9 14 18 14C19.1 14 20 13.1 20 12C20 10.9 19.1 10 18 10C16.9 10 16 10.9 16 12ZM10 12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12C14 10.9 13.1 10 12 10C10.9 10 10 10.9 10 12Z" id="mccsKZxKL3"></path></defs><g visibility="visible"><g><use xlink:href="#mccsKZxKL3" opacity="1" fill="#eaeaea" fill-opacity="1"></use><g><use xlink:href="#mccsKZxKL3" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 894 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M6 10c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm12 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm-6 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2z"/></svg>
|
||||
|
Before Width: | Height: | Size: 261 B |
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" width="9" height="9" viewBox="326.76441742035513 536.0133077721175 13 13"><defs><path d="M339.76 536.01L326.76 549.01L339.76 549.01L339.76 536.01Z" id="kt3PSf43ua"></path></defs><g visibility="visible"><g><use xlink:href="#kt3PSf43ua" opacity="1" fill="#dadada" fill-opacity="1"></use></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 586 B |
@@ -1,56 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Minio Browser</title>
|
||||
<link rel="stylesheet" href="loader.css" type="text/css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-load">
|
||||
<div class="pl-inner">
|
||||
<img src="logo.svg" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div id="root"></div>
|
||||
|
||||
<!--[if lt IE 11]>
|
||||
<div class="ie-warning">
|
||||
<div class="iw-inner">
|
||||
<i class="iwi-icon fa fa-warning"></i>
|
||||
|
||||
You are using Internet Explorer version 12.0 or lower. Due to security issues and lack of support for Web Standards it is highly recommended that you upgrade to a modern browser
|
||||
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="http://www.google.com/chrome/">
|
||||
<img src="chrome.png" alt="">
|
||||
<div>Chrome</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.mozilla.org/en-US/firefox/new/">
|
||||
<img src="firefox.png" alt="">
|
||||
<div>Firefox</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.apple.com/safari/">
|
||||
<img src="safari.png" alt="">
|
||||
<div>Safari</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="iwi-skip">Skip & Continue</div>
|
||||
</div>
|
||||
</div>
|
||||
<![endif]-->
|
||||
|
||||
<script>currentUiVersion = 'MINIO_UI_VERSION'</script>
|
||||
<script src="index_bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,118 +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 'babel-polyfill'
|
||||
|
||||
import './less/main.less'
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import thunkMiddleware from 'redux-thunk'
|
||||
import createStore from 'redux/lib/createStore'
|
||||
import applyMiddleware from 'redux/lib/applyMiddleware'
|
||||
|
||||
import Route from 'react-router/lib/Route'
|
||||
import Router from 'react-router/lib/Router'
|
||||
import browserHistory from 'react-router/lib/browserHistory'
|
||||
import IndexRoute from 'react-router/lib/IndexRoute'
|
||||
|
||||
import Provider from 'react-redux/lib/components/Provider'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
|
||||
import Moment from 'moment'
|
||||
|
||||
import { minioBrowserPrefix } from './js/constants.js'
|
||||
import * as actions from './js/actions.js'
|
||||
import reducer from './js/reducers.js'
|
||||
|
||||
import _Login from './js/components/Login.js'
|
||||
import _Browse from './js/components/Browse.js'
|
||||
import fontAwesome from 'font-awesome/css/font-awesome.css'
|
||||
|
||||
import MaterialDesignIconicFonts from 'material-design-iconic-font/dist/css/material-design-iconic-font.min.css'
|
||||
|
||||
import Web from './js/web'
|
||||
window.Web = Web
|
||||
|
||||
import storage from 'local-storage-fallback'
|
||||
const store = applyMiddleware(thunkMiddleware)(createStore)(reducer)
|
||||
const Browse = connect(state => state)(_Browse)
|
||||
const Login = connect(state => state)(_Login)
|
||||
|
||||
let web = new Web(`${window.location.protocol}//${window.location.host}${minioBrowserPrefix}/webrpc`, store.dispatch)
|
||||
|
||||
window.web = web
|
||||
|
||||
store.dispatch(actions.setWeb(web))
|
||||
|
||||
function authNeeded(nextState, replace, cb) {
|
||||
if (web.LoggedIn()) {
|
||||
return cb()
|
||||
}
|
||||
if (location.pathname === minioBrowserPrefix || location.pathname === minioBrowserPrefix + '/') {
|
||||
replace(`${minioBrowserPrefix}/login`)
|
||||
}
|
||||
return cb()
|
||||
}
|
||||
|
||||
function authNotNeeded(nextState, replace) {
|
||||
if (web.LoggedIn()) {
|
||||
replace(`${minioBrowserPrefix}`)
|
||||
}
|
||||
}
|
||||
|
||||
const App = (props) => {
|
||||
return <div>
|
||||
{ props.children }
|
||||
</div>
|
||||
}
|
||||
|
||||
ReactDOM.render((
|
||||
<Provider store={ store } web={ web }>
|
||||
<Router history={ browserHistory }>
|
||||
<Route path='/' component={ App }>
|
||||
<Route path={ minioBrowserPrefix } component={ App }>
|
||||
<IndexRoute component={ Browse } onEnter={ authNeeded } />
|
||||
<Route path='login' component={ Login } onEnter={ authNotNeeded } />
|
||||
<Route path=':bucket' component={ Browse } onEnter={ authNeeded } />
|
||||
<Route path=':bucket/*' component={ Browse } onEnter={ authNeeded } />
|
||||
</Route>
|
||||
</Route>
|
||||
</Router>
|
||||
</Provider>
|
||||
), document.getElementById('root'))
|
||||
|
||||
//Page loader
|
||||
let delay = [0, 400]
|
||||
let i = 0
|
||||
|
||||
function handleLoader() {
|
||||
if (i < 2) {
|
||||
setTimeout(function() {
|
||||
document.querySelector('.page-load').classList.add('pl-' + i)
|
||||
i++
|
||||
handleLoader()
|
||||
}, delay[i])
|
||||
}
|
||||
}
|
||||
handleLoader()
|
||||
|
||||
if (storage.getItem('newlyUpdated')) {
|
||||
store.dispatch(actions.showAlert({
|
||||
type: 'success',
|
||||
message: "Updated to the latest UI Version."
|
||||
}))
|
||||
storage.removeItem('newlyUpdated')
|
||||
}
|
||||
@@ -1,43 +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 expect from 'expect';
|
||||
import JSONrpc from '../jsonrpc';
|
||||
|
||||
describe('jsonrpc', () => {
|
||||
it('should fail with invalid endpoint', (done) => {
|
||||
try {
|
||||
let jsonRPC = new JSONrpc({
|
||||
endpoint: 'htt://localhost:9000',
|
||||
namespace: 'Test'
|
||||
});
|
||||
} catch (e) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
it('should succeed with valid endpoint', () => {
|
||||
let jsonRPC = new JSONrpc({
|
||||
endpoint: 'http://localhost:9000/webrpc',
|
||||
namespace: 'Test'
|
||||
});
|
||||
expect(jsonRPC.version).toEqual('2.0');
|
||||
expect(jsonRPC.host).toEqual('localhost');
|
||||
expect(jsonRPC.port).toEqual('9000');
|
||||
expect(jsonRPC.path).toEqual('/webrpc');
|
||||
expect(jsonRPC.scheme).toEqual('http');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2016, 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import Dropdown from 'react-bootstrap/lib/Dropdown'
|
||||
|
||||
let BrowserDropdown = ({fullScreenFunc, aboutFunc, settingsFunc, logoutFunc}) => {
|
||||
return (
|
||||
<li>
|
||||
<Dropdown pullRight id="top-right-menu">
|
||||
<Dropdown.Toggle noCaret>
|
||||
<i className="fa fa-reorder"></i>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
<li>
|
||||
<a target="_blank" href="https://github.com/minio/minio">Github <i className="fa fa-github"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ fullScreenFunc }>Fullscreen <i className="fa fa-expand"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://docs.minio.io/">Documentation <i className="fa fa-book"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://slack.minio.io">Ask for help <i className="fa fa-question-circle"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ aboutFunc }>About <i className="fa fa-info-circle"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ settingsFunc }>Settings <i className="fa fa-cog"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ logoutFunc }>Sign Out <i className="fa fa-sign-out"></i></a>
|
||||
</li>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => state)(BrowserDropdown)
|
||||
@@ -1,42 +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 connect from 'react-redux/lib/components/connect'
|
||||
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip'
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'
|
||||
|
||||
let BrowserUpdate = ({latestUiVersion}) => {
|
||||
// Don't show an update if we're already updated!
|
||||
if (latestUiVersion === currentUiVersion) return ( <noscript></noscript> )
|
||||
|
||||
return (
|
||||
<li className="hidden-xs hidden-sm">
|
||||
<a href="">
|
||||
<OverlayTrigger placement="left" overlay={ <Tooltip id="tt-version-update">
|
||||
New update available. Click to refresh.
|
||||
</Tooltip> }> <i className="fa fa-refresh"></i> </OverlayTrigger>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
latestUiVersion: state.latestUiVersion
|
||||
}
|
||||
})(BrowserUpdate)
|
||||
@@ -1,50 +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 Modal from 'react-bootstrap/lib/Modal'
|
||||
import ModalBody from 'react-bootstrap/lib/ModalBody'
|
||||
|
||||
let ConfirmModal = ({baseClass, icon, text, sub, okText, cancelText, okHandler, cancelHandler, show}) => {
|
||||
return (
|
||||
<Modal bsSize="small"
|
||||
animation={ false }
|
||||
show={ show }
|
||||
className={ "modal-confirm " + (baseClass || '') }>
|
||||
<ModalBody>
|
||||
<div className="mc-icon">
|
||||
<i className={ icon }></i>
|
||||
</div>
|
||||
<div className="mc-text">
|
||||
{ text }
|
||||
</div>
|
||||
<div className="mc-sub">
|
||||
{ sub }
|
||||
</div>
|
||||
</ModalBody>
|
||||
<div className="modal-footer">
|
||||
<button className="btn btn-danger" onClick={ okHandler }>
|
||||
{ okText }
|
||||
</button>
|
||||
<button className="btn btn-link" onClick={ cancelHandler }>
|
||||
{ cancelText }
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfirmModal
|
||||
@@ -1,66 +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 ReactDropzone from 'react-dropzone'
|
||||
import * as actions from '../actions'
|
||||
|
||||
// Dropzone is a drag-and-drop element for uploading files. It will create a
|
||||
// landing zone of sorts that automatically receives the files.
|
||||
export default class Dropzone extends React.Component {
|
||||
|
||||
onDrop(files) {
|
||||
// FIXME: Currently you can upload multiple files, but only one abort
|
||||
// modal will be shown, and progress updates will only occur for one
|
||||
// file at a time. See #171.
|
||||
files.forEach(file => {
|
||||
let req = new XMLHttpRequest()
|
||||
|
||||
// Dispatch the upload.
|
||||
web.dispatch(actions.uploadFile(file, req))
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
// Overwrite the default styling from react-dropzone; otherwise it
|
||||
// won't handle child elements correctly.
|
||||
const style = {
|
||||
height: '100%',
|
||||
borderWidth: '0',
|
||||
borderStyle: 'dashed',
|
||||
borderColor: '#fff'
|
||||
}
|
||||
const activeStyle = {
|
||||
borderWidth: '2px',
|
||||
borderColor: '#777'
|
||||
}
|
||||
const rejectStyle = {
|
||||
backgroundColor: '#ffdddd'
|
||||
}
|
||||
|
||||
// disableClick means that it won't trigger a file upload box when
|
||||
// the user clicks on a file.
|
||||
return (
|
||||
<ReactDropzone style={ style }
|
||||
activeStyle={ activeStyle }
|
||||
rejectStyle={ rejectStyle }
|
||||
disableClick={ true }
|
||||
onDrop={ this.onDrop }>
|
||||
{ this.props.children }
|
||||
</ReactDropzone>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,49 +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'
|
||||
|
||||
let InputGroup = ({label, id, name, value, onChange, type, spellCheck, required, readonly, autoComplete, align, className}) => {
|
||||
var input = <input id={ id }
|
||||
name={ name }
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
className="ig-text"
|
||||
type={ type }
|
||||
spellCheck={ spellCheck }
|
||||
required={ required }
|
||||
autoComplete={ autoComplete } />
|
||||
if (readonly)
|
||||
input = <input id={ id }
|
||||
name={ name }
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
className="ig-text"
|
||||
type={ type }
|
||||
spellCheck={ spellCheck }
|
||||
required={ required }
|
||||
autoComplete={ autoComplete }
|
||||
disabled />
|
||||
return <div className={ "input-group " + align + ' ' + className }>
|
||||
{ input }
|
||||
<i className="ig-helpers"></i>
|
||||
<label className="ig-label">
|
||||
{ label }
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default InputGroup
|
||||
@@ -1,128 +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 logo from '../../img/logo.svg'
|
||||
import Alert from 'react-bootstrap/lib/Alert'
|
||||
import * as actions from '../actions'
|
||||
import InputGroup from '../components/InputGroup'
|
||||
|
||||
export default class Login extends React.Component {
|
||||
handleSubmit(event) {
|
||||
event.preventDefault()
|
||||
const {web, dispatch, loginRedirectPath} = this.props
|
||||
let message = ''
|
||||
if (!document.getElementById('accessKey').value) {
|
||||
message = 'Secret Key cannot be empty'
|
||||
}
|
||||
if (!document.getElementById('secretKey').value) {
|
||||
message = 'Access Key cannot be empty'
|
||||
}
|
||||
if (message) {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message
|
||||
}))
|
||||
return
|
||||
}
|
||||
web.Login({
|
||||
username: document.getElementById('accessKey').value,
|
||||
password: document.getElementById('secretKey').value
|
||||
})
|
||||
.then((res) => {
|
||||
this.context.router.push(loginRedirectPath)
|
||||
})
|
||||
.catch(e => {
|
||||
dispatch(actions.setLoginError())
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const {dispatch} = this.props
|
||||
// Clear out any stale message in the alert of previous page
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: ''
|
||||
}))
|
||||
document.body.classList.add('is-guest')
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.body.classList.remove('is-guest')
|
||||
}
|
||||
|
||||
hideAlert() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideAlert())
|
||||
}
|
||||
|
||||
render() {
|
||||
const {alert} = this.props
|
||||
let alertBox = <Alert className={ 'alert animated ' + (alert.show ? 'fadeInDown' : 'fadeOutUp') } bsStyle={ alert.type } onDismiss={ this.hideAlert.bind(this) }>
|
||||
<div className='text-center'>
|
||||
{ alert.message }
|
||||
</div>
|
||||
</Alert>
|
||||
// Make sure you don't show a fading out alert box on the initial web-page load.
|
||||
if (!alert.message)
|
||||
alertBox = ''
|
||||
return (
|
||||
<div className="login">
|
||||
{ alertBox }
|
||||
<div className="l-wrap">
|
||||
<form onSubmit={ this.handleSubmit.bind(this) }>
|
||||
<InputGroup className="ig-dark"
|
||||
label="Access Key"
|
||||
id="accessKey"
|
||||
name="username"
|
||||
type="text"
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="username">
|
||||
</InputGroup>
|
||||
<InputGroup className="ig-dark"
|
||||
label="Secret Key"
|
||||
id="secretKey"
|
||||
name="password"
|
||||
type="password"
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="new-password">
|
||||
</InputGroup>
|
||||
<button className="lw-btn" type="submit">
|
||||
<i className="fa fa-sign-in"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="l-footer">
|
||||
<a className="lf-logo" href=""><img src={ logo } alt="" /></a>
|
||||
<div className="lf-server">
|
||||
{ window.location.host }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Login.contextTypes = {
|
||||
router: React.PropTypes.object.isRequired
|
||||
}
|
||||
@@ -1,96 +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 Moment from 'moment'
|
||||
import humanize from 'humanize'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import Dropdown from 'react-bootstrap/lib/Dropdown'
|
||||
|
||||
let ObjectsList = ({objects, currentPath, selectPrefix, dataType, showDeleteConfirmation, shareObject, loadPath, checkObject, checkedObjectsArray}) => {
|
||||
const list = objects.map((object, i) => {
|
||||
let size = object.name.endsWith('/') ? '-' : humanize.filesize(object.size)
|
||||
let lastModified = object.name.endsWith('/') ? '-' : Moment(object.lastModified).format('lll')
|
||||
let loadingClass = loadPath === `${currentPath}${object.name}` ? 'fesl-loading' : ''
|
||||
let actionButtons = ''
|
||||
let deleteButton = ''
|
||||
if (web.LoggedIn())
|
||||
deleteButton = <a href="" className="fiad-action" onClick={ (e) => showDeleteConfirmation(e, `${currentPath}${object.name}`) }><i className="fa fa-trash"></i></a>
|
||||
|
||||
if (!checkedObjectsArray.length > 0) {
|
||||
if (!object.name.endsWith('/')) {
|
||||
actionButtons = <Dropdown id={ "fia-dropdown-" + object.name.replace('.', '-') }>
|
||||
<Dropdown.Toggle noCaret className="fia-toggle"></Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<a href="" className="fiad-action" onClick={ (e) => shareObject(e, `${currentPath}${object.name}`) }><i className="fa fa-copy"></i></a>
|
||||
{ deleteButton }
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
}
|
||||
}
|
||||
|
||||
let activeClass = ''
|
||||
let isChecked = ''
|
||||
|
||||
if (checkedObjectsArray.indexOf(object.name) > -1) {
|
||||
activeClass = ' fesl-row-selected'
|
||||
isChecked = true
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={ i } className={ "fesl-row " + loadingClass + activeClass } data-type={ dataType(object.name, object.contentType) }>
|
||||
<div className="fesl-item fesl-item-icon">
|
||||
<div className="fi-select">
|
||||
<input type="checkbox"
|
||||
name={ object.name }
|
||||
checked={ isChecked }
|
||||
onChange={ (e) => checkObject(e, object.name) } />
|
||||
<i className="fis-icon"></i>
|
||||
<i className="fis-helper"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fesl-item fesl-item-name">
|
||||
<a href="" onClick={ (e) => selectPrefix(e, `${currentPath}${object.name}`) }>
|
||||
{ object.name }
|
||||
</a>
|
||||
</div>
|
||||
<div className="fesl-item fesl-item-size">
|
||||
{ size }
|
||||
</div>
|
||||
<div className="fesl-item fesl-item-modified">
|
||||
{ lastModified }
|
||||
</div>
|
||||
<div className="fesl-item fesl-item-actions">
|
||||
{ actionButtons }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div>
|
||||
{ list }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Subscribe it to state changes.
|
||||
export default connect(state => {
|
||||
return {
|
||||
objects: state.objects,
|
||||
currentPath: state.currentPath,
|
||||
loadPath: state.loadPath
|
||||
}
|
||||
})(ObjectsList)
|
||||
@@ -1,41 +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 connect from 'react-redux/lib/components/connect'
|
||||
|
||||
let Path = ({currentBucket, currentPath, selectPrefix}) => {
|
||||
let dirPath = []
|
||||
let path = ''
|
||||
if (currentPath) {
|
||||
path = currentPath.split('/').map((dir, i) => {
|
||||
dirPath.push(dir)
|
||||
let dirPath_ = dirPath.join('/') + '/'
|
||||
return <span key={ i }><a href="" onClick={ (e) => selectPrefix(e, dirPath_) }>{ dir }</a></span>
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<h2><span className="main"><a onClick={ (e) => selectPrefix(e, '') } href="">{ currentBucket }</a></span>{ path }</h2>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
currentBucket: state.currentBucket,
|
||||
currentPath: state.currentPath
|
||||
}
|
||||
})(Path)
|
||||
@@ -1,80 +0,0 @@
|
||||
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants'
|
||||
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import classnames from 'classnames'
|
||||
import * as actions from '../actions'
|
||||
|
||||
class Policy extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
handlePolicyChange(e) {
|
||||
this.setState({
|
||||
policy: {
|
||||
policy: e.target.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
removePolicy(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch, currentBucket, prefix} = this.props
|
||||
let newPrefix = prefix.replace(currentBucket + '/', '')
|
||||
newPrefix = newPrefix.replace('*', '')
|
||||
web.SetBucketPolicy({
|
||||
bucketName: currentBucket,
|
||||
prefix: newPrefix,
|
||||
policy: 'none'
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(actions.setPolicies(this.props.policies.filter(policy => policy.prefix != prefix)))
|
||||
})
|
||||
.catch(e => dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message,
|
||||
})))
|
||||
}
|
||||
|
||||
render() {
|
||||
const {policy, prefix, currentBucket} = this.props
|
||||
let newPrefix = prefix.replace(currentBucket + '/', '')
|
||||
newPrefix = newPrefix.replace('*', '')
|
||||
|
||||
if (!newPrefix)
|
||||
newPrefix = '*'
|
||||
|
||||
return (
|
||||
<div className="pmb-list">
|
||||
<div className="pmbl-item">
|
||||
{ newPrefix }
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<select className="form-control"
|
||||
disabled
|
||||
value={ policy }
|
||||
onChange={ this.handlePolicyChange.bind(this) }>
|
||||
<option value={ READ_ONLY }>
|
||||
Read Only
|
||||
</option>
|
||||
<option value={ WRITE_ONLY }>
|
||||
Write Only
|
||||
</option>
|
||||
<option value={ READ_WRITE }>
|
||||
Read and Write
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<button className="btn btn-block btn-danger" onClick={ this.removePolicy.bind(this) }>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => state)(Policy)
|
||||
@@ -1,98 +0,0 @@
|
||||
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import classnames from 'classnames'
|
||||
import * as actions from '../actions'
|
||||
|
||||
class PolicyInput extends Component {
|
||||
componentDidMount() {
|
||||
const {web, dispatch} = this.props
|
||||
this.prefix.focus()
|
||||
web.ListAllBucketPolicies({
|
||||
bucketName: this.props.currentBucket
|
||||
}).then(res => {
|
||||
let policies = res.policies
|
||||
if (policies) dispatch(actions.setPolicies(policies))
|
||||
}).catch(err => {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setPolicies([]))
|
||||
}
|
||||
|
||||
handlePolicySubmit(e) {
|
||||
e.preventDefault()
|
||||
const {web, dispatch, currentBucket} = this.props
|
||||
|
||||
let prefix = currentBucket + '/' + this.prefix.value
|
||||
let policy = this.policy.value
|
||||
|
||||
if (!prefix.endsWith('*')) prefix = prefix + '*'
|
||||
|
||||
let prefixAlreadyExists = this.props.policies.some(elem => prefix === elem.prefix)
|
||||
|
||||
if (prefixAlreadyExists) {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: "Policy for this prefix already exists."
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
web.SetBucketPolicy({
|
||||
bucketName: this.props.currentBucket,
|
||||
prefix: this.prefix.value,
|
||||
policy: this.policy.value
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(actions.setPolicies([{
|
||||
policy, prefix
|
||||
}, ...this.props.policies]))
|
||||
this.prefix.value = ''
|
||||
})
|
||||
.catch(e => dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message,
|
||||
})))
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<header className="pmb-list">
|
||||
<div className="pmbl-item">
|
||||
<input type="text"
|
||||
ref={ prefix => this.prefix = prefix }
|
||||
className="form-control"
|
||||
placeholder="Prefix"
|
||||
editable={ true } />
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<select ref={ policy => this.policy = policy } className="form-control">
|
||||
<option value={ READ_ONLY }>
|
||||
Read Only
|
||||
</option>
|
||||
<option value={ WRITE_ONLY }>
|
||||
Write Only
|
||||
</option>
|
||||
<option value={ READ_WRITE }>
|
||||
Read and Write
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<button className="btn btn-block btn-primary" onClick={ this.handlePolicySubmit.bind(this) }>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => state)(PolicyInput)
|
||||
@@ -1,204 +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 connect from 'react-redux/lib/components/connect'
|
||||
import * as actions from '../actions'
|
||||
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip'
|
||||
import Modal from 'react-bootstrap/lib/Modal'
|
||||
import ModalBody from 'react-bootstrap/lib/ModalBody'
|
||||
import ModalHeader from 'react-bootstrap/lib/ModalHeader'
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'
|
||||
import InputGroup from './InputGroup'
|
||||
|
||||
class SettingsModal extends React.Component {
|
||||
|
||||
// When the settings are shown, it loads the access key and secret key.
|
||||
componentWillMount() {
|
||||
const {web, dispatch} = this.props
|
||||
const {serverInfo} = this.props
|
||||
|
||||
let accessKeyEnv = ''
|
||||
let secretKeyEnv = ''
|
||||
// Check environment variables first.
|
||||
if (serverInfo.info.isEnvCreds) {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: 'xxxxxxxxx',
|
||||
secretKey: 'xxxxxxxxx',
|
||||
keysReadOnly: true
|
||||
}))
|
||||
} else {
|
||||
web.GetAuth()
|
||||
.then(data => {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: data.accessKey,
|
||||
secretKey: data.secretKey
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// When they are re-hidden, the keys are unloaded from memory.
|
||||
componentWillUnmount() {
|
||||
const {dispatch} = this.props
|
||||
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
secretKeyVisible: false
|
||||
}))
|
||||
dispatch(actions.hideSettings())
|
||||
}
|
||||
|
||||
// Handle field changes from inside the modal.
|
||||
accessKeyChange(e) {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: e.target.value
|
||||
}))
|
||||
}
|
||||
|
||||
secretKeyChange(e) {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setSettings({
|
||||
secretKey: e.target.value
|
||||
}))
|
||||
}
|
||||
|
||||
secretKeyVisible(secretKeyVisible) {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setSettings({
|
||||
secretKeyVisible
|
||||
}))
|
||||
}
|
||||
|
||||
// Save the auth params and set them.
|
||||
setAuth(e) {
|
||||
e.preventDefault()
|
||||
const {web, dispatch} = this.props
|
||||
|
||||
let accessKey = document.getElementById('accessKey').value
|
||||
let secretKey = document.getElementById('secretKey').value
|
||||
web.SetAuth({
|
||||
accessKey,
|
||||
secretKey
|
||||
})
|
||||
.then(data => {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
secretKeyVisible: false
|
||||
}))
|
||||
dispatch(actions.hideSettings())
|
||||
dispatch(actions.showAlert({
|
||||
type: 'success',
|
||||
message: 'Changed credentials'
|
||||
}))
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
secretKeyVisible: false
|
||||
}))
|
||||
dispatch(actions.hideSettings())
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
generateAuth(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
|
||||
web.GenerateAuth()
|
||||
.then(data => {
|
||||
dispatch(actions.setSettings({
|
||||
secretKeyVisible: true
|
||||
}))
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: data.accessKey,
|
||||
secretKey: data.secretKey
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
hideSettings(e) {
|
||||
e.preventDefault()
|
||||
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideSettings())
|
||||
}
|
||||
|
||||
render() {
|
||||
let {settings} = this.props
|
||||
|
||||
return (
|
||||
<Modal bsSize="sm" animation={ false } show={ true }>
|
||||
<ModalHeader>
|
||||
Change Password
|
||||
</ModalHeader>
|
||||
<ModalBody className="m-t-20">
|
||||
<InputGroup value={ settings.accessKey }
|
||||
onChange={ this.accessKeyChange.bind(this) }
|
||||
id="accessKey"
|
||||
label="Access Key"
|
||||
name="accesskey"
|
||||
type="text"
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="false"
|
||||
align="ig-left"
|
||||
readonly={ settings.keysReadOnly }></InputGroup>
|
||||
<i onClick={ this.secretKeyVisible.bind(this, !settings.secretKeyVisible) } className={ "toggle-password fa fa-eye " + (settings.secretKeyVisible ? "toggled" : "") } />
|
||||
<InputGroup value={ settings.secretKey }
|
||||
onChange={ this.secretKeyChange.bind(this) }
|
||||
id="secretKey"
|
||||
label="Secret Key"
|
||||
name="accesskey"
|
||||
type={ settings.secretKeyVisible ? "text" : "password" }
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="false"
|
||||
align="ig-left"
|
||||
readonly={ settings.keysReadOnly }></InputGroup>
|
||||
</ModalBody>
|
||||
<div className="modal-footer">
|
||||
<button className={ "btn btn-primary " + (settings.keysReadOnly ? "hidden" : "") } onClick={ this.generateAuth.bind(this) }>
|
||||
Generate
|
||||
</button>
|
||||
<button href="" className={ "btn btn-success " + (settings.keysReadOnly ? "hidden" : "") } onClick={ this.setAuth.bind(this) }>
|
||||
Update
|
||||
</button>
|
||||
<button href="" className="btn btn-link" onClick={ this.hideSettings.bind(this) }>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
web: state.web,
|
||||
settings: state.settings,
|
||||
serverInfo: state.serverInfo
|
||||
}
|
||||
})(SettingsModal)
|
||||
@@ -1,99 +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 ClickOutHandler from 'react-onclickout'
|
||||
import Scrollbars from 'react-custom-scrollbars/lib/Scrollbars'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
|
||||
import logo from '../../img/logo.svg'
|
||||
import Dropdown from 'react-bootstrap/lib/Dropdown'
|
||||
|
||||
let SideBar = ({visibleBuckets, loadBucket, currentBucket, selectBucket, searchBuckets, sidebarStatus, clickOutside, showPolicy, deleteBucket, toggleBucketDropdown, showBucketDropdown}) => {
|
||||
|
||||
const list = visibleBuckets.map((bucket, i) => {
|
||||
return <li className={ classNames({
|
||||
'active': bucket === currentBucket
|
||||
}) } key={ i } onClick={ (e) => selectBucket(e, bucket) }>
|
||||
<a href="" className={ classNames({
|
||||
'fesli-loading': bucket === loadBucket
|
||||
}) }>
|
||||
{ bucket }
|
||||
</a>
|
||||
<Dropdown open={bucket === currentBucket && showBucketDropdown} onToggle={toggleBucketDropdown} className="bucket-dropdown" id="bucket-dropdown">
|
||||
<Dropdown.Toggle noCaret>
|
||||
<i className="zmdi zmdi-more-vert" />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
<li>
|
||||
<a onClick={ showPolicy }>Edit policy</a>
|
||||
</li>
|
||||
<li>
|
||||
<a onClick={ (e) => deleteBucket(e, bucket) }>Delete</a>
|
||||
</li>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</li>
|
||||
})
|
||||
|
||||
return (
|
||||
<ClickOutHandler onClickOut={ clickOutside }>
|
||||
<div className={ classNames({
|
||||
'fe-sidebar': true,
|
||||
'toggled': sidebarStatus
|
||||
}) }>
|
||||
<div className="fes-header clearfix hidden-sm hidden-xs">
|
||||
<img src={ logo } alt="" />
|
||||
<h2>Minio Browser</h2>
|
||||
</div>
|
||||
<div className="fes-list">
|
||||
<div className="input-group ig-dark ig-left ig-search" style={ { display: web.LoggedIn() ? 'block' : 'none' } }>
|
||||
<input className="ig-text"
|
||||
type="text"
|
||||
onChange={ searchBuckets }
|
||||
placeholder="Search Buckets..." />
|
||||
<i className="ig-helpers"></i>
|
||||
</div>
|
||||
<div className="fesl-inner">
|
||||
<Scrollbars renderScrollbarVertical={ props => <div className="scrollbar-vertical" /> }>
|
||||
<ul>
|
||||
{ list }
|
||||
</ul>
|
||||
</Scrollbars>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fes-host">
|
||||
<i className="fa fa-globe"></i>
|
||||
<a href="/">
|
||||
{ window.location.host }
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</ClickOutHandler>
|
||||
)
|
||||
}
|
||||
|
||||
// Subscribe it to state changes that affect only the sidebar.
|
||||
export default connect(state => {
|
||||
return {
|
||||
visibleBuckets: state.visibleBuckets,
|
||||
loadBucket: state.loadBucket,
|
||||
currentBucket: state.currentBucket,
|
||||
showBucketDropdown: state.showBucketDropdown,
|
||||
sidebarStatus: state.sidebarStatus
|
||||
}
|
||||
})(SideBar)
|
||||
@@ -1,141 +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 humanize from 'humanize'
|
||||
import classNames from 'classnames'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
|
||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar'
|
||||
import ConfirmModal from './ConfirmModal'
|
||||
|
||||
import * as actions from '../actions'
|
||||
|
||||
// UploadModal is a modal that handles multiple file uploads.
|
||||
// During the upload, it displays a progress bar, and can transform into an
|
||||
// abort modal if the user decides to abort the uploads.
|
||||
class UploadModal extends React.Component {
|
||||
|
||||
// Abort all the current uploads.
|
||||
abortUploads(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch, uploads} = this.props
|
||||
|
||||
for (var slug in uploads) {
|
||||
let upload = uploads[slug]
|
||||
upload.xhr.abort()
|
||||
dispatch(actions.stopUpload({
|
||||
slug
|
||||
}))
|
||||
}
|
||||
|
||||
this.hideAbort(e)
|
||||
}
|
||||
|
||||
// Show the abort modal instead of the progress modal.
|
||||
showAbort(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
|
||||
dispatch(actions.setShowAbortModal(true))
|
||||
}
|
||||
|
||||
// Show the progress modal instead of the abort modal.
|
||||
hideAbort(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
|
||||
dispatch(actions.setShowAbortModal(false))
|
||||
}
|
||||
|
||||
render() {
|
||||
const {uploads, showAbortModal} = this.props
|
||||
|
||||
// Show the abort modal.
|
||||
if (showAbortModal) {
|
||||
let baseClass = classNames({
|
||||
'abort-upload': true
|
||||
})
|
||||
let okIcon = classNames({
|
||||
'fa': true,
|
||||
'fa-times': true
|
||||
})
|
||||
let cancelIcon = classNames({
|
||||
'fa': true,
|
||||
'fa-cloud-upload': true
|
||||
})
|
||||
|
||||
return (
|
||||
<ConfirmModal show={ true }
|
||||
baseClass={ baseClass }
|
||||
text='Abort uploads in progress?'
|
||||
icon='fa fa-info-circle mci-amber'
|
||||
sub='This cannot be undone!'
|
||||
okText='Abort'
|
||||
okIcon={ okIcon }
|
||||
cancelText='Upload'
|
||||
cancelIcon={ cancelIcon }
|
||||
okHandler={ this.abortUploads.bind(this) }
|
||||
cancelHandler={ this.hideAbort.bind(this) }>
|
||||
</ConfirmModal>
|
||||
)
|
||||
}
|
||||
|
||||
// If we don't have any files uploading, don't show anything.
|
||||
let numberUploading = Object.keys(uploads).length
|
||||
if (numberUploading == 0)
|
||||
return ( <noscript></noscript> )
|
||||
|
||||
let totalLoaded = 0
|
||||
let totalSize = 0
|
||||
|
||||
// Iterate over each upload, adding together the total size and that
|
||||
// which has been uploaded.
|
||||
for (var slug in uploads) {
|
||||
let upload = uploads[slug]
|
||||
totalLoaded += upload.loaded
|
||||
totalSize += upload.size
|
||||
}
|
||||
|
||||
let percent = (totalLoaded / totalSize) * 100
|
||||
|
||||
// If more than one: "Uploading files (5)..."
|
||||
// If only one: "Uploading myfile.txt..."
|
||||
let text = 'Uploading ' + (numberUploading == 1 ? `'${uploads[Object.keys(uploads)[0]].name}'` : `files (${numberUploading})`) + '...'
|
||||
|
||||
return (
|
||||
<div className="alert alert-info progress animated fadeInUp ">
|
||||
<button type="button" className="close" onClick={ this.showAbort.bind(this) }>
|
||||
<span>×</span>
|
||||
</button>
|
||||
<div className="text-center">
|
||||
<small>{ text }</small>
|
||||
</div>
|
||||
<ProgressBar now={ percent } />
|
||||
<div className="text-center">
|
||||
<small>{ humanize.filesize(totalLoaded) } ({ percent.toFixed(2) } %)</small>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
uploads: state.uploads,
|
||||
showAbortModal: state.showAbortModal
|
||||
}
|
||||
})(UploadModal)
|
||||
@@ -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')
|
||||
})
|
||||
});
|
||||
*/
|
||||
@@ -1,25 +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.
|
||||
*/
|
||||
|
||||
// File for all the browser constants.
|
||||
|
||||
// minioBrowserPrefix absolute path.
|
||||
var p = window.location.pathname
|
||||
export const minioBrowserPrefix = p.slice(0, p.indexOf("/", 1))
|
||||
|
||||
export const READ_ONLY = 'readonly'
|
||||
export const WRITE_ONLY = 'writeonly'
|
||||
export const READ_WRITE = 'readwrite'
|
||||
@@ -1,91 +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 SuperAgent from 'superagent-es6-promise';
|
||||
import url from 'url'
|
||||
import Moment from 'moment'
|
||||
|
||||
export default class JSONrpc {
|
||||
constructor(params) {
|
||||
this.endpoint = params.endpoint
|
||||
this.namespace = params.namespace
|
||||
this.version = '2.0';
|
||||
const parsedUrl = url.parse(this.endpoint)
|
||||
this.host = parsedUrl.hostname
|
||||
this.path = parsedUrl.path
|
||||
this.port = parsedUrl.port
|
||||
|
||||
switch (parsedUrl.protocol) {
|
||||
case 'http:': {
|
||||
this.scheme = 'http'
|
||||
if (parsedUrl.port === 0) {
|
||||
this.port = 80
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'https:': {
|
||||
this.scheme = 'https'
|
||||
if (parsedUrl.port === 0) {
|
||||
this.port = 443
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
throw new Error('Unknown protocol: ' + parsedUrl.protocol)
|
||||
}
|
||||
}
|
||||
}
|
||||
// call('Get', {id: NN, params: [...]}, function() {})
|
||||
call(method, options, token) {
|
||||
if (!options) {
|
||||
options = {}
|
||||
}
|
||||
if (!options.id) {
|
||||
options.id = 1;
|
||||
}
|
||||
if (!options.params) {
|
||||
options.params = {};
|
||||
}
|
||||
const dataObj = {
|
||||
id: options.id,
|
||||
jsonrpc: this.version,
|
||||
params: options.params ? options.params : {},
|
||||
method: this.namespace ? this.namespace + '.' + method : method
|
||||
}
|
||||
let requestParams = {
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
path: this.path,
|
||||
scheme: this.scheme,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-amz-date': Moment().utc().format('YYYYMMDDTHHmmss') + 'Z'
|
||||
}
|
||||
}
|
||||
|
||||
if (token) {
|
||||
requestParams.headers.Authorization = 'Bearer ' + token
|
||||
}
|
||||
|
||||
let req = SuperAgent.post(this.endpoint)
|
||||
for (let key in requestParams.headers) {
|
||||
req.set(key, requestParams.headers[key])
|
||||
}
|
||||
// req.set('Access-Control-Allow-Origin', 'http://localhost:8080')
|
||||
return req.send(JSON.stringify(dataObj)).then(res => res)
|
||||
}
|
||||
}
|
||||
@@ -1,106 +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 mimedb from 'mime-types'
|
||||
|
||||
const isFolder = (name, contentType) => {
|
||||
if (name.endsWith('/')) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const isPdf = (name, contentType) => {
|
||||
if (contentType === 'application/pdf') return true
|
||||
return false
|
||||
}
|
||||
|
||||
const isZip = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
if (contentType.split('/')[1].includes('zip')) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const isCode = (name, contentType) => {
|
||||
const codeExt = ['c', 'cpp', 'go', 'py', 'java', 'rb', 'js', 'pl', 'fs',
|
||||
'php', 'css', 'less', 'scss', 'coffee', 'net', 'html',
|
||||
'rs', 'exs', 'scala', 'hs', 'clj', 'el', 'scm', 'lisp',
|
||||
'asp', 'aspx']
|
||||
const ext = name.split('.').reverse()[0]
|
||||
for (var i in codeExt) {
|
||||
if (ext === codeExt[i]) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isExcel = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
const types = ['excel', 'spreadsheet']
|
||||
const subType = contentType.split('/')[1]
|
||||
for (var i in types) {
|
||||
if (subType.includes(types[i])) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isDoc = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
const types = ['word', '.document']
|
||||
const subType = contentType.split('/')[1]
|
||||
for (var i in types) {
|
||||
if (subType.includes(types[i])) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isPresentation = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
var types = ['powerpoint', 'presentation']
|
||||
const subType = contentType.split('/')[1]
|
||||
for (var i in types) {
|
||||
if (subType.includes(types[i])) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const typeToIcon = (type) => {
|
||||
return (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
if (contentType.split('/')[0] === type) return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const getDataType = (name, contentType) => {
|
||||
if (contentType === "") {
|
||||
contentType = mimedb.lookup(name) || 'application/octet-stream'
|
||||
}
|
||||
const check = [
|
||||
['folder', isFolder],
|
||||
['code', isCode],
|
||||
['audio', typeToIcon('audio')],
|
||||
['image', typeToIcon('image')],
|
||||
['video', typeToIcon('video')],
|
||||
['text', typeToIcon('text')],
|
||||
['pdf', isPdf],
|
||||
['zip', isZip],
|
||||
['excel', isExcel],
|
||||
['doc', isDoc],
|
||||
['presentation', isPresentation]
|
||||
]
|
||||
for (var i in check) {
|
||||
if (check[i][1](name, contentType)) return check[i][0]
|
||||
}
|
||||
return 'other'
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,85 +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 { minioBrowserPrefix } from './constants.js'
|
||||
|
||||
export const sortObjectsByName = (objects, order) => {
|
||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
||||
folders = folders.sort((a, b) => {
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
|
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
||||
return 0
|
||||
})
|
||||
files = files.sort((a, b) => {
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
|
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
||||
return 0
|
||||
})
|
||||
if (order) {
|
||||
folders = folders.reverse()
|
||||
files = files.reverse()
|
||||
}
|
||||
return [...folders, ...files]
|
||||
}
|
||||
|
||||
export const sortObjectsBySize = (objects, order) => {
|
||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
||||
files = files.sort((a, b) => a.size - b.size)
|
||||
if (order)
|
||||
files = files.reverse()
|
||||
return [...folders, ...files]
|
||||
}
|
||||
|
||||
export const sortObjectsByDate = (objects, order) => {
|
||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
||||
files = files.sort((a, b) => new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime())
|
||||
if (order)
|
||||
files = files.reverse()
|
||||
return [...folders, ...files]
|
||||
}
|
||||
|
||||
export const pathSlice = (path) => {
|
||||
path = path.replace(minioBrowserPrefix, '')
|
||||
let prefix = ''
|
||||
let bucket = ''
|
||||
if (!path) return {
|
||||
bucket,
|
||||
prefix
|
||||
}
|
||||
let objectIndex = path.indexOf('/', 1)
|
||||
if (objectIndex == -1) {
|
||||
bucket = path.slice(1)
|
||||
return {
|
||||
bucket,
|
||||
prefix
|
||||
}
|
||||
}
|
||||
bucket = path.slice(1, objectIndex)
|
||||
prefix = path.slice(objectIndex + 1)
|
||||
return {
|
||||
bucket,
|
||||
prefix
|
||||
}
|
||||
}
|
||||
|
||||
export const pathJoin = (bucket, prefix) => {
|
||||
if (!prefix)
|
||||
prefix = ''
|
||||
return minioBrowserPrefix + '/' + bucket + '/' + prefix
|
||||
}
|
||||
@@ -1,130 +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 { browserHistory } from 'react-router'
|
||||
import JSONrpc from './jsonrpc'
|
||||
import * as actions from './actions'
|
||||
import { minioBrowserPrefix } from './constants.js'
|
||||
import Moment from 'moment'
|
||||
import storage from 'local-storage-fallback'
|
||||
|
||||
export default class Web {
|
||||
constructor(endpoint, dispatch) {
|
||||
const namespace = 'Web'
|
||||
this.dispatch = dispatch
|
||||
this.JSONrpc = new JSONrpc({
|
||||
endpoint,
|
||||
namespace
|
||||
})
|
||||
}
|
||||
makeCall(method, options) {
|
||||
return this.JSONrpc.call(method, {
|
||||
params: options
|
||||
}, storage.getItem('token'))
|
||||
.catch(err => {
|
||||
if (err.status === 401) {
|
||||
storage.removeItem('token')
|
||||
browserHistory.push(`${minioBrowserPrefix}/login`)
|
||||
throw new Error('Please re-login.')
|
||||
}
|
||||
if (err.status)
|
||||
throw new Error(`Server returned error [${err.status}]`)
|
||||
throw new Error('Minio server is unreachable')
|
||||
})
|
||||
.then(res => {
|
||||
let json = JSON.parse(res.text)
|
||||
let result = json.result
|
||||
let error = json.error
|
||||
if (error) {
|
||||
throw new Error(error.message)
|
||||
}
|
||||
if (!Moment(result.uiVersion).isValid()) {
|
||||
throw new Error("Invalid UI version in the JSON-RPC response")
|
||||
}
|
||||
if (result.uiVersion !== currentUiVersion
|
||||
&& currentUiVersion !== 'MINIO_UI_VERSION') {
|
||||
storage.setItem('newlyUpdated', true)
|
||||
location.reload()
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
LoggedIn() {
|
||||
return !!storage.getItem('token')
|
||||
}
|
||||
Login(args) {
|
||||
return this.makeCall('Login', args)
|
||||
.then(res => {
|
||||
storage.setItem('token', `${res.token}`)
|
||||
return res
|
||||
})
|
||||
}
|
||||
Logout() {
|
||||
storage.removeItem('token')
|
||||
}
|
||||
ServerInfo() {
|
||||
return this.makeCall('ServerInfo')
|
||||
}
|
||||
StorageInfo() {
|
||||
return this.makeCall('StorageInfo')
|
||||
}
|
||||
ListBuckets() {
|
||||
return this.makeCall('ListBuckets')
|
||||
}
|
||||
MakeBucket(args) {
|
||||
return this.makeCall('MakeBucket', args)
|
||||
}
|
||||
DeleteBucket(args) {
|
||||
return this.makeCall('DeleteBucket', args)
|
||||
}
|
||||
ListObjects(args) {
|
||||
return this.makeCall('ListObjects', args)
|
||||
}
|
||||
PresignedGet(args) {
|
||||
return this.makeCall('PresignedGet', args)
|
||||
}
|
||||
PutObjectURL(args) {
|
||||
return this.makeCall('PutObjectURL', args)
|
||||
}
|
||||
RemoveObject(args) {
|
||||
return this.makeCall('RemoveObject', args)
|
||||
}
|
||||
GetAuth() {
|
||||
return this.makeCall('GetAuth')
|
||||
}
|
||||
GenerateAuth() {
|
||||
return this.makeCall('GenerateAuth')
|
||||
}
|
||||
SetAuth(args) {
|
||||
return this.makeCall('SetAuth', args)
|
||||
.then(res => {
|
||||
storage.setItem('token', `${res.token}`)
|
||||
return res
|
||||
})
|
||||
}
|
||||
CreateURLToken() {
|
||||
return this.makeCall('CreateURLToken')
|
||||
}
|
||||
GetBucketPolicy(args) {
|
||||
return this.makeCall('GetBucketPolicy', args)
|
||||
}
|
||||
SetBucketPolicy(args) {
|
||||
return this.makeCall('SetBucketPolicy', args)
|
||||
}
|
||||
ListAllBucketPolicies(args) {
|
||||
return this.makeCall('ListAllBucketPolicies', args)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
.alert {
|
||||
border: 0;
|
||||
position: fixed;
|
||||
max-width: 500px;
|
||||
margin: 0;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1);
|
||||
color: @white;
|
||||
width: 100%;
|
||||
right: 20px;
|
||||
border-radius: 3px;
|
||||
padding: 17px 50px 17px 17px;
|
||||
z-index: 10010;
|
||||
.animation-duration(800ms);
|
||||
.animation-fill-mode(both);
|
||||
|
||||
&:not(.progress) {
|
||||
top: 20px;
|
||||
|
||||
@media(min-width: (@screen-sm-min)) {
|
||||
left: 50%;
|
||||
margin-left: -250px;
|
||||
}
|
||||
}
|
||||
|
||||
&.progress {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
&.alert-danger {
|
||||
background: @red;
|
||||
}
|
||||
|
||||
&.alert-success {
|
||||
background: @green;
|
||||
}
|
||||
|
||||
&.alert-info {
|
||||
background: @blue;
|
||||
}
|
||||
|
||||
@media(max-width: (@screen-xs-max)) {
|
||||
left: 20px;
|
||||
width: ~"calc(100% - 40px)";
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin: 10px 10px 8px 0;
|
||||
height: 5px;
|
||||
box-shadow: none;
|
||||
border-radius: 1px;
|
||||
background-color: @blue;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
box-shadow: none;
|
||||
background-color: @white;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
.animated{
|
||||
&.infinite {
|
||||
.animation-iteration-count(infinite);
|
||||
}
|
||||
}
|
||||
|
||||
@import 'fadeIn';
|
||||
@import 'fadeInDown';
|
||||
@import 'fadeInUp';
|
||||
@import 'fadeOut';
|
||||
@import 'fadeOutDown';
|
||||
@import 'fadeOutUp';
|
||||
@import 'zoomIn';
|
||||
@@ -1,26 +0,0 @@
|
||||
@-webkit-keyframes fadeIn {
|
||||
0% {opacity: 0;}
|
||||
100% {opacity: 1;}
|
||||
}
|
||||
|
||||
@-moz-keyframes fadeIn {
|
||||
0% {opacity: 0;}
|
||||
100% {opacity: 1;}
|
||||
}
|
||||
|
||||
@-o-keyframes fadeIn {
|
||||
0% {opacity: 0;}
|
||||
100% {opacity: 1;}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {opacity: 0;}
|
||||
100% {opacity: 1;}
|
||||
}
|
||||
|
||||
.fadeIn {
|
||||
-webkit-animation-name: fadeIn;
|
||||
-moz-animation-name: fadeIn;
|
||||
-o-animation-name: fadeIn;
|
||||
animation-name: fadeIn;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
@-webkit-keyframes fadeInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(-20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes fadeInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-moz-transform: translateY(-20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-moz-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes fadeInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-ms-transform: translateY(-20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-ms-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fadeInDown {
|
||||
-webkit-animation-name: fadeInDown;
|
||||
-moz-animation-name: fadeInDown;
|
||||
-o-animation-name: fadeInDown;
|
||||
animation-name: fadeInDown;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
@-webkit-keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-moz-transform: translateY(20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-moz-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-o-transform: translateY(20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-o-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fadeInUp {
|
||||
-webkit-animation-name: fadeInUp;
|
||||
-moz-animation-name: fadeInUp;
|
||||
-o-animation-name: fadeInUp;
|
||||
animation-name: fadeInUp;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
@-webkit-keyframes fadeOut {
|
||||
0% {opacity: 1;}
|
||||
100% {opacity: 0;}
|
||||
}
|
||||
|
||||
@-moz-keyframes fadeOut {
|
||||
0% {opacity: 1;}
|
||||
100% {opacity: 0;}
|
||||
}
|
||||
|
||||
@-o-keyframes fadeOut {
|
||||
0% {opacity: 1;}
|
||||
100% {opacity: 0;}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
0% {opacity: 1;}
|
||||
100% {opacity: 0;}
|
||||
}
|
||||
|
||||
.fadeOut {
|
||||
-webkit-animation-name: fadeOut;
|
||||
-moz-animation-name: fadeOut;
|
||||
-o-animation-name: fadeOut;
|
||||
animation-name: fadeOut;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
@-webkit-keyframes fadeOutDown {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(20px);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes fadeOutDown {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-moz-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-moz-transform: translateY(20px);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes fadeOutDown {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-o-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-o-transform: translateY(20px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOutDown {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
}
|
||||
|
||||
.fadeOutDown {
|
||||
-webkit-animation-name: fadeOutDown;
|
||||
-moz-animation-name: fadeOutDown;
|
||||
-o-animation-name: fadeOutDown;
|
||||
animation-name: fadeOutDown;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
@-webkit-keyframes fadeOutUp {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes fadeOutUp {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-moz-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-moz-transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
@-o-keyframes fadeOutUp {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-o-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-o-transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
@keyframes fadeOutUp {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
.fadeOutUp {
|
||||
-webkit-animation-name: fadeOutUp;
|
||||
-moz-animation-name: fadeOutUp;
|
||||
-o-animation-name: fadeOutUp;
|
||||
animation-name: fadeOutUp;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
@-webkit-keyframes zoomIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale3d(.3, .3, .3);
|
||||
transform: scale3d(.3, .3, .3);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes zoomIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale3d(.3, .3, .3);
|
||||
transform: scale3d(.3, .3, .3);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 10px;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
.transition(color);
|
||||
.transition-duration(300ms);
|
||||
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
.btn {
|
||||
border: 0;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
border-radius: 2px;
|
||||
text-align: center;
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
.opacity(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------
|
||||
Button Variants
|
||||
------------------------------------*/
|
||||
.btn-variant(@bg-color, @color) {
|
||||
color: @color;
|
||||
background-color: @bg-color;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: @color;
|
||||
background-color: darken(@bg-color, 6%);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-white {
|
||||
.btn-variant(#fff, darken(@text-color, 20%));
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
.btn-variant(#eee, #545454);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
.btn-variant(@red, @white);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
.btn-variant(@blue, @white);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
.btn-variant(@green, @white);
|
||||
}
|
||||
//-----------------------------------
|
||||
@@ -1,26 +0,0 @@
|
||||
.dropdown-menu {
|
||||
padding: 15px 0;
|
||||
top: 0;
|
||||
margin-top: -1px;
|
||||
|
||||
& > li {
|
||||
& > a {
|
||||
padding: 8px 20px;
|
||||
font-size: 15px;
|
||||
|
||||
& > i {
|
||||
width: 20px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-right {
|
||||
& > li {
|
||||
& > a {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
/*------------------------------
|
||||
Layout
|
||||
--------------------------------*/
|
||||
.file-explorer {
|
||||
background-color: @white;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
&.toggled {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.fe-body {
|
||||
@media(min-width: @screen-md-min) {
|
||||
padding: 0 0 80px @fe-sidebar-width;
|
||||
}
|
||||
|
||||
@media(max-width: @screen-sm-max) {
|
||||
padding: 75px 0 80px;
|
||||
}
|
||||
|
||||
min-height:100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
/*------------------------------
|
||||
Create and Upload Button
|
||||
--------------------------------*/
|
||||
.feb-actions {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
|
||||
.dropdown-menu {
|
||||
min-width: 55px;
|
||||
width: 55px;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.open {
|
||||
.feba-btn {
|
||||
.scale(1);
|
||||
|
||||
&:first-child {
|
||||
.animation-name(feba-btn-anim);
|
||||
.animation-duration(300ms);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.animation-name(feba-btn-anim);
|
||||
.animation-duration(100ms);
|
||||
}
|
||||
}
|
||||
|
||||
.feba-toggle {
|
||||
background: darken(@red, 10%);
|
||||
|
||||
& > span {
|
||||
.rotate(135deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.feba-toggle {
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
line-height: 55px;
|
||||
border-radius: 50%;
|
||||
background: @red;
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.15);
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @white;
|
||||
font-size: 17px;
|
||||
line-height: 58px;
|
||||
}
|
||||
}
|
||||
|
||||
.feba-toggle,
|
||||
.feba-toggle > span {
|
||||
.transition(all);
|
||||
.transition-duration(250ms);
|
||||
.backface-visibility(hidden);
|
||||
}
|
||||
|
||||
.feba-btn {
|
||||
width: 40px;
|
||||
margin-top: 10px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
color: @white;
|
||||
line-height: 40px;
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.15);
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
position: relative;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: @white;
|
||||
}
|
||||
|
||||
label {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.feba-bucket {
|
||||
background: @orange;
|
||||
}
|
||||
|
||||
.feba-upload {
|
||||
background: @yellow;
|
||||
}
|
||||
|
||||
@-webkit-keyframes feba-btn-anim {
|
||||
from {
|
||||
.scale(0);
|
||||
.opacity(0);
|
||||
}
|
||||
to {
|
||||
.scale(1);
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes feba-btn-anim {
|
||||
from {
|
||||
.scale(0);
|
||||
.opacity(0);
|
||||
}
|
||||
to {
|
||||
.scale(1);
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url('../../fonts/lato/lato-normal.woff2') format('woff2'),
|
||||
url('../../fonts/lato/lato-normal.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
.form-control {
|
||||
border: 0;
|
||||
border-bottom: 1px solid @input-border;
|
||||
color: #32393F;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
select.form-control {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
border-radius: 0;
|
||||
background: url(../../img/select-caret.svg) no-repeat bottom 7px right;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Input Group
|
||||
----------------------------*/
|
||||
.input-group {
|
||||
position: relative;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
label:not(.ig-label) {
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.ig-label {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
bottom: 7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
.transition(all);
|
||||
.transition-duration(250ms);
|
||||
padding: 2px 0 3px;
|
||||
border-radius: 2px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.ig-helpers {
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
|
||||
&,
|
||||
&:before,
|
||||
&:after {
|
||||
position: absolute;
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
width: 0;
|
||||
.transition(all);
|
||||
.transition-duration(250ms);
|
||||
background-color: #03A9F4;
|
||||
}
|
||||
|
||||
&:before {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
&:after {
|
||||
right: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.ig-text {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border: 0;
|
||||
background: transparent !important;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border-bottom: 1px solid #eee;
|
||||
color: #32393F;
|
||||
font-size: 13px;
|
||||
|
||||
|
||||
&:focus + .ig-helpers {
|
||||
&:before,
|
||||
&:after {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&:valid,
|
||||
&:disabled,
|
||||
&:focus {
|
||||
& ~ .ig-label {
|
||||
bottom: 35px;
|
||||
font-size: 13px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
.opacity(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.ig-dark {
|
||||
.ig-text {
|
||||
color: @white !important;
|
||||
border-color: rgba(255,255,255,0.1) !important;
|
||||
}
|
||||
|
||||
.ig-helpers {
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: #dfdfdf;
|
||||
height: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ig-left {
|
||||
.ig-label,
|
||||
.ig-text {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.ig-error {
|
||||
.ig-label {
|
||||
color: #E23F3F;
|
||||
}
|
||||
.ig-helpers i {
|
||||
&:first-child,
|
||||
&:first-child:before,
|
||||
&:first-child:after {
|
||||
background: rgba(226, 63, 63, 0.43);
|
||||
}
|
||||
&:last-child,
|
||||
&:last-child:before,
|
||||
&:last-child:after {
|
||||
background: #E23F3F !important;
|
||||
}
|
||||
}
|
||||
&:after {
|
||||
content: "\f05a";
|
||||
font-family: FontAwesome;
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
right: 9px;
|
||||
font-size: 20px;
|
||||
color: #D33D3E;
|
||||
}
|
||||
}
|
||||
|
||||
.ig-search {
|
||||
&:before {
|
||||
font-family: @font-family-icon;
|
||||
content: '\f002';
|
||||
font-size: 15px;
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.ig-text {
|
||||
padding-left: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Share Spinners
|
||||
----------------------------*/
|
||||
.set-expire {
|
||||
border: 1px solid @input-border;
|
||||
margin: 35px 0 30px;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.set-expire-item {
|
||||
padding: 9px 5px 3px;
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
width: 1%;
|
||||
text-align: center;
|
||||
.user-select(none);
|
||||
|
||||
&:not(:last-child) {
|
||||
border-right: 1px solid @input-border;
|
||||
}
|
||||
}
|
||||
|
||||
.set-expire-title {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.set-expire-value {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
left: -8px;
|
||||
|
||||
input {
|
||||
.user-select(none);
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
right: -15px;
|
||||
border: 0;
|
||||
color: @text-strong-color;
|
||||
padding: 0;
|
||||
height: 25px;
|
||||
width: 100%;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.set-expire-decrease,
|
||||
.set-expire-increase {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: url(../../img/arrow.svg) no-repeat center;
|
||||
background-size: 85%;
|
||||
left: 50%;
|
||||
margin-left: -10px;
|
||||
.opacity(0.2);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
.opacity(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.set-expire-increase {
|
||||
top: -25px;
|
||||
}
|
||||
|
||||
.set-expire-decrease {
|
||||
bottom: -27px;
|
||||
.rotate(-180deg);
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*----------------------------
|
||||
Text Alignment
|
||||
-----------------------------*/
|
||||
.text-center { text-align: center !important; }
|
||||
.text-left { text-align: left !important; }
|
||||
.text-right { text-align: right !important; }
|
||||
|
||||
|
||||
/*----------------------------
|
||||
Float
|
||||
-----------------------------*/
|
||||
.clearfix { .clearfix(); }
|
||||
.pull-right { float: right !important; }
|
||||
.pull-left { float: left !important; }
|
||||
|
||||
|
||||
/*----------------------------
|
||||
Position
|
||||
-----------------------------*/
|
||||
.p-relative { position: relative; }
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
Generate Margin Class
|
||||
margin, margin-top, margin-bottom, margin-left, margin-right
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
.margin (@label, @size: 1, @key:1) when (@size =< 30){
|
||||
.m-@{key} {
|
||||
margin: @size !important;
|
||||
}
|
||||
|
||||
.m-t-@{key} {
|
||||
margin-top: @size !important;
|
||||
}
|
||||
|
||||
.m-b-@{key} {
|
||||
margin-bottom: @size !important;
|
||||
}
|
||||
|
||||
.m-l-@{key} {
|
||||
margin-left: @size !important;
|
||||
}
|
||||
|
||||
.m-r-@{key} {
|
||||
margin-right: @size !important;
|
||||
}
|
||||
|
||||
.margin(@label - 5; @size + 5; @key + 5);
|
||||
}
|
||||
|
||||
.margin(25, 0px, 0);
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
Generate Padding Class
|
||||
padding, padding-top, padding-bottom, padding-left, padding-right
|
||||
----------------------------------------------------------------------------*/
|
||||
.padding (@label, @size: 1, @key:1) when (@size =< 30){
|
||||
.p-@{key} {
|
||||
padding: @size !important;
|
||||
}
|
||||
|
||||
.p-t-@{key} {
|
||||
padding-top: @size !important;
|
||||
}
|
||||
|
||||
.p-b-@{key} {
|
||||
padding-bottom: @size !important;
|
||||
}
|
||||
|
||||
.p-l-@{key} {
|
||||
padding-left: @size !important;
|
||||
}
|
||||
|
||||
.p-r-@{key} {
|
||||
padding-right: @size !important;
|
||||
}
|
||||
|
||||
.padding(@label - 5; @size + 5; @key + 5);
|
||||
}
|
||||
|
||||
.padding(25, 0px, 0);
|
||||
@@ -1,240 +0,0 @@
|
||||
/*--------------------------
|
||||
Header
|
||||
----------------------------*/
|
||||
.fe-header {
|
||||
@media(min-width: (@screen-sm-min - 100)) {
|
||||
position: relative;
|
||||
padding: 40px 40px 20px 45px;
|
||||
}
|
||||
|
||||
@media(max-width: (@screen-xs-max - 100)) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
|
||||
& > span {
|
||||
margin-bottom: 7px;
|
||||
display: inline-block;
|
||||
|
||||
&:not(:first-child) {
|
||||
&:before {
|
||||
content: '/';
|
||||
margin: 0 4px;
|
||||
color: @text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Disk usage
|
||||
----------------------------*/
|
||||
.feh-usage {
|
||||
margin-top: 12px;
|
||||
max-width: 285px;
|
||||
|
||||
@media(max-width: (@screen-xs-max - 100px)) {
|
||||
max-width: 100%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
& > ul {
|
||||
margin-top: 7px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
& > li {
|
||||
padding-right: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fehu-chart {
|
||||
height: 5px;
|
||||
background: #eee;
|
||||
position: relative;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
|
||||
& > div {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: @link-color;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
Header Actions
|
||||
----------------------------*/
|
||||
.feh-actions {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 35px;
|
||||
top: 30px;
|
||||
z-index: 11;
|
||||
|
||||
@media(max-width: (@screen-sm-max)) {
|
||||
top: 7px;
|
||||
right: 10px;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
& > li {
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
line-height: 100%;
|
||||
|
||||
& > a,
|
||||
& > .btn-group > button {
|
||||
display: block;
|
||||
height: 45px;
|
||||
min-width: 45px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
|
||||
@media(min-width: @screen-md-min) {
|
||||
color: #7B7B7B;
|
||||
font-size: 21px;
|
||||
line-height: 45px;
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
|
||||
&:hover {
|
||||
background: rgba(0,0,0,0.09);
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: (@screen-sm-max)) {
|
||||
background: url(../../img/more-h-light.svg) no-repeat center;
|
||||
|
||||
.fa-reorder {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Mobile Header
|
||||
----------------------------*/
|
||||
@media(max-width: @screen-sm-max) {
|
||||
.fe-header-mobile {
|
||||
background-color: @dark-gray;
|
||||
padding: 10px 50px 9px 12px;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
.mh-logo {
|
||||
height: 35px;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.feh-trigger {
|
||||
width: 41px;
|
||||
height: 41px;
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
|
||||
}
|
||||
|
||||
&:after {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&:before {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
.scale(0);
|
||||
}
|
||||
}
|
||||
|
||||
.feht-toggled {
|
||||
&:before {
|
||||
.scale(1);
|
||||
}
|
||||
|
||||
.feht-lines {
|
||||
.rotate(180deg);
|
||||
|
||||
& > div {
|
||||
&.top {
|
||||
width: 12px;
|
||||
transform: translateX(8px) translateY(1px) rotate(45deg);
|
||||
-webkit-transform: translateX(8px) translateY(1px) rotate(45deg);
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
width: 12px;
|
||||
transform: translateX(8px) translateY(-1px) rotate(-45deg);
|
||||
-webkit-transform: translateX(8px) translateY(-1px) rotate(-45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.feht-lines,
|
||||
.feht-lines > div {
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
}
|
||||
|
||||
.feht-lines {
|
||||
width: 18px;
|
||||
height: 12px;
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
|
||||
& > div {
|
||||
background-color: #EAEAEA;
|
||||
width: 18px;
|
||||
height: 2px;
|
||||
|
||||
&.center {
|
||||
margin: 3px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
.ie-warning {
|
||||
background-color: #ff5252;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
text-align: center;
|
||||
|
||||
&:before {
|
||||
width: 1px;
|
||||
content: '';
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:before,
|
||||
.iw-inner {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.iw-inner {
|
||||
width: 470px;
|
||||
height: 300px;
|
||||
background-color: @white;
|
||||
border-radius: 5px;
|
||||
padding: 40px;
|
||||
position: relative;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 230px;
|
||||
margin-left: 80px;
|
||||
margin-top: 16px;
|
||||
|
||||
& > li {
|
||||
float: left;
|
||||
|
||||
& > a {
|
||||
display: block;
|
||||
padding: 10px 15px 7px;
|
||||
font-size: 14px;
|
||||
margin: 0 1px;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 40px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.iwi-icon {
|
||||
color: #ff5252;
|
||||
font-size: 40px;
|
||||
display: block;
|
||||
line-height: 100%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.iwi-skip {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -35px;
|
||||
width: 100%;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
@@ -1,486 +0,0 @@
|
||||
/*--------------------------
|
||||
Row
|
||||
----------------------------*/
|
||||
.fesl-row {
|
||||
position: relative;
|
||||
|
||||
@media (min-width: (@screen-sm-min - 100px)) {
|
||||
padding: 5px 20px 5px 40px;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@media(max-width: (@screen-xs-max - 100px)) {
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
.clearfix();
|
||||
}
|
||||
|
||||
header.fesl-row {
|
||||
@media (min-width:(@screen-sm-min - 100px)) {
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid lighten(@text-muted-color, 20%);
|
||||
padding-left: 30px;
|
||||
|
||||
.fesl-item,
|
||||
.fesli-sort {
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
}
|
||||
|
||||
.fesl-item {
|
||||
cursor: pointer;
|
||||
color: @text-color;
|
||||
font-weight: 500;
|
||||
margin-bottom: -5px;
|
||||
|
||||
& > .fesli-sort {
|
||||
float: right;
|
||||
margin: 4px 0 0;
|
||||
.opacity(0);
|
||||
color: @dark-gray;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&:hover:not(.fesl-item-actions) {
|
||||
background: lighten(@text-muted-color, 22%);
|
||||
color: @dark-gray;
|
||||
|
||||
& > .fesli-sort {
|
||||
.opacity(0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:(@screen-xs-max - 100px)) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.list-type(@background, @icon) {
|
||||
.fis-icon {
|
||||
background-color: @background;
|
||||
|
||||
&:before {
|
||||
content: @icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.fesl-row {
|
||||
border-bottom: 1px solid transparent;
|
||||
cursor: default;
|
||||
.transition(background-color);
|
||||
.transition-duration(500ms);
|
||||
|
||||
@media (max-width: (@screen-xs-max - 100px)) {
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
&:not(.fesl-row-selected) {
|
||||
&:nth-child(even) {
|
||||
background-color: @list-row-even-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.fis-icon {
|
||||
&:before {
|
||||
.opacity(0)
|
||||
}
|
||||
}
|
||||
|
||||
.fis-helper {
|
||||
&:before {
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
Icons
|
||||
----------------------------*/
|
||||
&[data-type=folder] { .list-type(#a1d6dd, '\f114'); }
|
||||
&[data-type=pdf] {.list-type(#fa7775, '\f1c1'); }
|
||||
&[data-type=zip] { .list-type(#427089, '\f1c6'); }
|
||||
&[data-type=audio] { .list-type(#009688, '\f1c7'); }
|
||||
&[data-type=code] { .list-type(#997867, "\f1c9"); }
|
||||
&[data-type=excel] { .list-type(#64c866, '\f1c3'); }
|
||||
&[data-type=image] { .list-type(#f06292, '\f1c5'); }
|
||||
&[data-type=video] { .list-type(#f8c363, '\f1c8'); }
|
||||
&[data-type=other] { .list-type(#afafaf, '\f016'); }
|
||||
&[data-type=text] { .list-type(#8a8a8a, '\f0f6'); }
|
||||
&[data-type=doc] { .list-type(#2196f5, '\f1c2'); }
|
||||
&[data-type=presentation] { .list-type(#896ea6, '\f1c4'); }
|
||||
|
||||
&.fesl-loading{
|
||||
&:before {
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
.list-loader(20px, 20px, rgba(255, 255, 255, 0.5), @white);
|
||||
left: 57px;
|
||||
top: 17px;
|
||||
|
||||
@media (max-width: (@screen-xs-max - 100px)) {
|
||||
left: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.fesl-row-selected {
|
||||
background-color: @list-row-selected-bg;
|
||||
|
||||
&, .fesl-item a {
|
||||
color: darken(@text-color, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.fi-select {
|
||||
float: left;
|
||||
position: relative;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
margin: 3px 0;
|
||||
|
||||
@media(max-width: (@screen-xs-max - 100px)) {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
z-index: 8;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:checked {
|
||||
& ~ .fis-icon {
|
||||
background-color: #32393F;
|
||||
|
||||
&:before {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
& ~ .fis-helper {
|
||||
&:before {
|
||||
.scale(0);
|
||||
}
|
||||
|
||||
&:after {
|
||||
.scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fis-icon {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
border-radius: 50%;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
.transition(background-color);
|
||||
.transition-duration(250ms);
|
||||
|
||||
&:before {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
font-family: @font-family-icon;
|
||||
line-height: 35px;
|
||||
font-size: 16px;
|
||||
color: @white;
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.fis-helper {
|
||||
&:before,
|
||||
&:after {
|
||||
position: absolute;
|
||||
.transition(all);
|
||||
.transition-duration(250ms);
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border: 2px solid @white;
|
||||
z-index: 7;
|
||||
border-radius: 2px;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
font-family: @font-family-icon;
|
||||
content: '\f00c';
|
||||
top: 8px;
|
||||
left: 9px;
|
||||
color: @white;
|
||||
font-size: 14px;
|
||||
.scale(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Files and Folders
|
||||
----------------------------*/
|
||||
.fesl-item {
|
||||
display: block;
|
||||
|
||||
a {
|
||||
color: darken(@text-color, 5%);
|
||||
}
|
||||
|
||||
@media(min-width: (@screen-sm-min - 100px)) {
|
||||
&:not(.fesl-item-actions):not(.fesl-item-icon) {
|
||||
text-overflow: ellipsis;
|
||||
padding: 10px 15px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.fesl-item-name {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
&.fesl-item-size {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
&.fesl-item-modified {
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
&.fesl-item-actions {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: (@screen-xs-max - 100px)) {
|
||||
padding: 0;
|
||||
|
||||
&.fesl-item-name {
|
||||
width: 100%;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
&.fesl-item-size,
|
||||
&.fesl-item-modified {
|
||||
font-size: 12px;
|
||||
color: #B5B5B5;
|
||||
float: left;
|
||||
}
|
||||
|
||||
&.fesl-item-modified {
|
||||
max-width: 72px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.fesl-item-size {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&.fesl-item-actions {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Action buttons
|
||||
----------------------------*/
|
||||
.fia-toggle {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
background: transparent url(../../img/more-h.svg) no-repeat center;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
.opacity(0.4);
|
||||
|
||||
&:hover {
|
||||
.opacity(0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.fesl-item-actions {
|
||||
.dropdown-menu {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
right: 38px;
|
||||
left: auto;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
&.open {
|
||||
.dropdown-menu {
|
||||
.fiad-action {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fiad-action {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
background: @amber;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 35px;
|
||||
font-weight: normal;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
margin-left: 5px;
|
||||
.animation-name(fiad-action-anim);
|
||||
.transform-origin(center center);
|
||||
.backface-visibility(none);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&:nth-child(2) {
|
||||
.animation-duration(100ms);
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
.animation-duration(250ms);
|
||||
}
|
||||
|
||||
& > i {
|
||||
font-size: 14px;
|
||||
color: @white;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: darken(@amber, 3%);
|
||||
}
|
||||
}
|
||||
|
||||
.list-actions {
|
||||
position: fixed;
|
||||
.translate3d(0, -100%, 0);
|
||||
.opacity(0);
|
||||
.transition(all);
|
||||
.transition-duration(200ms);
|
||||
padding: 20px 70px 20px 25px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: @brand-primary;
|
||||
z-index: 20;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||
text-align: center;
|
||||
|
||||
&.list-actions-toggled {
|
||||
.translate3d(0, 0, 0);
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
.la-close {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 0;
|
||||
color: #fff;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 30px !important;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
font-weight: normal;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.la-label {
|
||||
color: @white;
|
||||
float: left;
|
||||
padding: 4px 0;
|
||||
|
||||
.fa {
|
||||
font-size: 22px;
|
||||
vertical-align: top;
|
||||
margin-right: 10px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.la-actions {
|
||||
button {
|
||||
background-color: transparent;
|
||||
border: 2px solid rgba(255,255,255,0.9);
|
||||
color: @white;
|
||||
border-radius: 2px;
|
||||
padding: 5px 10px;
|
||||
font-size: 13px;
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
margin-left: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: @white;
|
||||
color: @brand-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fiad-action-anim {
|
||||
from {
|
||||
.scale(0);
|
||||
.opacity(0);
|
||||
right: -20px;
|
||||
}
|
||||
to {
|
||||
.scale(1);
|
||||
.opacity(1);
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fiad-action-anim {
|
||||
from {
|
||||
.scale(0);
|
||||
.opacity(0);
|
||||
right: -20px;
|
||||
}
|
||||
to {
|
||||
.scale(1);
|
||||
.opacity(1);
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
.login {
|
||||
height: 100vh;
|
||||
min-height: 500px;
|
||||
background: @dark-gray;
|
||||
|
||||
text-align: center;
|
||||
&:before {
|
||||
height: ~"calc(100% - 110px)";
|
||||
width: 1px;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.l-wrap,
|
||||
.login:before {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.l-wrap {
|
||||
width: 80%;
|
||||
max-width: 500px;
|
||||
margin-top: -50px;
|
||||
&.toggled {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.input-group:not(:last-child) {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.l-footer {
|
||||
height: 110px;
|
||||
padding: 0 50px;
|
||||
}
|
||||
|
||||
.lf-logo {
|
||||
float: right;
|
||||
img {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.lf-server {
|
||||
float: left;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
@media (max-width: @screen-sm-min) {
|
||||
.lf-logo,
|
||||
.lf-server {
|
||||
float: none;
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lf-logo {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.lf-server {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.lw-btn {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 1px solid @white;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
font-size: 22px;
|
||||
color: @white;
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
opacity: 0.3;
|
||||
background-color: transparent;
|
||||
line-height: 45px;
|
||||
padding: 0;
|
||||
&:hover {
|
||||
color: @white;
|
||||
opacity: 0.8;
|
||||
border-color: @white;
|
||||
}
|
||||
|
||||
i {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------
|
||||
Chrome autofill fix
|
||||
-------------------------------*/
|
||||
input:-webkit-autofill {
|
||||
-webkit-box-shadow:0 0 0 50px @dark-gray inset !important;
|
||||
-webkit-text-fill-color: @white !important;
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*--------------------------
|
||||
Close
|
||||
----------------------------*/
|
||||
.close-variant(@color, @bg-color, @color-hover, @bg-color-hover) {
|
||||
span {
|
||||
background-color: @bg-color;
|
||||
color: @color;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
span {
|
||||
background-color: @bg-color-hover;
|
||||
color: @color-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
right: 15px;
|
||||
font-weight: normal;
|
||||
opacity: 1;
|
||||
font-size: 18px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 16px;
|
||||
z-index: 1;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
|
||||
span {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
line-height: 24px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
&:not(.close-alt) {
|
||||
.close-variant(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1), @white, rgba(255, 255, 255, 0.2));
|
||||
}
|
||||
}
|
||||
|
||||
.close-alt {
|
||||
.close-variant(#989898, #efefef, #7b7b7b, #e8e8e8);
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Hidden
|
||||
----------------------------*/
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Copy text
|
||||
----------------------------*/
|
||||
.copy-text {
|
||||
input {
|
||||
width: 100%;
|
||||
border-radius: 1px;
|
||||
border: 1px solid @input-border;
|
||||
padding: 7px 12px;
|
||||
font-size: 13px;
|
||||
line-height: 100%;
|
||||
cursor: text;
|
||||
.transition(border-color);
|
||||
.transition-duration(300ms);
|
||||
|
||||
&:hover {
|
||||
border-color: darken(@input-border, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
Sharing
|
||||
----------------------------*/
|
||||
.share-availability {
|
||||
margin-bottom: 40px;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
position: absolute;
|
||||
bottom: -30px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '01 Sec';
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '7 days';
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.modal-aheader {
|
||||
height: 100px;
|
||||
|
||||
&:before {
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*--------------------------
|
||||
User Select
|
||||
----------------------------*/
|
||||
.user-select(@value) {
|
||||
-webkit-user-select: @value;
|
||||
-moz-user-select: @value;
|
||||
-ms-user-select: @value;
|
||||
user-select: @value;
|
||||
}
|
||||
|
||||
|
||||
/*----------------------------------------
|
||||
CSS Animations based on animate.css
|
||||
-----------------------------------------*/
|
||||
.animated(@name, @duration) {
|
||||
-webkit-animation-name: @name;
|
||||
animation-name: @name;
|
||||
-webkit-animation-duration: @duration;
|
||||
animation-duration: @duration;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------
|
||||
For loop mixin for generate custom classes
|
||||
--------------------------------------------------*/
|
||||
.for(@i, @n) {.-each(@i)}
|
||||
.for(@n) when (isnumber(@n)) {.for(1, @n)}
|
||||
.for(@i, @n) when not (@i = @n) {
|
||||
.for((@i + (@n - @i) / abs(@n - @i)), @n);
|
||||
}
|
||||
|
||||
.for(@array) when (default()) {.for-impl_(length(@array))}
|
||||
.for-impl_(@i) when (@i > 1) {.for-impl_((@i - 1))}
|
||||
.for-impl_(@i) when (@i > 0) {.-each(extract(@array, @i))}
|
||||
|
||||
/*----------------------------------------
|
||||
List Loader
|
||||
-----------------------------------------*/
|
||||
.list-loader(@width, @height, @borderColor, @borderColorBottom) {
|
||||
content: '';
|
||||
width: @width;
|
||||
height: @height;
|
||||
border-radius: 50%;
|
||||
.animated(zoomIn, 500ms);
|
||||
border: 2px solid @borderColor;
|
||||
border-bottom-color: @borderColorBottom;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
-webkit-animation: zoomIn 250ms, spin 700ms 250ms infinite linear;
|
||||
animation: zoomIn 250ms, spin 700ms 250ms infinite linear;
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
/*--------------------------
|
||||
Modal
|
||||
----------------------------*/
|
||||
.modal {
|
||||
@media(min-width: @screen-sm-min) {
|
||||
text-align: center;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
text-align: left;
|
||||
margin: 10px auto;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dark {
|
||||
.modal-header {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
|
||||
small {
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: @dark-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
.animated(fadeIn, 200ms);
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
.animated(zoomIn, 200ms);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
color: @text-strong-color;
|
||||
position: relative;
|
||||
|
||||
small {
|
||||
display: block;
|
||||
text-transform: none;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
color: #a8a8a8;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 3px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 0 30px 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Dialog
|
||||
----------------------------*/
|
||||
.modal-confirm {
|
||||
.modal-dialog {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mc-icon {
|
||||
margin: 0 0 10px;
|
||||
|
||||
& > i {
|
||||
font-size: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.mci-red {
|
||||
color: #ff8f8f;
|
||||
}
|
||||
|
||||
.mci-amber {
|
||||
color: @amber;
|
||||
}
|
||||
|
||||
.mci-green {
|
||||
color: #64e096;
|
||||
}
|
||||
|
||||
.mc-text {
|
||||
color: @text-strong-color;
|
||||
}
|
||||
|
||||
.mc-sub {
|
||||
color: @text-muted-color;
|
||||
margin-top: 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
//--------------------------
|
||||
|
||||
|
||||
/*--------------------------
|
||||
About
|
||||
----------------------------*/
|
||||
.modal-about {
|
||||
@media (max-width: @screen-xs-max) {
|
||||
text-align: center;
|
||||
|
||||
.modal-dialog {
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
margin: 20px auto 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ma-inner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
min-height: 350px;
|
||||
position: relative;
|
||||
|
||||
@media (min-width: @screen-sm-min) {
|
||||
&:before {
|
||||
content: '';
|
||||
width: 150px;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
border-radius: 3px 0 0px 3px;
|
||||
background-color: #23282C;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mai-item {
|
||||
&:first-child {
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
flex: 4;
|
||||
padding: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.maii-logo {
|
||||
width: 70px;
|
||||
position: relative;
|
||||
|
||||
}
|
||||
|
||||
.maii-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
& > li {
|
||||
margin-bottom: 15px;
|
||||
|
||||
div {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-transform: uppercase;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
//--------------------------
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Preferences
|
||||
----------------------------*/
|
||||
.toggle-password {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
right: 35px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
background-color: @white;
|
||||
padding-top: 5px;
|
||||
|
||||
&.toggled {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
//--------------------------
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Policy
|
||||
----------------------------*/
|
||||
.pm-body {
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.pmb-header {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.pmb-list {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 35px;
|
||||
|
||||
&:nth-child(even) {
|
||||
background-color: #F7F7F7;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
header.pmb-list {
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
.pmbl-item {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
|
||||
&:nth-child(1) {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
margin: 0 25px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
div.pmb-list {
|
||||
select {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.pml-item {
|
||||
&:not(:last-child) {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
//--------------------------
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Create Bucket
|
||||
----------------------------*/
|
||||
.modal-create-bucket {
|
||||
.modal-dialog {
|
||||
position: fixed;
|
||||
right: 25px;
|
||||
bottom: 95px;
|
||||
margin: 0;
|
||||
height: 110px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
//--------------------------
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
/*--------------------------
|
||||
Sidebar
|
||||
----------------------------*/
|
||||
.fe-sidebar {
|
||||
width: @fe-sidebar-width;
|
||||
background-color: @dark-gray;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding: 25px;
|
||||
|
||||
@media(min-width: @screen-md-min) {
|
||||
.translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
@media(max-width: @screen-sm-max) {
|
||||
padding-top: 85px;
|
||||
z-index: 9;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.65);
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
.translate3d((-@fe-sidebar-width - 15px), 0, 0);
|
||||
|
||||
&.toggled {
|
||||
.translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
|
||||
&:hover {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
Header
|
||||
----------------------------*/
|
||||
.fes-header {
|
||||
margin-bottom: 40px;
|
||||
|
||||
img,
|
||||
h2 {
|
||||
float: left;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 13px 0 0 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
List
|
||||
----------------------------*/
|
||||
.fesl-inner {
|
||||
height: ~"calc(100vh - 260px)";
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
margin: 0 -25px;
|
||||
|
||||
& li {
|
||||
position: relative;
|
||||
|
||||
& > a {
|
||||
display: block;
|
||||
padding: 10px 45px 12px 55px;
|
||||
word-wrap: break-word;
|
||||
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
content: '\f0a0';
|
||||
font-size: 17px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 25px;
|
||||
.opacity(0.8);
|
||||
}
|
||||
|
||||
&.fesli-loading {
|
||||
&:before {
|
||||
.list-loader(20px, 20px, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.5));
|
||||
left: 32px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #282e32;
|
||||
|
||||
& > a {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
|
||||
& > a {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.bucket-dropdown .dropdown-toggle {
|
||||
.opacity(0.6);
|
||||
|
||||
&:hover {
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&:hover .scrollbar-vertical {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dropdown */
|
||||
.bucket-dropdown {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
width: 35px;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
color: @white;
|
||||
|
||||
.dropdown-toggle {
|
||||
.opacity(0);
|
||||
.transition(all);
|
||||
.transition-duration(200ms);
|
||||
font-size: 20px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.dropdown-menu-right {
|
||||
padding: 15px 0;
|
||||
margin-top: -1px;
|
||||
& li {
|
||||
a:before {
|
||||
content: none;
|
||||
-webkit-box-shadow: @dropdown-shadow;
|
||||
box-shadow: @dropdown-shadow;
|
||||
}
|
||||
&:not(.active):hover {
|
||||
& > a {
|
||||
color: @dropdown-link-hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
.scrollbar-vertical {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
.transition(opacity);
|
||||
.transition-duration(300ms);
|
||||
|
||||
div {
|
||||
border-radius: 1px !important;
|
||||
background-color: #6a6a6a !important;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
Host
|
||||
----------------------------*/
|
||||
.fes-host {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
background: @dark-gray;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
width: @fe-sidebar-width;
|
||||
padding: 20px;
|
||||
.text-overflow();
|
||||
|
||||
& > i {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/*--------------------------
|
||||
Base
|
||||
----------------------------*/
|
||||
@font-family-sans-serif : 'Lato', sans-serif;
|
||||
@font-family-icon : 'fontAwesome';
|
||||
@body-bg : #edecec;
|
||||
@text-color : #8e8e8e;
|
||||
@font-size-base : 15px;
|
||||
@link-color : #46a5e0;
|
||||
@link-hover-decoration : none;
|
||||
|
||||
|
||||
/*--------------------------
|
||||
File Explorer
|
||||
----------------------------*/
|
||||
@fe-sidebar-width : 320px;
|
||||
@text-muted-color : #BDBDBD;
|
||||
@text-strong-color : #333;
|
||||
|
||||
/*--------------------------
|
||||
Colors
|
||||
----------------------------*/
|
||||
@cyan : #2ED2FF;
|
||||
@amber : #ffc107;
|
||||
@red : #ff726f;
|
||||
@grey : #f5f5f5;
|
||||
@dark-blue : #0084d3;
|
||||
@blue : #00a6f7;
|
||||
@white : #ffffff;
|
||||
@black : #1b1e25;
|
||||
@blue : #50b2ff;
|
||||
@light-blue : #c1d1e8;
|
||||
@green : #33d46f;
|
||||
@yellow : #FFC107;
|
||||
@orange : #ffc155;
|
||||
@purple : #9C27B0;
|
||||
@teal : #009688;
|
||||
@brown : #795548;
|
||||
@blue-gray : #374952;
|
||||
@dark-gray : #32393F;
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Dropdown
|
||||
----------------------------*/
|
||||
@dropdown-fallback-border : transparent;
|
||||
@dropdown-border : transparent;
|
||||
@dropdown-divider-bg : '';
|
||||
@dropdown-link-hover-bg : rgba(0,0,0,0.05);
|
||||
@dropdown-link-color : @text-color;
|
||||
@dropdown-link-hover-color : #333;
|
||||
@dropdown-link-disabled-color : #e4e4e4;
|
||||
@dropdown-divider-bg : rgba(0,0,0,0.08);
|
||||
@dropdown-link-active-color : #333;
|
||||
@dropdown-link-active-bg : rgba(0, 0, 0, 0.075);
|
||||
@dropdown-shadow : 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Modal
|
||||
----------------------------*/
|
||||
@modal-content-fallback-border-color: transparent;
|
||||
@modal-content-border-color: transparent;
|
||||
@modal-backdrop-bg: rgba(0,0,0,0.1);
|
||||
@modal-header-border-color: transparent;
|
||||
@modal-title-line-height: transparent;
|
||||
@modal-footer-border-color: transparent;
|
||||
@modal-inner-padding: 30px 35px;
|
||||
@modal-title-padding: 30px 35px 0px;
|
||||
@modal-sm: 400px;
|
||||
|
||||
|
||||
/*-------------------------
|
||||
Buttons
|
||||
--------------------------*/
|
||||
@btn-border-radius-large: 2px;
|
||||
@btn-border-radius-small: 2px;
|
||||
@btn-border-radius-base: 2px;
|
||||
|
||||
|
||||
/*-------------------------
|
||||
Colors
|
||||
--------------------------*/
|
||||
@brand-primary: #2298f7;
|
||||
@brand-success: #4CAF50;
|
||||
@brand-info: #00BCD4;
|
||||
@brand-warning: #FF9800;
|
||||
@brand-danger: #FF5722;
|
||||
|
||||
|
||||
/*-------------------------
|
||||
Form
|
||||
--------------------------*/
|
||||
@input-border: #eee;
|
||||
|
||||
|
||||
/*-------------------------
|
||||
List
|
||||
--------------------------*/
|
||||
@list-row-selected-bg: #fbf2bf;
|
||||
@list-row-even-bg: #fafafa;
|
||||
@@ -1,39 +0,0 @@
|
||||
/*----------------------------
|
||||
Bootstrap
|
||||
-----------------------------*/
|
||||
@import "../../node_modules/bootstrap/less/scaffolding.less";
|
||||
@import "../../node_modules/bootstrap/less/variables.less";
|
||||
@import "../../node_modules/bootstrap/less/grid.less";
|
||||
@import "../../node_modules/bootstrap/less/mixins.less";
|
||||
@import "../../node_modules/bootstrap/less/normalize.less";
|
||||
@import "../../node_modules/bootstrap/less/dropdowns.less";
|
||||
@import "../../node_modules/bootstrap/less/modals.less";
|
||||
@import "../../node_modules/bootstrap/less/tooltip.less";
|
||||
@import "../../node_modules/bootstrap/less/responsive-utilities.less";
|
||||
|
||||
|
||||
/*----------------------------
|
||||
App
|
||||
-----------------------------*/
|
||||
@import 'inc/mixin';
|
||||
@import 'inc/variables';
|
||||
@import 'inc/base';
|
||||
@import 'inc/animate/animate';
|
||||
@import 'inc/generics';
|
||||
@import 'inc/font';
|
||||
@import 'inc/form';
|
||||
@import 'inc/buttons';
|
||||
@import 'inc/misc';
|
||||
@import 'inc/login';
|
||||
@import 'inc/header';
|
||||
@import 'inc/sidebar';
|
||||
@import 'inc/list';
|
||||
@import 'inc/file-explorer';
|
||||
@import 'inc/ie-warning';
|
||||
|
||||
/*----------------------------
|
||||
Boostrap
|
||||
-----------------------------*/
|
||||
@import 'inc/dropdown';
|
||||
@import 'inc/alert';
|
||||
@import 'inc/modal';
|
||||
@@ -1,126 +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.
|
||||
*/
|
||||
|
||||
var moment = require('moment')
|
||||
var async = require('async')
|
||||
var exec = require('child_process').exec
|
||||
var fs = require('fs')
|
||||
|
||||
var isProduction = process.env.NODE_ENV == 'production' ? true : false
|
||||
var assetsFileName = ''
|
||||
var commitId = ''
|
||||
var date = moment.utc()
|
||||
var version = date.format('YYYY-MM-DDTHH:mm:ss') + 'Z'
|
||||
var releaseTag = date.format('YYYY-MM-DDTHH-mm-ss') + 'Z'
|
||||
var buildType = 'DEVELOPMENT'
|
||||
if (process.env.MINIO_UI_BUILD) buildType = process.env.MINIO_UI_BUILD
|
||||
|
||||
rmDir = function(dirPath) {
|
||||
try { var files = fs.readdirSync(dirPath); }
|
||||
catch(e) { return; }
|
||||
if (files.length > 0)
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var filePath = dirPath + '/' + files[i];
|
||||
if (fs.statSync(filePath).isFile())
|
||||
fs.unlinkSync(filePath);
|
||||
else
|
||||
rmDir(filePath);
|
||||
}
|
||||
fs.rmdirSync(dirPath);
|
||||
};
|
||||
|
||||
async.waterfall([
|
||||
function(cb) {
|
||||
rmDir('production');
|
||||
rmDir('dev');
|
||||
var cmd = 'webpack -p --config webpack.production.config.js'
|
||||
if (!isProduction) {
|
||||
cmd = 'webpack';
|
||||
}
|
||||
console.log('Running', cmd)
|
||||
exec(cmd, cb)
|
||||
},
|
||||
function(stdout, stderr, cb) {
|
||||
if (isProduction) {
|
||||
fs.renameSync('production/index_bundle.js',
|
||||
'production/index_bundle-' + releaseTag + '.js')
|
||||
} else {
|
||||
fs.renameSync('dev/index_bundle.js',
|
||||
'dev/index_bundle-' + releaseTag + '.js')
|
||||
}
|
||||
var cmd = 'git log --format="%H" -n1'
|
||||
console.log('Running', cmd)
|
||||
exec(cmd, cb)
|
||||
},
|
||||
function(stdout, stderr, cb) {
|
||||
if (!stdout) throw new Error('commitId is empty')
|
||||
commitId = stdout.replace('\n', '')
|
||||
if (commitId.length !== 40) throw new Error('commitId invalid : ' + commitId)
|
||||
assetsFileName = 'ui-assets.go';
|
||||
var cmd = 'go-bindata-assetfs -pkg browser -nocompress=true production/...'
|
||||
if (!isProduction) {
|
||||
cmd = 'go-bindata-assetfs -pkg browser -nocompress=true dev/...'
|
||||
}
|
||||
console.log('Running', cmd)
|
||||
exec(cmd, cb)
|
||||
},
|
||||
function(stdout, stderr, cb) {
|
||||
var cmd = 'gofmt -s -w -l bindata_assetfs.go'
|
||||
console.log('Running', cmd)
|
||||
exec(cmd, cb)
|
||||
},
|
||||
function(stdout, stderr, cb) {
|
||||
fs.renameSync('bindata_assetfs.go', assetsFileName)
|
||||
fs.appendFileSync(assetsFileName, '\n')
|
||||
fs.appendFileSync(assetsFileName, 'var UIReleaseTag = "' + buildType + '.' +
|
||||
releaseTag + '"\n')
|
||||
fs.appendFileSync(assetsFileName, 'var UICommitID = "' + commitId + '"\n')
|
||||
fs.appendFileSync(assetsFileName, 'var UIVersion = "' + version + '"')
|
||||
fs.appendFileSync(assetsFileName, '\n')
|
||||
var contents;
|
||||
if (isProduction) {
|
||||
contents = fs.readFileSync(assetsFileName, 'utf8')
|
||||
.replace(/_productionIndexHtml/g, '_productionIndexHTML')
|
||||
.replace(/productionIndexHtmlBytes/g, 'productionIndexHTMLBytes')
|
||||
.replace(/productionIndexHtml/g, 'productionIndexHTML')
|
||||
.replace(/_productionIndex_bundleJs/g, '_productionIndexBundleJs')
|
||||
.replace(/productionIndex_bundleJsBytes/g, 'productionIndexBundleJsBytes')
|
||||
.replace(/productionIndex_bundleJs/g, 'productionIndexBundleJs')
|
||||
.replace(/_productionJqueryUiMinJs/g, '_productionJqueryUIMinJs')
|
||||
.replace(/productionJqueryUiMinJsBytes/g, 'productionJqueryUIMinJsBytes')
|
||||
.replace(/productionJqueryUiMinJs/g, 'productionJqueryUIMinJs');
|
||||
} else {
|
||||
contents = fs.readFileSync(assetsFileName, 'utf8')
|
||||
.replace(/_devIndexHtml/g, '_devIndexHTML')
|
||||
.replace(/devIndexHtmlBytes/g, 'devIndexHTMLBytes')
|
||||
.replace(/devIndexHtml/g, 'devIndexHTML')
|
||||
.replace(/_devIndex_bundleJs/g, '_devIndexBundleJs')
|
||||
.replace(/devIndex_bundleJsBytes/g, 'devIndexBundleJsBytes')
|
||||
.replace(/devIndex_bundleJs/g, 'devIndexBundleJs')
|
||||
.replace(/_devJqueryUiMinJs/g, '_devJqueryUIMinJs')
|
||||
.replace(/devJqueryUiMinJsBytes/g, 'devJqueryUIMinJsBytes')
|
||||
.replace(/devJqueryUiMinJs/g, 'devJqueryUIMinJs');
|
||||
}
|
||||
contents = contents.replace(/MINIO_UI_VERSION/g, version)
|
||||
contents = contents.replace(/index_bundle.js/g, 'index_bundle-' + releaseTag + '.js')
|
||||
|
||||
fs.writeFileSync(assetsFileName, contents, 'utf8')
|
||||
console.log('UI assets file :', assetsFileName)
|
||||
cb()
|
||||
}
|
||||
], function(err) {
|
||||
if (err) return console.log(err)
|
||||
})
|
||||
@@ -1,40 +0,0 @@
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
browsers: [ process.env.CONTINUOUS_INTEGRATION ? 'Firefox' : 'Chrome' ],
|
||||
singleRun: true,
|
||||
frameworks: [ 'mocha' ],
|
||||
files: [
|
||||
'tests.webpack.js'
|
||||
],
|
||||
preprocessors: {
|
||||
'tests.webpack.js': [ 'webpack' ]
|
||||
},
|
||||
reporters: [ 'dots' ],
|
||||
webpack: {
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
loader: 'babel',
|
||||
query: {
|
||||
presets: ['react', 'es2015']
|
||||
}
|
||||
}, {
|
||||
test: /\.less$/,
|
||||
loader: 'style!css!less'
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
loader: 'style!css'
|
||||
}, {
|
||||
test: /\.(eot|woff|woff2|ttf|svg|png)/,
|
||||
loader: 'url'
|
||||
}]
|
||||
}
|
||||
},
|
||||
webpackServer: {
|
||||
noInfo: true
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,84 +0,0 @@
|
||||
{
|
||||
"name": "browser",
|
||||
"version": "0.0.1",
|
||||
"description": "Minio Browser",
|
||||
"scripts": {
|
||||
"test": "karma start",
|
||||
"dev": "NODE_ENV=dev webpack-dev-server --devtool eval --progress --colors --hot --content-base dev",
|
||||
"build": "NODE_ENV=dev node build.js",
|
||||
"release": "NODE_ENV=production MINIO_UI_BUILD=RELEASE node build.js",
|
||||
"format": "esformatter -i 'app/**/*.js'"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/minio/minio"
|
||||
},
|
||||
"author": "Minio Inc",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/minio/minio/issues"
|
||||
},
|
||||
"homepage": "https://github.com/minio/minio",
|
||||
"devDependencies": {
|
||||
"async": "^1.5.2",
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-core": "^6.14.0",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.8.0",
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"babel-preset-react": "^6.11.1",
|
||||
"babel-register": "^6.14.0",
|
||||
"copy-webpack-plugin": "^0.3.3",
|
||||
"css-loader": "^0.23.1",
|
||||
"esformatter": "^0.10.0",
|
||||
"esformatter-jsx": "^7.4.1",
|
||||
"esformatter-jsx-ignore": "^1.0.6",
|
||||
"html-webpack-plugin": "^2.22.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"karma": "^0.13.22",
|
||||
"karma-chrome-launcher": "^0.2.3",
|
||||
"karma-cli": "^0.1.2",
|
||||
"karma-firefox-launcher": "^0.1.7",
|
||||
"karma-mocha": "^0.2.2",
|
||||
"karma-webpack": "^1.7.0",
|
||||
"less": "^2.7.1",
|
||||
"less-loader": "^2.2.3",
|
||||
"mocha": "^2.5.3",
|
||||
"purifycss-webpack-plugin": "^2.0.3",
|
||||
"style-loader": "^0.13.1",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^3.3.6",
|
||||
"classnames": "^2.2.3",
|
||||
"expect": "^1.20.2",
|
||||
"font-awesome": "^4.7.0",
|
||||
"history": "^1.17.0",
|
||||
"humanize": "0.0.9",
|
||||
"json-loader": "^0.5.4",
|
||||
"local-storage-fallback": "^1.3.0",
|
||||
"material-design-iconic-font": "^2.2.0",
|
||||
"mime-db": "^1.25.0",
|
||||
"mime-types": "^2.1.13",
|
||||
"moment": "^2.15.1",
|
||||
"react": "^0.14.8",
|
||||
"react-addons-test-utils": "^0.14.8",
|
||||
"react-bootstrap": "^0.28.5",
|
||||
"react-copy-to-clipboard": "^4.2.3",
|
||||
"react-custom-scrollbars": "^2.2.2",
|
||||
"react-dom": "^0.14.6",
|
||||
"react-dropzone": "^3.5.3",
|
||||
"react-infinite-scroller": "^1.0.6",
|
||||
"react-onclickout": "2.0.4",
|
||||
"react-redux": "^4.4.5",
|
||||
"react-router": "^2.8.1",
|
||||
"redux": "^3.6.0",
|
||||
"redux-thunk": "^1.0.3",
|
||||
"superagent": "^1.8.4",
|
||||
"superagent-es6-promise": "^1.0.0",
|
||||
"webpack": "^1.12.11"
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
var context = require.context('./app', true, /-test\.js$/);
|
||||
context.keys().forEach(context);
|
||||
@@ -1,109 +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.
|
||||
*/
|
||||
|
||||
var webpack = require('webpack')
|
||||
var path = require('path')
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
var purify = require("purifycss-webpack-plugin")
|
||||
|
||||
var exports = {
|
||||
context: __dirname,
|
||||
entry: [
|
||||
path.resolve(__dirname, 'app/index.js')
|
||||
],
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dev'),
|
||||
filename: 'index_bundle.js',
|
||||
publicPath: '/minio/'
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
loader: 'babel',
|
||||
query: {
|
||||
presets: ['react', 'es2015']
|
||||
}
|
||||
}, {
|
||||
test: /\.less$/,
|
||||
loader: 'style!css!less'
|
||||
}, {
|
||||
test: /\.json$/,
|
||||
loader: 'json-loader'
|
||||
},{
|
||||
test: /\.css$/,
|
||||
loader: 'style!css'
|
||||
}, {
|
||||
test: /\.(eot|woff|woff2|ttf|svg|png)/,
|
||||
loader: 'url'
|
||||
}]
|
||||
},
|
||||
node:{
|
||||
fs:'empty'
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: {
|
||||
index: '/minio/'
|
||||
},
|
||||
proxy: {
|
||||
'/minio/webrpc': {
|
||||
target: 'http://localhost:9000',
|
||||
secure: false
|
||||
},
|
||||
'/minio/upload/*': {
|
||||
target: 'http://localhost:9000',
|
||||
secure: false
|
||||
},
|
||||
'/minio/download/*': {
|
||||
target: 'http://localhost:9000',
|
||||
secure: false
|
||||
},
|
||||
'/minio/zip': {
|
||||
target: 'http://localhost:9000',
|
||||
secure: false
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
{from: 'app/css/loader.css'},
|
||||
{from: 'app/img/favicon.ico'},
|
||||
{from: 'app/img/browsers/chrome.png'},
|
||||
{from: 'app/img/browsers/firefox.png'},
|
||||
{from: 'app/img/browsers/safari.png'},
|
||||
{from: 'app/img/logo.svg'},
|
||||
{from: 'app/index.html'}
|
||||
]),
|
||||
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en)$/),
|
||||
new purify({
|
||||
basePath: __dirname,
|
||||
paths: [
|
||||
"app/index.html",
|
||||
"app/js/*.js"
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'dev') {
|
||||
exports.entry = [
|
||||
'webpack/hot/dev-server',
|
||||
'webpack-dev-server/client?http://localhost:8080',
|
||||
path.resolve(__dirname, 'app/index.js')
|
||||
]
|
||||
}
|
||||
|
||||
module.exports = exports
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Isomorphic Javascript library for Minio Browser JSON-RPC API, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var webpack = require('webpack')
|
||||
var path = require('path')
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
var purify = require("purifycss-webpack-plugin")
|
||||
|
||||
var exports = {
|
||||
context: __dirname,
|
||||
entry: [
|
||||
path.resolve(__dirname, 'app/index.js')
|
||||
],
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'production'),
|
||||
filename: 'index_bundle.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
loader: 'babel',
|
||||
query: {
|
||||
presets: ['react', 'es2015']
|
||||
}
|
||||
}, {
|
||||
test: /\.less$/,
|
||||
loader: 'style!css!less'
|
||||
}, {
|
||||
test: /\.json$/,
|
||||
loader: 'json-loader'
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
loader: 'style!css'
|
||||
}, {
|
||||
test: /\.(eot|woff|woff2|ttf|svg|png)/,
|
||||
loader: 'url'
|
||||
}]
|
||||
},
|
||||
node:{
|
||||
fs:'empty'
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
{from: 'app/css/loader.css'},
|
||||
{from: 'app/img/favicon.ico'},
|
||||
{from: 'app/img/browsers/chrome.png'},
|
||||
{from: 'app/img/browsers/firefox.png'},
|
||||
{from: 'app/img/browsers/safari.png'},
|
||||
{from: 'app/img/logo.svg'},
|
||||
{from: 'app/index.html'}
|
||||
]),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
}),
|
||||
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en)$/),
|
||||
new purify({
|
||||
basePath: __dirname,
|
||||
paths: [
|
||||
"app/index.html",
|
||||
"app/js/*.js"
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'dev') {
|
||||
exports.entry = [
|
||||
'webpack/hot/dev-server',
|
||||
'webpack-dev-server/client?http://localhost:8080',
|
||||
path.resolve(__dirname, 'app/index.js')
|
||||
]
|
||||
}
|
||||
|
||||
module.exports = exports
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
|
||||
_init() {
|
||||
# Save release LDFLAGS
|
||||
LDFLAGS=$(go run buildscripts/gen-ldflags.go)
|
||||
|
||||
# Extract release tag
|
||||
release_tag=$(echo $LDFLAGS | awk {'print $4'} | cut -f2 -d=)
|
||||
|
||||
# Verify release tag.
|
||||
if [ -z "$release_tag" ]; then
|
||||
echo "Release tag cannot be empty. Please check return value of \`go run buildscripts/gen-ldflags.go\`"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
# Extract release string.
|
||||
release_str=$(echo $MINIO_RELEASE | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# Verify release string.
|
||||
if [ -z "$release_str" ]; then
|
||||
echo "Release string cannot be empty. Please set \`MINIO_RELEASE\` env variable."
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
# List of supported architectures
|
||||
SUPPORTED_OSARCH='linux/386 linux/amd64 linux/arm windows/386 windows/amd64 darwin/amd64 freebsd/amd64'
|
||||
|
||||
## System binaries
|
||||
CP=`which cp`
|
||||
SHASUM=`which shasum`
|
||||
SED=`which sed`
|
||||
}
|
||||
|
||||
go_build() {
|
||||
local osarch=$1
|
||||
os=$(echo $osarch | cut -f1 -d'/')
|
||||
arch=$(echo $osarch | cut -f2 -d'/')
|
||||
package=$(go list -f '{{.ImportPath}}')
|
||||
echo -n "-->"
|
||||
printf "%15s:%s\n" "${osarch}" "${package}"
|
||||
|
||||
# Release binary name
|
||||
release_bin="$release_str/$os-$arch/$(basename $package).$release_tag"
|
||||
# Release binary downloadable name
|
||||
release_real_bin="$release_str/$os-$arch/$(basename $package)"
|
||||
# Release shasum name
|
||||
release_shasum="$release_str/$os-$arch/$(basename $package).shasum"
|
||||
|
||||
# Go build to build the binary.
|
||||
GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin
|
||||
|
||||
# Create copy
|
||||
if [ $os == "windows" ]; then
|
||||
$CP -p $release_bin ${release_real_bin}.exe
|
||||
else
|
||||
$CP -p $release_bin $release_real_bin
|
||||
fi
|
||||
|
||||
# Calculate shasum
|
||||
shasum_str=$(${SHASUM} ${release_bin})
|
||||
echo ${shasum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_shasum
|
||||
}
|
||||
|
||||
main() {
|
||||
# Build releases.
|
||||
echo "Executing $release_str builds for OS: ${SUPPORTED_OSARCH}"
|
||||
echo "Choose an OS Arch from the below"
|
||||
for osarch in ${SUPPORTED_OSARCH}; do
|
||||
echo ${osarch}
|
||||
done
|
||||
|
||||
read -p "If you want to build for all, Just press Enter: " chosen_osarch
|
||||
if [ "$chosen_osarch" = "" ]; then
|
||||
for each_osarch in ${SUPPORTED_OSARCH}; do
|
||||
go_build ${each_osarch}
|
||||
done
|
||||
else
|
||||
go_build ${chosen_osarch}
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# Run main.
|
||||
_init && main
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Minio Cloud Storage, (C) 2014-2018 Minio, Inc.
|
||||
# Minio Cloud Storage, (C) 2015 Minio, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -21,26 +21,14 @@ _init() {
|
||||
|
||||
## Minimum required versions for build dependencies
|
||||
GIT_VERSION="1.0"
|
||||
GO_VERSION="1.9.4"
|
||||
GO_VERSION="1.6"
|
||||
OSX_VERSION="10.8"
|
||||
KNAME=$(uname -s)
|
||||
ARCH=$(uname -m)
|
||||
case "${KNAME}" in
|
||||
SunOS )
|
||||
ARCH=$(isainfo -k)
|
||||
;;
|
||||
esac
|
||||
UNAME=$(uname -sm)
|
||||
|
||||
## Check all dependencies are present
|
||||
MISSING=""
|
||||
}
|
||||
|
||||
## FIXME:
|
||||
## In OSX, 'readlink -f' option does not exist, hence
|
||||
## we have our own readlink -f behaviour here.
|
||||
## Once OSX has the option, below function is good enough.
|
||||
##
|
||||
## readlink() {
|
||||
## return /bin/readlink -f "$1"
|
||||
## }
|
||||
##
|
||||
readlink() {
|
||||
TARGET_FILE=$1
|
||||
|
||||
@@ -62,101 +50,172 @@ readlink() {
|
||||
echo $RESULT
|
||||
}
|
||||
|
||||
## FIXME:
|
||||
## In OSX, 'sort -V' option does not exist, hence
|
||||
## we have our own version compare function.
|
||||
## Once OSX has the option, below function is good enough.
|
||||
##
|
||||
## check_minimum_version() {
|
||||
## versions=($(echo -e "$1\n$2" | sort -V))
|
||||
## return [ "$1" == "${versions[0]}" ]
|
||||
## }
|
||||
##
|
||||
check_minimum_version() {
|
||||
IFS='.' read -r -a varray1 <<< "$1"
|
||||
IFS='.' read -r -a varray2 <<< "$2"
|
||||
###
|
||||
#
|
||||
# Takes two arguments
|
||||
# arg1: version number in `x.x.x` format
|
||||
# arg2: version number in `x.x.x` format
|
||||
#
|
||||
# example: check_version "$version1" "$version2"
|
||||
#
|
||||
# returns:
|
||||
# 0 - Installed version is equal to required
|
||||
# 1 - Installed version is greater than required
|
||||
# 2 - Installed version is lesser than required
|
||||
# 3 - If args have length zero
|
||||
#
|
||||
####
|
||||
check_version() {
|
||||
## validate args
|
||||
[[ -z "$1" ]] && return 3
|
||||
[[ -z "$2" ]] && return 3
|
||||
|
||||
if [[ $1 == $2 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local IFS=.
|
||||
local i ver1=($1) ver2=($2)
|
||||
# fill empty fields in ver1 with zeros
|
||||
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do
|
||||
ver1[i]=0
|
||||
done
|
||||
for ((i=0; i<${#ver1[@]}; i++)); do
|
||||
if [[ -z ${ver2[i]} ]]; then
|
||||
# fill empty fields in ver2 with zeros
|
||||
ver2[i]=0
|
||||
fi
|
||||
if ((10#${ver1[i]} > 10#${ver2[i]})); then
|
||||
|
||||
for i in "${!varray1[@]}"; do
|
||||
if [[ ${varray1[i]} -lt ${varray2[i]} ]]; then
|
||||
return 0
|
||||
elif [[ ${varray1[i]} -gt ${varray2[i]} ]]; then
|
||||
return 1
|
||||
fi
|
||||
if ((10#${ver1[i]} < 10#${ver2[i]})); then
|
||||
## Installed version is lesser than required - Bad condition
|
||||
return 2
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
assert_is_supported_arch() {
|
||||
case "${ARCH}" in
|
||||
x86_64 | amd64 | aarch64 | arm* )
|
||||
return
|
||||
;;
|
||||
*)
|
||||
echo "Arch '${ARCH}' is not supported. Supported Arch: [x86_64, amd64, aarch64, arm*]"
|
||||
exit 1
|
||||
esac
|
||||
check_golang_env() {
|
||||
echo ${GOROOT:?} 2>&1 >/dev/null
|
||||
if [ $? -eq 1 ]; then
|
||||
echo "ERROR"
|
||||
echo "GOROOT environment variable missing, please refer to Go installation document"
|
||||
echo "https://github.com/minio/minio/blob/master/INSTALLGO.md#install-go-13"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ${GOPATH:?} 2>&1 >/dev/null
|
||||
if [ $? -eq 1 ]; then
|
||||
echo "ERROR"
|
||||
echo "GOPATH environment variable missing, please refer to Go installation document"
|
||||
echo "https://github.com/minio/minio/blob/master/INSTALLGO.md#install-go-13"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local go_binary_path=$(which go)
|
||||
|
||||
if [ -z "${go_binary_path}" ] ; then
|
||||
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document"
|
||||
echo "https://github.com/minio/minio/blob/master/INSTALLGO.md#install-go-13"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
local new_go_binary_path=${go_binary_path}
|
||||
if [ -h "${go_binary_path}" ]; then
|
||||
new_go_binary_path=$(readlink ${go_binary_path})
|
||||
fi
|
||||
|
||||
if [[ !"$(dirname ${new_go_binary_path})" =~ *"${GOROOT%%*(/)}"* ]] ; then
|
||||
echo "The go binary found in your PATH configuration does not belong to the Go installation pointed by your GOROOT environment," \
|
||||
"please refer to Go installation document"
|
||||
echo "https://github.com/minio/minio/blob/master/INSTALLGO.md#install-go-13"
|
||||
exit -1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_is_supported_os() {
|
||||
case "${KNAME}" in
|
||||
Linux | FreeBSD | OpenBSD | NetBSD | DragonFly | SunOS )
|
||||
return
|
||||
is_supported_os() {
|
||||
case ${UNAME%% *} in
|
||||
"Linux")
|
||||
os="linux"
|
||||
;;
|
||||
Darwin )
|
||||
"FreeBSD")
|
||||
os="freebsd"
|
||||
;;
|
||||
"Darwin")
|
||||
osx_host_version=$(env sw_vers -productVersion)
|
||||
if ! check_minimum_version "${OSX_VERSION}" "${osx_host_version}"; then
|
||||
echo "OSX version '${osx_host_version}' is not supported. Minimum supported version: ${OSX_VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
return
|
||||
check_version "${osx_host_version}" "${OSX_VERSION}"
|
||||
[[ $? -ge 2 ]] && die "Minimum OSX version supported is ${OSX_VERSION}"
|
||||
;;
|
||||
*)
|
||||
echo "OS '${KNAME}' is not supported. Supported OS: [Linux, FreeBSD, OpenBSD, NetBSD, Darwin, DragonFly]"
|
||||
exit 1
|
||||
"*")
|
||||
echo "Exiting.. unsupported operating system found"
|
||||
exit 1;
|
||||
esac
|
||||
}
|
||||
|
||||
assert_check_golang_env() {
|
||||
if ! which go >/dev/null 2>&1; then
|
||||
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at https://docs.minio.io/docs/how-to-install-golang"
|
||||
exit 1
|
||||
is_supported_arch() {
|
||||
local supported
|
||||
case ${UNAME##* } in
|
||||
"x86_64" | "amd64")
|
||||
supported=1
|
||||
;;
|
||||
"arm"*)
|
||||
supported=1
|
||||
;;
|
||||
*)
|
||||
supported=0
|
||||
;;
|
||||
esac
|
||||
if [ $supported -eq 0 ]; then
|
||||
echo "Invalid arch: ${UNAME} not supported, please use x86_64/amd64"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
installed_go_version=$(go version | sed 's/^.* go\([0-9.]*\).*$/\1/')
|
||||
if ! check_minimum_version "${GO_VERSION}" "${installed_go_version}"; then
|
||||
echo "Go runtime version '${installed_go_version}' is unsupported. Minimum supported version: ${GO_VERSION} to compile."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${GOPATH}" ]; then
|
||||
echo "GOPATH environment variable missing, please refer to Go installation document at https://docs.minio.io/docs/how-to-install-golang"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
assert_check_deps() {
|
||||
# support unusual Git versions such as: 2.7.4 (Apple Git-66)
|
||||
installed_git_version=$(git version | perl -ne '$_ =~ m/git version (.*?)( |$)/; print "$1\n";')
|
||||
if ! check_minimum_version "${GIT_VERSION}" "${installed_git_version}"; then
|
||||
echo "Git version '${installed_git_version}' is not supported. Minimum supported version: ${GIT_VERSION}"
|
||||
exit 1
|
||||
check_deps() {
|
||||
check_version "$(env go version 2>/dev/null | sed 's/^.* go\([0-9.]*\).*$/\1/')" "${GO_VERSION}"
|
||||
if [ $? -ge 2 ]; then
|
||||
MISSING="${MISSING} golang(${GO_VERSION})"
|
||||
fi
|
||||
|
||||
check_version "$(env git --version 2>/dev/null | sed -e 's/^.* \([0-9.\].*\).*$/\1/' -e 's/^\([0-9.\]*\).*/\1/g')" "${GIT_VERSION}"
|
||||
if [ $? -ge 2 ]; then
|
||||
MISSING="${MISSING} git"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
## Check for supported arch
|
||||
assert_is_supported_arch
|
||||
echo -n "Check for supported arch.. "
|
||||
is_supported_arch
|
||||
|
||||
## Check for supported os
|
||||
assert_is_supported_os
|
||||
echo -n "Check for supported os.. "
|
||||
is_supported_os
|
||||
|
||||
## Check for Go environment
|
||||
assert_check_golang_env
|
||||
echo -n "Checking if proper environment variables are set.. "
|
||||
check_golang_env
|
||||
|
||||
## Check for dependencies
|
||||
assert_check_deps
|
||||
echo "Done"
|
||||
echo "Using GOPATH=${GOPATH} and GOROOT=${GOROOT}"
|
||||
|
||||
echo -n "Checking dependencies for Minio.. "
|
||||
check_deps
|
||||
|
||||
## If dependencies are missing, warn the user and abort
|
||||
if [ "x${MISSING}" != "x" ]; then
|
||||
echo "ERROR"
|
||||
echo
|
||||
echo "The following build tools are missing:"
|
||||
echo
|
||||
echo "** ${MISSING} **"
|
||||
echo
|
||||
echo "Please install them "
|
||||
echo "${MISSING}"
|
||||
echo
|
||||
echo "Follow https://docs.minio.io/docs/how-to-install-golang for further instructions"
|
||||
exit 1
|
||||
fi
|
||||
echo "Done"
|
||||
}
|
||||
|
||||
_init && main "$@"
|
||||
|
||||
@@ -15,21 +15,26 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
main() {
|
||||
IFS=':' read -r -a paths <<< "$GOPATH"
|
||||
for path in "${paths[@]}"; do
|
||||
minio_path="$path/src/github.com/minio/minio"
|
||||
if [ -d "$minio_path" ]; then
|
||||
if [ "$minio_path" -ef "$PWD" ]; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
_init() {
|
||||
|
||||
echo "ERROR"
|
||||
echo "Project not found in ${GOPATH}."
|
||||
echo "Follow instructions at https://github.com/minio/minio/blob/master/CONTRIBUTING.md#setup-your-minio-github-repository"
|
||||
exit 1
|
||||
shopt -s extglob
|
||||
|
||||
PWD=$(pwd -P)
|
||||
GOPATH=$(cd "$(go env GOPATH)" ; env pwd -P)
|
||||
}
|
||||
|
||||
main
|
||||
main() {
|
||||
echo "Checking if project is at ${GOPATH}"
|
||||
for minio in $(echo ${GOPATH} | tr ':' ' '); do
|
||||
if [ ! -d ${minio}/src/github.com/minio/minio ]; then
|
||||
echo "Project not found in ${minio}, please follow instructions provided at https://github.com/minio/minio/blob/master/CONTRIBUTING.md#setup-your-minio-github-repository" \
|
||||
&& exit 1
|
||||
fi
|
||||
if [ "x${minio}/src/github.com/minio/minio" != "x${PWD}" ]; then
|
||||
echo "Build outside of ${minio}, two source checkouts found. Exiting." && exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
_init && main
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
)
|
||||
|
||||
func genLDFlags(version string) string {
|
||||
ldflagsStr := "-s -w"
|
||||
ldflagsStr += " -X github.com/minio/minio/cmd.Version=" + version
|
||||
var ldflagsStr string
|
||||
ldflagsStr = "-X github.com/minio/minio/cmd.Version=" + version
|
||||
ldflagsStr += " -X github.com/minio/minio/cmd.ReleaseTag=" + releaseTag(version)
|
||||
ldflagsStr += " -X github.com/minio/minio/cmd.CommitID=" + commitID()
|
||||
ldflagsStr += " -X github.com/minio/minio/cmd.ShortCommitID=" + commitID()[:12]
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v browser); do
|
||||
go test -coverprofile=profile.out -covermode=atomic "$d"
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -coverprofile=profile.out -covermode=atomic $d
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Minio Cloud Storage, (C) 2017, 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
set -E
|
||||
set -o pipefail
|
||||
|
||||
if [ ! -x "$PWD/minio" ]; then
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||
|
||||
export MINT_MODE=core
|
||||
export MINT_DATA_DIR="$WORK_DIR/data"
|
||||
export SERVER_ENDPOINT="127.0.0.1:9000"
|
||||
export ACCESS_KEY="minio"
|
||||
export SECRET_KEY="minio123"
|
||||
export ENABLE_HTTPS=0
|
||||
|
||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" )
|
||||
|
||||
FILE_1_MB="$MINT_DATA_DIR/datafile-1-MB"
|
||||
FILE_65_MB="$MINT_DATA_DIR/datafile-65-MB"
|
||||
|
||||
FUNCTIONAL_TESTS="$WORK_DIR/functional-tests.sh"
|
||||
|
||||
function start_minio_fs()
|
||||
{
|
||||
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
|
||||
minio_pid=$!
|
||||
sleep 10
|
||||
|
||||
echo "$minio_pid"
|
||||
}
|
||||
|
||||
function start_minio_erasure()
|
||||
{
|
||||
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
|
||||
minio_pid=$!
|
||||
sleep 15
|
||||
|
||||
echo "$minio_pid"
|
||||
}
|
||||
|
||||
function start_minio_erasure_sets()
|
||||
{
|
||||
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk-sets{1...32}" >"$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
||||
minio_pid=$!
|
||||
sleep 15
|
||||
|
||||
echo "$minio_pid"
|
||||
}
|
||||
|
||||
function start_minio_dist_erasure_sets()
|
||||
{
|
||||
declare -a minio_pids
|
||||
"${MINIO[@]}" server --address=:9000 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9000.log" 2>&1 &
|
||||
minio_pids[0]=$!
|
||||
"${MINIO[@]}" server --address=:9001 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9001.log" 2>&1 &
|
||||
minio_pids[1]=$!
|
||||
"${MINIO[@]}" server --address=:9002 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9002.log" 2>&1 &
|
||||
minio_pids[2]=$!
|
||||
"${MINIO[@]}" server --address=:9003 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9003.log" 2>&1 &
|
||||
minio_pids[3]=$!
|
||||
"${MINIO[@]}" server --address=:9004 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9004.log" 2>&1 &
|
||||
minio_pids[4]=$!
|
||||
"${MINIO[@]}" server --address=:9005 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9005.log" 2>&1 &
|
||||
minio_pids[5]=$!
|
||||
"${MINIO[@]}" server --address=:9006 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9006.log" 2>&1 &
|
||||
minio_pids[6]=$!
|
||||
"${MINIO[@]}" server --address=:9007 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9007.log" 2>&1 &
|
||||
minio_pids[7]=$!
|
||||
"${MINIO[@]}" server --address=:9008 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9008.log" 2>&1 &
|
||||
minio_pids[8]=$!
|
||||
"${MINIO[@]}" server --address=:9009 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9009.log" 2>&1 &
|
||||
minio_pids[9]=$!
|
||||
|
||||
sleep 30
|
||||
echo "${minio_pids[@]}"
|
||||
}
|
||||
|
||||
function start_minio_dist_erasure()
|
||||
{
|
||||
declare -a minio_pids
|
||||
"${MINIO[@]}" server --address=:9000 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9000.log" 2>&1 &
|
||||
minio_pids[0]=$!
|
||||
"${MINIO[@]}" server --address=:9001 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9001.log" 2>&1 &
|
||||
minio_pids[1]=$!
|
||||
"${MINIO[@]}" server --address=:9002 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9002.log" 2>&1 &
|
||||
minio_pids[2]=$!
|
||||
"${MINIO[@]}" server --address=:9003 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9003.log" 2>&1 &
|
||||
minio_pids[3]=$!
|
||||
|
||||
sleep 30
|
||||
echo "${minio_pids[@]}"
|
||||
}
|
||||
|
||||
function start_minio_gateway_s3()
|
||||
{
|
||||
MINIO_ACCESS_KEY=Q3AM3UQ867SPQQA43P2F MINIO_SECRET_KEY=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG \
|
||||
"${MINIO[@]}" gateway s3 https://play.minio.io:9000 >"$WORK_DIR/minio-gateway-s3.log" 2>&1 &
|
||||
minio_pid=$!
|
||||
sleep 3
|
||||
|
||||
echo "$minio_pid"
|
||||
}
|
||||
|
||||
function run_test_fs()
|
||||
{
|
||||
minio_pid="$(start_minio_fs)"
|
||||
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
kill "$minio_pid"
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
cat "$WORK_DIR/fs-minio.log"
|
||||
fi
|
||||
rm -f "$WORK_DIR/fs-minio.log"
|
||||
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function run_test_erasure_sets() {
|
||||
minio_pid="$(start_minio_erasure_sets)"
|
||||
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
kill "$minio_pid"
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
cat "$WORK_DIR/erasure-minio-sets.log"
|
||||
fi
|
||||
rm -f "$WORK_DIR/erasure-minio-sets.log"
|
||||
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function run_test_dist_erasure_sets()
|
||||
{
|
||||
minio_pids=( $(start_minio_dist_erasure_sets) )
|
||||
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
for pid in "${minio_pids[@]}"; do
|
||||
kill "$pid"
|
||||
done
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
for i in $(seq 0 9); do
|
||||
echo "server$i log:"
|
||||
cat "$WORK_DIR/dist-minio-900$i.log"
|
||||
done
|
||||
fi
|
||||
|
||||
for i in $(seq 0 9); do
|
||||
rm -f "$WORK_DIR/dist-minio-900$i.log"
|
||||
done
|
||||
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function run_test_erasure()
|
||||
{
|
||||
minio_pid="$(start_minio_erasure)"
|
||||
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
kill "$minio_pid"
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
cat "$WORK_DIR/erasure-minio.log"
|
||||
fi
|
||||
rm -f "$WORK_DIR/erasure-minio.log"
|
||||
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function run_test_dist_erasure()
|
||||
{
|
||||
minio_pids=( $(start_minio_dist_erasure) )
|
||||
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
for pid in "${minio_pids[@]}"; do
|
||||
kill "$pid"
|
||||
done
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
echo "server1 log:"
|
||||
cat "$WORK_DIR/dist-minio-9000.log"
|
||||
echo "server2 log:"
|
||||
cat "$WORK_DIR/dist-minio-9001.log"
|
||||
echo "server3 log:"
|
||||
cat "$WORK_DIR/dist-minio-9002.log"
|
||||
echo "server4 log:"
|
||||
cat "$WORK_DIR/dist-minio-9003.log"
|
||||
fi
|
||||
|
||||
rm -f "$WORK_DIR/dist-minio-9000.log" "$WORK_DIR/dist-minio-9001.log" "$WORK_DIR/dist-minio-9002.log" "$WORK_DIR/dist-minio-9003.log"
|
||||
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function run_test_gateway_s3()
|
||||
{
|
||||
minio_pid="$(start_minio_gateway_s3)"
|
||||
|
||||
export ACCESS_KEY=Q3AM3UQ867SPQQA43P2F
|
||||
export SECRET_KEY=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
kill "$minio_pid"
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
cat "$WORK_DIR/minio-gateway-s3.log"
|
||||
fi
|
||||
rm -f "$WORK_DIR/minio-gateway-s3.log"
|
||||
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function __init__()
|
||||
{
|
||||
echo "Initializing environment"
|
||||
mkdir -p "$WORK_DIR"
|
||||
mkdir -p "$MINIO_CONFIG_DIR"
|
||||
mkdir -p "$MINT_DATA_DIR"
|
||||
|
||||
if ! go get -u github.com/minio/mc; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
exit 1
|
||||
fi
|
||||
/bin/cp -a "$(go env GOPATH)"/bin/mc "$WORK_DIR/mc"
|
||||
|
||||
chmod a+x "$WORK_DIR/mc"
|
||||
|
||||
shred -n 1 -s 1M - 1>"$FILE_1_MB" 2>/dev/null
|
||||
shred -n 1 -s 65M - 1>"$FILE_65_MB" 2>/dev/null
|
||||
|
||||
## version is purposefully set to '3' for minio to migrate configuration file
|
||||
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' > "$MINIO_CONFIG_DIR/config.json"
|
||||
|
||||
if ! wget -q -O "$FUNCTIONAL_TESTS" https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh; then
|
||||
echo "failed to download https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod a+x "$FUNCTIONAL_TESTS"
|
||||
}
|
||||
|
||||
function main()
|
||||
{
|
||||
echo "Testing in FS setup"
|
||||
if ! run_test_fs; then
|
||||
echo "FAILED"
|
||||
rm -fr "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing in Erasure setup"
|
||||
if ! run_test_erasure; then
|
||||
echo "FAILED"
|
||||
rm -fr "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing in Distributed Erasure setup"
|
||||
if ! run_test_dist_erasure; then
|
||||
echo "FAILED"
|
||||
rm -fr "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing in Erasure setup as sets"
|
||||
if ! run_test_erasure_sets; then
|
||||
echo "FAILED"
|
||||
rm -fr "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing in Distributed Erasure setup as sets"
|
||||
if ! run_test_dist_erasure_sets; then
|
||||
echo "FAILED"
|
||||
rm -fr "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing in Gateway S3 setup"
|
||||
if ! run_test_gateway_s3; then
|
||||
echo "FAILED"
|
||||
rm -fr "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -fr "$WORK_DIR"
|
||||
}
|
||||
|
||||
( __init__ "$@" && main "$@" )
|
||||
rv=$?
|
||||
rm -fr "$WORK_DIR"
|
||||
exit "$rv"
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2015, 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.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// credential container for access and secret keys.
|
||||
type credential struct {
|
||||
AccessKeyID string `json:"accessKey"`
|
||||
SecretAccessKey string `json:"secretKey"`
|
||||
}
|
||||
|
||||
const (
|
||||
minioAccessID = 20
|
||||
minioSecretID = 40
|
||||
)
|
||||
|
||||
// isValidSecretKey - validate secret key.
|
||||
var isValidSecretKey = regexp.MustCompile(`^.{8,40}$`)
|
||||
|
||||
// isValidAccessKey - validate access key.
|
||||
var isValidAccessKey = regexp.MustCompile(`^[a-zA-Z0-9\\-\\.\\_\\~]{5,20}$`)
|
||||
|
||||
// mustGenAccessKeys - must generate access credentials.
|
||||
func mustGenAccessKeys() (creds credential) {
|
||||
creds, err := genAccessKeys()
|
||||
fatalIf(err, "Unable to generate access keys.")
|
||||
return creds
|
||||
}
|
||||
|
||||
// genAccessKeys - generate access credentials.
|
||||
func genAccessKeys() (credential, error) {
|
||||
accessKeyID, err := genAccessKeyID()
|
||||
if err != nil {
|
||||
return credential{}, err
|
||||
}
|
||||
secretAccessKey, err := genSecretAccessKey()
|
||||
if err != nil {
|
||||
return credential{}, err
|
||||
}
|
||||
creds := credential{
|
||||
AccessKeyID: string(accessKeyID),
|
||||
SecretAccessKey: string(secretAccessKey),
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
// genAccessKeyID - generate random alpha numeric value using only uppercase characters
|
||||
// takes input as size in integer
|
||||
func genAccessKeyID() ([]byte, error) {
|
||||
alpha := make([]byte, minioAccessID)
|
||||
if _, err := rand.Read(alpha); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < minioAccessID; i++ {
|
||||
alpha[i] = alphaNumericTable[alpha[i]%byte(len(alphaNumericTable))]
|
||||
}
|
||||
return alpha, nil
|
||||
}
|
||||
|
||||
// genSecretAccessKey - generate random base64 numeric value from a random seed.
|
||||
func genSecretAccessKey() ([]byte, error) {
|
||||
rb := make([]byte, minioSecretID)
|
||||
if _, err := rand.Read(rb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(base64.StdEncoding.EncodeToString(rb))[:minioSecretID], nil
|
||||
}
|
||||
@@ -1,819 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016, 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/handlers"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
const (
|
||||
minioConfigTmpFormat = "config-%s.json"
|
||||
|
||||
maxConfigJSONSize = 256 * 1024 // 256KiB
|
||||
)
|
||||
|
||||
// Type-safe query params.
|
||||
type mgmtQueryKey string
|
||||
|
||||
// Only valid query params for mgmt admin APIs.
|
||||
const (
|
||||
mgmtBucket mgmtQueryKey = "bucket"
|
||||
mgmtPrefix mgmtQueryKey = "prefix"
|
||||
mgmtLockOlderThan mgmtQueryKey = "older-than"
|
||||
mgmtClientToken mgmtQueryKey = "clientToken"
|
||||
mgmtForceStart mgmtQueryKey = "forceStart"
|
||||
)
|
||||
|
||||
var (
|
||||
// This struct literal represents the Admin API version that
|
||||
// the server uses.
|
||||
adminAPIVersionInfo = madmin.AdminAPIVersionInfo{
|
||||
Version: "1",
|
||||
}
|
||||
)
|
||||
|
||||
// VersionHandler - GET /minio/admin/version
|
||||
// -----------
|
||||
// Returns Administration API version
|
||||
func (a adminAPIHandlers) VersionHandler(w http.ResponseWriter, r *http.Request) {
|
||||
adminAPIErr := checkAdminRequestAuthType(r, globalServerConfig.GetRegion())
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponse(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(adminAPIVersionInfo)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(w, ErrInternalError, r.URL)
|
||||
errorIf(err, "Failed to marshal Admin API Version to JSON.")
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessResponseJSON(w, jsonBytes)
|
||||
}
|
||||
|
||||
// ServiceStatusHandler - GET /minio/admin/v1/service
|
||||
// ----------
|
||||
// Returns server version and uptime.
|
||||
func (a adminAPIHandlers) ServiceStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
adminAPIErr := checkAdminRequestAuthType(r, globalServerConfig.GetRegion())
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponseJSON(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch server version
|
||||
serverVersion := madmin.ServerVersion{
|
||||
Version: Version,
|
||||
CommitID: CommitID,
|
||||
}
|
||||
|
||||
// Fetch uptimes from all peers. This may fail to due to lack
|
||||
// of read-quorum availability.
|
||||
uptime, err := getPeerUptimes(globalAdminPeers)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(w, toAPIErrorCode(err), r.URL)
|
||||
errorIf(err, "Possibly failed to get uptime from majority of servers.")
|
||||
return
|
||||
}
|
||||
|
||||
// Create API response
|
||||
serverStatus := madmin.ServiceStatus{
|
||||
ServerVersion: serverVersion,
|
||||
Uptime: uptime,
|
||||
}
|
||||
|
||||
// Marshal API response
|
||||
jsonBytes, err := json.Marshal(serverStatus)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(w, ErrInternalError, r.URL)
|
||||
errorIf(err, "Failed to marshal storage info into json.")
|
||||
return
|
||||
}
|
||||
// Reply with storage information (across nodes in a
|
||||
// distributed setup) as json.
|
||||
writeSuccessResponseJSON(w, jsonBytes)
|
||||
}
|
||||
|
||||
// ServiceStopNRestartHandler - POST /minio/admin/v1/service
|
||||
// Body: {"action": <restart-action>}
|
||||
// ----------
|
||||
// Restarts/Stops minio server gracefully. In a distributed setup,
|
||||
// restarts all the servers in the cluster.
|
||||
func (a adminAPIHandlers) ServiceStopNRestartHandler(w http.ResponseWriter, r *http.Request) {
|
||||
adminAPIErr := checkAdminRequestAuthType(r, globalServerConfig.GetRegion())
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponseJSON(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var sa madmin.ServiceAction
|
||||
err := json.NewDecoder(r.Body).Decode(&sa)
|
||||
if err != nil {
|
||||
errorIf(err, "Error parsing body JSON")
|
||||
writeErrorResponseJSON(w, ErrRequestBodyParse, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var serviceSig serviceSignal
|
||||
switch sa.Action {
|
||||
case madmin.ServiceActionValueRestart:
|
||||
serviceSig = serviceRestart
|
||||
case madmin.ServiceActionValueStop:
|
||||
serviceSig = serviceStop
|
||||
default:
|
||||
writeErrorResponseJSON(w, ErrMalformedPOSTRequest, r.URL)
|
||||
errorIf(err, "Invalid service action received")
|
||||
return
|
||||
}
|
||||
|
||||
// Reply to the client before restarting minio server.
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
|
||||
sendServiceCmd(globalAdminPeers, serviceSig)
|
||||
}
|
||||
|
||||
// ServerProperties holds some server information such as, version, region
|
||||
// uptime, etc..
|
||||
type ServerProperties struct {
|
||||
Uptime time.Duration `json:"uptime"`
|
||||
Version string `json:"version"`
|
||||
CommitID string `json:"commitID"`
|
||||
Region string `json:"region"`
|
||||
SQSARN []string `json:"sqsARN"`
|
||||
}
|
||||
|
||||
// ServerConnStats holds transferred bytes from/to the server
|
||||
type ServerConnStats struct {
|
||||
TotalInputBytes uint64 `json:"transferred"`
|
||||
TotalOutputBytes uint64 `json:"received"`
|
||||
Throughput uint64 `json:"throughput,omitempty"`
|
||||
}
|
||||
|
||||
// ServerHTTPMethodStats holds total number of HTTP operations from/to the server,
|
||||
// including the average duration the call was spent.
|
||||
type ServerHTTPMethodStats struct {
|
||||
Count uint64 `json:"count"`
|
||||
AvgDuration string `json:"avgDuration"`
|
||||
}
|
||||
|
||||
// ServerHTTPStats holds all type of http operations performed to/from the server
|
||||
// including their average execution time.
|
||||
type ServerHTTPStats struct {
|
||||
TotalHEADStats ServerHTTPMethodStats `json:"totalHEADs"`
|
||||
SuccessHEADStats ServerHTTPMethodStats `json:"successHEADs"`
|
||||
TotalGETStats ServerHTTPMethodStats `json:"totalGETs"`
|
||||
SuccessGETStats ServerHTTPMethodStats `json:"successGETs"`
|
||||
TotalPUTStats ServerHTTPMethodStats `json:"totalPUTs"`
|
||||
SuccessPUTStats ServerHTTPMethodStats `json:"successPUTs"`
|
||||
TotalPOSTStats ServerHTTPMethodStats `json:"totalPOSTs"`
|
||||
SuccessPOSTStats ServerHTTPMethodStats `json:"successPOSTs"`
|
||||
TotalDELETEStats ServerHTTPMethodStats `json:"totalDELETEs"`
|
||||
SuccessDELETEStats ServerHTTPMethodStats `json:"successDELETEs"`
|
||||
}
|
||||
|
||||
// ServerInfoData holds storage, connections and other
|
||||
// information of a given server.
|
||||
type ServerInfoData struct {
|
||||
StorageInfo StorageInfo `json:"storage"`
|
||||
ConnStats ServerConnStats `json:"network"`
|
||||
HTTPStats ServerHTTPStats `json:"http"`
|
||||
Properties ServerProperties `json:"server"`
|
||||
}
|
||||
|
||||
// ServerInfo holds server information result of one node
|
||||
type ServerInfo struct {
|
||||
Error string `json:"error"`
|
||||
Addr string `json:"addr"`
|
||||
Data *ServerInfoData `json:"data"`
|
||||
}
|
||||
|
||||
// ServerInfoHandler - GET /minio/admin/v1/info
|
||||
// ----------
|
||||
// Get server information
|
||||
func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Authenticate request
|
||||
adminAPIErr := checkAdminRequestAuthType(r, globalServerConfig.GetRegion())
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponseJSON(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Web service response
|
||||
reply := make([]ServerInfo, len(globalAdminPeers))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Gather server information for all nodes
|
||||
for i, p := range globalAdminPeers {
|
||||
wg.Add(1)
|
||||
|
||||
// Gather information from a peer in a goroutine
|
||||
go func(idx int, peer adminPeer) {
|
||||
defer wg.Done()
|
||||
|
||||
// Initialize server info at index
|
||||
reply[idx] = ServerInfo{Addr: peer.addr}
|
||||
|
||||
serverInfoData, err := peer.cmdRunner.ServerInfoData()
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to get server info from %s.", peer.addr)
|
||||
reply[idx].Error = err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
reply[idx].Data = &serverInfoData
|
||||
}(i, p)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Marshal API response
|
||||
jsonBytes, err := json.Marshal(reply)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(w, ErrInternalError, r.URL)
|
||||
errorIf(err, "Failed to marshal storage info into json.")
|
||||
return
|
||||
}
|
||||
|
||||
// Reply with storage information (across nodes in a
|
||||
// distributed setup) as json.
|
||||
writeSuccessResponseJSON(w, jsonBytes)
|
||||
}
|
||||
|
||||
// validateLockQueryParams - Validates query params for list/clear
|
||||
// locks management APIs.
|
||||
func validateLockQueryParams(vars url.Values) (string, string, time.Duration,
|
||||
APIErrorCode) {
|
||||
|
||||
bucket := vars.Get(string(mgmtBucket))
|
||||
prefix := vars.Get(string(mgmtPrefix))
|
||||
olderThanStr := vars.Get(string(mgmtLockOlderThan))
|
||||
|
||||
// N B empty bucket name is invalid
|
||||
if !IsValidBucketName(bucket) {
|
||||
return "", "", time.Duration(0), ErrInvalidBucketName
|
||||
}
|
||||
// empty prefix is valid.
|
||||
if !IsValidObjectPrefix(prefix) {
|
||||
return "", "", time.Duration(0), ErrInvalidObjectName
|
||||
}
|
||||
|
||||
// If older-than parameter was empty then set it to 0s to list
|
||||
// all locks older than now.
|
||||
if olderThanStr == "" {
|
||||
olderThanStr = "0s"
|
||||
}
|
||||
duration, err := time.ParseDuration(olderThanStr)
|
||||
if err != nil {
|
||||
errorIf(err, "Failed to parse duration passed as query value.")
|
||||
return "", "", time.Duration(0), ErrInvalidDuration
|
||||
}
|
||||
|
||||
return bucket, prefix, duration, ErrNone
|
||||
}
|
||||
|
||||
// ListLocksHandler - GET /minio/admin/v1/locks?bucket=mybucket&prefix=myprefix&older-than=10s
|
||||
// - bucket is a mandatory query parameter
|
||||
// - prefix and older-than are optional query parameters
|
||||
// ---------
|
||||
// Lists locks held on a given bucket, prefix and duration it was held for.
|
||||
func (a adminAPIHandlers) ListLocksHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
adminAPIErr := checkAdminRequestAuthType(r, globalServerConfig.GetRegion())
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponseJSON(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
vars := r.URL.Query()
|
||||
bucket, prefix, duration, adminAPIErr := validateLockQueryParams(vars)
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponseJSON(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch lock information of locks matching bucket/prefix that
|
||||
// are available for longer than duration.
|
||||
volLocks, err := listPeerLocksInfo(globalAdminPeers, bucket, prefix,
|
||||
duration)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(w, ErrInternalError, r.URL)
|
||||
errorIf(err, "Failed to fetch lock information from remote nodes.")
|
||||
return
|
||||
}
|
||||
|
||||
// Marshal list of locks as json.
|
||||
jsonBytes, err := json.Marshal(volLocks)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(w, ErrInternalError, r.URL)
|
||||
errorIf(err, "Failed to marshal lock information into json.")
|
||||
return
|
||||
}
|
||||
|
||||
// Reply with list of locks held on bucket, matching prefix
|
||||
// held longer than duration supplied, as json.
|
||||
writeSuccessResponseJSON(w, jsonBytes)
|
||||
}
|
||||
|
||||
// ClearLocksHandler - DELETE /minio/admin/v1/locks?bucket=mybucket&prefix=myprefix&duration=duration
|
||||
// - bucket is a mandatory query parameter
|
||||
// - prefix and older-than are optional query parameters
|
||||
// ---------
|
||||
// Clear locks held on a given bucket, prefix and duration it was held for.
|
||||
func (a adminAPIHandlers) ClearLocksHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, "ClearLocks")
|
||||
|
||||
adminAPIErr := checkAdminRequestAuthType(r, globalServerConfig.GetRegion())
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponseJSON(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
vars := r.URL.Query()
|
||||
bucket, prefix, duration, adminAPIErr := validateLockQueryParams(vars)
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponseJSON(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch lock information of locks matching bucket/prefix that
|
||||
// are held for longer than duration.
|
||||
volLocks, err := listPeerLocksInfo(globalAdminPeers, bucket, prefix,
|
||||
duration)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(w, ErrInternalError, r.URL)
|
||||
errorIf(err, "Failed to fetch lock information from remote nodes.")
|
||||
return
|
||||
}
|
||||
|
||||
// Marshal list of locks as json.
|
||||
jsonBytes, err := json.Marshal(volLocks)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(w, ErrInternalError, r.URL)
|
||||
errorIf(err, "Failed to marshal lock information into json.")
|
||||
return
|
||||
}
|
||||
|
||||
newObjectLayerFn().ClearLocks(ctx, volLocks)
|
||||
|
||||
// Reply with list of locks cleared, as json.
|
||||
writeSuccessResponseJSON(w, jsonBytes)
|
||||
}
|
||||
|
||||
// extractHealInitParams - Validates params for heal init API.
|
||||
func extractHealInitParams(r *http.Request) (bucket, objPrefix string,
|
||||
hs madmin.HealOpts, clientToken string, forceStart bool,
|
||||
err APIErrorCode) {
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket = vars[string(mgmtBucket)]
|
||||
objPrefix = vars[string(mgmtPrefix)]
|
||||
|
||||
if bucket == "" {
|
||||
if objPrefix != "" {
|
||||
// Bucket is required if object-prefix is given
|
||||
err = ErrHealMissingBucket
|
||||
return
|
||||
}
|
||||
} else if !IsValidBucketName(bucket) {
|
||||
err = ErrInvalidBucketName
|
||||
return
|
||||
}
|
||||
|
||||
// empty prefix is valid.
|
||||
if !IsValidObjectPrefix(objPrefix) {
|
||||
err = ErrInvalidObjectName
|
||||
return
|
||||
}
|
||||
|
||||
qParms := r.URL.Query()
|
||||
if len(qParms[string(mgmtClientToken)]) > 0 {
|
||||
clientToken = qParms[string(mgmtClientToken)][0]
|
||||
}
|
||||
if _, ok := qParms[string(mgmtForceStart)]; ok {
|
||||
forceStart = true
|
||||
}
|
||||
|
||||
// ignore body if clientToken is provided
|
||||
if clientToken == "" {
|
||||
jerr := json.NewDecoder(r.Body).Decode(&hs)
|
||||
if jerr != nil {
|
||||
errorIf(jerr, "Error parsing body JSON")
|
||||
err = ErrRequestBodyParse
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = ErrNone
|
||||
return
|
||||
}
|
||||
|
||||
// HealHandler - POST /minio/admin/v1/heal/
|
||||
// -----------
|
||||
// Start heal processing and return heal status items.
|
||||
//
|
||||
// On a successful heal sequence start, a unique client token is
|
||||
// returned. Subsequent requests to this endpoint providing the client
|
||||
// token will receive heal status records from the running heal
|
||||
// sequence.
|
||||
//
|
||||
// If no client token is provided, and a heal sequence is in progress
|
||||
// an error is returned with information about the running heal
|
||||
// sequence. However, if the force-start flag is provided, the server
|
||||
// aborts the running heal sequence and starts a new one.
|
||||
func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, "Heal")
|
||||
|
||||
// Get object layer instance.
|
||||
objLayer := newObjectLayerFn()
|
||||
if objLayer == nil {
|
||||
writeErrorResponseJSON(w, ErrServerNotInitialized, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate request signature.
|
||||
adminAPIErr := checkAdminRequestAuthType(r, globalServerConfig.GetRegion())
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponseJSON(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this setup has an erasure coded backend.
|
||||
if !globalIsXL {
|
||||
writeErrorResponseJSON(w, ErrHealNotImplemented, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
bucket, objPrefix, hs, clientToken, forceStart, apiErr := extractHealInitParams(r)
|
||||
if apiErr != ErrNone {
|
||||
writeErrorResponseJSON(w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
type healResp struct {
|
||||
respBytes []byte
|
||||
errCode APIErrorCode
|
||||
errBody string
|
||||
}
|
||||
|
||||
// Define a closure to start sending whitespace to client
|
||||
// after 10s unless a response item comes in
|
||||
keepConnLive := func(w http.ResponseWriter, respCh chan healResp) {
|
||||
ticker := time.NewTicker(time.Second * 10)
|
||||
started := false
|
||||
forLoop:
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if !started {
|
||||
// Start writing response to client
|
||||
started = true
|
||||
setCommonHeaders(w)
|
||||
w.Header().Set("Content-Type", string(mimeJSON))
|
||||
// Set 200 OK status
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
// Send whitespace and keep connection open
|
||||
w.Write([]byte("\n\r"))
|
||||
w.(http.Flusher).Flush()
|
||||
case hr := <-respCh:
|
||||
switch {
|
||||
case hr.errCode == ErrNone:
|
||||
writeSuccessResponseJSON(w, hr.respBytes)
|
||||
case hr.errBody == "":
|
||||
writeErrorResponseJSON(w, hr.errCode, r.URL)
|
||||
default:
|
||||
writeCustomErrorResponseJSON(w, hr.errCode, hr.errBody, r.URL)
|
||||
}
|
||||
break forLoop
|
||||
}
|
||||
}
|
||||
ticker.Stop()
|
||||
}
|
||||
|
||||
// find number of disks in the setup
|
||||
info := objLayer.StorageInfo(ctx)
|
||||
numDisks := info.Backend.OfflineDisks + info.Backend.OnlineDisks
|
||||
|
||||
if clientToken == "" {
|
||||
// Not a status request
|
||||
nh := newHealSequence(bucket, objPrefix, handlers.GetSourceIP(r),
|
||||
numDisks, hs, forceStart)
|
||||
|
||||
respCh := make(chan healResp)
|
||||
go func() {
|
||||
respBytes, errCode, errMsg := globalAllHealState.LaunchNewHealSequence(nh)
|
||||
hr := healResp{respBytes, errCode, errMsg}
|
||||
respCh <- hr
|
||||
}()
|
||||
|
||||
// Due to the force-starting functionality, the Launch
|
||||
// call above can take a long time - to keep the
|
||||
// connection alive, we start sending whitespace
|
||||
keepConnLive(w, respCh)
|
||||
} else {
|
||||
// Since clientToken is given, fetch heal status from running
|
||||
// heal sequence.
|
||||
path := bucket + "/" + objPrefix
|
||||
respBytes, errCode := globalAllHealState.PopHealStatusJSON(
|
||||
path, clientToken)
|
||||
if errCode != ErrNone {
|
||||
writeErrorResponseJSON(w, errCode, r.URL)
|
||||
} else {
|
||||
writeSuccessResponseJSON(w, respBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfigHandler - GET /minio/admin/v1/config
|
||||
// Get config.json of this minio setup.
|
||||
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Validate request signature.
|
||||
adminAPIErr := checkAdminRequestAuthType(r, globalServerConfig.GetRegion())
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponseJSON(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// check if objectLayer is initialized, if not return.
|
||||
if newObjectLayerFn() == nil {
|
||||
writeErrorResponseJSON(w, ErrServerNotInitialized, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Take a read lock on minio/config.json. NB minio is a
|
||||
// reserved bucket name and wouldn't conflict with normal
|
||||
// object operations.
|
||||
configLock := globalNSMutex.NewNSLock(minioReservedBucket, minioConfigFile)
|
||||
if configLock.GetRLock(globalObjectTimeout) != nil {
|
||||
writeErrorResponseJSON(w, ErrOperationTimedOut, r.URL)
|
||||
return
|
||||
}
|
||||
defer configLock.RUnlock()
|
||||
|
||||
// Get config.json - in distributed mode, the configuration
|
||||
// occurring on a quorum of the servers is returned.
|
||||
configBytes, err := getPeerConfig(globalAdminPeers)
|
||||
if err != nil {
|
||||
errorIf(err, "Failed to get config from peers")
|
||||
writeErrorResponseJSON(w, toAdminAPIErrCode(err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessResponseJSON(w, configBytes)
|
||||
}
|
||||
|
||||
// toAdminAPIErrCode - converts errXLWriteQuorum error to admin API
|
||||
// specific error.
|
||||
func toAdminAPIErrCode(err error) APIErrorCode {
|
||||
switch err {
|
||||
case errXLWriteQuorum:
|
||||
return ErrAdminConfigNoQuorum
|
||||
}
|
||||
return toAPIErrorCode(err)
|
||||
}
|
||||
|
||||
// SetConfigResult - represents detailed results of a set-config
|
||||
// operation.
|
||||
type nodeSummary struct {
|
||||
Name string `json:"name"`
|
||||
ErrSet bool `json:"errSet"`
|
||||
ErrMsg string `json:"errMsg"`
|
||||
}
|
||||
|
||||
type setConfigResult struct {
|
||||
NodeResults []nodeSummary `json:"nodeResults"`
|
||||
Status bool `json:"status"`
|
||||
}
|
||||
|
||||
// writeSetConfigResponse - writes setConfigResult value as json
|
||||
// depending on the status.
|
||||
func writeSetConfigResponse(w http.ResponseWriter, peers adminPeers,
|
||||
errs []error, status bool, reqURL *url.URL) {
|
||||
|
||||
var nodeResults []nodeSummary
|
||||
// Build nodeResults based on error values received during
|
||||
// set-config operation.
|
||||
for i := range errs {
|
||||
nodeResults = append(nodeResults, nodeSummary{
|
||||
Name: peers[i].addr,
|
||||
ErrSet: errs[i] != nil,
|
||||
ErrMsg: fmt.Sprintf("%v", errs[i]),
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
result := setConfigResult{
|
||||
Status: status,
|
||||
NodeResults: nodeResults,
|
||||
}
|
||||
|
||||
// The following elaborate json encoding is to avoid escaping
|
||||
// '<', '>' in <nil>. Note: json.Encoder.Encode() adds a
|
||||
// gratuitous "\n".
|
||||
var resultBuf bytes.Buffer
|
||||
enc := json.NewEncoder(&resultBuf)
|
||||
enc.SetEscapeHTML(false)
|
||||
jsonErr := enc.Encode(result)
|
||||
if jsonErr != nil {
|
||||
writeErrorResponseJSON(w, toAPIErrorCode(jsonErr), reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessResponseJSON(w, resultBuf.Bytes())
|
||||
return
|
||||
}
|
||||
|
||||
// SetConfigHandler - PUT /minio/admin/v1/config
|
||||
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil {
|
||||
writeErrorResponseJSON(w, ErrServerNotInitialized, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate request signature.
|
||||
adminAPIErr := checkAdminRequestAuthType(r, globalServerConfig.GetRegion())
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponseJSON(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Read configuration bytes from request body.
|
||||
configBuf := make([]byte, maxConfigJSONSize+1)
|
||||
n, err := io.ReadFull(r.Body, configBuf)
|
||||
if err == nil {
|
||||
// More than maxConfigSize bytes were available
|
||||
writeErrorResponse(w, ErrAdminConfigTooLarge, r.URL)
|
||||
return
|
||||
}
|
||||
if err != io.ErrUnexpectedEOF {
|
||||
errorIf(err, "Failed to read config from request body.")
|
||||
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
configBytes := configBuf[:n]
|
||||
|
||||
// Validate JSON provided in the request body: check the
|
||||
// client has not sent JSON objects with duplicate keys.
|
||||
if err = checkDupJSONKeys(string(configBytes)); err != nil {
|
||||
errorIf(err, "config contains duplicate JSON entries.")
|
||||
writeErrorResponse(w, ErrAdminConfigBadJSON, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var config serverConfig
|
||||
err = json.Unmarshal(configBytes, &config)
|
||||
if err != nil {
|
||||
errorIf(err, "Failed to unmarshal JSON configuration", err)
|
||||
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// If credentials for the server are provided via environment,
|
||||
// then credentials in the provided configuration must match.
|
||||
if globalIsEnvCreds {
|
||||
creds := globalServerConfig.GetCredential()
|
||||
if config.Credential.AccessKey != creds.AccessKey ||
|
||||
config.Credential.SecretKey != creds.SecretKey {
|
||||
writeErrorResponseJSON(w, ErrAdminCredentialsMismatch, r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Write config received from request onto a temporary file on
|
||||
// all nodes.
|
||||
tmpFileName := fmt.Sprintf(minioConfigTmpFormat, mustGetUUID())
|
||||
errs := writeTmpConfigPeers(globalAdminPeers, tmpFileName, configBytes)
|
||||
|
||||
// Check if the operation succeeded in quorum or more nodes.
|
||||
rErr := reduceWriteQuorumErrs(errs, nil, len(globalAdminPeers)/2+1)
|
||||
if rErr != nil {
|
||||
writeSetConfigResponse(w, globalAdminPeers, errs, false, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Take a lock on minio/config.json. NB minio is a reserved
|
||||
// bucket name and wouldn't conflict with normal object
|
||||
// operations.
|
||||
configLock := globalNSMutex.NewNSLock(minioReservedBucket, minioConfigFile)
|
||||
if configLock.GetLock(globalObjectTimeout) != nil {
|
||||
writeErrorResponseJSON(w, ErrOperationTimedOut, r.URL)
|
||||
return
|
||||
}
|
||||
defer configLock.Unlock()
|
||||
|
||||
// Rename the temporary config file to config.json
|
||||
errs = commitConfigPeers(globalAdminPeers, tmpFileName)
|
||||
rErr = reduceWriteQuorumErrs(errs, nil, len(globalAdminPeers)/2+1)
|
||||
if rErr != nil {
|
||||
writeSetConfigResponse(w, globalAdminPeers, errs, false, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// serverMux (cmd/server-mux.go) implements graceful shutdown,
|
||||
// where all listeners are closed and process restart/shutdown
|
||||
// happens after 5s or completion of all ongoing http
|
||||
// requests, whichever is earlier.
|
||||
writeSetConfigResponse(w, globalAdminPeers, errs, true, r.URL)
|
||||
|
||||
// Restart all node for the modified config to take effect.
|
||||
sendServiceCmd(globalAdminPeers, serviceRestart)
|
||||
}
|
||||
|
||||
// ConfigCredsHandler - POST /minio/admin/v1/config/credential
|
||||
// ----------
|
||||
// Update credentials in a minio server. In a distributed setup,
|
||||
// update all the servers in the cluster.
|
||||
func (a adminAPIHandlers) UpdateCredentialsHandler(w http.ResponseWriter,
|
||||
r *http.Request) {
|
||||
|
||||
// Authenticate request
|
||||
adminAPIErr := checkAdminRequestAuthType(r, globalServerConfig.GetRegion())
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponse(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Avoid setting new credentials when they are already passed
|
||||
// by the environment.
|
||||
if globalIsEnvCreds {
|
||||
writeErrorResponse(w, ErrMethodNotAllowed, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Decode request body
|
||||
var req madmin.SetCredsReq
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
errorIf(err, "Error parsing body JSON")
|
||||
writeErrorResponseJSON(w, ErrRequestBodyParse, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
creds, err := auth.CreateCredentials(req.AccessKey, req.SecretKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Take a lock on minio/config.json. Prevents concurrent
|
||||
// config file updates.
|
||||
configLock := globalNSMutex.NewNSLock(minioReservedBucket, minioConfigFile)
|
||||
if configLock.GetLock(globalObjectTimeout) != nil {
|
||||
writeErrorResponseJSON(w, ErrOperationTimedOut, r.URL)
|
||||
return
|
||||
}
|
||||
defer configLock.Unlock()
|
||||
|
||||
// Acquire lock before updating global configuration.
|
||||
globalServerConfigMu.Lock()
|
||||
defer globalServerConfigMu.Unlock()
|
||||
|
||||
// Notify all other Minio peers to update credentials
|
||||
updateErrs := updateCredsOnPeers(creds)
|
||||
for peer, err := range updateErrs {
|
||||
errorIf(err, "Unable to update credentials on peer %s.", peer)
|
||||
}
|
||||
|
||||
// Update local credentials in memory.
|
||||
globalServerConfig.SetCredential(creds)
|
||||
if err = globalServerConfig.Save(); err != nil {
|
||||
writeErrorResponse(w, ErrInternalError, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// At this stage, the operation is successful, return 200 OK
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
@@ -1,655 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
// healStatusSummary - overall short summary of a healing sequence
|
||||
type healStatusSummary string
|
||||
|
||||
// healStatusSummary constants
|
||||
const (
|
||||
healNotStartedStatus healStatusSummary = "not started"
|
||||
healRunningStatus = "running"
|
||||
healStoppedStatus = "stopped"
|
||||
healFinishedStatus = "finished"
|
||||
)
|
||||
|
||||
const (
|
||||
// a heal sequence with this many un-consumed heal result
|
||||
// items blocks until heal-status consumption resumes or is
|
||||
// aborted due to timeout.
|
||||
maxUnconsumedHealResultItems = 1000
|
||||
|
||||
// if no heal-results are consumed (via the heal-status API)
|
||||
// for this timeout duration, the heal sequence is aborted.
|
||||
healUnconsumedTimeout = 24 * time.Hour
|
||||
|
||||
// time-duration to keep heal sequence state after it
|
||||
// completes.
|
||||
keepHealSeqStateDuration = time.Minute * 10
|
||||
)
|
||||
|
||||
var (
|
||||
errHealIdleTimeout = fmt.Errorf("healing results were not consumed for too long")
|
||||
errHealPushStopNDiscard = fmt.Errorf("heal push stopped due to heal stop signal")
|
||||
errHealStopSignalled = fmt.Errorf("heal stop signalled")
|
||||
|
||||
errFnHealFromAPIErr = func(err error) error {
|
||||
errCode := toAPIErrorCode(err)
|
||||
apiErr := getAPIError(errCode)
|
||||
return fmt.Errorf("Heal internal error: %s: %s",
|
||||
apiErr.Code, apiErr.Description)
|
||||
}
|
||||
)
|
||||
|
||||
// healSequenceStatus - accumulated status of the heal sequence
|
||||
type healSequenceStatus struct {
|
||||
// lock to update this structure as it is concurrently
|
||||
// accessed
|
||||
updateLock *sync.RWMutex
|
||||
|
||||
// summary and detail for failures
|
||||
Summary healStatusSummary `json:"Summary"`
|
||||
FailureDetail string `json:"Detail,omitempty"`
|
||||
StartTime time.Time `json:"StartTime"`
|
||||
|
||||
// disk information
|
||||
NumDisks int `json:"NumDisks"`
|
||||
|
||||
// settings for the heal sequence
|
||||
HealSettings madmin.HealOpts `json:"Settings"`
|
||||
|
||||
// slice of available heal result records
|
||||
Items []madmin.HealResultItem `json:"Items"`
|
||||
}
|
||||
|
||||
// structure to hold state of all heal sequences in server memory
|
||||
type allHealState struct {
|
||||
sync.Mutex
|
||||
|
||||
// map of heal path to heal sequence
|
||||
healSeqMap map[string]*healSequence
|
||||
}
|
||||
|
||||
var (
|
||||
// global server heal state
|
||||
globalAllHealState allHealState
|
||||
)
|
||||
|
||||
// initAllHealState - initialize healing apparatus
|
||||
func initAllHealState(isErasureMode bool) {
|
||||
if !isErasureMode {
|
||||
return
|
||||
}
|
||||
|
||||
globalAllHealState = allHealState{
|
||||
healSeqMap: make(map[string]*healSequence),
|
||||
}
|
||||
}
|
||||
|
||||
// getHealSequence - Retrieve a heal sequence by path. The second
|
||||
// argument returns if a heal sequence actually exists.
|
||||
func (ahs *allHealState) getHealSequence(path string) (h *healSequence, exists bool) {
|
||||
ahs.Lock()
|
||||
defer ahs.Unlock()
|
||||
h, exists = ahs.healSeqMap[path]
|
||||
return h, exists
|
||||
}
|
||||
|
||||
// LaunchNewHealSequence - launches a background routine that performs
|
||||
// healing according to the healSequence argument. For each heal
|
||||
// sequence, state is stored in the `globalAllHealState`, which is a
|
||||
// map of the heal path to `healSequence` which holds state about the
|
||||
// heal sequence.
|
||||
//
|
||||
// Heal results are persisted in server memory for
|
||||
// `keepHealSeqStateDuration`. This function also launches a
|
||||
// background routine to clean up heal results after the
|
||||
// aforementioned duration.
|
||||
func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
|
||||
respBytes []byte, errCode APIErrorCode, errMsg string) {
|
||||
|
||||
existsAndLive := false
|
||||
he, exists := ahs.getHealSequence(h.path)
|
||||
if exists {
|
||||
if !he.hasEnded() || len(he.currentStatus.Items) > 0 {
|
||||
existsAndLive = true
|
||||
}
|
||||
}
|
||||
if existsAndLive {
|
||||
// A heal sequence exists on the given path.
|
||||
if h.forceStarted {
|
||||
// stop the running heal sequence - wait for
|
||||
// it to finish.
|
||||
he.stop()
|
||||
for !he.hasEnded() {
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
} else {
|
||||
errMsg = "Heal is already running on the given path " +
|
||||
"(use force-start option to stop and start afresh). " +
|
||||
fmt.Sprintf("The heal was started by IP %s at %s",
|
||||
h.clientAddress, h.startTime)
|
||||
|
||||
return nil, ErrHealAlreadyRunning, errMsg
|
||||
}
|
||||
}
|
||||
|
||||
ahs.Lock()
|
||||
defer ahs.Unlock()
|
||||
|
||||
// Check if new heal sequence to be started overlaps with any
|
||||
// existing, running sequence
|
||||
for k, hSeq := range ahs.healSeqMap {
|
||||
if !hSeq.hasEnded() && (strings.HasPrefix(k, h.path) ||
|
||||
strings.HasPrefix(h.path, k)) {
|
||||
|
||||
errMsg = "The provided heal sequence path overlaps with an existing " +
|
||||
fmt.Sprintf("heal path: %s", k)
|
||||
return nil, ErrHealOverlappingPaths, errMsg
|
||||
}
|
||||
}
|
||||
|
||||
// Add heal state and start sequence
|
||||
ahs.healSeqMap[h.path] = h
|
||||
|
||||
// Launch top-level background heal go-routine
|
||||
go h.healSequenceStart()
|
||||
|
||||
// Launch clean-up routine to remove this heal sequence (after
|
||||
// it ends) from the global state after timeout has elapsed.
|
||||
go func() {
|
||||
var keepStateTimeout <-chan time.Time
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
defer ticker.Stop()
|
||||
everyMinute := ticker.C
|
||||
for {
|
||||
select {
|
||||
// Check every minute if heal sequence has ended.
|
||||
case <-everyMinute:
|
||||
if h.hasEnded() {
|
||||
keepStateTimeout = time.After(keepHealSeqStateDuration)
|
||||
everyMinute = nil
|
||||
}
|
||||
|
||||
// This case does not fire until the heal
|
||||
// sequence completes.
|
||||
case <-keepStateTimeout:
|
||||
// Heal sequence has ended, keep
|
||||
// results state duration has elapsed,
|
||||
// so purge state.
|
||||
ahs.Lock()
|
||||
defer ahs.Unlock()
|
||||
delete(ahs.healSeqMap, h.path)
|
||||
return
|
||||
|
||||
case <-globalServiceDoneCh:
|
||||
// server could be restarting - need
|
||||
// to exit immediately
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
b, err := json.Marshal(madmin.HealStartSuccess{
|
||||
h.clientToken,
|
||||
h.clientAddress,
|
||||
h.startTime,
|
||||
})
|
||||
if err != nil {
|
||||
errorIf(err, "Failed to marshal heal result into json.")
|
||||
return nil, ErrInternalError, ""
|
||||
}
|
||||
return b, ErrNone, ""
|
||||
}
|
||||
|
||||
// PopHealStatusJSON - Called by heal-status API. It fetches the heal
|
||||
// status results from global state and returns its JSON
|
||||
// representation. The clientToken helps ensure there aren't
|
||||
// conflicting clients fetching status.
|
||||
func (ahs *allHealState) PopHealStatusJSON(path string,
|
||||
clientToken string) ([]byte, APIErrorCode) {
|
||||
|
||||
// fetch heal state for given path
|
||||
h, exists := ahs.getHealSequence(path)
|
||||
if !exists {
|
||||
// If there is no such heal sequence, return error.
|
||||
return nil, ErrHealNoSuchProcess
|
||||
}
|
||||
|
||||
// Check if client-token is valid
|
||||
if clientToken != h.clientToken {
|
||||
return nil, ErrHealInvalidClientToken
|
||||
}
|
||||
|
||||
// Take lock to access and update the heal-sequence
|
||||
h.currentStatus.updateLock.Lock()
|
||||
defer h.currentStatus.updateLock.Unlock()
|
||||
|
||||
numItems := len(h.currentStatus.Items)
|
||||
|
||||
// calculate index of most recently available heal result
|
||||
// record.
|
||||
lastResultIndex := h.lastSentResultIndex
|
||||
if numItems > 0 {
|
||||
lastResultIndex = h.currentStatus.Items[numItems-1].ResultIndex
|
||||
}
|
||||
|
||||
// After sending status to client, and before relinquishing
|
||||
// the updateLock, reset Item to nil and record the result
|
||||
// index sent to the client.
|
||||
defer func(i int64) {
|
||||
h.lastSentResultIndex = i
|
||||
h.currentStatus.Items = nil
|
||||
}(lastResultIndex)
|
||||
|
||||
jbytes, err := json.Marshal(h.currentStatus)
|
||||
if err != nil {
|
||||
errorIf(err, "Failed to marshal heal result into json.")
|
||||
return nil, ErrInternalError
|
||||
}
|
||||
|
||||
return jbytes, ErrNone
|
||||
}
|
||||
|
||||
// healSequence - state for each heal sequence initiated on the
|
||||
// server.
|
||||
type healSequence struct {
|
||||
// bucket, and prefix on which heal seq. was initiated
|
||||
bucket, objPrefix string
|
||||
|
||||
// path is just bucket + "/" + objPrefix
|
||||
path string
|
||||
|
||||
// time at which heal sequence was started
|
||||
startTime time.Time
|
||||
|
||||
// Heal client info
|
||||
clientToken, clientAddress string
|
||||
|
||||
// was this heal sequence force started?
|
||||
forceStarted bool
|
||||
|
||||
// heal settings applied to this heal sequence
|
||||
settings madmin.HealOpts
|
||||
|
||||
// current accumulated status of the heal sequence
|
||||
currentStatus healSequenceStatus
|
||||
|
||||
// channel signalled by background routine when traversal has
|
||||
// completed
|
||||
traverseAndHealDoneCh chan error
|
||||
|
||||
// channel to signal heal sequence to stop (e.g. from the
|
||||
// heal-stop API)
|
||||
stopSignalCh chan struct{}
|
||||
|
||||
// the last result index sent to client
|
||||
lastSentResultIndex int64
|
||||
|
||||
// Holds the request-info for logging
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewHealSequence - creates healSettings, assumes bucket and
|
||||
// objPrefix are already validated.
|
||||
func newHealSequence(bucket, objPrefix, clientAddr string,
|
||||
numDisks int, hs madmin.HealOpts, forceStart bool) *healSequence {
|
||||
|
||||
ctx := logger.SetContext(context.Background(), &logger.ReqInfo{clientAddr, "", "", "Heal", bucket, objPrefix, nil})
|
||||
|
||||
return &healSequence{
|
||||
bucket: bucket,
|
||||
objPrefix: objPrefix,
|
||||
path: bucket + "/" + objPrefix,
|
||||
startTime: UTCNow(),
|
||||
clientToken: mustGetUUID(),
|
||||
clientAddress: clientAddr,
|
||||
forceStarted: forceStart,
|
||||
settings: hs,
|
||||
currentStatus: healSequenceStatus{
|
||||
Summary: healNotStartedStatus,
|
||||
HealSettings: hs,
|
||||
NumDisks: numDisks,
|
||||
updateLock: &sync.RWMutex{},
|
||||
},
|
||||
traverseAndHealDoneCh: make(chan error),
|
||||
stopSignalCh: make(chan struct{}),
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// isQuitting - determines if the heal sequence is quitting (due to an
|
||||
// external signal)
|
||||
func (h *healSequence) isQuitting() bool {
|
||||
select {
|
||||
case <-h.stopSignalCh:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// check if the heal sequence has ended
|
||||
func (h *healSequence) hasEnded() bool {
|
||||
h.currentStatus.updateLock.RLock()
|
||||
summary := h.currentStatus.Summary
|
||||
h.currentStatus.updateLock.RUnlock()
|
||||
return summary == healStoppedStatus || summary == healFinishedStatus
|
||||
}
|
||||
|
||||
// stops the heal sequence - safe to call multiple times.
|
||||
func (h *healSequence) stop() {
|
||||
select {
|
||||
case <-h.stopSignalCh:
|
||||
default:
|
||||
close(h.stopSignalCh)
|
||||
}
|
||||
}
|
||||
|
||||
// pushHealResultItem - pushes a heal result item for consumption in
|
||||
// the heal-status API. It blocks if there are
|
||||
// maxUnconsumedHealResultItems. When it blocks, the heal sequence
|
||||
// routine is effectively paused - this happens when the server has
|
||||
// accumulated the maximum number of heal records per heal
|
||||
// sequence. When the client consumes further records, the heal
|
||||
// sequence automatically resumes. The return value indicates if the
|
||||
// operation succeeded.
|
||||
func (h *healSequence) pushHealResultItem(r madmin.HealResultItem) error {
|
||||
|
||||
// start a timer to keep an upper time limit to find an empty
|
||||
// slot to add the given heal result - if no slot is found it
|
||||
// means that the server is holding the maximum amount of
|
||||
// heal-results in memory and the client has not consumed it
|
||||
// for too long.
|
||||
unconsumedTimer := time.NewTimer(healUnconsumedTimeout)
|
||||
defer func() {
|
||||
// stop the timeout timer so it is garbage collected.
|
||||
if !unconsumedTimer.Stop() {
|
||||
<-unconsumedTimer.C
|
||||
}
|
||||
}()
|
||||
|
||||
var itemsLen int
|
||||
for {
|
||||
h.currentStatus.updateLock.Lock()
|
||||
itemsLen = len(h.currentStatus.Items)
|
||||
if itemsLen == maxUnconsumedHealResultItems {
|
||||
// unlock and wait to check again if we can push
|
||||
h.currentStatus.updateLock.Unlock()
|
||||
|
||||
// wait for a second, or quit if an external
|
||||
// stop signal is received or the
|
||||
// unconsumedTimer fires.
|
||||
select {
|
||||
// Check after a second
|
||||
case <-time.After(time.Second):
|
||||
continue
|
||||
|
||||
case <-h.stopSignalCh:
|
||||
// discard result and return.
|
||||
return errHealPushStopNDiscard
|
||||
|
||||
// Timeout if no results consumed for too
|
||||
// long.
|
||||
case <-unconsumedTimer.C:
|
||||
return errHealIdleTimeout
|
||||
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Set the correct result index for the new result item
|
||||
if itemsLen > 0 {
|
||||
r.ResultIndex = 1 + h.currentStatus.Items[itemsLen-1].ResultIndex
|
||||
} else {
|
||||
r.ResultIndex = 1 + h.lastSentResultIndex
|
||||
}
|
||||
|
||||
// append to results
|
||||
h.currentStatus.Items = append(h.currentStatus.Items, r)
|
||||
|
||||
// release lock
|
||||
h.currentStatus.updateLock.Unlock()
|
||||
|
||||
// This is a "safe" point for the heal sequence to quit if
|
||||
// signalled externally.
|
||||
if h.isQuitting() {
|
||||
return errHealStopSignalled
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// healSequenceStart - this is the top-level background heal
|
||||
// routine. It launches another go-routine that actually traverses
|
||||
// on-disk data, checks and heals according to the selected
|
||||
// settings. This go-routine itself, (1) monitors the traversal
|
||||
// routine for completion, and (2) listens for external stop
|
||||
// signals. When either event happens, it sets the finish status for
|
||||
// the heal-sequence.
|
||||
func (h *healSequence) healSequenceStart() {
|
||||
// Set status as running
|
||||
h.currentStatus.updateLock.Lock()
|
||||
h.currentStatus.Summary = healRunningStatus
|
||||
h.currentStatus.StartTime = UTCNow()
|
||||
h.currentStatus.updateLock.Unlock()
|
||||
|
||||
go h.traverseAndHeal()
|
||||
|
||||
select {
|
||||
case err, ok := <-h.traverseAndHealDoneCh:
|
||||
h.currentStatus.updateLock.Lock()
|
||||
defer h.currentStatus.updateLock.Unlock()
|
||||
// Heal traversal is complete.
|
||||
if ok {
|
||||
// heal traversal had an error.
|
||||
h.currentStatus.Summary = healStoppedStatus
|
||||
h.currentStatus.FailureDetail = err.Error()
|
||||
} else {
|
||||
// heal traversal succeeded.
|
||||
h.currentStatus.Summary = healFinishedStatus
|
||||
}
|
||||
|
||||
case <-h.stopSignalCh:
|
||||
h.currentStatus.updateLock.Lock()
|
||||
h.currentStatus.Summary = healStoppedStatus
|
||||
h.currentStatus.FailureDetail = errHealStopSignalled.Error()
|
||||
h.currentStatus.updateLock.Unlock()
|
||||
|
||||
// drain traverse channel so the traversal
|
||||
// go-routine does not leak.
|
||||
go func() {
|
||||
// Eventually the traversal go-routine closes
|
||||
// the channel and returns, so this go-routine
|
||||
// itself will not leak.
|
||||
<-h.traverseAndHealDoneCh
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// traverseAndHeal - traverses on-disk data and performs healing
|
||||
// according to settings. At each "safe" point it also checks if an
|
||||
// external quit signal has been received and quits if so. Since the
|
||||
// healing traversal may be mutating on-disk data when an external
|
||||
// quit signal is received, this routine cannot quit immediately and
|
||||
// has to wait until a safe point is reached, such as between scanning
|
||||
// two objects.
|
||||
func (h *healSequence) traverseAndHeal() {
|
||||
var err error
|
||||
checkErr := func(f func() error) {
|
||||
switch {
|
||||
case err != nil:
|
||||
return
|
||||
case h.isQuitting():
|
||||
err = errHealStopSignalled
|
||||
return
|
||||
}
|
||||
err = f()
|
||||
}
|
||||
|
||||
// Start with format healing
|
||||
checkErr(h.healDiskFormat)
|
||||
|
||||
// Heal buckets and objects
|
||||
checkErr(h.healBuckets)
|
||||
|
||||
if err != nil {
|
||||
h.traverseAndHealDoneCh <- err
|
||||
}
|
||||
|
||||
close(h.traverseAndHealDoneCh)
|
||||
}
|
||||
|
||||
// healDiskFormat - heals format.json, return value indicates if a
|
||||
// failure error occurred.
|
||||
func (h *healSequence) healDiskFormat() error {
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
res, err := objectAPI.HealFormat(h.ctx, h.settings.DryRun)
|
||||
if err != nil {
|
||||
return errFnHealFromAPIErr(err)
|
||||
}
|
||||
|
||||
peersReInitFormat(globalAdminPeers, h.settings.DryRun)
|
||||
|
||||
// Push format heal result
|
||||
return h.pushHealResultItem(res)
|
||||
}
|
||||
|
||||
// healBuckets - check for all buckets heal or just particular bucket.
|
||||
func (h *healSequence) healBuckets() error {
|
||||
// 1. If a bucket was specified, heal only the bucket.
|
||||
if h.bucket != "" {
|
||||
return h.healBucket(h.bucket)
|
||||
}
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
buckets, err := objectAPI.ListBucketsHeal(h.ctx)
|
||||
if err != nil {
|
||||
return errFnHealFromAPIErr(err)
|
||||
}
|
||||
|
||||
for _, bucket := range buckets {
|
||||
if err = h.healBucket(bucket.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// healBucket - traverses and heals given bucket
|
||||
func (h *healSequence) healBucket(bucket string) error {
|
||||
if h.isQuitting() {
|
||||
return errHealStopSignalled
|
||||
}
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
results, err := objectAPI.HealBucket(h.ctx, bucket, h.settings.DryRun)
|
||||
// push any available results before checking for error
|
||||
for _, result := range results {
|
||||
if perr := h.pushHealResultItem(result); perr != nil {
|
||||
return perr
|
||||
}
|
||||
}
|
||||
// handle heal-bucket error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !h.settings.Recursive {
|
||||
if h.objPrefix != "" {
|
||||
// Check if an object named as the objPrefix exists,
|
||||
// and if so heal it.
|
||||
_, err = objectAPI.GetObjectInfo(h.ctx, bucket, h.objPrefix)
|
||||
if err == nil {
|
||||
err = h.healObject(bucket, h.objPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
marker := ""
|
||||
isTruncated := true
|
||||
for isTruncated {
|
||||
objectInfos, err := objectAPI.ListObjectsHeal(h.ctx, bucket,
|
||||
h.objPrefix, marker, "", 1000)
|
||||
if err != nil {
|
||||
return errFnHealFromAPIErr(err)
|
||||
}
|
||||
|
||||
for _, o := range objectInfos.Objects {
|
||||
if err := h.healObject(o.Bucket, o.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
isTruncated = objectInfos.IsTruncated
|
||||
marker = objectInfos.NextMarker
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// healObject - heal the given object and record result
|
||||
func (h *healSequence) healObject(bucket, object string) error {
|
||||
if h.isQuitting() {
|
||||
return errHealStopSignalled
|
||||
}
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
hri, err := objectAPI.HealObject(h.ctx, bucket, object, h.settings.DryRun)
|
||||
if err != nil {
|
||||
hri.Detail = err.Error()
|
||||
}
|
||||
return h.pushHealResultItem(hri)
|
||||
}
|
||||