Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44d8e9b975 | ||
|
|
f3d7e61ddb | ||
|
|
526c0f4796 | ||
|
|
fe1acaa4b6 | ||
|
|
8e9bd8728a | ||
|
|
9df9309c66 | ||
|
|
b85712e29e | ||
|
|
c32df86c76 | ||
|
|
0f52136fd2 | ||
|
|
605b80037a | ||
|
|
63b430e354 | ||
|
|
d9c212fe2f | ||
|
|
5fa0a0fca8 | ||
|
|
068ac281ea | ||
|
|
0bcf88eb7c | ||
|
|
5c137a8678 | ||
|
|
540ff31784 | ||
|
|
82ea3c1ac4 | ||
|
|
e1f177257a | ||
|
|
0211827c74 | ||
|
|
ac5732970c | ||
|
|
1b1ed55252 | ||
|
|
5c5e84b289 | ||
|
|
5755b98b66 | ||
|
|
86ee1eea6d | ||
|
|
adcbf61049 | ||
|
|
e197399441 | ||
|
|
ff2438a877 | ||
|
|
c85067dfba | ||
|
|
b1df170d80 | ||
|
|
9566f6e579 | ||
|
|
12a682e9f6 | ||
|
|
e0bb098e47 | ||
|
|
b390ce309a | ||
|
|
3dac86d3ce | ||
|
|
2001ab6dae | ||
|
|
31f0655ff6 | ||
|
|
775874cf86 | ||
|
|
9ca4daa906 | ||
|
|
2318a8a82b | ||
|
|
69ed6f5ca4 | ||
|
|
3a96e6d7e7 | ||
|
|
9a2b10476c | ||
|
|
c8938dc131 |
2
.github/workflows/go.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
GO111MODULE: on
|
||||
GOOS: linux
|
||||
run: |
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.21.0
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0
|
||||
$(go env GOPATH)/bin/golangci-lint run --timeout=5m --config ./.golangci.yml
|
||||
go mod vendor
|
||||
go test -v -race ./...
|
||||
|
||||
9
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
*.dylib
|
||||
.DS_Store
|
||||
*.swp
|
||||
*.orig
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
@@ -22,3 +23,11 @@ mcs
|
||||
!mcs/
|
||||
|
||||
dist/
|
||||
|
||||
# Ignore tls cert and key
|
||||
private.key
|
||||
public.crt
|
||||
|
||||
# Ignore VsCode files
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
|
||||
59
CONTRIBUTING.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# MinIO Console Server Contribution Guide [](https://slack.min.io)
|
||||
|
||||
This is a REST portal server created using [go-swagger](https://github.com/go-swagger/go-swagger)
|
||||
|
||||
The API handlers are created using a YAML definition located in `swagger.YAML`.
|
||||
|
||||
To add new api, the YAML file needs to be updated with all the desired apis using the [Swagger Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/), this includes paths, parameters, definitions, tags, etc.
|
||||
|
||||
## Generate server from YAML
|
||||
Once the YAML file is ready we can autogenerate the code needed for the new api by just running:
|
||||
|
||||
Validate it:
|
||||
```
|
||||
swagger validate ./swagger.yml
|
||||
```
|
||||
Update server code:
|
||||
```
|
||||
make swagger-gen
|
||||
```
|
||||
|
||||
This will update all the necessary code.
|
||||
|
||||
`./restapi/configure_mcs.go` is a file that contains the handlers to be used by the application, here is the only place where we need to update our code to support the new apis. This file is not affected when running the swagger generator and it is safe to edit.
|
||||
|
||||
## Unit Tests
|
||||
`./restapi/handlers_test.go` needs to be updated with the proper tests for the new api.
|
||||
|
||||
To run tests:
|
||||
```
|
||||
go test ./restapi
|
||||
```
|
||||
|
||||
## 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 ``mcs`` manages dependencies?
|
||||
``MinIO`` uses `go mod` to manage its dependencies.
|
||||
- Run `go get foo/bar` in the source folder to add the dependency to `go.mod` file.
|
||||
|
||||
To remove a dependency
|
||||
- Edit your code and remove the import reference.
|
||||
- Run `go mod tidy` in the source folder to remove dependency from `go.mod` file.
|
||||
|
||||
### What are the coding guidelines for mcs?
|
||||
``mcs`` 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](https://slack.min.io).
|
||||
21
Makefile
@@ -1,23 +1,34 @@
|
||||
PWD := $(shell pwd)
|
||||
GOPATH := $(shell go env GOPATH)
|
||||
|
||||
default: mcs
|
||||
|
||||
.PHONY: mcs
|
||||
mcs:
|
||||
@echo "Building mcs binary to './mcs'"
|
||||
@(CGO_ENABLED=0 go build --tags=kqueue --ldflags "-s -w" -o mcs ./cmd/mcs)
|
||||
@(CGO_ENABLED=0 go build -trimpath --tags=kqueue --ldflags "-s -w" -o mcs ./cmd/mcs)
|
||||
|
||||
install: mcs
|
||||
@echo "Installing mcs binary to '$(GOPATH)/bin/mcs'"
|
||||
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/mcs $(GOPATH)/bin/mcs
|
||||
@echo "Installation successful. To learn more, try \"mcs --help\"."
|
||||
|
||||
swagger-gen:
|
||||
@echo "Generating swagger server code from yaml"
|
||||
@swagger generate server -A mcs --main-package=mcs --exclude-main -P models.Principal -f ./swagger.yml -r NOTICE
|
||||
|
||||
build:
|
||||
assets:
|
||||
@(cd portal-ui; yarn install; make build-static; cd ..)
|
||||
@(CGO_ENABLED=0 go build --tags kqueue --ldflags "-s -w" -o mcs ./cmd/mcs)
|
||||
|
||||
test:
|
||||
@(go test ./restapi -v)
|
||||
@(go test -race -v github.com/minio/mcs/restapi/...)
|
||||
@(go test -race -v github.com/minio/mcs/pkg/auth/...)
|
||||
|
||||
coverage:
|
||||
@(go test ./restapi -v -coverprofile=coverage.out && go tool cover -html=coverage.out && open coverage.html)
|
||||
@(go test -v -coverprofile=coverage.out github.com/minio/mcs/restapi/... && go tool cover -html=coverage.out && open coverage.html)
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up all the generated files"
|
||||
@find . -name '*.test' | xargs rm -fv
|
||||
@find . -name '*~' | xargs rm -fv
|
||||
@rm -vf mcs
|
||||
|
||||
45
README.md
@@ -1,6 +1,6 @@
|
||||
# MCS Minio Console Service
|
||||
# Minio Console Server
|
||||
|
||||
This is a REST portal server created using [go-swagger](https://github.com/go-swagger/go-swagger)
|
||||
A graphical user interface for [MinIO](https://github.com/minio/minio)
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -54,40 +54,21 @@ $ mc admin policy set myminio mcsAdmin user=mcs
|
||||
To run the server:
|
||||
|
||||
```
|
||||
export MCS_HMAC_JWT_SECRET=YOURJWTSIGNINGSECRET
|
||||
|
||||
#required to encrypt jwet payload
|
||||
export MCS_PBKDF_PASSPHRASE=SECRET
|
||||
|
||||
#required to encrypt jwet payload
|
||||
export MCS_PBKDF_SALT=SECRET
|
||||
|
||||
export MCS_ACCESS_KEY=mcs
|
||||
export MCS_SECRET_KEY=YOURMCSSECRET
|
||||
export MCS_MINIO_SERVER=http://localhost:9000
|
||||
./mcs
|
||||
./mcs server
|
||||
```
|
||||
|
||||
You can verify that the apis work by doing the request on `localhost:9090/api/v1/...`
|
||||
|
||||
# Development
|
||||
|
||||
The API handlers are created using a YAML definition located in `swagger.YAML`.
|
||||
|
||||
To add new api, the YAML file needs to be updated with all the desired apis using the [Swagger Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/), this includes paths, parameters, definitions, tags, etc.
|
||||
|
||||
## Generate server from YAML
|
||||
Once the YAML file is ready we can autogenerate the code needed for the new api by just running:
|
||||
|
||||
Validate it:
|
||||
```
|
||||
swagger validate ./swagger.yml
|
||||
```
|
||||
Update server code:
|
||||
```
|
||||
make swagger-gen
|
||||
```
|
||||
|
||||
This will update all the necessary code.
|
||||
|
||||
`./restapi/configure_mcs.go` is a file that contains the handlers to be used by the application, here is the only place where we need to update our code to support the new apis. This file is not affected when running the swagger generator and it is safe to edit.
|
||||
|
||||
## Unit Tests
|
||||
`./restapi/handlers_test.go` needs to be updated with the proper tests for the new api.
|
||||
|
||||
To run tests:
|
||||
```
|
||||
go test ./restapi
|
||||
```
|
||||
# Contribute to mcs Project
|
||||
Please follow mcs [Contributor's Guide](https://github.com/minio/mcs/blob/master/CONTRIBUTING.md)
|
||||
|
||||
41
SECURITY.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We always provide security updates for the [latest release](https://github.com/minio/mcs/releases/latest).
|
||||
Whenever there is a security update you just need to upgrade to the latest version.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
All security bugs in [minio/mcs](https://github,com/minio/mcs) (or other minio/* repositories)
|
||||
should be reported by email to security@min.io. Your email will be acknowledged within 48 hours,
|
||||
and you'll receive a more detailed response to your email within 72 hours indicating the next steps
|
||||
in handling your report.
|
||||
|
||||
Please, provide a detailed explanation of the issue. In particular, outline the type of the security
|
||||
issue (DoS, authentication bypass, information disclose, ...) and the assumptions you're making (e.g. do
|
||||
you need access credentials for a successful exploit).
|
||||
|
||||
If you have not received a reply to your email within 48 hours or you have not heard from the security team
|
||||
for the past five days please contact the security team directly:
|
||||
- Primary security coordinator: lenin@min.io
|
||||
- Secondary coordinator: daniel@min.io, cesar@min.io
|
||||
- If you receive no response: dev@min.io
|
||||
|
||||
### Disclosure Process
|
||||
|
||||
MinIO uses the following disclosure process:
|
||||
|
||||
1. Once the security report is received one member of the security team tries to verify and reproduce
|
||||
the issue and determines the impact it has.
|
||||
2. A member of the security team will respond and either confirm or reject the security report.
|
||||
If the report is rejected the response explains why.
|
||||
3. Code is audited to find any potential similar problems.
|
||||
4. Fixes are prepared for the latest release.
|
||||
5. On the date that the fixes are applied a security advisory will be published on https://blog.min.io.
|
||||
Please inform us in your report email whether MinIO should mention your contribution w.r.t. fixing
|
||||
the security issue. By default MinIO will **not** publish this information to protect your privacy.
|
||||
|
||||
This process can take some time, especially when coordination is required with maintainers of other projects.
|
||||
Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we
|
||||
follow the process described above to ensure that disclosures are handled consistently.
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/minio/m3/mcs/pkg"
|
||||
"github.com/minio/mcs/pkg"
|
||||
|
||||
"github.com/minio/minio/pkg/console"
|
||||
"github.com/minio/minio/pkg/trie"
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
"github.com/minio/cli"
|
||||
)
|
||||
|
||||
// Help template for m3.
|
||||
// Help template for mcs.
|
||||
var mcsHelpTemplate = `NAME:
|
||||
{{.Name}} - {{.Usage}}
|
||||
|
||||
@@ -53,14 +53,13 @@ VERSION:
|
||||
|
||||
var appCmds = []cli.Command{
|
||||
serverCmd,
|
||||
versionCmd,
|
||||
}
|
||||
|
||||
func newApp(name string) *cli.App {
|
||||
// Collection of m3 commands currently supported are.
|
||||
commands := []cli.Command{}
|
||||
// Collection of mcs commands currently supported are.
|
||||
var commands []cli.Command
|
||||
|
||||
// Collection of m3 commands currently supported in a trie tree.
|
||||
// Collection of mcs commands currently supported in a trie tree.
|
||||
commandsTree := trie.NewTrie()
|
||||
|
||||
// registerCommand registers a cli command.
|
||||
@@ -106,7 +105,7 @@ func newApp(name string) *cli.App {
|
||||
app.Name = name
|
||||
app.Version = pkg.Version
|
||||
app.Author = "MinIO, Inc."
|
||||
app.Usage = "mcs COMMAND"
|
||||
app.Usage = "mcs"
|
||||
app.Description = `MinIO Console Server`
|
||||
app.Commands = commands
|
||||
app.HideHelpCommand = true // Hide `help, h` command, we already have `minio --help`.
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/m3/mcs/restapi"
|
||||
"github.com/minio/m3/mcs/restapi/operations"
|
||||
"github.com/minio/mcs/restapi"
|
||||
"github.com/minio/mcs/restapi/operations"
|
||||
)
|
||||
|
||||
// starts the server
|
||||
@@ -35,10 +35,35 @@ var serverCmd = cli.Command{
|
||||
Usage: "starts mcs server",
|
||||
Action: startServer,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "host",
|
||||
Value: restapi.GetHostname(),
|
||||
Usage: "HTTP server hostname",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "port",
|
||||
Value: 9090,
|
||||
Usage: "Server port",
|
||||
Value: restapi.GetPort(),
|
||||
Usage: "HTTP Server port",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tls-host",
|
||||
Value: restapi.GetSSLHostname(),
|
||||
Usage: "HTTPS server hostname",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "tls-port",
|
||||
Value: restapi.GetSSLPort(),
|
||||
Usage: "HTTPS server port",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tls-certificate",
|
||||
Value: "",
|
||||
Usage: "filename of public cert",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tls-key",
|
||||
Value: "",
|
||||
Usage: "filename of private key",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -74,11 +99,32 @@ func startServer(ctx *cli.Context) error {
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
// Parse flags
|
||||
flag.Parse()
|
||||
server.ConfigureAPI()
|
||||
|
||||
server.Host = ctx.String("host")
|
||||
server.Port = ctx.Int("port")
|
||||
|
||||
restapi.Hostname = ctx.String("host")
|
||||
restapi.Port = fmt.Sprintf("%v", ctx.Int("port"))
|
||||
|
||||
tlsCertificatePath := ctx.String("tls-certificate")
|
||||
tlsCertificateKeyPath := ctx.String("tls-key")
|
||||
|
||||
if tlsCertificatePath != "" && tlsCertificateKeyPath != "" {
|
||||
server.TLSCertificate = flags.Filename(tlsCertificatePath)
|
||||
server.TLSCertificateKey = flags.Filename(tlsCertificateKeyPath)
|
||||
// If TLS certificates are provided enforce the HTTPS schema, meaning mcs will redirect
|
||||
// plain HTTP connections to HTTPS server
|
||||
server.EnabledListeners = []string{"http", "https"}
|
||||
server.TLSPort = ctx.Int("tls-port")
|
||||
server.TLSHost = ctx.String("tls-host")
|
||||
// Need to store tls-port, tls-host un config variables so secure.middleware can read from there
|
||||
restapi.TLSPort = fmt.Sprintf("%v", ctx.Int("tls-port"))
|
||||
restapi.TLSHostname = ctx.String("tls-host")
|
||||
restapi.TLSRedirect = "on"
|
||||
}
|
||||
|
||||
server.ConfigureAPI()
|
||||
|
||||
if err := server.Serve(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
80
code_of_conduct.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior, in compliance with the
|
||||
licensing terms applying to the Project developments.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful. However, these actions shall respect the
|
||||
licensing terms of the Project Developments that will always supersede such
|
||||
Code of Conduct.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at dev@min.io. The project team
|
||||
will review and investigate all complaints, and will respond in a way that it deems
|
||||
appropriate to the circumstances. The project team is obligated to maintain
|
||||
confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
This version includes a clarification to ensure that the code of conduct is in
|
||||
compliance with the free software licensing terms of the project.
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
19
go.mod
@@ -1,8 +1,10 @@
|
||||
module github.com/minio/m3/mcs
|
||||
module github.com/minio/mcs
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0
|
||||
github.com/go-openapi/errors v0.19.4
|
||||
github.com/go-openapi/loads v0.19.5
|
||||
@@ -11,11 +13,18 @@ require (
|
||||
github.com/go-openapi/strfmt v0.19.5
|
||||
github.com/go-openapi/swag v0.19.8
|
||||
github.com/go-openapi/validate v0.19.7
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/json-iterator/go v1.1.9
|
||||
github.com/minio/cli v1.22.0
|
||||
github.com/minio/mc v0.0.0-20200403024131-4d36c1f8b856
|
||||
github.com/minio/minio v0.0.0-20200327214830-6f992134a25f
|
||||
github.com/minio/minio-go/v6 v6.0.51-0.20200401083717-eadbcae2a0e6
|
||||
github.com/minio/mc v0.0.0-20200415193718-68b638f2f96c
|
||||
github.com/minio/minio v0.0.0-20200428222040-c3c3e9087bc1
|
||||
github.com/minio/minio-go/v6 v6.0.55-0.20200424204115-7506d2996b22
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||
github.com/unrolled/secure v1.0.7
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
|
||||
)
|
||||
|
||||
82
go.sum
@@ -2,6 +2,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.39.0 h1:UgQP9na6OTfp4dsAiz/eFpFA1C6tPdH5wiRdi19tuMw=
|
||||
cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.5.0 h1:TKXjQSRS0/cCDrP7KvkgU6SmILtF/yV2TOs/02K/WZQ=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
|
||||
git.apache.org/thrift.git v0.13.0 h1:/3bz5WZ+sqYArk7MBBBbDufMxKKOA56/6JO6psDpUDY=
|
||||
git.apache.org/thrift.git v0.13.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
@@ -9,6 +10,7 @@ github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZ
|
||||
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
|
||||
github.com/Azure/azure-storage-blob-go v0.8.0 h1:53qhf0Oxa0nOjgbDeeYPUeyiNmafAFEY95rZLK0Tj6o=
|
||||
github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
|
||||
github.com/Azure/go-autorest v11.7.1+incompatible h1:M2YZIajBBVekV86x0rr1443Lc1F/Ylxb9w+5EtSyX3Q=
|
||||
github.com/Azure/go-autorest v11.7.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
@@ -19,6 +21,7 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Shopify/sarama v1.24.1 h1:svn9vfN3R1Hz21WR2Gj0VW9ehaDGkiOS+VqlIcZOkMI=
|
||||
github.com/Shopify/sarama v1.24.1/go.mod h1:fGP8eQ6PugKEI0iUETYYtnP6d1pH/bdDMTel1X5ajsU=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
@@ -31,6 +34,7 @@ github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 h1:nWDRPC
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
@@ -38,6 +42,7 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/aws/aws-sdk-go v1.20.21/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2 h1:M+TYzBcNIRyzPRg66ndEqUMd7oWDmhvdQmaPC6EZNwM=
|
||||
github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2/go.mod h1:RDu/qcrnpEdJC/p8tx34+YBFqqX71lB7dOX9QE+ZC4M=
|
||||
@@ -47,6 +52,7 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@@ -55,12 +61,16 @@ github.com/cheggaaa/pb v1.0.28/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXH
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
|
||||
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
|
||||
github.com/coredns/coredns v1.4.0 h1:RubBkYmkByUqZWWkjRHvNLnUHgkRVqAWgSMmRFvpE1A=
|
||||
github.com/coredns/coredns v1.4.0/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.12+incompatible h1:pAWNwdf7QiT1zfaWyqCtNZQWCLByQyA3JrSQyuYAqnQ=
|
||||
github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
|
||||
@@ -95,8 +105,10 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ=
|
||||
github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk=
|
||||
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
|
||||
@@ -245,6 +257,8 @@ github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
|
||||
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
|
||||
@@ -262,8 +276,11 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
@@ -284,6 +301,7 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/raft v1.1.1-0.20190703171940-f639636d18e0 h1:msEDtkZC3STZq6Pthlju+jKruuNHXCZAWhghDK47HcM=
|
||||
github.com/hashicorp/raft v1.1.1-0.20190703171940-f639636d18e0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8=
|
||||
github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk=
|
||||
github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU=
|
||||
@@ -338,8 +356,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3 h1:1sl2HmNtqGnDuydLgCJwZIpDLGqZOdwOkcY8WtUl8Cw=
|
||||
github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
|
||||
github.com/kurin/blazer v0.5.4-0.20200327014341-8f90a40f8af7 h1:smZXPopqRVVywwzou4WYWvUbJvSAzIDFizfWElpmAqY=
|
||||
github.com/kurin/blazer v0.5.4-0.20200327014341-8f90a40f8af7/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
|
||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
@@ -383,17 +401,18 @@ github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2
|
||||
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
|
||||
github.com/minio/lsync v1.0.1 h1:AVvILxA976xc27hstd1oR+X9PQG0sPSom1MNb1ImfUs=
|
||||
github.com/minio/lsync v1.0.1/go.mod h1:tCFzfo0dlvdGl70IT4IAK/5Wtgb0/BrTmo/jE8pArKA=
|
||||
github.com/minio/mc v0.0.0-20200403024131-4d36c1f8b856 h1:4uIc5fw4tVr5glh2Mc8GFuiY04pTGEhmihPxJPUvCoU=
|
||||
github.com/minio/mc v0.0.0-20200403024131-4d36c1f8b856/go.mod h1:IDy4dA4aFY6zFFNkYgdUztl0jcYuev/Ubg3NadoaMKc=
|
||||
github.com/minio/minio v0.0.0-20200327214830-6f992134a25f h1:RoOBi0vhXkZqe2b6RTROOsVJUwMqLMoet9r7eL01euo=
|
||||
github.com/minio/minio v0.0.0-20200327214830-6f992134a25f/go.mod h1:BzbIyKUJPp+4f03i2XF7+GsijXnxMakUe5x+lm2WNc8=
|
||||
github.com/minio/minio-go/v6 v6.0.45/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
|
||||
github.com/minio/minio-go/v6 v6.0.51-0.20200319192131-097caa7760c7 h1:WQmYVUDRGdcEWhJeb42/Fn1IO7SBLem173DTE4+jp/E=
|
||||
github.com/minio/minio-go/v6 v6.0.51-0.20200319192131-097caa7760c7/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
|
||||
github.com/minio/minio-go/v6 v6.0.51-0.20200401083717-eadbcae2a0e6 h1:7JhqKjmt1Tv6co7eP/40/xtDLrzzWu1UwUNwQzSvcb0=
|
||||
github.com/minio/minio-go/v6 v6.0.51-0.20200401083717-eadbcae2a0e6/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
|
||||
github.com/minio/parquet-go v0.0.0-20200125064549-a1e49702e174 h1:WYFHZIJ5LTWd4C3CW26jguaBLLDdX7l1/Xa3QSKGkIc=
|
||||
github.com/minio/parquet-go v0.0.0-20200125064549-a1e49702e174/go.mod h1:PXYM9yI2l0YPmxHUXe6mFTmkQcyaVasDshAPTbGpDoo=
|
||||
github.com/minio/mc v0.0.0-20200415193718-68b638f2f96c h1:JLr0fYpCleodj9nGB5hfsJU2zPdnNQKqa2bYsIvPhVw=
|
||||
github.com/minio/mc v0.0.0-20200415193718-68b638f2f96c/go.mod h1:l9PuOY62zT7AQJqopDjfo/T22AIBJSb2yXPVZf4RlhM=
|
||||
github.com/minio/minio v0.0.0-20200415191640-bde0f444dbab h1:9hlqghJl3e3HorXa6ADWsz6ECq790t4iQs07VD9JctM=
|
||||
github.com/minio/minio v0.0.0-20200415191640-bde0f444dbab/go.mod h1:v8oQPMMaTkjDwp5cOz1WCElA4Ik+X+0y4On+VMk0fis=
|
||||
github.com/minio/minio v0.0.0-20200428222040-c3c3e9087bc1 h1:DQjH/653WCerOeZCp3BxAgkmRiQybHYiprbTFs+brgA=
|
||||
github.com/minio/minio v0.0.0-20200428222040-c3c3e9087bc1/go.mod h1:HxnN5FYGIii8ZH6d+LH5UNOSSIonbJkYPqP6gWelVO0=
|
||||
github.com/minio/minio-go/v6 v6.0.53 h1:8jzpwiOzZ5Iz7/goFWqNZRICbyWYShbb5rARjrnSCNI=
|
||||
github.com/minio/minio-go/v6 v6.0.53/go.mod h1:DIvC/IApeHX8q1BAMVCXSXwpmrmM+I+iBvhvztQorfI=
|
||||
github.com/minio/minio-go/v6 v6.0.55-0.20200424204115-7506d2996b22 h1:nZEve4vdUhwHBoV18zRvPDgjL6NYyDJE5QJvz3l9bRs=
|
||||
github.com/minio/minio-go/v6 v6.0.55-0.20200424204115-7506d2996b22/go.mod h1:KQMM+/44DSlSGSQWSfRrAZ12FVMmpWNuX37i2AX0jfI=
|
||||
github.com/minio/parquet-go v0.0.0-20200414234858-838cfa8aae61 h1:pUSI/WKPdd77gcuoJkSzhJ4wdS8OMDOsOu99MtpXEQA=
|
||||
github.com/minio/parquet-go v0.0.0-20200414234858-838cfa8aae61/go.mod h1:4trzEJ7N1nBTd5Tt7OCZT5SEin+WiAXpdJ/WgPkESA8=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/minio/simdjson-go v0.1.5-0.20200303142138-b17fe061ea37 h1:pDeao6M5AEd8hwTtGmE0pVKomlL56JFRa5SiXDZAuJE=
|
||||
@@ -422,14 +441,20 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ
|
||||
github.com/montanaflynn/stats v0.5.0 h1:2EkzeTSqBB4V4bJwWrt5gIIrZmpJBcoIRGS2kWLgzmk=
|
||||
github.com/montanaflynn/stats v0.5.0/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/gnatsd v1.4.1 h1:RconcfDeWpKCD6QIIwiVFcvForlXpWeJP7i5/lDLy44=
|
||||
github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ=
|
||||
github.com/nats-io/go-nats v1.7.2 h1:cJujlwCYR8iMz5ofZSD/p2WLW8FabhkQ2lIEVbSvNSA=
|
||||
github.com/nats-io/go-nats v1.7.2/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0=
|
||||
github.com/nats-io/go-nats-streaming v0.4.4 h1:1I3lkZDRdQYXb+holjdqZ2J6xyekrD06o9Fd8rWlgP4=
|
||||
github.com/nats-io/go-nats-streaming v0.4.4/go.mod h1:gfq4R3c9sKAINOpelo0gn/b9QDMBZnmrttcsNF+lqyo=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
github.com/nats-io/nats-server v1.4.1 h1:Ul1oSOGNV/L8kjr4v6l2f9Yet6WY+LevH1/7cRZ/qyA=
|
||||
github.com/nats-io/nats-server v1.4.1/go.mod h1:c8f/fHd2B6Hgms3LtCaI7y6pC4WD1f4SUxcCud5vhBc=
|
||||
github.com/nats-io/nats-server/v2 v2.1.2 h1:i2Ly0B+1+rzNZHHWtD4ZwKi+OU5l+uQo1iDHZ2PmiIc=
|
||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||
github.com/nats-io/nats-streaming-server v0.14.2 h1:WjQMDqVOwsI0Nb0E+XmEs1LY17CwHRbTCSTWKhw9fXs=
|
||||
github.com/nats-io/nats-streaming-server v0.14.2/go.mod h1:RyqtDJZvMZO66YmyjIYdIvS69zu/wDAkyNWa8PIUa5c=
|
||||
github.com/nats-io/nats.go v1.9.1 h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
@@ -446,6 +471,7 @@ github.com/nsqio/go-nsq v1.0.7 h1:O0pIZJYTf+x7cZBA0UMY8WxFG79lYTURmWzAAh48ljY=
|
||||
github.com/nsqio/go-nsq v1.0.7/go.mod h1:XP5zaUs3pqf+Q71EqUJs3HYfBIqfK6G83WQMdNN+Ito=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
@@ -468,6 +494,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.2-0.20190702141536-6ffe496ea953 h1:oBvgW8IvwF278gJ3R4hH0gD3ZeJxjwBXVIScRR0dRc8=
|
||||
github.com/posener/complete v1.2.2-0.20190702141536-6ffe496ea953/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA=
|
||||
@@ -497,6 +525,7 @@ github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/secure-io/sio-go v0.3.0 h1:QKGb6rGJeiExac9wSWxnWPYo8O8OFN7lxXQvHshX6vo=
|
||||
github.com/secure-io/sio-go v0.3.0/go.mod h1:D3KmXgKETffyYxBdFRN+Hpd2WzhzqS0EQwT3XWsAcBU=
|
||||
@@ -508,8 +537,8 @@ github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e h1:jrZSSgPUDtBeJbGXqgGUeupQH8I+ZvGXfhpIahye2Bc=
|
||||
github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e/go.mod h1:d8hQseuYt4rJoOo21lFzYJdhMjmDqLY++ayArbgYjWI=
|
||||
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 h1:hBSHahWMEgzwRyS6dRpxY0XyjZsHyQ61s084wo5PJe0=
|
||||
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@@ -518,6 +547,8 @@ github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 h1:0ngsPmuP6XIjiFRNFYlvKwSr5zff2v+uPHaffZ6/M4k=
|
||||
@@ -549,9 +580,15 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
|
||||
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/unrolled/secure v1.0.7 h1:BcQHp3iKZyZCKj5gRqwQG+5urnGBF00wGgoPPwtheVQ=
|
||||
github.com/unrolled/secure v1.0.7/go.mod h1:uGc1OcRF8gCVBA+ANksKmvM85Hka6SZtQIbrKc3sHS4=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
|
||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/willf/bloom v2.0.3+incompatible h1:QDacWdqcAUI1MPOwIQZRy9kOR7yxfyEmxX8Wdm2/JPA=
|
||||
github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8=
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
@@ -614,11 +651,9 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
|
||||
@@ -628,6 +663,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -649,8 +685,8 @@ golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200320181252-af34d8274f85 h1:fD99hd4ciR6T3oPhr2EkmuKe9oHixHx9Hj/hND89j3g=
|
||||
golang.org/x/sys v0.0.0-20200320181252-af34d8274f85/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@@ -711,8 +747,8 @@ gopkg.in/h2non/filetype.v1 v1.0.5/go.mod h1:M0yem4rwSX5lLVrkEuRRp2/NinFMD5vgJ4Dl
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.48.0 h1:URjZc+8ugRY5mL5uUeQH/a63JcHwdX9xZaWvmNWD7z8=
|
||||
gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4=
|
||||
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
|
||||
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
|
||||
|
||||
147
models/add_notification_endpoint.go
Normal file
@@ -0,0 +1,147 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// AddNotificationEndpoint add notification endpoint
|
||||
//
|
||||
// swagger:model addNotificationEndpoint
|
||||
type AddNotificationEndpoint struct {
|
||||
|
||||
// account
|
||||
Account string `json:"account,omitempty"`
|
||||
|
||||
// properties
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
|
||||
// service
|
||||
// Enum: [webhook amqp kafka mqtt nats nsq mysql postgres elasticsearch redis]
|
||||
Service string `json:"service,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this add notification endpoint
|
||||
func (m *AddNotificationEndpoint) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateService(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var addNotificationEndpointTypeServicePropEnum []interface{}
|
||||
|
||||
func init() {
|
||||
var res []string
|
||||
if err := json.Unmarshal([]byte(`["webhook","amqp","kafka","mqtt","nats","nsq","mysql","postgres","elasticsearch","redis"]`), &res); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range res {
|
||||
addNotificationEndpointTypeServicePropEnum = append(addNotificationEndpointTypeServicePropEnum, v)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
// AddNotificationEndpointServiceWebhook captures enum value "webhook"
|
||||
AddNotificationEndpointServiceWebhook string = "webhook"
|
||||
|
||||
// AddNotificationEndpointServiceAmqp captures enum value "amqp"
|
||||
AddNotificationEndpointServiceAmqp string = "amqp"
|
||||
|
||||
// AddNotificationEndpointServiceKafka captures enum value "kafka"
|
||||
AddNotificationEndpointServiceKafka string = "kafka"
|
||||
|
||||
// AddNotificationEndpointServiceMqtt captures enum value "mqtt"
|
||||
AddNotificationEndpointServiceMqtt string = "mqtt"
|
||||
|
||||
// AddNotificationEndpointServiceNats captures enum value "nats"
|
||||
AddNotificationEndpointServiceNats string = "nats"
|
||||
|
||||
// AddNotificationEndpointServiceNsq captures enum value "nsq"
|
||||
AddNotificationEndpointServiceNsq string = "nsq"
|
||||
|
||||
// AddNotificationEndpointServiceMysql captures enum value "mysql"
|
||||
AddNotificationEndpointServiceMysql string = "mysql"
|
||||
|
||||
// AddNotificationEndpointServicePostgres captures enum value "postgres"
|
||||
AddNotificationEndpointServicePostgres string = "postgres"
|
||||
|
||||
// AddNotificationEndpointServiceElasticsearch captures enum value "elasticsearch"
|
||||
AddNotificationEndpointServiceElasticsearch string = "elasticsearch"
|
||||
|
||||
// AddNotificationEndpointServiceRedis captures enum value "redis"
|
||||
AddNotificationEndpointServiceRedis string = "redis"
|
||||
)
|
||||
|
||||
// prop value enum
|
||||
func (m *AddNotificationEndpoint) validateServiceEnum(path, location string, value string) error {
|
||||
if err := validate.Enum(path, location, value, addNotificationEndpointTypeServicePropEnum); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AddNotificationEndpoint) validateService(formats strfmt.Registry) error {
|
||||
|
||||
if swag.IsZero(m.Service) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
// value enum
|
||||
if err := m.validateServiceEnum("service", "body", m.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *AddNotificationEndpoint) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *AddNotificationEndpoint) UnmarshalBinary(b []byte) error {
|
||||
var res AddNotificationEndpoint
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
@@ -34,12 +34,13 @@ import (
|
||||
// swagger:model addPolicyRequest
|
||||
type AddPolicyRequest struct {
|
||||
|
||||
// definition
|
||||
Definition string `json:"definition,omitempty"`
|
||||
|
||||
// name
|
||||
// Required: true
|
||||
Name *string `json:"name"`
|
||||
|
||||
// policy
|
||||
// Required: true
|
||||
Policy *string `json:"policy"`
|
||||
}
|
||||
|
||||
// Validate validates this add policy request
|
||||
@@ -50,6 +51,10 @@ func (m *AddPolicyRequest) Validate(formats strfmt.Registry) error {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validatePolicy(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
@@ -65,6 +70,15 @@ func (m *AddPolicyRequest) validateName(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AddPolicyRequest) validatePolicy(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("policy", "body", m.Policy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *AddPolicyRequest) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
|
||||
@@ -38,6 +38,10 @@ type AddUserRequest struct {
|
||||
// Required: true
|
||||
AccessKey *string `json:"accessKey"`
|
||||
|
||||
// groups
|
||||
// Required: true
|
||||
Groups []string `json:"groups"`
|
||||
|
||||
// secret key
|
||||
// Required: true
|
||||
SecretKey *string `json:"secretKey"`
|
||||
@@ -51,6 +55,10 @@ func (m *AddUserRequest) Validate(formats strfmt.Registry) error {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateGroups(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateSecretKey(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
@@ -70,6 +78,15 @@ func (m *AddUserRequest) validateAccessKey(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AddUserRequest) validateGroups(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("groups", "body", m.Groups); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AddUserRequest) validateSecretKey(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("secretKey", "body", m.SecretKey); err != nil {
|
||||
|
||||
98
models/bulk_user_groups.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// BulkUserGroups bulk user groups
|
||||
//
|
||||
// swagger:model bulkUserGroups
|
||||
type BulkUserGroups struct {
|
||||
|
||||
// groups
|
||||
// Required: true
|
||||
Groups []string `json:"groups"`
|
||||
|
||||
// users
|
||||
// Required: true
|
||||
Users []string `json:"users"`
|
||||
}
|
||||
|
||||
// Validate validates this bulk user groups
|
||||
func (m *BulkUserGroups) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateGroups(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateUsers(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *BulkUserGroups) validateGroups(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("groups", "body", m.Groups); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *BulkUserGroups) validateUsers(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("users", "body", m.Users); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *BulkUserGroups) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *BulkUserGroups) UnmarshalBinary(b []byte) error {
|
||||
var res BulkUserGroups
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
98
models/login_oauth2_auth_request.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// LoginOauth2AuthRequest login oauth2 auth request
|
||||
//
|
||||
// swagger:model loginOauth2AuthRequest
|
||||
type LoginOauth2AuthRequest struct {
|
||||
|
||||
// code
|
||||
// Required: true
|
||||
Code *string `json:"code"`
|
||||
|
||||
// state
|
||||
// Required: true
|
||||
State *string `json:"state"`
|
||||
}
|
||||
|
||||
// Validate validates this login oauth2 auth request
|
||||
func (m *LoginOauth2AuthRequest) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateCode(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateState(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *LoginOauth2AuthRequest) validateCode(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("code", "body", m.Code); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *LoginOauth2AuthRequest) validateState(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("state", "body", m.State); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *LoginOauth2AuthRequest) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *LoginOauth2AuthRequest) UnmarshalBinary(b []byte) error {
|
||||
var res LoginOauth2AuthRequest
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
104
models/nofitication_service.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// NofiticationService nofitication service
|
||||
//
|
||||
// swagger:model nofiticationService
|
||||
type NofiticationService string
|
||||
|
||||
const (
|
||||
|
||||
// NofiticationServiceWebhook captures enum value "webhook"
|
||||
NofiticationServiceWebhook NofiticationService = "webhook"
|
||||
|
||||
// NofiticationServiceAmqp captures enum value "amqp"
|
||||
NofiticationServiceAmqp NofiticationService = "amqp"
|
||||
|
||||
// NofiticationServiceKafka captures enum value "kafka"
|
||||
NofiticationServiceKafka NofiticationService = "kafka"
|
||||
|
||||
// NofiticationServiceMqtt captures enum value "mqtt"
|
||||
NofiticationServiceMqtt NofiticationService = "mqtt"
|
||||
|
||||
// NofiticationServiceNats captures enum value "nats"
|
||||
NofiticationServiceNats NofiticationService = "nats"
|
||||
|
||||
// NofiticationServiceNsq captures enum value "nsq"
|
||||
NofiticationServiceNsq NofiticationService = "nsq"
|
||||
|
||||
// NofiticationServiceMysql captures enum value "mysql"
|
||||
NofiticationServiceMysql NofiticationService = "mysql"
|
||||
|
||||
// NofiticationServicePostgres captures enum value "postgres"
|
||||
NofiticationServicePostgres NofiticationService = "postgres"
|
||||
|
||||
// NofiticationServiceElasticsearch captures enum value "elasticsearch"
|
||||
NofiticationServiceElasticsearch NofiticationService = "elasticsearch"
|
||||
|
||||
// NofiticationServiceRedis captures enum value "redis"
|
||||
NofiticationServiceRedis NofiticationService = "redis"
|
||||
)
|
||||
|
||||
// for schema
|
||||
var nofiticationServiceEnum []interface{}
|
||||
|
||||
func init() {
|
||||
var res []NofiticationService
|
||||
if err := json.Unmarshal([]byte(`["webhook","amqp","kafka","mqtt","nats","nsq","mysql","postgres","elasticsearch","redis"]`), &res); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range res {
|
||||
nofiticationServiceEnum = append(nofiticationServiceEnum, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (m NofiticationService) validateNofiticationServiceEnum(path, location string, value NofiticationService) error {
|
||||
if err := validate.Enum(path, location, value, nofiticationServiceEnum); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates this nofitication service
|
||||
func (m NofiticationService) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
// value enum
|
||||
if err := m.validateNofiticationServiceEnum("", "body", m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
97
models/notif_endpoint_response.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// NotifEndpointResponse notif endpoint response
|
||||
//
|
||||
// swagger:model notifEndpointResponse
|
||||
type NotifEndpointResponse struct {
|
||||
|
||||
// notification endpoints
|
||||
NotificationEndpoints []*NotificationEndpointItem `json:"notification_endpoints"`
|
||||
}
|
||||
|
||||
// Validate validates this notif endpoint response
|
||||
func (m *NotifEndpointResponse) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateNotificationEndpoints(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NotifEndpointResponse) validateNotificationEndpoints(formats strfmt.Registry) error {
|
||||
|
||||
if swag.IsZero(m.NotificationEndpoints) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.NotificationEndpoints); i++ {
|
||||
if swag.IsZero(m.NotificationEndpoints[i]) { // not required
|
||||
continue
|
||||
}
|
||||
|
||||
if m.NotificationEndpoints[i] != nil {
|
||||
if err := m.NotificationEndpoints[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("notification_endpoints" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *NotifEndpointResponse) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *NotifEndpointResponse) UnmarshalBinary(b []byte) error {
|
||||
var res NotifEndpointResponse
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
129
models/notification_delete_request.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// NotificationDeleteRequest notification delete request
|
||||
//
|
||||
// swagger:model notificationDeleteRequest
|
||||
type NotificationDeleteRequest struct {
|
||||
|
||||
// filter specific type of event. Defaults to all event (default: '[put,delete,get]')
|
||||
// Required: true
|
||||
// Min Length: 1
|
||||
Events []NotificationEventType `json:"events"`
|
||||
|
||||
// filter event associated to the specified prefix
|
||||
// Required: true
|
||||
Prefix *string `json:"prefix"`
|
||||
|
||||
// filter event associated to the specified suffix
|
||||
// Required: true
|
||||
Suffix *string `json:"suffix"`
|
||||
}
|
||||
|
||||
// Validate validates this notification delete request
|
||||
func (m *NotificationDeleteRequest) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateEvents(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validatePrefix(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateSuffix(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NotificationDeleteRequest) validateEvents(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("events", "body", m.Events); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.Events); i++ {
|
||||
|
||||
if err := m.Events[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("events" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NotificationDeleteRequest) validatePrefix(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("prefix", "body", m.Prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NotificationDeleteRequest) validateSuffix(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("suffix", "body", m.Suffix); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *NotificationDeleteRequest) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *NotificationDeleteRequest) UnmarshalBinary(b []byte) error {
|
||||
var res NotificationDeleteRequest
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
114
models/notification_endpoint.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// NotificationEndpoint notification endpoint
|
||||
//
|
||||
// swagger:model notificationEndpoint
|
||||
type NotificationEndpoint struct {
|
||||
|
||||
// account id
|
||||
// Required: true
|
||||
AccountID *string `json:"account_id"`
|
||||
|
||||
// properties
|
||||
// Required: true
|
||||
Properties map[string]string `json:"properties"`
|
||||
|
||||
// service
|
||||
// Required: true
|
||||
Service NofiticationService `json:"service"`
|
||||
}
|
||||
|
||||
// Validate validates this notification endpoint
|
||||
func (m *NotificationEndpoint) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateAccountID(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateProperties(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateService(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NotificationEndpoint) validateAccountID(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("account_id", "body", m.AccountID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NotificationEndpoint) validateProperties(formats strfmt.Registry) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NotificationEndpoint) validateService(formats strfmt.Registry) error {
|
||||
|
||||
if err := m.Service.Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("service")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *NotificationEndpoint) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *NotificationEndpoint) UnmarshalBinary(b []byte) error {
|
||||
var res NotificationEndpoint
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
92
models/notification_endpoint_item.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// NotificationEndpointItem notification endpoint item
|
||||
//
|
||||
// swagger:model notificationEndpointItem
|
||||
type NotificationEndpointItem struct {
|
||||
|
||||
// account id
|
||||
AccountID string `json:"account_id,omitempty"`
|
||||
|
||||
// service
|
||||
Service NofiticationService `json:"service,omitempty"`
|
||||
|
||||
// status
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this notification endpoint item
|
||||
func (m *NotificationEndpointItem) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateService(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NotificationEndpointItem) validateService(formats strfmt.Registry) error {
|
||||
|
||||
if swag.IsZero(m.Service) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := m.Service.Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("service")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *NotificationEndpointItem) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *NotificationEndpointItem) UnmarshalBinary(b []byte) error {
|
||||
var res NotificationEndpointItem
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
@@ -23,9 +23,6 @@ package models
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
@@ -38,49 +35,12 @@ type Policy struct {
|
||||
// name
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// statements
|
||||
Statements []*Statement `json:"statements"`
|
||||
|
||||
// version
|
||||
Version string `json:"version,omitempty"`
|
||||
// policy
|
||||
Policy string `json:"policy,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this policy
|
||||
func (m *Policy) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateStatements(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Policy) validateStatements(formats strfmt.Registry) error {
|
||||
|
||||
if swag.IsZero(m.Statements) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.Statements); i++ {
|
||||
if swag.IsZero(m.Statements[i]) { // not required
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Statements[i] != nil {
|
||||
if err := m.Statements[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("statements" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
72
models/policy_statement.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// PolicyStatement policy statement
|
||||
//
|
||||
// swagger:model policyStatement
|
||||
type PolicyStatement struct {
|
||||
|
||||
// action
|
||||
Action []string `json:"Action"`
|
||||
|
||||
// condition
|
||||
Condition string `json:"Condition,omitempty"`
|
||||
|
||||
// effect
|
||||
Effect string `json:"Effect,omitempty"`
|
||||
|
||||
// resource
|
||||
Resource string `json:"Resource,omitempty"`
|
||||
|
||||
// sid
|
||||
Sid string `json:"Sid,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this policy statement
|
||||
func (m *PolicyStatement) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *PolicyStatement) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *PolicyStatement) UnmarshalBinary(b []byte) error {
|
||||
var res PolicyStatement
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
62
models/policy_statements.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// PolicyStatements policy statements
|
||||
//
|
||||
// swagger:model policyStatements
|
||||
type PolicyStatements []*PolicyStatement
|
||||
|
||||
// Validate validates this policy statements
|
||||
func (m PolicyStatements) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
for i := 0; i < len(m); i++ {
|
||||
if swag.IsZero(m[i]) { // not required
|
||||
continue
|
||||
}
|
||||
|
||||
if m[i] != nil {
|
||||
if err := m[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName(strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -27,28 +27,22 @@ import (
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// Statement statement
|
||||
// ServiceAccount service account
|
||||
//
|
||||
// swagger:model statement
|
||||
type Statement struct {
|
||||
// swagger:model serviceAccount
|
||||
type ServiceAccount struct {
|
||||
|
||||
// actions
|
||||
Actions []string `json:"actions"`
|
||||
|
||||
// effect
|
||||
Effect string `json:"effect,omitempty"`
|
||||
|
||||
// resources
|
||||
Resources []string `json:"resources"`
|
||||
// policy to be applied to the Service Account if any
|
||||
Policy string `json:"policy,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this statement
|
||||
func (m *Statement) Validate(formats strfmt.Registry) error {
|
||||
// Validate validates this service account
|
||||
func (m *ServiceAccount) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *Statement) MarshalBinary() ([]byte, error) {
|
||||
func (m *ServiceAccount) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -56,8 +50,8 @@ func (m *Statement) MarshalBinary() ([]byte, error) {
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *Statement) UnmarshalBinary(b []byte) error {
|
||||
var res Statement
|
||||
func (m *ServiceAccount) UnmarshalBinary(b []byte) error {
|
||||
var res ServiceAccount
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
63
models/service_account_creds.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// ServiceAccountCreds service account creds
|
||||
//
|
||||
// swagger:model serviceAccountCreds
|
||||
type ServiceAccountCreds struct {
|
||||
|
||||
// access key
|
||||
AccessKey string `json:"accessKey,omitempty"`
|
||||
|
||||
// secret key
|
||||
SecretKey string `json:"secretKey,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this service account creds
|
||||
func (m *ServiceAccountCreds) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *ServiceAccountCreds) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *ServiceAccountCreds) UnmarshalBinary(b []byte) error {
|
||||
var res ServiceAccountCreds
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
98
models/update_user.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// UpdateUser update user
|
||||
//
|
||||
// swagger:model updateUser
|
||||
type UpdateUser struct {
|
||||
|
||||
// groups
|
||||
// Required: true
|
||||
Groups []string `json:"groups"`
|
||||
|
||||
// status
|
||||
// Required: true
|
||||
Status *string `json:"status"`
|
||||
}
|
||||
|
||||
// Validate validates this update user
|
||||
func (m *UpdateUser) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateGroups(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateStatus(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UpdateUser) validateGroups(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("groups", "body", m.Groups); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UpdateUser) validateStatus(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("status", "body", m.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *UpdateUser) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *UpdateUser) UnmarshalBinary(b []byte) error {
|
||||
var res UpdateUser
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
81
models/update_user_groups.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// UpdateUserGroups update user groups
|
||||
//
|
||||
// swagger:model updateUserGroups
|
||||
type UpdateUserGroups struct {
|
||||
|
||||
// groups
|
||||
// Required: true
|
||||
Groups []string `json:"groups"`
|
||||
}
|
||||
|
||||
// Validate validates this update user groups
|
||||
func (m *UpdateUserGroups) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateGroups(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UpdateUserGroups) validateGroups(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("groups", "body", m.Groups); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *UpdateUserGroups) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *UpdateUserGroups) UnmarshalBinary(b []byte) error {
|
||||
var res UpdateUserGroups
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
49
pkg/auth/idp.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/minio/mcs/pkg/auth/idp/oauth2"
|
||||
)
|
||||
|
||||
// IdentityProviderClient interface with all functions to be implemented
|
||||
// by mock when testing, it should include all IdentityProviderClient respective api calls
|
||||
// that are used within this project.
|
||||
type IdentityProviderClient interface {
|
||||
VerifyIdentity(ctx context.Context, code, state string) (*oauth2.User, error)
|
||||
GenerateLoginURL() string
|
||||
}
|
||||
|
||||
// Interface implementation
|
||||
//
|
||||
// Define the structure of a IdentityProvider Client and define the functions that are actually used
|
||||
// during the authentication flow.
|
||||
type IdentityProvider struct {
|
||||
Client IdentityProviderClient
|
||||
}
|
||||
|
||||
// VerifyIdentity will verify the user identity against the idp using the authorization code flow
|
||||
func (c IdentityProvider) VerifyIdentity(ctx context.Context, code, state string) (*oauth2.User, error) {
|
||||
return c.Client.VerifyIdentity(ctx, code, state)
|
||||
}
|
||||
|
||||
// GenerateLoginURL returns a new URL used by the user to login against the idp
|
||||
func (c IdentityProvider) GenerateLoginURL() string {
|
||||
return c.Client.GenerateLoginURL()
|
||||
}
|
||||
71
pkg/auth/idp/oauth2/config.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package oauth2 contains all the necessary configurations to initialize the
|
||||
// idp communication using oauth2 protocol
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"github.com/minio/mcs/pkg/auth/utils"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
)
|
||||
|
||||
func GetIdpURL() string {
|
||||
return env.Get(McsIdpURL, "")
|
||||
}
|
||||
|
||||
func GetIdpClientID() string {
|
||||
return env.Get(McsIdpClientID, "")
|
||||
}
|
||||
|
||||
func GetIdpSecret() string {
|
||||
return env.Get(McsIdpSecret, "")
|
||||
}
|
||||
|
||||
// Public endpoint used by the identity oidcProvider when redirecting the user after identity verification
|
||||
func GetIdpCallbackURL() string {
|
||||
return env.Get(McsIdpCallbackURL, "")
|
||||
}
|
||||
|
||||
func GetIdpAdminRoles() string {
|
||||
return env.Get(McsIdpAdminRoles, "")
|
||||
}
|
||||
|
||||
func IsIdpEnabled() bool {
|
||||
return GetIdpURL() != "" &&
|
||||
GetIdpClientID() != "" &&
|
||||
GetIdpSecret() != "" &&
|
||||
GetIdpCallbackURL() != ""
|
||||
}
|
||||
|
||||
var defaultPassphraseForIdpHmac = utils.RandomCharString(64)
|
||||
|
||||
// GetPassphraseForIdpHmac returns passphrase for the pbkdf2 function used to sign the oauth2 state parameter
|
||||
func getPassphraseForIdpHmac() string {
|
||||
return env.Get(McsIdpHmacPassphrase, defaultPassphraseForIdpHmac)
|
||||
}
|
||||
|
||||
var defaultSaltForIdpHmac = utils.RandomCharString(64)
|
||||
|
||||
// GetSaltForIdpHmac returns salt for the pbkdf2 function used to sign the oauth2 state parameter
|
||||
func getSaltForIdpHmac() string {
|
||||
return env.Get(McsIdpHmacSalt, defaultSaltForIdpHmac)
|
||||
}
|
||||
|
||||
// GetSaltForIdpHmac returns the policy to be assigned to the users authenticating via an IDP
|
||||
func GetIDPPolicyForUser() string {
|
||||
return env.Get(McsIdpPolicyUser, "mcsAdmin")
|
||||
}
|
||||
29
pkg/auth/idp/oauth2/const.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package oauth2
|
||||
|
||||
const (
|
||||
// const for idp configuration
|
||||
McsIdpURL = "MCS_IDP_URL"
|
||||
McsIdpClientID = "MCS_IDP_CLIENT_ID"
|
||||
McsIdpSecret = "MCS_IDP_SECRET"
|
||||
McsIdpCallbackURL = "MCS_IDP_CALLBACK"
|
||||
McsIdpAdminRoles = "MCS_IDP_ADMIN_ROLES"
|
||||
McsIdpHmacPassphrase = "MCS_IDP_HMAC_PASSPHRASE"
|
||||
McsIdpHmacSalt = "MCS_IDP_HMAC_SALT"
|
||||
McsIdpPolicyUser = "MCS_IDP_POLICY_USER"
|
||||
)
|
||||
229
pkg/auth/idp/oauth2/provider.go
Normal file
@@ -0,0 +1,229 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/minio/mcs/pkg/auth/utils"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
xoauth2 "golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
errGeneric = errors.New("an error occurred, please try again")
|
||||
)
|
||||
|
||||
type Configuration interface {
|
||||
Exchange(ctx context.Context, code string, opts ...xoauth2.AuthCodeOption) (*xoauth2.Token, error)
|
||||
AuthCodeURL(state string, opts ...xoauth2.AuthCodeOption) string
|
||||
PasswordCredentialsToken(ctx context.Context, username string, password string) (*xoauth2.Token, error)
|
||||
Client(ctx context.Context, t *xoauth2.Token) *http.Client
|
||||
TokenSource(ctx context.Context, t *xoauth2.Token) xoauth2.TokenSource
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
xoauth2.Config
|
||||
}
|
||||
|
||||
func (ac Config) Exchange(ctx context.Context, code string, opts ...xoauth2.AuthCodeOption) (*xoauth2.Token, error) {
|
||||
return ac.Exchange(ctx, code, opts...)
|
||||
}
|
||||
|
||||
func (ac Config) AuthCodeURL(state string, opts ...xoauth2.AuthCodeOption) string {
|
||||
return ac.AuthCodeURL(state, opts...)
|
||||
}
|
||||
|
||||
func (ac Config) PasswordCredentialsToken(ctx context.Context, username string, password string) (*xoauth2.Token, error) {
|
||||
return ac.PasswordCredentialsToken(ctx, username, password)
|
||||
}
|
||||
|
||||
func (ac Config) Client(ctx context.Context, t *xoauth2.Token) *http.Client {
|
||||
return ac.Client(ctx, t)
|
||||
}
|
||||
|
||||
func (ac Config) TokenSource(ctx context.Context, t *xoauth2.Token) xoauth2.TokenSource {
|
||||
return ac.TokenSource(ctx, t)
|
||||
}
|
||||
|
||||
// Provider is a wrapper of the oauth2 configuration and the oidc provider
|
||||
type Provider struct {
|
||||
// oauth2Config is an interface configuration that contains the following fields
|
||||
// Config{
|
||||
// ClientID string
|
||||
// ClientSecret string
|
||||
// RedirectURL string
|
||||
// Endpoint oauth2.Endpoint
|
||||
// Scopes []string
|
||||
// }
|
||||
// - ClientID is the public identifier for this application
|
||||
// - ClientSecret is a shared secret between this application and the authorization server
|
||||
// - RedirectURL is the URL to redirect users going through
|
||||
// the OAuth flow, after the resource owner's URLs.
|
||||
// - Endpoint contains the resource server's token endpoint
|
||||
// URLs. These are constants specific to each server and are
|
||||
// often available via site-specific packages, such as
|
||||
// google.Endpoint or github.Endpoint.
|
||||
// - Scopes specifies optional requested permissions.
|
||||
ClientID string
|
||||
oauth2Config Configuration
|
||||
oidcProvider *oidc.Provider
|
||||
}
|
||||
|
||||
// derivedKey is the key used to compute the HMAC for signing the oauth state parameter
|
||||
// its derived using pbkdf on MCS_IDP_HMAC_PASSPHRASE with MCS_IDP_HMAC_SALT
|
||||
var derivedKey = pbkdf2.Key([]byte(getPassphraseForIdpHmac()), []byte(getSaltForIdpHmac()), 4096, 32, sha1.New)
|
||||
|
||||
// NewOauth2ProviderClient instantiates a new oauth2 client using the configured credentials
|
||||
// it returns a *Provider object that contains the necessary configuration to initiate an
|
||||
// oauth2 authentication flow
|
||||
func NewOauth2ProviderClient(ctx context.Context, scopes []string) (*Provider, error) {
|
||||
provider, err := oidc.NewProvider(ctx, GetIdpURL())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If provided scopes are empty we use a default list
|
||||
if len(scopes) == 0 {
|
||||
scopes = []string{oidc.ScopeOpenID, "profile", "app_metadata", "user_metadata", "email"}
|
||||
}
|
||||
client := new(Provider)
|
||||
config := xoauth2.Config{
|
||||
ClientID: GetIdpClientID(),
|
||||
ClientSecret: GetIdpSecret(),
|
||||
RedirectURL: GetIdpCallbackURL(),
|
||||
Endpoint: provider.Endpoint(),
|
||||
Scopes: scopes,
|
||||
}
|
||||
client.oauth2Config = &config
|
||||
client.oidcProvider = provider
|
||||
client.ClientID = GetIdpClientID()
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
type User struct {
|
||||
AppMetadata map[string]interface{} `json:"app_metadata"`
|
||||
Blocked bool `json:"blocked"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
FamilyName string `json:"family_name"`
|
||||
GivenName string `json:"given_name"`
|
||||
Identities []interface{} `json:"identities"`
|
||||
LastIP string `json:"last_ip"`
|
||||
LastLogin string `json:"last_login"`
|
||||
LastPasswordReset string `json:"last_password_reset"`
|
||||
LoginsCount int `json:"logins_count"`
|
||||
Mltifactor string `json:"multifactor"`
|
||||
Name string `json:"name"`
|
||||
Nickname string `json:"nickname"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
Picture string `json:"picture"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
UserID string `json:"user_id"`
|
||||
UserMetadata map[string]interface{} `json:"user_metadata"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// VerifyIdentity will contact the configured IDP and validate the user identity based on the authorization code
|
||||
func (client *Provider) VerifyIdentity(ctx context.Context, code, state string) (*User, error) {
|
||||
// verify the provided state is valid (prevents CSRF attacks)
|
||||
if !validateOauth2State(state) {
|
||||
return nil, errGeneric
|
||||
}
|
||||
// verify the authorization code against the identity oidcProvider
|
||||
// idp will return a token in exchange
|
||||
token, err := client.oauth2Config.Exchange(ctx, code)
|
||||
if err != nil {
|
||||
log.Println("Failed to verify authorization code", err)
|
||||
return nil, errGeneric
|
||||
}
|
||||
// extract and check id_token field is provided in the response
|
||||
rawIDToken, ok := token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
log.Println("No id_token field in oauth2 token")
|
||||
return nil, errGeneric
|
||||
}
|
||||
config := &oidc.Config{
|
||||
ClientID: client.ClientID,
|
||||
}
|
||||
idToken, err := client.oidcProvider.Verifier(config).Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
log.Println("Failed to verify ID token", err)
|
||||
return nil, errGeneric
|
||||
}
|
||||
var profile User
|
||||
// Populate the profile object using the claims included in the token
|
||||
if err := idToken.Claims(&profile); err != nil {
|
||||
log.Println("Failed to read profile information", err)
|
||||
return nil, errGeneric
|
||||
}
|
||||
return &profile, nil
|
||||
}
|
||||
|
||||
// validateOauth2State validates the provided state was originated using the same
|
||||
// instance (or one configured using the same secrets) of MCS, this is basically used to prevent CSRF attacks
|
||||
// https://security.stackexchange.com/questions/20187/oauth2-cross-site-request-forgery-and-state-parameter
|
||||
func validateOauth2State(state string) bool {
|
||||
// state contains a base64 encoded string that may ends with "==", the browser encodes that to "%3D%3D"
|
||||
// query unescape is need it before trying to decode the base64 string
|
||||
encodedMessage, err := url.QueryUnescape(state)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false
|
||||
}
|
||||
// decode the state parameter value
|
||||
message, err := base64.StdEncoding.DecodeString(encodedMessage)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false
|
||||
}
|
||||
s := strings.Split(string(message), ":")
|
||||
// Validate that the decoded message has the right format "message:hmac"
|
||||
if len(s) != 2 {
|
||||
return false
|
||||
}
|
||||
// extract the state and hmac
|
||||
incomingState, incomingHmac := s[0], s[1]
|
||||
// validate that hmac(incomingState + pbkdf2(secret, salt)) == incomingHmac
|
||||
return utils.ComputeHmac256(incomingState, derivedKey) == incomingHmac
|
||||
}
|
||||
|
||||
// GetRandomStateWithHMAC computes message + hmac(message, pbkdf2(key, salt)) to be used as state during the oauth authorization
|
||||
func GetRandomStateWithHMAC(length int) string {
|
||||
state := utils.RandomCharString(length)
|
||||
hmac := utils.ComputeHmac256(state, derivedKey)
|
||||
return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", state, hmac)))
|
||||
}
|
||||
|
||||
// GenerateLoginURL returns a new login URL based on the configured IDP
|
||||
func (client *Provider) GenerateLoginURL() string {
|
||||
// generates random state and sign it using HMAC256
|
||||
state := GetRandomStateWithHMAC(25)
|
||||
loginURL := client.oauth2Config.AuthCodeURL(state)
|
||||
return strings.TrimSpace(loginURL)
|
||||
}
|
||||
98
pkg/auth/idp/oauth2/provider_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type Oauth2configMock struct{}
|
||||
|
||||
var oauth2ConfigExchangeMock func(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error)
|
||||
var oauth2ConfigAuthCodeURLMock func(state string, opts ...oauth2.AuthCodeOption) string
|
||||
var oauth2ConfigPasswordCredentialsTokenMock func(ctx context.Context, username string, password string) (*oauth2.Token, error)
|
||||
var oauth2ConfigClientMock func(ctx context.Context, t *oauth2.Token) *http.Client
|
||||
var oauth2ConfigokenSourceMock func(ctx context.Context, t *oauth2.Token) oauth2.TokenSource
|
||||
|
||||
func (ac Oauth2configMock) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
|
||||
return oauth2ConfigExchangeMock(ctx, code, opts...)
|
||||
}
|
||||
|
||||
func (ac Oauth2configMock) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
|
||||
return oauth2ConfigAuthCodeURLMock(state, opts...)
|
||||
}
|
||||
|
||||
func (ac Oauth2configMock) PasswordCredentialsToken(ctx context.Context, username string, password string) (*oauth2.Token, error) {
|
||||
return oauth2ConfigPasswordCredentialsTokenMock(ctx, username, password)
|
||||
}
|
||||
|
||||
func (ac Oauth2configMock) Client(ctx context.Context, t *oauth2.Token) *http.Client {
|
||||
return oauth2ConfigClientMock(ctx, t)
|
||||
}
|
||||
|
||||
func (ac Oauth2configMock) TokenSource(ctx context.Context, t *oauth2.Token) oauth2.TokenSource {
|
||||
return oauth2ConfigokenSourceMock(ctx, t)
|
||||
}
|
||||
|
||||
func TestGenerateLoginURL(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
oauth2Provider := Provider{
|
||||
oauth2Config: Oauth2configMock{},
|
||||
oidcProvider: &oidc.Provider{},
|
||||
}
|
||||
// Test-1 : GenerateLoginURL() generates URL correctly with provided state
|
||||
oauth2ConfigAuthCodeURLMock = func(state string, opts ...oauth2.AuthCodeOption) string {
|
||||
// Internally we are testing the private method getRandomStateWithHMAC, this function should always returns
|
||||
// a non-empty string
|
||||
return state
|
||||
}
|
||||
url := oauth2Provider.GenerateLoginURL()
|
||||
funcAssert.NotEqual("", url)
|
||||
}
|
||||
|
||||
func TestVerifyIdentity(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
funcAssert := assert.New(t)
|
||||
// mock data
|
||||
oauth2Provider := Provider{
|
||||
oauth2Config: Oauth2configMock{},
|
||||
oidcProvider: &oidc.Provider{},
|
||||
}
|
||||
// Test-1 : VerifyIdentity() should fail because of bad state token
|
||||
_, err := oauth2Provider.VerifyIdentity(ctx, "AAABBBCCCDDDEEEFFF", "badtoken")
|
||||
funcAssert.NotNil(err)
|
||||
// Test-2 : VerifyIdentity() should fail because no id_token is provided by the idp
|
||||
oauth2ConfigExchangeMock = func(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
|
||||
return &oauth2.Token{}, nil
|
||||
}
|
||||
state := GetRandomStateWithHMAC(32)
|
||||
code := "AAABBBCCCDDDEEEFFF"
|
||||
_, err = oauth2Provider.VerifyIdentity(ctx, code, state)
|
||||
funcAssert.NotNil(err)
|
||||
// Test-3 : VerifyIdentity() should fail because no id_token is provided by the idp
|
||||
// TODO
|
||||
// Test-4 : VerifyIdentity() should fail because oidcProvider.Verifier returned an error
|
||||
// TODO
|
||||
// Test-5 : VerifyIdentity() should fail because idToken.Claims contains invalid fields
|
||||
// TODO
|
||||
}
|
||||
181
pkg/auth/jwt.go
Normal file
@@ -0,0 +1,181 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
xjwt "github.com/minio/mcs/pkg/auth/jwt"
|
||||
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||
"github.com/minio/minio/cmd"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
var (
|
||||
errAuthentication = errors.New("Authentication failed, check your access credentials")
|
||||
errNoAuthToken = errors.New("JWT token missing")
|
||||
errReadingToken = errors.New("JWT internal data is malformed")
|
||||
errClaimsFormat = errors.New("encrypted jwt claims not in the right format")
|
||||
)
|
||||
|
||||
// derivedKey is the key used to encrypt the JWT claims, its derived using pbkdf on MCS_PBKDF_PASSPHRASE with MCS_PBKDF_SALT
|
||||
var derivedKey = pbkdf2.Key([]byte(xjwt.GetPBKDFPassphrase()), []byte(xjwt.GetPBKDFSalt()), 4096, 32, sha1.New)
|
||||
|
||||
// IsJWTValid returns true or false depending if the provided jwt is valid or not
|
||||
func IsJWTValid(token string) bool {
|
||||
_, err := JWTAuthenticate(token)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// DecryptedClaims claims struct for decrypted credentials
|
||||
type DecryptedClaims struct {
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
SessionToken string
|
||||
}
|
||||
|
||||
// JWTAuthenticate takes a jwt, decode it, extract claims and validate the signature
|
||||
// if the jwt claims.Data is valid we proceed to decrypt the information inside
|
||||
//
|
||||
// returns claims after validation in the following format:
|
||||
//
|
||||
// type DecryptedClaims struct {
|
||||
// AccessKeyID
|
||||
// SecretAccessKey
|
||||
// SessionToken
|
||||
// }
|
||||
func JWTAuthenticate(token string) (*DecryptedClaims, error) {
|
||||
if token == "" {
|
||||
return nil, errNoAuthToken
|
||||
}
|
||||
// initialize claims object
|
||||
claims := xjwt.NewMapClaims()
|
||||
// populate the claims object
|
||||
if err := xjwt.ParseWithClaims(token, claims); err != nil {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
// decrypt the claims.Data field
|
||||
claimTokens, err := decryptClaims(claims.Data)
|
||||
if err != nil {
|
||||
// we print decryption token error information for debugging purposes
|
||||
log.Println(err)
|
||||
// we return a generic error that doesn't give any information to attackers
|
||||
return nil, errReadingToken
|
||||
}
|
||||
// claimsTokens contains the decrypted STS claims
|
||||
return claimTokens, nil
|
||||
}
|
||||
|
||||
// NewJWTWithClaimsForClient generates a new jwt with claims based on the provided STS credentials, first
|
||||
// encrypts the claims and the sign them
|
||||
func NewJWTWithClaimsForClient(credentials *credentials.Value, audience string) (string, error) {
|
||||
if credentials != nil {
|
||||
encryptedClaims, err := encryptClaims(credentials.AccessKeyID, credentials.SecretAccessKey, credentials.SessionToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
claims := xjwt.NewStandardClaims()
|
||||
claims.SetExpiry(cmd.UTCNow().Add(xjwt.GetMcsSTSAndJWTDurationTime()))
|
||||
claims.SetSubject(uuid.NewV4().String())
|
||||
claims.SetData(encryptedClaims)
|
||||
claims.SetAudience(audience)
|
||||
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
|
||||
return jwt.SignedString([]byte(xjwt.GetHmacJWTSecret()))
|
||||
}
|
||||
return "", errors.New("provided credentials are empty")
|
||||
}
|
||||
|
||||
// encryptClaims() receives the 3 STS claims, concatenate them and encrypt them using AES-GCM
|
||||
// returns a base64 encoded ciphertext
|
||||
func encryptClaims(accessKeyID, secretAccessKey, sessionToken string) (string, error) {
|
||||
payload := []byte(fmt.Sprintf("%s:%s:%s", accessKeyID, secretAccessKey, sessionToken))
|
||||
ciphertext, err := encrypt(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
// decryptClaims() receives base64 encoded ciphertext, decode it, decrypt it (AES-GCM) and produces a *DecryptedClaims object
|
||||
func decryptClaims(ciphertext string) (*DecryptedClaims, error) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, errClaimsFormat
|
||||
}
|
||||
plaintext, err := decrypt(decoded)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, errClaimsFormat
|
||||
}
|
||||
s := strings.Split(string(plaintext), ":")
|
||||
// Validate that the decrypted string has the right format "accessKeyID:secretAccessKey:sessionToken"
|
||||
if len(s) != 3 {
|
||||
return nil, errClaimsFormat
|
||||
}
|
||||
accessKeyID, secretAccessKey, sessionToken := s[0], s[1], s[2]
|
||||
return &DecryptedClaims{
|
||||
AccessKeyID: accessKeyID,
|
||||
SecretAccessKey: secretAccessKey,
|
||||
SessionToken: sessionToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Encrypt a blob of data using AEAD (AES-GCM) with a pbkdf2 derived key
|
||||
func encrypt(plaintext []byte) ([]byte, error) {
|
||||
block, _ := aes.NewCipher(derivedKey)
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cipherText := gcm.Seal(nonce, nonce, plaintext, nil)
|
||||
return cipherText, nil
|
||||
}
|
||||
|
||||
// Decrypts a blob of data using AEAD (AES-GCM) with a pbkdf2 derived key
|
||||
func decrypt(data []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(derivedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nonceSize := gcm.NonceSize()
|
||||
nonce, cipherText := data[:nonceSize], data[nonceSize:]
|
||||
plaintext, err := gcm.Open(nil, nonce, cipherText, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
63
pkg/auth/jwt/config.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/minio/mcs/pkg/auth/utils"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
)
|
||||
|
||||
// defaultHmacJWTPassphrase will be used by default if application is not configured with a custom MCS_HMAC_JWT_SECRET secret
|
||||
var defaultHmacJWTPassphrase = utils.RandomCharString(64)
|
||||
|
||||
// GetHmacJWTSecret returns the 64 bytes secret used for signing the generated JWT for the application
|
||||
func GetHmacJWTSecret() string {
|
||||
return env.Get(McsHmacJWTSecret, defaultHmacJWTPassphrase)
|
||||
}
|
||||
|
||||
// McsSTSAndJWTDurationSeconds returns the default session duration for the STS requested tokens and the generated JWTs.
|
||||
// Ideally both values should match so jwt and Minio sts sessions expires at the same time.
|
||||
func GetMcsSTSAndJWTDurationInSeconds() int {
|
||||
duration, err := strconv.Atoi(env.Get(McsSTSAndJWTDurationSeconds, "3600"))
|
||||
if err != nil {
|
||||
duration = 3600
|
||||
}
|
||||
return duration
|
||||
}
|
||||
|
||||
// GetMcsSTSAndJWTDurationTime returns GetMcsSTSAndJWTDurationInSeconds in duration format
|
||||
func GetMcsSTSAndJWTDurationTime() time.Duration {
|
||||
duration := GetMcsSTSAndJWTDurationInSeconds()
|
||||
return time.Duration(duration) * time.Second
|
||||
}
|
||||
|
||||
var defaultPBKDFPassphrase = utils.RandomCharString(64)
|
||||
|
||||
// GetPBKDFPassphrase returns passphrase for the pbkdf2 function used to encrypt JWT payload
|
||||
func GetPBKDFPassphrase() string {
|
||||
return env.Get(McsPBKDFPassphrase, defaultPBKDFPassphrase)
|
||||
}
|
||||
|
||||
var defaultPBKDFSalt = utils.RandomCharString(64)
|
||||
|
||||
// GetPBKDFSalt returns salt for the pbkdf2 function used to encrypt JWT payload
|
||||
func GetPBKDFSalt() string {
|
||||
return env.Get(McsPBKDFSalt, defaultPBKDFSalt)
|
||||
}
|
||||
24
pkg/auth/jwt/const.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package jwt
|
||||
|
||||
const (
|
||||
McsHmacJWTSecret = "MCS_HMAC_JWT_SECRET"
|
||||
McsSTSAndJWTDurationSeconds = "MCS_STS_AND_JWT_DURATION_SECONDS"
|
||||
McsPBKDFPassphrase = "MCS_PBKDF_PASSPHRASE"
|
||||
McsPBKDFSalt = "MCS_PBKDF_SALT"
|
||||
)
|
||||
281
pkg/auth/jwt/parser.go
Normal file
@@ -0,0 +1,281 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package jwt
|
||||
|
||||
// This file is a re-implementation of the original code here with some
|
||||
// additional allocation tweaks reproduced using GODEBUG=allocfreetrace=1
|
||||
// original file https://github.com/dgrijalva/jwt-go/blob/master/parser.go
|
||||
// borrowed under MIT License https://github.com/dgrijalva/jwt-go/blob/master/LICENSE
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
const (
|
||||
claimData = "data"
|
||||
claimSub = "sub"
|
||||
)
|
||||
|
||||
// SigningMethodHMAC - Implements the HMAC-SHA family of signing methods signing methods
|
||||
// Expects key type of []byte for both signing and validation
|
||||
type SigningMethodHMAC struct {
|
||||
Name string
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
// Specific instances for HS256, HS384, HS512
|
||||
var (
|
||||
SigningMethodHS256 *SigningMethodHMAC
|
||||
SigningMethodHS384 *SigningMethodHMAC
|
||||
SigningMethodHS512 *SigningMethodHMAC
|
||||
)
|
||||
|
||||
var (
|
||||
base64BufPool sync.Pool
|
||||
hmacSigners []*SigningMethodHMAC
|
||||
)
|
||||
|
||||
func init() {
|
||||
base64BufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
buf := make([]byte, 8192)
|
||||
return &buf
|
||||
},
|
||||
}
|
||||
|
||||
hmacSigners = []*SigningMethodHMAC{
|
||||
{"HS256", crypto.SHA256},
|
||||
{"HS384", crypto.SHA384},
|
||||
{"HS512", crypto.SHA512},
|
||||
}
|
||||
}
|
||||
|
||||
// StandardClaims are basically standard claims with "Data"
|
||||
type StandardClaims struct {
|
||||
Data string `json:"data,omitempty"`
|
||||
jwtgo.StandardClaims
|
||||
}
|
||||
|
||||
// MapClaims - implements custom unmarshaller
|
||||
type MapClaims struct {
|
||||
Data string `json:"data,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
jwtgo.MapClaims
|
||||
}
|
||||
|
||||
// NewStandardClaims - initializes standard claims
|
||||
func NewStandardClaims() *StandardClaims {
|
||||
return &StandardClaims{}
|
||||
}
|
||||
|
||||
// SetIssuer sets issuer for these claims
|
||||
func (c *StandardClaims) SetIssuer(issuer string) {
|
||||
c.Issuer = issuer
|
||||
}
|
||||
|
||||
// SetAudience sets audience for these claims
|
||||
func (c *StandardClaims) SetAudience(aud string) {
|
||||
c.Audience = aud
|
||||
}
|
||||
|
||||
// SetExpiry sets expiry in unix epoch secs
|
||||
func (c *StandardClaims) SetExpiry(t time.Time) {
|
||||
c.ExpiresAt = t.Unix()
|
||||
}
|
||||
|
||||
// SetSubject sets unique identifier for the jwt
|
||||
func (c *StandardClaims) SetSubject(subject string) {
|
||||
c.Subject = subject
|
||||
}
|
||||
|
||||
// SetData sets the "Data" custom field.
|
||||
func (c *StandardClaims) SetData(data string) {
|
||||
c.Data = data
|
||||
}
|
||||
|
||||
// Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible
|
||||
// claims interface, additionally validates "Data" field.
|
||||
func (c *StandardClaims) Valid() error {
|
||||
if err := c.StandardClaims.Valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Data == "" || c.Subject == "" {
|
||||
return jwtgo.NewValidationError("data/sub",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewMapClaims - Initializes a new map claims
|
||||
func NewMapClaims() *MapClaims {
|
||||
return &MapClaims{MapClaims: jwtgo.MapClaims{}}
|
||||
}
|
||||
|
||||
// Lookup returns the value and if the key is found.
|
||||
func (c *MapClaims) Lookup(key string) (value string, ok bool) {
|
||||
var vinterface interface{}
|
||||
vinterface, ok = c.MapClaims[key]
|
||||
if ok {
|
||||
value, ok = vinterface.(string)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetExpiry sets expiry in unix epoch secs
|
||||
func (c *MapClaims) SetExpiry(t time.Time) {
|
||||
c.MapClaims["exp"] = t.Unix()
|
||||
}
|
||||
|
||||
// SetData sets the "Data" custom field.
|
||||
func (c *MapClaims) SetData(data string) {
|
||||
c.MapClaims[claimData] = data
|
||||
}
|
||||
|
||||
// Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible
|
||||
// claims interface, additionally validates "Data" field.
|
||||
func (c *MapClaims) Valid() error {
|
||||
if err := c.MapClaims.Valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Data == "" || c.Subject == "" {
|
||||
return jwtgo.NewValidationError("data/subject",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map returns underlying low-level map claims.
|
||||
func (c *MapClaims) Map() map[string]interface{} {
|
||||
return c.MapClaims
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the MapClaims struct
|
||||
func (c *MapClaims) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(c.MapClaims)
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7519#page-11
|
||||
type jwtHeader struct {
|
||||
Algorithm string `json:"alg"`
|
||||
Type string `json:"typ"`
|
||||
}
|
||||
|
||||
// ParseWithClaims - parse the token string, valid methods.
|
||||
func ParseWithClaims(tokenStr string, claims *MapClaims) error {
|
||||
bufp := base64BufPool.Get().(*[]byte)
|
||||
defer base64BufPool.Put(bufp)
|
||||
|
||||
signer, err := parseUnverifiedMapClaims(tokenStr, claims, *bufp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i := strings.LastIndex(tokenStr, ".")
|
||||
if i < 0 {
|
||||
return jwtgo.ErrSignatureInvalid
|
||||
}
|
||||
|
||||
n, err := base64Decode(tokenStr[i+1:], *bufp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
|
||||
claims.Data, ok = claims.Lookup(claimData)
|
||||
if !ok {
|
||||
return jwtgo.NewValidationError("data missing",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
|
||||
claims.Subject, ok = claims.Lookup(claimSub)
|
||||
if !ok {
|
||||
return jwtgo.NewValidationError("sub missing",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
|
||||
hasher := hmac.New(signer.Hash.New, []byte(GetHmacJWTSecret()))
|
||||
hasher.Write([]byte(tokenStr[:i]))
|
||||
if !hmac.Equal((*bufp)[:n], hasher.Sum(nil)) {
|
||||
return jwtgo.ErrSignatureInvalid
|
||||
}
|
||||
|
||||
// Signature is valid, lets validate the claims for
|
||||
// other fields such as expiry etc.
|
||||
return claims.Valid()
|
||||
}
|
||||
|
||||
// base64Decode returns the bytes represented by the base64 string s.
|
||||
func base64Decode(s string, buf []byte) (int, error) {
|
||||
return base64.RawURLEncoding.Decode(buf, []byte(s))
|
||||
}
|
||||
|
||||
// ParseUnverifiedMapClaims - WARNING: Don't use this method unless you know what you're doing
|
||||
//
|
||||
// This method parses the token but doesn't validate the signature. It's only
|
||||
// ever useful in cases where you know the signature is valid (because it has
|
||||
// been checked previously in the stack) and you want to extract values from
|
||||
// it.
|
||||
func parseUnverifiedMapClaims(tokenString string, claims *MapClaims, buf []byte) (*SigningMethodHMAC, error) {
|
||||
if strings.Count(tokenString, ".") != 2 {
|
||||
return nil, jwtgo.ErrSignatureInvalid
|
||||
}
|
||||
|
||||
i := strings.Index(tokenString, ".")
|
||||
j := strings.LastIndex(tokenString, ".")
|
||||
|
||||
n, err := base64Decode(tokenString[:i], buf)
|
||||
if err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
var header = jwtHeader{}
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
if err = json.Unmarshal(buf[:n], &header); err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
n, err = base64Decode(tokenString[i+1:j], buf)
|
||||
if err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(buf[:n], &claims.MapClaims); err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
for _, signer := range hmacSigners {
|
||||
if header.Algorithm == signer.Name {
|
||||
return signer, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, jwtgo.NewValidationError(fmt.Sprintf("signing method (%s) is unavailable.", header.Algorithm),
|
||||
jwtgo.ValidationErrorUnverifiable)
|
||||
}
|
||||
80
pkg/auth/jwt_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var audience = ""
|
||||
var creds = &credentials.Value{
|
||||
AccessKeyID: "fakeAccessKeyID",
|
||||
SecretAccessKey: "fakeSecretAccessKey",
|
||||
SessionToken: "fakeSessionToken",
|
||||
SignerType: 0,
|
||||
}
|
||||
var goodToken = ""
|
||||
var badToken = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiRDMwYWE0ekQ1bWtFaFRyWm5yOWM3NWh0Yko0MkROOWNDZVQ5RHVHUkg1U25SR3RyTXZNOXBMdnlFSVJAAAE5eWxxekhYMXllck8xUXpzMlZzRVFKeUF2ZmpOaDkrTVdoUURWZ2FhK2R5emxzSjNpK0k1dUdoeW5DNWswUW83WEY0UWszY0RtUTdUQUVROVFEbWRKdjBkdVB5L25hQk5vM3dIdlRDZHFNRDJZN3kycktJbmVUbUlFNmVveW9EWmprcW5tckVoYmMrTlhTRU81WjZqa1kwZ1E2eXZLaWhUZGxBRS9zS1lBNlc4Q1R1cm1MU0E0b0dIcGtldFZWU0VXMHEzNU9TU1VaczRXNkxHdGMxSTFWVFZLWUo3ZTlHR2REQ3hMWGtiZHQwcjl0RDNMWUhWRndra0dSZit5ZHBzS1Y3L1Jtbkp3SHNqNVVGV0w5WGVHUkZVUjJQclJTN2plVzFXeGZuYitVeXoxNVpOMzZsZ01GNnBlWFd1LzJGcEtrb2Z2QzNpY2x5Rmp0SE45ZkxYTVpVSFhnV2lsQWVSa3oiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJleHAiOjE1ODc1MTY1NzEsInN1YiI6ImZmYmY4YzljLTJlMjYtNGMwYS1iMmI0LTYyMmVhM2I1YjZhYiJ9.P392RUwzsrBeJOO3fS1xMZcF-lWiDvWZ5hM7LZOyFMmoG5QLccDU5eAPSm8obzPoznX1b7eCFLeEmKK-vKgjiQ"
|
||||
|
||||
func TestNewJWTWithClaimsForClient(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : NewJWTWithClaimsForClient() is generated correctly without errors
|
||||
function := "NewJWTWithClaimsForClient()"
|
||||
jwt, err := NewJWTWithClaimsForClient(creds, audience)
|
||||
if err != nil || jwt == "" {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err)
|
||||
}
|
||||
// saving jwt for future tests
|
||||
goodToken = jwt
|
||||
// Test-2 : NewJWTWithClaimsForClient() throws error because of empty credentials
|
||||
if _, err = NewJWTWithClaimsForClient(nil, audience); err != nil {
|
||||
funcAssert.Equal("provided credentials are empty", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWTAuthenticate(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : JWTAuthenticate() should correctly return the claims
|
||||
function := "JWTAuthenticate()"
|
||||
claims, err := JWTAuthenticate(goodToken)
|
||||
if err != nil || claims == nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err)
|
||||
} else {
|
||||
funcAssert.Equal(claims.AccessKeyID, creds.AccessKeyID)
|
||||
funcAssert.Equal(claims.SecretAccessKey, creds.SecretAccessKey)
|
||||
funcAssert.Equal(claims.SessionToken, creds.SessionToken)
|
||||
}
|
||||
// Test-2 : JWTAuthenticate() return an error because of a tampered jwt
|
||||
if _, err := JWTAuthenticate(badToken); err != nil {
|
||||
funcAssert.Equal("Authentication failed, check your access credentials", err.Error())
|
||||
}
|
||||
// Test-3 : JWTAuthenticate() return an error because of an empty jwt
|
||||
if _, err := JWTAuthenticate(""); err != nil {
|
||||
funcAssert.Equal("JWT token missing", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJWTValid(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : JWTAuthenticate() provided token is valid
|
||||
funcAssert.Equal(true, IsJWTValid(goodToken))
|
||||
// Test-2 : JWTAuthenticate() provided token is invalid
|
||||
funcAssert.Equal(false, IsJWTValid(badToken))
|
||||
}
|
||||
@@ -14,52 +14,17 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package sessions
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
mcCmd "github.com/minio/mc/cmd"
|
||||
)
|
||||
|
||||
type Singleton struct {
|
||||
sessions map[string]*mcCmd.Config
|
||||
}
|
||||
|
||||
var instance *Singleton
|
||||
var once sync.Once
|
||||
|
||||
// Returns a Singleton instance that keeps the sessions
|
||||
func GetInstance() *Singleton {
|
||||
once.Do(func() {
|
||||
//build sessions hash
|
||||
sessions := make(map[string]*mcCmd.Config)
|
||||
|
||||
instance = &Singleton{
|
||||
sessions: sessions,
|
||||
}
|
||||
})
|
||||
return instance
|
||||
}
|
||||
|
||||
func (s *Singleton) NewSession(cfg *mcCmd.Config) string {
|
||||
// genereate random session id
|
||||
sessionID := RandomCharString(64)
|
||||
// store the cfg under that session id
|
||||
s.sessions[sessionID] = cfg
|
||||
return sessionID
|
||||
}
|
||||
|
||||
func (s *Singleton) ValidSession(sessionID string) bool {
|
||||
if _, ok := s.sessions[sessionID]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Do not use:
|
||||
// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
|
||||
// It relies on math/rand and therefore not on a cryptographically secure RNG => It must not be used
|
||||
@@ -87,3 +52,9 @@ func RandomCharString(n int) string {
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func ComputeHmac256(message string, key []byte) string {
|
||||
h := hmac.New(sha256.New, key)
|
||||
h.Write([]byte(message))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
46
pkg/auth/utils/utils_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
func TestRandomCharString(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : RandomCharString() should return string with expected length
|
||||
length := 32
|
||||
token := RandomCharString(length)
|
||||
funcAssert.Equal(length, len(token))
|
||||
// Test-2 : RandomCharString() should output random string, new generated string should not be equal to the previous one
|
||||
newToken := RandomCharString(length)
|
||||
funcAssert.NotEqual(token, newToken)
|
||||
}
|
||||
|
||||
func TestComputeHmac256(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : ComputeHmac256() should return the right Hmac256 string based on a derived key
|
||||
var derivedKey = pbkdf2.Key([]byte("secret"), []byte("salt"), 4096, 32, sha1.New)
|
||||
var message = "hello world"
|
||||
var expectedHmac = "5r32q7W+0hcBnqzQwJJUDzVGoVivXGSodTcHSqG/9Q8="
|
||||
hmac := ComputeHmac256(message, derivedKey)
|
||||
funcAssert.Equal(hmac, expectedHmac)
|
||||
}
|
||||
56
pkg/ws/websocket.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package ws contains websocket utils for mcs project
|
||||
package ws
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/minio/mcs/pkg/auth"
|
||||
)
|
||||
|
||||
// Authenticate validates websocket header and returns mcs jwt claims
|
||||
//
|
||||
// Authorization Header needs to be like "Authorization Bearer <jwt_token>"
|
||||
func Authenticate(r *http.Request) (*auth.DecryptedClaims, error) {
|
||||
// Get Auth token
|
||||
var reqToken string
|
||||
|
||||
// Token might come either as a Cookie or as a Header
|
||||
// if not set in cookie, check if it is set on Header.
|
||||
tokenCookie, err := r.Cookie("token")
|
||||
if err != nil {
|
||||
headerToken := r.Header.Get("Authorization")
|
||||
// reqToken should come as "Bearer <token>"
|
||||
splitHeaderToken := strings.Split(headerToken, "Bearer")
|
||||
if len(splitHeaderToken) <= 1 {
|
||||
return nil, errors.New(http.StatusBadRequest, "Authentication not valid")
|
||||
}
|
||||
reqToken = strings.TrimSpace(splitHeaderToken[1])
|
||||
} else {
|
||||
reqToken = strings.TrimSpace(tokenCookie.Value)
|
||||
}
|
||||
|
||||
// Perform authentication before upgrading to a Websocket Connection
|
||||
claims, err := auth.JWTAuthenticate(reqToken)
|
||||
if err != nil {
|
||||
return nil, errors.New(http.StatusUnauthorized, err.Error())
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.7.4",
|
||||
"@babel/plugin-transform-react-jsx-development": "^7.9.0",
|
||||
"@material-ui/core": "^4.9.8",
|
||||
"@material-ui/core": "^4.9.12",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/history": "^4.7.3",
|
||||
"@types/jest": "24.0.23",
|
||||
@@ -19,6 +19,7 @@
|
||||
"@types/recharts": "^1.8.9",
|
||||
"@types/superagent": "^4.1.4",
|
||||
"@types/webpack-env": "^1.14.1",
|
||||
"@types/websocket": "^1.0.0",
|
||||
"codemirror": "^5.52.2",
|
||||
"history": "^4.10.1",
|
||||
"local-storage-fallback": "^4.1.1",
|
||||
@@ -37,7 +38,8 @@
|
||||
"redux-thunk": "^2.3.0",
|
||||
"superagent": "^5.1.0",
|
||||
"typeface-roboto": "^0.0.75",
|
||||
"typescript": "3.6.4"
|
||||
"typescript": "3.6.4",
|
||||
"websocket": "^1.0.31"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "PORT=5000 react-scripts start",
|
||||
@@ -60,7 +62,7 @@
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"proxy": "http://localhost:9090",
|
||||
"proxy": "http://localhost:9090/",
|
||||
"devDependencies": {
|
||||
"prettier": "^1.19.1"
|
||||
}
|
||||
|
||||
BIN
portal-ui/public/amqp.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
portal-ui/public/elasticsearch.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@@ -12,7 +12,7 @@
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Questrial&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/apple-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="%PUBLIC_URL%/favicon-96x96.png">
|
||||
|
||||
BIN
portal-ui/public/kafka.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
portal-ui/public/mqtt.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
portal-ui/public/mysql.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
portal-ui/public/nats.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
portal-ui/public/postgres.png
Normal file
|
After Width: | Height: | Size: 301 KiB |
BIN
portal-ui/public/redis.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
@@ -15,15 +15,16 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { Route, Router, Switch } from "react-router-dom";
|
||||
import { Redirect, Route, Router, Switch } from "react-router-dom";
|
||||
import history from "./history";
|
||||
import Login from "./screens/LoginPage";
|
||||
import Login from "./screens/LoginPage/LoginPage";
|
||||
import Console from "./screens/Console/Console";
|
||||
import NotFoundPage from "./screens/NotFoundPage";
|
||||
import storage from "local-storage-fallback";
|
||||
import { connect } from "react-redux";
|
||||
import { AppState } from "./store";
|
||||
import { userLoggedIn } from "./actions";
|
||||
import LoginCallback from "./screens/LoginPage/LoginCallback";
|
||||
|
||||
const isLoggedIn = () => {
|
||||
return (
|
||||
@@ -55,6 +56,7 @@ class Routes extends React.Component<RoutesProps> {
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route exact path="/oauth_callback" component={LoginCallback} />
|
||||
<Route exact path="/login" component={Login} />
|
||||
{this.props.loggedIn ? (
|
||||
<Switch>
|
||||
@@ -64,7 +66,7 @@ class Routes extends React.Component<RoutesProps> {
|
||||
) : (
|
||||
<Switch>
|
||||
<Route exact path="/" component={Login} />
|
||||
<Route component={NotFoundPage} />
|
||||
<Redirect to="/" />
|
||||
</Switch>
|
||||
)}
|
||||
</Switch>
|
||||
|
||||
@@ -14,7 +14,12 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { MENU_OPEN, USER_LOGGED } from "./types";
|
||||
import {
|
||||
MENU_OPEN,
|
||||
SERVER_IS_LOADING,
|
||||
SERVER_NEEDS_RESTART,
|
||||
USER_LOGGED
|
||||
} from "./types";
|
||||
|
||||
export function userLoggedIn(loggedIn: boolean) {
|
||||
return {
|
||||
@@ -29,3 +34,17 @@ export function setMenuOpen(open: boolean) {
|
||||
open: open
|
||||
};
|
||||
}
|
||||
|
||||
export function serverNeedsRestart(needsRestart: boolean) {
|
||||
return {
|
||||
type: SERVER_NEEDS_RESTART,
|
||||
needsRestart: needsRestart
|
||||
};
|
||||
}
|
||||
|
||||
export function serverIsLoading(isLoading: boolean) {
|
||||
return {
|
||||
type: SERVER_IS_LOADING,
|
||||
isLoading: isLoading
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,7 +24,14 @@ export class API {
|
||||
.set("Authorization", `Bearer ${token}`)
|
||||
.send(data)
|
||||
.then(res => res.body)
|
||||
.catch(err => this.onError(err));
|
||||
.catch(err => {
|
||||
// if we get unauthorized, kick out the user
|
||||
if (err.status === 401) {
|
||||
storage.removeItem("token");
|
||||
window.location.href = "/";
|
||||
}
|
||||
return this.onError(err);
|
||||
});
|
||||
}
|
||||
|
||||
onError(err: any) {
|
||||
@@ -38,5 +45,6 @@ export class API {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const api = new API();
|
||||
export default api;
|
||||
|
||||
40
portal-ui/src/common/utils.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
export const niceBytes = (x: string) => {
|
||||
let l = 0,
|
||||
n = parseInt(x, 10) || 0;
|
||||
|
||||
while (n >= 1024 && ++l) {
|
||||
n = n / 1024;
|
||||
}
|
||||
//include a decimal point and a tenths-place digit if presenting
|
||||
//less than ten of KB or greater units
|
||||
return n.toFixed(n < 10 && l > 0 ? 1 : 0) + " " + units[l];
|
||||
};
|
||||
|
||||
export const setCookie = (name: string, val: string) => {
|
||||
const date = new Date();
|
||||
const value = val;
|
||||
|
||||
// Set it expire in 45 minutes
|
||||
date.setTime(date.getTime() + 45 * 60 * 1000);
|
||||
|
||||
// Set it
|
||||
document.cookie =
|
||||
name + "=" + value + "; expires=" + date.toUTCString() + "; path=/";
|
||||
};
|
||||
1
portal-ui/src/icons/minio_console_logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 81.879 23.119"><defs><style>.cls-1{fill:#1b1556;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M10.086,13.4,8.969,14.573a4.2,4.2,0,0,0-3.01-1.279,3.886,3.886,0,0,0-3.884,4.093,3.888,3.888,0,0,0,3.884,4.1A4.4,4.4,0,0,0,9,20.2l1.082,1.186A5.344,5.344,0,0,1,6,23.119a5.512,5.512,0,0,1-5.7-5.732A5.509,5.509,0,0,1,6,11.667,5.328,5.328,0,0,1,10.086,13.4Z"/><path class="cls-1" d="M23.608,17.387a5.637,5.637,0,0,1-5.8,5.732,5.628,5.628,0,0,1-5.791-5.732,5.626,5.626,0,0,1,5.791-5.72A5.635,5.635,0,0,1,23.608,17.387Zm-9.825,0a4.024,4.024,0,1,0,8.046,0,4.024,4.024,0,1,0-8.046,0Z"/><path class="cls-1" d="M36.862,16.073V22.9H35.129V16.643c0-2.093-1.163-3.325-3.174-3.325a3.24,3.24,0,0,0-3.371,3.372V22.9H26.839V11.888H28.56v1.569a4.354,4.354,0,0,1,3.709-1.79A4.261,4.261,0,0,1,36.862,16.073Z"/><path class="cls-1" d="M48.2,14.225a6.872,6.872,0,0,0-3.605-1.035c-1.569,0-2.6.663-2.6,1.732,0,.919.8,1.372,2.244,1.547l1.3.163c2.338.3,3.7,1.267,3.7,3.069,0,2.093-1.884,3.407-4.849,3.407A7.725,7.725,0,0,1,39.791,21.7l.8-1.3a5.8,5.8,0,0,0,3.815,1.2c1.86,0,3.034-.616,3.034-1.778,0-.884-.744-1.419-2.3-1.605l-1.314-.151c-2.477-.3-3.639-1.408-3.639-3.046,0-2.082,1.755-3.338,4.4-3.338a8.067,8.067,0,0,1,4.372,1.2Z"/><path class="cls-1" d="M63.033,17.387a5.8,5.8,0,0,1-11.593,0,5.8,5.8,0,0,1,11.593,0Zm-9.825,0a4.023,4.023,0,1,0,8.045,0,4.023,4.023,0,1,0-8.045,0Z"/><path class="cls-1" d="M68.008,22.9H66.264V6.155h1.744Z"/><path class="cls-1" d="M81.879,17.353a5.606,5.606,0,0,1-.035.65H73.019a3.743,3.743,0,0,0,3.9,3.593A5.1,5.1,0,0,0,80.4,20.213l.931,1.186a6.179,6.179,0,0,1-4.524,1.72A5.394,5.394,0,0,1,71.24,17.4a5.406,5.406,0,0,1,5.465-5.732C79.693,11.667,81.856,14,81.879,17.353ZM73.043,16.6h7.069a3.446,3.446,0,0,0-3.442-3.384A3.59,3.59,0,0,0,73.043,16.6Z"/><rect class="cls-1" x="13.484" y="0.12" width="2.328" height="6.875"/><path class="cls-1" d="M10.662.215,5.936,3.1a.21.21,0,0,1-.219,0L.992.215A.651.651,0,0,0,.654.12H.648A.648.648,0,0,0,0,.768v6.22H2.327V4.028a.233.233,0,0,1,.354-.2l2.648,1.62a.829.829,0,0,0,.853.008l2.8-1.639a.232.232,0,0,1,.35.2V6.988h2.327V.768A.648.648,0,0,0,11.006.12H11A.651.651,0,0,0,10.662.215Z"/><path class="cls-1" d="M27.422.12H25.061V3.25a.233.233,0,0,1-.342.205L18.6.2A.662.662,0,0,0,18.3.12h0a.648.648,0,0,0-.648.648v6.22h2.342V3.863a.233.233,0,0,1,.342-.206L26.47,6.915a.646.646,0,0,0,.3.076h0a.648.648,0,0,0,.648-.648Z"/><path class="cls-1" d="M29.252,7V.12h1.072V7Z"/><path class="cls-1" d="M36.629,7.119c-2.882,0-4.927-1.368-4.927-3.56S33.759,0,36.629,0s4.938,1.367,4.938,3.559S39.547,7.119,36.629,7.119Zm0-6.208c-2.143,0-3.794.936-3.794,2.648s1.651,2.648,3.794,2.648,3.805-.923,3.805-2.648S38.772.911,36.629.911Z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
BIN
portal-ui/src/icons/postgres.png
Normal file
|
After Width: | Height: | Size: 301 KiB |
@@ -1,8 +1,6 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-family: 'Lato', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
@@ -14,13 +14,22 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import {MENU_OPEN, SystemActionTypes, SystemState, USER_LOGGED} from "./types";
|
||||
import {
|
||||
MENU_OPEN,
|
||||
SERVER_IS_LOADING,
|
||||
SERVER_NEEDS_RESTART,
|
||||
SystemActionTypes,
|
||||
SystemState,
|
||||
USER_LOGGED
|
||||
} from "./types";
|
||||
|
||||
const initialState: SystemState = {
|
||||
loggedIn: false,
|
||||
session: "",
|
||||
userName: "",
|
||||
sidebarOpen:true,
|
||||
sidebarOpen: true,
|
||||
serverNeedsRestart: false,
|
||||
serverIsLoading: false
|
||||
};
|
||||
|
||||
export function systemReducer(
|
||||
@@ -38,6 +47,17 @@ export function systemReducer(
|
||||
...state,
|
||||
sidebarOpen: action.open
|
||||
};
|
||||
case SERVER_NEEDS_RESTART:
|
||||
return {
|
||||
...state,
|
||||
serverNeedsRestart: action.needsRestart
|
||||
};
|
||||
|
||||
case SERVER_IS_LOADING:
|
||||
return {
|
||||
...state,
|
||||
serverIsLoading: action.isLoading
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -35,9 +35,8 @@ import { AppState } from "../../../store";
|
||||
import { setMenuOpen } from "../../../actions";
|
||||
import { ThemedComponentProps } from "@material-ui/core/styles/withTheme";
|
||||
import NotFoundPage from "../../NotFoundPage";
|
||||
import BucketList from "./ListBuckets/ListBuckets";
|
||||
import ViewBucket from "./ViewBucket/ViewBucket";
|
||||
import ListBuckets from "./ListBuckets/ListBuckets";
|
||||
import ViewBucket from "./ViewBucket/ViewBucket";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
||||
@@ -16,27 +16,20 @@
|
||||
|
||||
import React from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Title from "../../../../common/Title";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
LinearProgress,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField
|
||||
} from "@material-ui/core";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import api from "../../../../common/api";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -50,28 +43,25 @@ interface IAddBucketState {
|
||||
addLoading: boolean;
|
||||
addError: string;
|
||||
bucketName: string;
|
||||
accessPolicy: string;
|
||||
}
|
||||
|
||||
class AddBucket extends React.Component<IAddBucketProps, IAddBucketState> {
|
||||
state: IAddBucketState = {
|
||||
addLoading: false,
|
||||
addError: "",
|
||||
bucketName: "",
|
||||
accessPolicy: ""
|
||||
bucketName: ""
|
||||
};
|
||||
|
||||
addRecord(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
const { bucketName, addLoading, accessPolicy } = this.state;
|
||||
const { bucketName, addLoading } = this.state;
|
||||
if (addLoading) {
|
||||
return;
|
||||
}
|
||||
this.setState({ addLoading: true }, () => {
|
||||
api
|
||||
.invoke("POST", "/api/v1/buckets", {
|
||||
name: bucketName,
|
||||
access: accessPolicy
|
||||
name: bucketName
|
||||
})
|
||||
.then(res => {
|
||||
this.setState(
|
||||
@@ -95,10 +85,11 @@ class AddBucket extends React.Component<IAddBucketProps, IAddBucketState> {
|
||||
|
||||
render() {
|
||||
const { classes, open } = this.props;
|
||||
const { addLoading, addError, accessPolicy } = this.state;
|
||||
const { addLoading, addError, bucketName } = this.state;
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
<ModalWrapper
|
||||
title="Create Bucket"
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
this.setState({ addError: "" }, () => {
|
||||
this.props.closeModalAndRefresh();
|
||||
@@ -107,81 +98,58 @@ class AddBucket extends React.Component<IAddBucketProps, IAddBucketState> {
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
<Title>Create Bucket</Title>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
this.addRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
this.addRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
fullWidth
|
||||
label="Bucket Name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ bucketName: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormControl className={classes.formControl} fullWidth>
|
||||
<InputLabel id="select-access-policy">
|
||||
Access Policy
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="select-access-policy"
|
||||
id="select-access-policy"
|
||||
value={accessPolicy}
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ accessPolicy: e.target.value as string });
|
||||
}}
|
||||
>
|
||||
<MenuItem value="PRIVATE">Private</MenuItem>
|
||||
<MenuItem value="PUBLIC">Public</MenuItem>
|
||||
<MenuItem value="CUSTOM">Custom</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={addLoading}
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="bucket-name"
|
||||
name="bucket-name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ bucketName: e.target.value });
|
||||
}}
|
||||
label="Bucket Name"
|
||||
value={bucketName}
|
||||
/>
|
||||
</Grid>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addLoading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,34 +16,20 @@
|
||||
|
||||
import React from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import api from "../../../../common/api";
|
||||
import { Bucket, BucketList } from "../types";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
} from "@material-ui/core";
|
||||
import { Button } from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import AddBucket from "./AddBucket";
|
||||
import DeleteBucket from "./DeleteBucket";
|
||||
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
|
||||
import { CreateIcon } from "../../../../icons";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import Moment from "react-moment";
|
||||
import { Link } from "react-router-dom";
|
||||
import ViewIcon from "@material-ui/icons/Visibility";
|
||||
import api from "../../../../common/api";
|
||||
import { Bucket, BucketList } from "../types";
|
||||
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
|
||||
import AddBucket from "./AddBucket";
|
||||
import DeleteBucket from "./DeleteBucket";
|
||||
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
|
||||
import { CreateIcon } from "../../../../icons";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -134,8 +120,8 @@ class ListBuckets extends React.Component<
|
||||
.then((res: BucketList) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
records: res.buckets,
|
||||
totalRecords: res.total,
|
||||
records: res.buckets || [],
|
||||
totalRecords: !res.buckets ? 0 : res.total,
|
||||
error: ""
|
||||
});
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
@@ -208,14 +194,48 @@ class ListBuckets extends React.Component<
|
||||
this.setState({ deleteOpen: true, selectedBucket: bucket });
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", to: `/buckets`, sendOnlyId: true },
|
||||
{ type: "delete", onClick: confirmDeleteBucket, sendOnlyId: true }
|
||||
];
|
||||
|
||||
const displayParsedDate = (date: string) => {
|
||||
return <Moment>{date}</Moment>;
|
||||
};
|
||||
|
||||
const filteredRecords = records
|
||||
.slice(offset, offset + rowsPerPage)
|
||||
.filter((b: Bucket) => {
|
||||
if (filterBuckets === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterBuckets) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<AddBucket
|
||||
open={addScreenOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
this.closeAddModalAndRefresh();
|
||||
}}
|
||||
/>
|
||||
{addScreenOpen && (
|
||||
<AddBucket
|
||||
open={addScreenOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
this.closeAddModalAndRefresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{deleteOpen && (
|
||||
<DeleteBucket
|
||||
deleteOpen={deleteOpen}
|
||||
selectedBucket={selectedBucket}
|
||||
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
||||
this.closeDeleteModalAndRefresh(refresh);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Buckets</Typography>
|
||||
@@ -260,88 +280,37 @@ class ListBuckets extends React.Component<
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Creation Date</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records
|
||||
.slice(offset, offset + rowsPerPage)
|
||||
.filter((b: Bucket) => {
|
||||
if (filterBuckets === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterBuckets) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(row => (
|
||||
<TableRow key={row.name}>
|
||||
<TableCell>{row.name}</TableCell>
|
||||
<TableCell>
|
||||
<Moment>{row.creation_date}</Moment>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Link to={`/buckets/${row.name}`}>
|
||||
<IconButton aria-label="delete">
|
||||
<ViewIcon />
|
||||
</IconButton>
|
||||
</Link>
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
confirmDeleteBucket(row.name);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
colSpan={3}
|
||||
count={totalRecords}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
SelectProps={{
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
}}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
ActionsComponent={MinTablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
) : (
|
||||
<div>No Buckets</div>
|
||||
)}
|
||||
</Paper>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{
|
||||
label: "Creation Date",
|
||||
elementKey: "creation_date",
|
||||
renderFunction: displayParsedDate
|
||||
}
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="Buckets"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<DeleteBucket
|
||||
deleteOpen={deleteOpen}
|
||||
selectedBucket={selectedBucket}
|
||||
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
||||
this.closeDeleteModalAndRefresh(refresh);
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,20 +16,8 @@
|
||||
|
||||
import React, { ChangeEvent } from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Title from "../../../../common/Title";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
LinearProgress,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField
|
||||
} from "@material-ui/core";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import api from "../../../../common/api";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
@@ -38,7 +26,10 @@ import TableCell from "@material-ui/core/TableCell";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import { ArnList, BucketEventList } from "../types";
|
||||
import { ArnList } from "../types";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -52,6 +43,9 @@ const styles = (theme: Theme) =>
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -97,7 +91,7 @@ class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
arn: arn,
|
||||
events: selectedEvents,
|
||||
prefix: prefix,
|
||||
sufix: suffix
|
||||
suffix: suffix
|
||||
},
|
||||
ignoreExisting: true
|
||||
})
|
||||
@@ -148,7 +142,15 @@ class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
|
||||
render() {
|
||||
const { classes, open } = this.props;
|
||||
const { addLoading, addError, arn, selectedEvents, arnList } = this.state;
|
||||
const {
|
||||
addLoading,
|
||||
addError,
|
||||
arn,
|
||||
selectedEvents,
|
||||
arnList,
|
||||
prefix,
|
||||
suffix
|
||||
} = this.state;
|
||||
|
||||
const events = [
|
||||
{ label: "PUT - Object Uploaded", value: "put" },
|
||||
@@ -156,26 +158,6 @@ class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
{ label: "DELETE - Object Deleted", value: "delete" }
|
||||
];
|
||||
|
||||
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const targetD = e.target;
|
||||
const value = targetD.value;
|
||||
const checked = targetD.checked;
|
||||
|
||||
let elements: string[] = [...selectedEvents]; // We clone the selectedGroups array
|
||||
|
||||
if (checked) {
|
||||
// If the user has checked this field we need to push this to selectedGroupsList
|
||||
elements.push(value);
|
||||
} else {
|
||||
// User has unchecked this field, we need to remove it from the list
|
||||
elements = elements.filter(element => element !== value);
|
||||
}
|
||||
|
||||
this.setState({ selectedEvents: selectedEvents });
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
const handleClick = (
|
||||
event: React.MouseEvent<unknown> | ChangeEvent<unknown>,
|
||||
name: string
|
||||
@@ -199,134 +181,131 @@ class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
this.setState({ selectedEvents: newSelected });
|
||||
};
|
||||
|
||||
const arnValues = arnList.map(arnConstant => ({
|
||||
label: arnConstant,
|
||||
value: arnConstant
|
||||
}));
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
this.setState({ addError: "" }, () => {
|
||||
this.props.closeModalAndRefresh();
|
||||
});
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
title="Subscribe To Event"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
<Title>Subscribe To Event</Title>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
this.addRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
this.addRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<FormControl className={classes.formControl} fullWidth>
|
||||
<InputLabel id="select-access-policy">ARN</InputLabel>
|
||||
<Select
|
||||
labelId="select-access-policy"
|
||||
id="select-access-policy"
|
||||
value={arn}
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ arn: e.target.value as string });
|
||||
}}
|
||||
>
|
||||
{arnList.map(arn => (
|
||||
<MenuItem value={arn}>{arn}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Event</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{events.map(row => (
|
||||
<TableRow
|
||||
key={`group-${row.value}`}
|
||||
onClick={event => handleClick(event, row.value)}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value={row.value}
|
||||
color="primary"
|
||||
inputProps={{
|
||||
"aria-label": "secondary checkbox"
|
||||
}}
|
||||
onChange={event => handleClick(event, row.value)}
|
||||
checked={selectedEvents.includes(row.value)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.label}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
fullWidth
|
||||
label="Prefix"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ prefix: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
fullWidth
|
||||
label="Suffix"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ suffix: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={addLoading}
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ arn: e.target.value as string });
|
||||
}}
|
||||
id="select-access-policy"
|
||||
name="select-access-policy"
|
||||
label={"ARN"}
|
||||
value={arn}
|
||||
options={arnValues}
|
||||
/>
|
||||
</Grid>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Grid item xs={12}>
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Event</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{events.map(row => (
|
||||
<TableRow
|
||||
key={`group-${row.value}`}
|
||||
onClick={event => handleClick(event, row.value)}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value={row.value}
|
||||
color="primary"
|
||||
inputProps={{
|
||||
"aria-label": "secondary checkbox"
|
||||
}}
|
||||
onChange={event => handleClick(event, row.value)}
|
||||
checked={selectedEvents.includes(row.value)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.label}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="prefix-input"
|
||||
name="prefix-input"
|
||||
label="Prefix"
|
||||
value={prefix}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ prefix: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="suffix-input"
|
||||
name="suffix-input"
|
||||
label="Suffix"
|
||||
value={suffix}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ suffix: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addLoading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ class DeleteEvent extends React.Component<
|
||||
if (bucketEvent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ deleteLoading: true }, () => {
|
||||
api
|
||||
.invoke(
|
||||
@@ -97,7 +98,7 @@ class DeleteEvent extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, deleteOpen, selectedBucket } = this.props;
|
||||
const { classes, deleteOpen } = this.props;
|
||||
const { deleteLoading, deleteError } = this.state;
|
||||
|
||||
return (
|
||||
|
||||
@@ -15,22 +15,12 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Title from "../../../../common/Title";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
LinearProgress,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField
|
||||
} from "@material-ui/core";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import api from "../../../../common/api";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -98,81 +88,71 @@ class SetAccessPolicy extends React.Component<
|
||||
const { classes, open } = this.props;
|
||||
const { addLoading, addError, accessPolicy } = this.state;
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
<ModalWrapper
|
||||
title="Change Access Policy"
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
this.setState({ addError: "" }, () => {
|
||||
this.props.closeModalAndRefresh();
|
||||
});
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
<Title>Change Access Policy</Title>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
this.addRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
this.addRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<FormControl className={classes.formControl} fullWidth>
|
||||
<InputLabel id="select-access-policy">
|
||||
Access Policy
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="select-access-policy"
|
||||
id="select-access-policy"
|
||||
value={accessPolicy}
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ accessPolicy: e.target.value as string });
|
||||
}}
|
||||
>
|
||||
<MenuItem value="PRIVATE">Private</MenuItem>
|
||||
<MenuItem value="PUBLIC">Public</MenuItem>
|
||||
<MenuItem value="CUSTOM">Custom</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={addLoading}
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
Set
|
||||
</Button>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
value={accessPolicy}
|
||||
label="Access Policy"
|
||||
id="select-access-policy"
|
||||
name="select-access-policy"
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ accessPolicy: e.target.value as string });
|
||||
}}
|
||||
options={[
|
||||
{ value: "PRIVATE", label: "Private" },
|
||||
{ value: "PUBLIC", label: "Public" }
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={addLoading}
|
||||
>
|
||||
Set
|
||||
</Button>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,31 +15,19 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import get from "lodash/get";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import api from "../../../../common/api";
|
||||
import { BucketEvent, BucketEventList, BucketInfo } from "../types";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
} from "@material-ui/core";
|
||||
import { Button } from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import SetAccessPolicy from "./SetAccessPolicy";
|
||||
import DeleteBucket from "../ListBuckets/DeleteBucket";
|
||||
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
|
||||
import { CreateIcon } from "../../../../icons";
|
||||
import AddEvent from "./AddEvent";
|
||||
import DeleteEvent from "./DeleteEvent";
|
||||
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -130,25 +118,23 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
|
||||
fetchEvents() {
|
||||
this.setState({ loading: true }, () => {
|
||||
const { page, rowsPerPage } = this.state;
|
||||
const { page } = this.state;
|
||||
const { match } = this.props;
|
||||
const bucketName = match.params["bucketName"];
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/events`)
|
||||
.then((res: BucketEventList) => {
|
||||
const events = get(res, "events", []);
|
||||
const total = get(res, "total", 0);
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
records: res.events,
|
||||
totalRecords: res.total,
|
||||
records: events || [],
|
||||
totalRecords: total,
|
||||
error: ""
|
||||
});
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if (
|
||||
(res.events === undefined ||
|
||||
res.events == null ||
|
||||
res.events.length === 0) &&
|
||||
page > 0
|
||||
) {
|
||||
if ((!events || res.events.length === 0) && page > 0) {
|
||||
const newPage = page - 1;
|
||||
this.setState({ page: newPage }, () => {
|
||||
this.fetchEvents();
|
||||
@@ -205,7 +191,6 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
addScreenOpen,
|
||||
selectedBucket,
|
||||
selectedEvent
|
||||
} = this.state;
|
||||
|
||||
@@ -233,6 +218,14 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
accessPolicy = info.access;
|
||||
}
|
||||
|
||||
const eventsDisplay = (events: string[]) => {
|
||||
return <React.Fragment>{events.join(", ")}</React.Fragment>;
|
||||
};
|
||||
|
||||
const tableActions = [{ type: "delete", onClick: confirmDeleteEvent }];
|
||||
|
||||
const filteredRecords = records.slice(offset, offset + rowsPerPage);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<AddEvent
|
||||
@@ -303,62 +296,37 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>SQS</TableCell>
|
||||
<TableCell>Events</TableCell>
|
||||
<TableCell>Prefix</TableCell>
|
||||
<TableCell>Suffix</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.slice(offset, offset + rowsPerPage).map(row => (
|
||||
<TableRow key={row.id}>
|
||||
<TableCell>{row.arn}</TableCell>
|
||||
<TableCell>{row.events.join(", ")}</TableCell>
|
||||
<TableCell>{row.prefix}</TableCell>
|
||||
<TableCell>{row.suffix}</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
confirmDeleteEvent(row);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
colSpan={3}
|
||||
count={totalRecords}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
SelectProps={{
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
}}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
ActionsComponent={MinTablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
) : (
|
||||
<div className={classes.noRecords}>No Events</div>
|
||||
)}
|
||||
</Paper>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "SQS", elementKey: "arn" },
|
||||
{
|
||||
label: "Events",
|
||||
elementKey: "events",
|
||||
renderFunction: eventsDisplay
|
||||
},
|
||||
{ label: "Prefix", elementKey: "prefix" },
|
||||
{ label: "Suffix", elementKey: "suffix" }
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="Events"
|
||||
idField="id"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
createRef,
|
||||
ChangeEvent,
|
||||
useCallback
|
||||
} from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import get from "lodash/get";
|
||||
import InputBoxWrapper from "../InputBoxWrapper/InputBoxWrapper";
|
||||
import { InputLabel, Tooltip } from "@material-ui/core";
|
||||
import { fieldBasic } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
interface ICSVMultiSelector {
|
||||
elements: string;
|
||||
name: string;
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
classes: any;
|
||||
onChange: (elements: string) => void;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
inputLabel: {
|
||||
...fieldBasic.inputLabel,
|
||||
marginBottom: 10
|
||||
},
|
||||
inputContainer: {
|
||||
height: 150,
|
||||
overflowY: "auto",
|
||||
padding: 15,
|
||||
position: "relative",
|
||||
border: "1px solid #c4c4c4",
|
||||
marginBottom: 10
|
||||
},
|
||||
labelContainer: {
|
||||
display: "flex"
|
||||
}
|
||||
});
|
||||
|
||||
const CSVMultiSelector = ({
|
||||
elements,
|
||||
name,
|
||||
label,
|
||||
tooltip = "",
|
||||
onChange,
|
||||
classes
|
||||
}: ICSVMultiSelector) => {
|
||||
const [currentElements, setCurrentElements] = useState<string[]>([""]);
|
||||
const bottomList = createRef<HTMLDivElement>();
|
||||
|
||||
// Use effect to get the initial values from props
|
||||
useCallback(() => {
|
||||
if (currentElements.length === 1 && currentElements[0] === "") {
|
||||
const elementsSplitted = elements.split(",");
|
||||
if (elementsSplitted[elementsSplitted.length - 1].trim() !== "") {
|
||||
elementsSplitted.push("");
|
||||
}
|
||||
setCurrentElements(elementsSplitted);
|
||||
}
|
||||
}, [elements, setCurrentElements, currentElements]);
|
||||
|
||||
// Use effect to send new values to onChange
|
||||
useEffect(() => {
|
||||
const elementsString = currentElements
|
||||
.filter(element => element.trim() !== "")
|
||||
.join(",");
|
||||
onChange(elementsString);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentElements]);
|
||||
|
||||
// If the last input is not empty, we add a new one
|
||||
const addEmptyLine = (elementsUp: string[]) => {
|
||||
if (elementsUp[elementsUp.length - 1].trim() !== "") {
|
||||
elementsUp.push("");
|
||||
const refScroll = bottomList.current;
|
||||
|
||||
if (refScroll) {
|
||||
refScroll.scrollIntoView(false);
|
||||
}
|
||||
}
|
||||
|
||||
return elementsUp;
|
||||
};
|
||||
|
||||
// Onchange function for input box, we get the dataset-index & only update that value in the array
|
||||
const onChangeElement = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
e.persist();
|
||||
|
||||
let updatedElement = [...currentElements];
|
||||
const index = get(e.target, "dataset.index", 0);
|
||||
updatedElement[index] = e.target.value;
|
||||
|
||||
updatedElement = addEmptyLine(updatedElement);
|
||||
setCurrentElements(updatedElement);
|
||||
};
|
||||
|
||||
const inputs = currentElements.map((element, index) => {
|
||||
return (
|
||||
<InputBoxWrapper
|
||||
id={`${name}-${index.toString()}`}
|
||||
label={""}
|
||||
name={`${name}-${index.toString()}`}
|
||||
value={currentElements[index]}
|
||||
onChange={onChangeElement}
|
||||
index={index}
|
||||
key={`csv-${name}-${index.toString()}`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
<InputLabel className={classes.inputLabel}>{label}</InputLabel>
|
||||
<Grid item xs={12} className={classes.inputContainer}>
|
||||
{inputs}
|
||||
<div ref={bottomList} />
|
||||
</Grid>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="left">
|
||||
<HelpIcon />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(CSVMultiSelector);
|
||||
@@ -0,0 +1,132 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import {
|
||||
Grid,
|
||||
InputLabel,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
Tooltip
|
||||
} from "@material-ui/core";
|
||||
import { OutlinedInputProps } from "@material-ui/core/OutlinedInput";
|
||||
import {
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Theme,
|
||||
withStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import { fieldBasic } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
interface InputBoxProps {
|
||||
label: string;
|
||||
classes: any;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
value: string | boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
multiline?: boolean;
|
||||
type?: string;
|
||||
tooltip?: string;
|
||||
autoComplete?: string;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
textBoxContainer: {
|
||||
flexGrow: 1
|
||||
}
|
||||
});
|
||||
|
||||
const inputStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
borderColor: "#393939",
|
||||
borderRadius: 0
|
||||
},
|
||||
input: {
|
||||
padding: "11px 20px",
|
||||
color: "#393939",
|
||||
fontSize: 14
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
function InputField(props: TextFieldProps) {
|
||||
const classes = inputStyles();
|
||||
|
||||
return (
|
||||
<TextField
|
||||
InputProps={{ classes } as Partial<OutlinedInputProps>}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const InputBoxWrapper = ({
|
||||
label,
|
||||
onChange,
|
||||
value,
|
||||
id,
|
||||
name,
|
||||
type = "text",
|
||||
autoComplete = "off",
|
||||
disabled = false,
|
||||
multiline = false,
|
||||
tooltip = "",
|
||||
index = 0,
|
||||
classes
|
||||
}: InputBoxProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
{label}
|
||||
</InputLabel>
|
||||
)}
|
||||
<div className={classes.textBoxContainer}>
|
||||
<InputField
|
||||
className={classes.boxDesign}
|
||||
id={id}
|
||||
name={name}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
type={type}
|
||||
multiline={multiline}
|
||||
autoComplete={autoComplete}
|
||||
inputProps={{ "data-index": index }}
|
||||
/>
|
||||
</div>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="left">
|
||||
<HelpIcon />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(InputBoxWrapper);
|
||||
@@ -0,0 +1,154 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio, { RadioProps } from "@material-ui/core/Radio";
|
||||
import { InputLabel, Tooltip } from "@material-ui/core";
|
||||
import {
|
||||
createStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
makeStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import { fieldBasic } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
export interface SelectorTypes {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface RadioGroupProps {
|
||||
selectorOptions: SelectorTypes[];
|
||||
currentSelection: string;
|
||||
label: string;
|
||||
id: string;
|
||||
name: string;
|
||||
tooltip?: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
classes: any;
|
||||
displayInColumn?: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
radioBoxContainer: {
|
||||
flexGrow: 1
|
||||
}
|
||||
});
|
||||
|
||||
const radioStyles = makeStyles({
|
||||
root: {
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent"
|
||||
}
|
||||
},
|
||||
icon: {
|
||||
borderRadius: "100%",
|
||||
width: 14,
|
||||
height: 14,
|
||||
border: "1px solid #000"
|
||||
},
|
||||
checkedIcon: {
|
||||
borderRadius: "100%",
|
||||
width: 14,
|
||||
height: 14,
|
||||
border: "1px solid #000",
|
||||
padding: 4,
|
||||
position: "relative",
|
||||
"&::after": {
|
||||
content: '" "',
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "100%",
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
backgroundColor: "#000",
|
||||
top: 2,
|
||||
left: 2
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const RadioButton = (props: RadioProps) => {
|
||||
const classes = radioStyles();
|
||||
|
||||
return (
|
||||
<Radio
|
||||
className={classes.root}
|
||||
disableRipple
|
||||
color="default"
|
||||
checkedIcon={<span className={classes.checkedIcon} />}
|
||||
icon={<span className={classes.icon} />}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const RadioGroupSelector = ({
|
||||
selectorOptions = [],
|
||||
currentSelection,
|
||||
label,
|
||||
id,
|
||||
name,
|
||||
onChange,
|
||||
tooltip = "",
|
||||
classes,
|
||||
displayInColumn = false
|
||||
}: RadioGroupProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
{label}
|
||||
</InputLabel>
|
||||
<div className={classes.radioBoxContainer}>
|
||||
<RadioGroup
|
||||
aria-label={id}
|
||||
id={id}
|
||||
name={name}
|
||||
value={currentSelection}
|
||||
onChange={onChange}
|
||||
row={!displayInColumn}
|
||||
>
|
||||
{selectorOptions.map(selectorOption => {
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={`rd-${name}-${selectorOption.value}`}
|
||||
value={selectorOption.value}
|
||||
control={<RadioButton />}
|
||||
label={selectorOption.label}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{tooltip !== "" && (
|
||||
<div>
|
||||
<Tooltip title={tooltip} placement="left">
|
||||
<HelpIcon />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(RadioGroupSelector);
|
||||
@@ -0,0 +1,112 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import {
|
||||
FormControl,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
InputBase
|
||||
} from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { fieldBasic } from "../common/styleLibrary";
|
||||
|
||||
interface selectorTypes {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface SelectProps {
|
||||
options: selectorTypes[];
|
||||
value: string;
|
||||
label: string;
|
||||
id: string;
|
||||
name: string;
|
||||
onChange: (
|
||||
e: React.ChangeEvent<{ name?: string | undefined; value: unknown }>
|
||||
) => void;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic
|
||||
});
|
||||
|
||||
const SelectStyled = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
"label + &": {
|
||||
marginTop: theme.spacing(3)
|
||||
}
|
||||
},
|
||||
input: {
|
||||
borderRadius: 0,
|
||||
position: "relative",
|
||||
color: "#393939",
|
||||
fontSize: 14,
|
||||
padding: "11px 20px",
|
||||
border: "1px solid #c4c4c4",
|
||||
"&:hover": {
|
||||
borderColor: "#393939"
|
||||
},
|
||||
"&:focus": {
|
||||
backgroundColor: "#fff"
|
||||
}
|
||||
}
|
||||
})
|
||||
)(InputBase);
|
||||
|
||||
const SelectWrapper = ({
|
||||
classes,
|
||||
id,
|
||||
name,
|
||||
onChange,
|
||||
options,
|
||||
label,
|
||||
value
|
||||
}: SelectProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
{label}
|
||||
</InputLabel>
|
||||
<FormControl variant="outlined" fullWidth>
|
||||
<Select
|
||||
id={id}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
input={<SelectStyled />}
|
||||
>
|
||||
{options.map(option => (
|
||||
<MenuItem
|
||||
value={option.value}
|
||||
key={`select-${name}-${option.label}`}
|
||||
>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(SelectWrapper);
|
||||
@@ -0,0 +1,35 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// This object contains variables that will be used across form components.
|
||||
|
||||
export const fieldBasic = {
|
||||
inputLabel: {
|
||||
fontWeight: 500,
|
||||
marginRight: 16,
|
||||
minWidth: 90,
|
||||
fontSize: 14,
|
||||
color: "#393939"
|
||||
},
|
||||
fieldContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: 10
|
||||
},
|
||||
tooltipContainer: {
|
||||
marginLeft: 5
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import { Dialog, DialogContent, DialogTitle } from "@material-ui/core";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
|
||||
interface IModalProps {
|
||||
classes: any;
|
||||
onClose: () => void;
|
||||
modalOpen: boolean;
|
||||
title: string;
|
||||
children: any;
|
||||
}
|
||||
|
||||
const baseCloseLine = {
|
||||
content: '" "',
|
||||
borderLeft: "2px solid #707070",
|
||||
height: 33,
|
||||
width: 1,
|
||||
position: "absolute"
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
dialogContainer: {
|
||||
padding: "12px 26px 22px"
|
||||
},
|
||||
closeContainer: {
|
||||
textAlign: "right"
|
||||
},
|
||||
closeButton: {
|
||||
width: 45,
|
||||
height: 45,
|
||||
padding: 0,
|
||||
backgroundColor: "initial",
|
||||
"&:hover": {
|
||||
backgroundColor: "initial"
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: "initial"
|
||||
}
|
||||
},
|
||||
modalCloseIcon: {
|
||||
fontSize: 35,
|
||||
color: "#707070",
|
||||
fontWeight: 300,
|
||||
"&:hover": {
|
||||
color: "#000"
|
||||
}
|
||||
},
|
||||
closeIcon: {
|
||||
"&::before": {
|
||||
...baseCloseLine,
|
||||
transform: "rotate(45deg)"
|
||||
},
|
||||
"&::after": {
|
||||
...baseCloseLine,
|
||||
transform: "rotate(-45deg)"
|
||||
},
|
||||
"&:hover::before, &:hover::after": {
|
||||
borderColor: "#000"
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
display: "block",
|
||||
position: "relative"
|
||||
},
|
||||
titleClass: {
|
||||
padding: "0px 24px 12px"
|
||||
}
|
||||
});
|
||||
|
||||
const ModalWrapper = ({
|
||||
onClose,
|
||||
modalOpen,
|
||||
title,
|
||||
children,
|
||||
classes
|
||||
}: IModalProps) => {
|
||||
return (
|
||||
<Dialog
|
||||
open={modalOpen}
|
||||
onClose={onClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
maxWidth={"md"}
|
||||
fullWidth
|
||||
>
|
||||
<div className={classes.dialogContainer}>
|
||||
<div className={classes.closeContainer}>
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
className={classes.closeButton}
|
||||
onClick={onClose}
|
||||
disableRipple
|
||||
>
|
||||
<span className={classes.closeIcon} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<DialogTitle id="alert-dialog-title" className={classes.titleClass}>
|
||||
{title}
|
||||
</DialogTitle>
|
||||
<DialogContent>{children}</DialogContent>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ModalWrapper);
|
||||
@@ -0,0 +1,84 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import isString from "lodash/isString";
|
||||
import { IconButton } from "@material-ui/core";
|
||||
import ViewIcon from "./TableActionIcons/ViewIcon";
|
||||
import PencilIcon from "./TableActionIcons/PencilIcon";
|
||||
import DeleteIcon from "./TableActionIcons/DeleteIcon";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
interface IActionButton {
|
||||
type: string;
|
||||
onClick?: (id: string) => any;
|
||||
to?: string;
|
||||
valueToSend: any;
|
||||
selected: boolean;
|
||||
sendOnlyId?: boolean;
|
||||
idField: string;
|
||||
}
|
||||
|
||||
const defineIcon = (type: string, selected: boolean) => {
|
||||
switch (type) {
|
||||
case "view":
|
||||
return <ViewIcon active={selected} />;
|
||||
case "edit":
|
||||
return <PencilIcon active={selected} />;
|
||||
case "delete":
|
||||
return <DeleteIcon active={selected} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const TableActionButton = ({
|
||||
type,
|
||||
onClick,
|
||||
valueToSend,
|
||||
idField,
|
||||
selected,
|
||||
to,
|
||||
sendOnlyId = false
|
||||
}: IActionButton) => {
|
||||
const valueClick = sendOnlyId ? valueToSend[idField] : valueToSend;
|
||||
|
||||
const buttonElement = (
|
||||
<IconButton
|
||||
aria-label={type}
|
||||
onClick={
|
||||
onClick
|
||||
? () => {
|
||||
onClick(valueClick);
|
||||
}
|
||||
: () => null
|
||||
}
|
||||
>
|
||||
{defineIcon(type, selected)}
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
if (onClick) {
|
||||
return buttonElement;
|
||||
}
|
||||
|
||||
if (isString(to)) {
|
||||
return <Link to={`${to}/${valueClick}`}>{buttonElement}</Link>;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default TableActionButton;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { IIcon, selected, unSelected } from "./common";
|
||||
|
||||
const DeleteIcon = ({ active = false }: IIcon) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<g transform="translate(-1225 -657)">
|
||||
<path
|
||||
fill={active ? selected : unSelected}
|
||||
d="M0,8H0a8,8,0,0,0,8,8H8a8,8,0,0,0,8-8h0A8,8,0,0,0,8,0H8A8,8,0,0,0,0,8Zm10.007,3.489L8,9.482,5.993,11.489,4.511,10.007,6.518,8,4.511,5.993,5.993,4.511,8,6.518l2.007-2.007,1.482,1.482L9.482,8l2.007,2.007Z"
|
||||
transform="translate(1225 657)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { IIcon, selected, unSelected } from "./common";
|
||||
|
||||
const PencilIcon = ({ active = false }: IIcon) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 13.833 13.833"
|
||||
>
|
||||
<path
|
||||
fill={active ? selected : unSelected}
|
||||
d="M2.934,16H0V13.066L10.607,2.459a1,1,0,0,1,1.414,0l1.52,1.52a1,1,0,0,1,0,1.414Z"
|
||||
transform="translate(0 -2.167)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PencilIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { IIcon, selected, unSelected } from "./common";
|
||||
|
||||
const ViewIcon = ({ active = false }: IIcon) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 11.856"
|
||||
>
|
||||
<path
|
||||
fill={active ? selected : unSelected}
|
||||
d="M-54,8l1.764,2.614A7.52,7.52,0,0,0-46,13.928h0a7.52,7.52,0,0,0,6.234-3.314L-38,8l-1.764-2.614A7.52,7.52,0,0,0-46,2.072h0a7.52,7.52,0,0,0-6.234,3.314Zm10.286,0A2.285,2.285,0,0,1-46,10.286,2.285,2.285,0,0,1-48.286,8,2.285,2.285,0,0,1-46,5.714,2.285,2.285,0,0,1-43.714,8Zm1.3,0A3.59,3.59,0,0,1-46,11.59,3.59,3.59,0,0,1-49.59,8,3.59,3.59,0,0,1-46,4.41,3.59,3.59,0,0,1-42.41,8Z"
|
||||
transform="translate(54 -2.072)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewIcon;
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface IIcon {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export const unSelected = "#adadad";
|
||||
export const selected = "#201763";
|
||||
@@ -0,0 +1,322 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import get from "lodash/get";
|
||||
import isString from "lodash/isString";
|
||||
import {
|
||||
LinearProgress,
|
||||
TablePagination,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Grid,
|
||||
Checkbox
|
||||
} from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { TablePaginationActionsProps } from "@material-ui/core/TablePagination/TablePaginationActions";
|
||||
import TableActionButton from "./TableActionButton";
|
||||
|
||||
//Interfaces for table Items
|
||||
|
||||
interface ItemActions {
|
||||
type: string;
|
||||
onClick?(valueToSend: any): any;
|
||||
to?: string;
|
||||
sendOnlyId?: boolean;
|
||||
}
|
||||
|
||||
interface IColumns {
|
||||
label: string;
|
||||
elementKey: string;
|
||||
sortable?: boolean;
|
||||
renderFunction?: (input: any) => any;
|
||||
}
|
||||
|
||||
interface IPaginatorConfig {
|
||||
rowsPerPageOptions: number[];
|
||||
colSpan: number;
|
||||
count: number;
|
||||
rowsPerPage: number;
|
||||
page: number;
|
||||
SelectProps: any;
|
||||
onChangePage: (
|
||||
event: React.MouseEvent<HTMLButtonElement> | null,
|
||||
page: number
|
||||
) => void;
|
||||
onChangeRowsPerPage?: React.ChangeEventHandler<
|
||||
HTMLTextAreaElement | HTMLInputElement
|
||||
>;
|
||||
ActionsComponent?: React.ElementType<TablePaginationActionsProps>;
|
||||
}
|
||||
|
||||
interface TableWrapperProps {
|
||||
itemActions?: ItemActions[] | null;
|
||||
columns: IColumns[];
|
||||
onSelect?: (e: React.ChangeEvent<HTMLInputElement>) => any;
|
||||
idField: string;
|
||||
isLoading: boolean;
|
||||
records: any[];
|
||||
classes: any;
|
||||
entityName: string;
|
||||
selectedItems?: string[];
|
||||
stickyHeader?: boolean;
|
||||
paginatorConfig?: IPaginatorConfig;
|
||||
}
|
||||
|
||||
const borderColor = "#eaeaea";
|
||||
|
||||
const rowText = {
|
||||
fontWeight: 400,
|
||||
fontSize: 14,
|
||||
borderColor: borderColor
|
||||
};
|
||||
|
||||
const checkBoxBasic = {
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 3
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
dialogContainer: {
|
||||
padding: "12px 26px 22px"
|
||||
},
|
||||
paper: {
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column",
|
||||
padding: "19px 38px"
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: 700,
|
||||
fontSize: 14,
|
||||
paddingBottom: 15,
|
||||
borderColor: borderColor
|
||||
}
|
||||
}
|
||||
},
|
||||
rowUnselected: {
|
||||
...rowText
|
||||
},
|
||||
rowSelected: {
|
||||
...rowText,
|
||||
color: "#201763"
|
||||
},
|
||||
paginatorContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
padding: "5px 38px"
|
||||
},
|
||||
checkBoxHeader: {
|
||||
"&.MuiTableCell-paddingCheckbox": {
|
||||
paddingBottom: 9
|
||||
}
|
||||
},
|
||||
actionsContainer: {
|
||||
width: 120,
|
||||
borderColor: borderColor
|
||||
},
|
||||
paginatorComponent: {
|
||||
borderBottom: 0
|
||||
},
|
||||
unCheckedIcon: { ...checkBoxBasic, border: "1px solid #d0d0d0" },
|
||||
checkedIcon: {
|
||||
...checkBoxBasic,
|
||||
border: "1px solid #201763",
|
||||
backgroundColor: "#201763"
|
||||
},
|
||||
checkBoxRow: {
|
||||
borderColor: borderColor
|
||||
}
|
||||
});
|
||||
|
||||
// Function that renders Title Columns
|
||||
const titleColumnsMap = (columns: IColumns[]) => {
|
||||
return columns.map((column: IColumns, index: number) => {
|
||||
return (
|
||||
<TableCell key={`tbCT-${column.elementKey}-${index}`}>
|
||||
{column.label}
|
||||
</TableCell>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Function that renders Rows
|
||||
const rowColumnsMap = (
|
||||
columns: IColumns[],
|
||||
itemData: any,
|
||||
classes: any,
|
||||
isSelected: boolean
|
||||
) => {
|
||||
return columns.map((column: IColumns, index: number) => {
|
||||
const itemElement = isString(itemData)
|
||||
? itemData
|
||||
: get(itemData, column.elementKey, null); // If the element is just a string, we render it as it is
|
||||
const renderElement = column.renderFunction
|
||||
? column.renderFunction(itemElement)
|
||||
: itemElement; // If render function is set, we send the value to the function.
|
||||
return (
|
||||
<TableCell
|
||||
key={`tbRE-${column.elementKey}-${index}`}
|
||||
className={isSelected ? classes.rowSelected : classes.rowUnselected}
|
||||
>
|
||||
{renderElement}
|
||||
</TableCell>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Function to render the action buttons
|
||||
const elementActions = (
|
||||
actions: ItemActions[],
|
||||
valueToSend: any,
|
||||
selected: boolean,
|
||||
idField: string
|
||||
) => {
|
||||
return actions.map((action: ItemActions, index: number) => {
|
||||
return (
|
||||
<TableActionButton
|
||||
type={action.type}
|
||||
onClick={action.onClick}
|
||||
to={action.to}
|
||||
valueToSend={valueToSend}
|
||||
selected={selected}
|
||||
key={`actions-${action.type}-${index.toString()}`}
|
||||
idField={idField}
|
||||
sendOnlyId={!!action.sendOnlyId}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Main function to render the Table Wrapper
|
||||
const TableWrapper = ({
|
||||
itemActions,
|
||||
columns,
|
||||
onSelect,
|
||||
records,
|
||||
isLoading,
|
||||
entityName,
|
||||
selectedItems,
|
||||
idField,
|
||||
classes,
|
||||
stickyHeader = false,
|
||||
paginatorConfig
|
||||
}: TableWrapperProps) => {
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{isLoading && <LinearProgress />}
|
||||
{records && records.length > 0 ? (
|
||||
<Table size="small" stickyHeader={stickyHeader}>
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
{onSelect && selectedItems && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
align="center"
|
||||
className={classes.checkBoxHeader}
|
||||
>
|
||||
Select
|
||||
</TableCell>
|
||||
)}
|
||||
{titleColumnsMap(columns)}
|
||||
{itemActions && itemActions.length > 0 && (
|
||||
<TableCell
|
||||
align="center"
|
||||
className={classes.actionsContainer}
|
||||
>
|
||||
Actions
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.map((record: any, index: number) => {
|
||||
const isSelected = selectedItems
|
||||
? selectedItems.includes(
|
||||
isString(record) ? record : record[idField]
|
||||
)
|
||||
: false;
|
||||
|
||||
return (
|
||||
<TableRow key={`tb-${entityName}-${index.toString()}`}>
|
||||
{onSelect && selectedItems && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
align="center"
|
||||
className={classes.checkBoxRow}
|
||||
>
|
||||
<Checkbox
|
||||
value={isString(record) ? record : record[idField]}
|
||||
color="primary"
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
checked={isSelected}
|
||||
onChange={onSelect}
|
||||
checkedIcon={<span className={classes.checkedIcon} />}
|
||||
icon={<span className={classes.unCheckedIcon} />}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
{rowColumnsMap(columns, record, classes, isSelected)}
|
||||
{itemActions && itemActions.length > 0 && (
|
||||
<TableCell
|
||||
align="center"
|
||||
className={classes.actionsContainer}
|
||||
>
|
||||
{elementActions(
|
||||
itemActions,
|
||||
record,
|
||||
isSelected,
|
||||
idField
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
<div>{`There are no ${entityName} yet.`}</div>
|
||||
)}
|
||||
</Paper>
|
||||
{paginatorConfig && (
|
||||
<Grid item xs={12} className={classes.paginatorContainer}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
{...paginatorConfig}
|
||||
className={classes.paginatorComponent}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(TableWrapper);
|
||||
@@ -0,0 +1,134 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
import { IElementValue, KVField } from "./types";
|
||||
import CSVMultiSelector from "../Common/FormComponents/CSVMultiSelector/CSVMultiSelector";
|
||||
|
||||
interface IConfGenericProps {
|
||||
onChange: (newValue: IElementValue[]) => void;
|
||||
fields: KVField[];
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const ConfTargetGeneric = ({
|
||||
onChange,
|
||||
fields,
|
||||
classes
|
||||
}: IConfGenericProps) => {
|
||||
const [valueHolder, setValueHolder] = useState<IElementValue[]>([]);
|
||||
const fieldsElements = !fields ? [] : fields;
|
||||
|
||||
// Effect to create all the values to hold
|
||||
useEffect(() => {
|
||||
const values: IElementValue[] = [];
|
||||
fields.forEach(field => {
|
||||
const stateInsert: IElementValue = {
|
||||
key: field.name,
|
||||
value: field.type === "on|off" ? "false" : ""
|
||||
};
|
||||
values.push(stateInsert);
|
||||
});
|
||||
|
||||
setValueHolder(values);
|
||||
}, [fields]);
|
||||
|
||||
useEffect(() => {
|
||||
onChange(valueHolder);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [valueHolder]);
|
||||
|
||||
const setValueElement = (key: string, value: string, index: number) => {
|
||||
const valuesDup = [...valueHolder];
|
||||
valuesDup[index] = { key, value };
|
||||
|
||||
setValueHolder(valuesDup);
|
||||
};
|
||||
|
||||
const fieldDefinition = (field: KVField, item: number) => {
|
||||
switch (field.type) {
|
||||
case "on|off":
|
||||
return (
|
||||
<RadioGroupSelector
|
||||
currentSelection={valueHolder[item] ? valueHolder[item].value : ""}
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
tooltip={field.tooltip}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValueElement(field.name, e.target.value, item)
|
||||
}
|
||||
selectorOptions={[
|
||||
{ label: "On", value: "true" },
|
||||
{ label: "Off", value: "false" }
|
||||
]}
|
||||
/>
|
||||
);
|
||||
case "csv":
|
||||
return (
|
||||
<CSVMultiSelector
|
||||
elements={valueHolder[item] ? valueHolder[item].value : ""}
|
||||
label={field.label}
|
||||
name={field.name}
|
||||
onChange={(value: string) =>
|
||||
setValueElement(field.name, value, item)
|
||||
}
|
||||
tooltip={field.tooltip}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<InputBoxWrapper
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
tooltip={field.tooltip}
|
||||
value={valueHolder[item] ? valueHolder[item].value : ""}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValueElement(field.name, e.target.value, item)
|
||||
}
|
||||
multiline={!!field.multiline}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
{fieldsElements.map((field, item) => (
|
||||
<React.Fragment key={field.name}>
|
||||
<Grid item xs={12}>
|
||||
{fieldDefinition(field, item)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ConfTargetGeneric);
|
||||
@@ -0,0 +1,155 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState } from "react";
|
||||
import get from "lodash/get";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { TextField } from "@material-ui/core";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import history from "../../../../history";
|
||||
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
|
||||
import { configurationElements } from "../utils";
|
||||
import { IConfigurationElement } from "../types";
|
||||
import EditConfiguration from "../CustomForms/EditConfiguration";
|
||||
|
||||
interface IListConfiguration {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
},
|
||||
iconText: {
|
||||
lineHeight: "24px"
|
||||
}
|
||||
});
|
||||
|
||||
const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
const [editScreenOpen, setEditScreenOpen] = useState(false);
|
||||
const [selectedConfiguration, setSelectedConfiguration] = useState({
|
||||
configuration_id: "",
|
||||
configuration_label: ""
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
const tableActions = [
|
||||
{
|
||||
type: "edit",
|
||||
onClick: (element: IConfigurationElement) => {
|
||||
const url = get(element, "url", "");
|
||||
if (url !== "") {
|
||||
// We redirect Browser
|
||||
history.push(url);
|
||||
} else {
|
||||
setSelectedConfiguration(element);
|
||||
setEditScreenOpen(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const filteredRecords: IConfigurationElement[] = configurationElements.filter(
|
||||
elementItem =>
|
||||
elementItem.configuration_id
|
||||
.toLocaleLowerCase()
|
||||
.includes(filter.toLocaleLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{editScreenOpen && (
|
||||
<EditConfiguration
|
||||
open={editScreenOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
setIsLoading(true);
|
||||
setEditScreenOpen(false);
|
||||
}}
|
||||
selectedConfiguration={selectedConfiguration}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Configurations List</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
{error !== "" && <Grid container>{error}</Grid>}
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Filter"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={event => {
|
||||
setFilter(event.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Configuration", elementKey: "configuration_id" }
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
records={filteredRecords}
|
||||
entityName="Configurations"
|
||||
idField="configuration_id"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ConfigurationsList);
|
||||
@@ -0,0 +1,188 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState } from "react";
|
||||
import get from "lodash/get";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Button, TextField } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import AddIcon from "@material-ui/icons/Add";
|
||||
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
|
||||
import EditConfiguration from "../CustomForms/EditConfiguration";
|
||||
|
||||
interface IMatchParams {
|
||||
isExact: boolean;
|
||||
params: any;
|
||||
path: string;
|
||||
}
|
||||
|
||||
interface IWebhookPanel {
|
||||
match: IMatchParams;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
interface IWebhook {
|
||||
name: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
},
|
||||
iconText: {
|
||||
lineHeight: "24px"
|
||||
}
|
||||
});
|
||||
|
||||
const panels = {
|
||||
logger: {
|
||||
main: "logger",
|
||||
title: "Logger Webhook Configuration",
|
||||
modalTitle: "Logger Webhook",
|
||||
apiURL: "",
|
||||
configuration: {
|
||||
configuration_id: "logger_webhook",
|
||||
configuration_label: "Logger Webhook"
|
||||
}
|
||||
},
|
||||
audit: {
|
||||
main: "audit",
|
||||
title: "Audit Webhook Configuration",
|
||||
modalTitle: "Audit Webhook",
|
||||
apiURL: "",
|
||||
configuration: {
|
||||
configuration_id: "audit_webhook",
|
||||
configuration_label: "Audit Webhook"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
|
||||
const [addWebhookOpen, setAddWebhookOpen] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [webhooks, setWebhooks] = useState<IWebhook[]>([]);
|
||||
|
||||
const pathIn = get(match, "path", "");
|
||||
const panelToDisplay = pathIn.split("/");
|
||||
const panelData = get(panels, panelToDisplay[2], false);
|
||||
|
||||
if (!panelData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filteredRecords: IWebhook[] = webhooks.filter(elementItem =>
|
||||
elementItem.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())
|
||||
);
|
||||
|
||||
const tableActions = [
|
||||
{
|
||||
type: "edit",
|
||||
onClick: () => {}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addWebhookOpen && (
|
||||
<EditConfiguration
|
||||
open={addWebhookOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
setIsLoading(true);
|
||||
setAddWebhookOpen(false);
|
||||
}}
|
||||
selectedConfiguration={panelData.configuration}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">{panelData.title}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
{error !== "" && <Grid container>{error}</Grid>}
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Filter"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={event => {
|
||||
setFilter(event.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => {
|
||||
setAddWebhookOpen(true);
|
||||
}}
|
||||
>
|
||||
Add Webhook Configuration
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[{ label: "Name", elementKey: "name" }]}
|
||||
isLoading={isLoading}
|
||||
records={filteredRecords}
|
||||
entityName="Webhook Configurations"
|
||||
idField="name"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(WebhookPanel);
|
||||
@@ -0,0 +1,292 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { FormControlLabel, Switch } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
import { IElementValue } from "../types";
|
||||
|
||||
interface IConfMySqlProps {
|
||||
onChange: (newValue: IElementValue[]) => void;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
//Local States
|
||||
const [useDsnString, setUseDsnString] = useState<boolean>(false);
|
||||
const [dsnString, setDsnString] = useState<string>("");
|
||||
const [host, setHostname] = useState<string>("");
|
||||
const [dbName, setDbName] = useState<string>("");
|
||||
const [port, setPort] = useState<string>("");
|
||||
const [user, setUser] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
|
||||
const [table, setTable] = useState<string>("");
|
||||
const [format, setFormat] = useState<string>("namespace");
|
||||
const [queueDir, setQueueDir] = useState<string>("");
|
||||
const [queueLimit, setQueueLimit] = useState<string>("");
|
||||
const [comment, setComment] = useState<string>("");
|
||||
|
||||
// dsn_string* (string) MySQL data-source-name connection string e.g. "<user>:<password>@tcp(<host>:<port>)/<database>"
|
||||
// table* (string) DB table name to store/update events, table is auto-created
|
||||
// format* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
|
||||
// queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
|
||||
// queue_limit (number) maximum limit for undelivered messages, defaults to '100000'
|
||||
// comment (sentence) optionally add a comment to this setting
|
||||
|
||||
const parseDsnString = (
|
||||
input: string,
|
||||
keys: string[]
|
||||
): Map<string, string> => {
|
||||
let kvFields: Map<string, string> = new Map();
|
||||
const regex = /(.*?):(.*?)@tcp\((.*?):(.*?)\)\/(.*?)$/gm;
|
||||
let m;
|
||||
|
||||
while ((m = regex.exec(input)) !== null) {
|
||||
// This is necessary to avoid infinite loops with zero-width matches
|
||||
if (m.index === regex.lastIndex) {
|
||||
regex.lastIndex++;
|
||||
}
|
||||
|
||||
kvFields.set("user", m[1]);
|
||||
kvFields.set("password", m[2]);
|
||||
kvFields.set("host", m[3]);
|
||||
kvFields.set("port", m[4]);
|
||||
kvFields.set("dbname", m[5]);
|
||||
}
|
||||
|
||||
return kvFields;
|
||||
};
|
||||
|
||||
const configToDsnString = useCallback((): string => {
|
||||
return `${user}:${password}@tcp(${host}:${port})/${dbName}`;
|
||||
}, [user, password, host, port, dbName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dsnString !== "") {
|
||||
const formValues = [
|
||||
{ key: "dsn_string", value: dsnString },
|
||||
{ key: "table", value: table },
|
||||
{ key: "format", value: format },
|
||||
{ key: "queue_dir", value: queueDir },
|
||||
{ key: "queue_limit", value: queueLimit },
|
||||
{ key: "comment", value: comment }
|
||||
];
|
||||
|
||||
onChange(formValues);
|
||||
}
|
||||
}, [dsnString, table, format, queueDir, queueLimit, comment, onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
const cs = configToDsnString();
|
||||
setDsnString(cs);
|
||||
}, [user, dbName, password, port, host, setDsnString, configToDsnString]);
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={useDsnString}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
// build dsn_string
|
||||
const cs = configToDsnString();
|
||||
setDsnString(cs);
|
||||
} else {
|
||||
// parse dsn_string
|
||||
const kv = parseDsnString(dsnString, [
|
||||
"host",
|
||||
"port",
|
||||
"dbname",
|
||||
"user",
|
||||
"password"
|
||||
]);
|
||||
setHostname(kv.get("host") ? kv.get("host") + "" : "");
|
||||
setPort(kv.get("port") ? kv.get("port") + "" : "");
|
||||
setDbName(kv.get("dbname") ? kv.get("dbname") + "" : "");
|
||||
setUser(kv.get("user") ? kv.get("user") + "" : "");
|
||||
setPassword(
|
||||
kv.get("password") ? kv.get("password") + "" : ""
|
||||
);
|
||||
}
|
||||
|
||||
setUseDsnString(event.target.checked);
|
||||
}}
|
||||
name="checkedB"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Enter DSN String"
|
||||
/>
|
||||
</Grid>
|
||||
{useDsnString ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="dsn-string"
|
||||
name="dsn_string"
|
||||
label="DSN String"
|
||||
value={dsnString}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDsnString(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="host"
|
||||
name="host"
|
||||
label="Host"
|
||||
value={host}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setHostname(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="db-name"
|
||||
name="db-name"
|
||||
label="DB Name"
|
||||
value={dbName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDbName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="port"
|
||||
name="port"
|
||||
label="Port"
|
||||
value={port}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPort(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="user"
|
||||
name="user"
|
||||
label="User"
|
||||
value={user}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUser(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="password"
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="table"
|
||||
name="table"
|
||||
label="Table"
|
||||
value={table}
|
||||
tooltip="DB table name to store/update events, table is auto-created"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTable(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroupSelector
|
||||
currentSelection={format}
|
||||
id="format"
|
||||
name="format"
|
||||
label="Format"
|
||||
onChange={e => {
|
||||
setFormat(e.target.value);
|
||||
}}
|
||||
tooltip="'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'"
|
||||
selectorOptions={[
|
||||
{ label: "Namespace", value: "namespace" },
|
||||
{ label: "Access", value: "access" }
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="queue-dir"
|
||||
name="queue_dir"
|
||||
label="Queue Dir"
|
||||
value={queueDir}
|
||||
tooltip="staging dir for undelivered messages e.g. '/home/events'"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQueueDir(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="queue-limit"
|
||||
name="queue_limit"
|
||||
label="Queue Limit"
|
||||
type="number"
|
||||
value={queueLimit}
|
||||
tooltip="maximum limit for undelivered messages, defaults to '10000'"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQueueLimit(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="comment"
|
||||
name="comment"
|
||||
label="Comment"
|
||||
multiline={true}
|
||||
value={comment}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setComment(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ConfMySql);
|
||||
@@ -0,0 +1,380 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { FormControlLabel, Switch } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import { IElementValue } from "../types";
|
||||
|
||||
interface IConfPostgresProps {
|
||||
onChange: (newValue: IElementValue[]) => void;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
//Local States
|
||||
const [useConnectionString, setUseConnectionString] = useState<boolean>(
|
||||
false
|
||||
);
|
||||
const [connectionString, setConnectionString] = useState<string>("");
|
||||
const [host, setHostname] = useState<string>("");
|
||||
const [dbName, setDbName] = useState<string>("");
|
||||
const [port, setPort] = useState<string>("");
|
||||
const [user, setUser] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [sslMode, setSslMode] = useState<string>("require");
|
||||
|
||||
const [table, setTable] = useState<string>("");
|
||||
const [format, setFormat] = useState<string>("namespace");
|
||||
const [queueDir, setQueueDir] = useState<string>("");
|
||||
const [queueLimit, setQueueLimit] = useState<string>("");
|
||||
const [comment, setComment] = useState<string>("");
|
||||
|
||||
// connection_string* (string) Postgres server connection-string e.g. "host=localhost port=5432 dbname=minio_events user=postgres password=password sslmode=disable"
|
||||
|
||||
// "host=localhost
|
||||
// port=5432
|
||||
//dbname=minio_events
|
||||
//user=postgres
|
||||
//password=password
|
||||
//sslmode=disable"
|
||||
|
||||
// table* (string) DB table name to store/update events, table is auto-created
|
||||
// format* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
|
||||
// queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
|
||||
// queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
|
||||
// comment (sentence) optionally add a comment to this setting
|
||||
|
||||
const KvSeparator = "=";
|
||||
const parseConnectionString = (
|
||||
input: string,
|
||||
keys: string[]
|
||||
): Map<string, string> => {
|
||||
let valueIndexes: number[] = [];
|
||||
|
||||
for (const key of keys) {
|
||||
const i = input.indexOf(key + KvSeparator);
|
||||
if (i === -1) {
|
||||
continue;
|
||||
}
|
||||
valueIndexes.push(i);
|
||||
}
|
||||
valueIndexes.sort((n1, n2) => n1 - n2);
|
||||
|
||||
let kvFields = new Map<string, string>();
|
||||
let fields: string[] = new Array<string>(valueIndexes.length);
|
||||
for (let i = 0; i < valueIndexes.length; i++) {
|
||||
const j = i + 1;
|
||||
if (j < valueIndexes.length) {
|
||||
fields[i] = input.substr(
|
||||
valueIndexes[i],
|
||||
valueIndexes[j] - valueIndexes[i]
|
||||
);
|
||||
} else {
|
||||
fields[i] = input.substr(valueIndexes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (let field of fields) {
|
||||
if (field === undefined) {
|
||||
continue;
|
||||
}
|
||||
const key = field.substr(0, field.indexOf("="));
|
||||
const value = field.substr(field.indexOf("=") + 1).trim();
|
||||
kvFields.set(key, value);
|
||||
}
|
||||
return kvFields;
|
||||
};
|
||||
|
||||
const configToString = useCallback((): string => {
|
||||
let strValue = "";
|
||||
if (host !== "") {
|
||||
strValue = `${strValue} host=${host}`;
|
||||
}
|
||||
if (dbName !== "") {
|
||||
strValue = `${strValue} dbname=${dbName}`;
|
||||
}
|
||||
if (user !== "") {
|
||||
strValue = `${strValue} user=${user}`;
|
||||
}
|
||||
if (password !== "") {
|
||||
strValue = `${strValue} password=${password}`;
|
||||
}
|
||||
if (port !== "") {
|
||||
strValue = `${strValue} port=${port}`;
|
||||
}
|
||||
|
||||
strValue = `${strValue} sslmode=${sslMode}`;
|
||||
|
||||
return strValue.trim();
|
||||
}, [host, dbName, user, password, port, sslMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (connectionString !== "") {
|
||||
const formValues = [
|
||||
{ key: "connection_string", value: connectionString },
|
||||
{ key: "table", value: table },
|
||||
{ key: "format", value: format },
|
||||
{ key: "queue_dir", value: queueDir },
|
||||
{ key: "queue_limit", value: queueLimit },
|
||||
{ key: "comment", value: comment }
|
||||
];
|
||||
|
||||
onChange(formValues);
|
||||
}
|
||||
}, [
|
||||
connectionString,
|
||||
table,
|
||||
format,
|
||||
queueDir,
|
||||
queueLimit,
|
||||
comment,
|
||||
onChange
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const cs = configToString();
|
||||
setConnectionString(cs);
|
||||
}, [
|
||||
user,
|
||||
dbName,
|
||||
password,
|
||||
port,
|
||||
sslMode,
|
||||
host,
|
||||
setConnectionString,
|
||||
configToString
|
||||
]);
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={useConnectionString}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
// build connection_string
|
||||
const cs = configToString();
|
||||
setConnectionString(cs);
|
||||
} else {
|
||||
// parse connection_string
|
||||
const kv = parseConnectionString(connectionString, [
|
||||
"host",
|
||||
"port",
|
||||
"dbname",
|
||||
"user",
|
||||
"password",
|
||||
"sslmode"
|
||||
]);
|
||||
setHostname(kv.get("host") ? kv.get("host") + "" : "");
|
||||
setPort(kv.get("port") ? kv.get("port") + "" : "");
|
||||
setDbName(kv.get("dbname") ? kv.get("dbname") + "" : "");
|
||||
setUser(kv.get("user") ? kv.get("user") + "" : "");
|
||||
setPassword(
|
||||
kv.get("password") ? kv.get("password") + "" : ""
|
||||
);
|
||||
setSslMode(
|
||||
kv.get("sslmode") ? kv.get("sslmode") + "" : "require"
|
||||
);
|
||||
}
|
||||
|
||||
setUseConnectionString(event.target.checked);
|
||||
}}
|
||||
name="checkedB"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Enter Connection String"
|
||||
/>
|
||||
</Grid>
|
||||
{useConnectionString ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="connection-string"
|
||||
name="connection_string"
|
||||
label="Connection String"
|
||||
value={connectionString}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setConnectionString(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="host"
|
||||
name="host"
|
||||
label="Host"
|
||||
value={host}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setHostname(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="db-name"
|
||||
name="db-name"
|
||||
label="DB Name"
|
||||
value={dbName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDbName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="port"
|
||||
name="port"
|
||||
label="Port"
|
||||
value={port}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPort(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
value={sslMode}
|
||||
label="SSL Mode"
|
||||
id="sslmode"
|
||||
name="sslmode"
|
||||
onChange={(e): void => {
|
||||
if (e.target.value !== undefined) {
|
||||
setSslMode(e.target.value + "");
|
||||
}
|
||||
}}
|
||||
options={[
|
||||
{ label: "Require", value: "require" },
|
||||
{ label: "Disable", value: "disable" },
|
||||
{ label: "Verify CA", value: "verify-ca" },
|
||||
{ label: "Verify Full", value: "verify-full" }
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="user"
|
||||
name="user"
|
||||
label="User"
|
||||
value={user}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUser(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="password"
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="table"
|
||||
name="table"
|
||||
label="Table"
|
||||
value={table}
|
||||
tooltip="DB table name to store/update events, table is auto-created"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTable(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroupSelector
|
||||
currentSelection={format}
|
||||
id="format"
|
||||
name="format"
|
||||
label="Format"
|
||||
onChange={e => {
|
||||
setFormat(e.target.value);
|
||||
}}
|
||||
tooltip="'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'"
|
||||
selectorOptions={[
|
||||
{ label: "Namespace", value: "namespace" },
|
||||
{ label: "Access", value: "access" }
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="queue-dir"
|
||||
name="queue_dir"
|
||||
label="Queue Dir"
|
||||
value={queueDir}
|
||||
tooltip="staging dir for undelivered messages e.g. '/home/events'"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQueueDir(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="queue-limit"
|
||||
name="queue_limit"
|
||||
label="Queue Limit"
|
||||
type="number"
|
||||
value={queueLimit}
|
||||
tooltip="maximum limit for undelivered messages, defaults to '10000'"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQueueLimit(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="comment"
|
||||
name="comment"
|
||||
label="Comment"
|
||||
multiline={true}
|
||||
value={comment}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setComment(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ConfPostgres);
|
||||
@@ -0,0 +1,160 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Button } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import api from "../../../../common/api";
|
||||
import { serverNeedsRestart } from "../../../../actions";
|
||||
import { connect } from "react-redux";
|
||||
import ConfTargetGeneric from "../ConfTargetGeneric";
|
||||
import { fieldsConfigurations, removeEmptyFields } from "../utils";
|
||||
import { IConfigurationElement, IElementValue } from "../types";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
},
|
||||
logoButton: {
|
||||
height: "80px"
|
||||
}
|
||||
});
|
||||
|
||||
interface IAddNotificationEndpointProps {
|
||||
open: boolean;
|
||||
closeModalAndRefresh: any;
|
||||
serverNeedsRestart: typeof serverNeedsRestart;
|
||||
selectedConfiguration: IConfigurationElement;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const EditConfiguration = ({
|
||||
open,
|
||||
closeModalAndRefresh,
|
||||
serverNeedsRestart,
|
||||
selectedConfiguration,
|
||||
classes
|
||||
}: IAddNotificationEndpointProps) => {
|
||||
//Local States
|
||||
const [valuesObj, setValueObj] = useState<IElementValue[]>([]);
|
||||
const [saving, setSaving] = useState<boolean>(false);
|
||||
const [addError, setError] = useState<string>("");
|
||||
|
||||
//Effects
|
||||
|
||||
useEffect(() => {
|
||||
if (saving) {
|
||||
const payload = {
|
||||
key_values: removeEmptyFields(valuesObj)
|
||||
};
|
||||
api
|
||||
.invoke(
|
||||
"PUT",
|
||||
`/api/v1/configs/${selectedConfiguration.configuration_id}`,
|
||||
payload
|
||||
)
|
||||
.then(res => {
|
||||
setSaving(false);
|
||||
setError("");
|
||||
serverNeedsRestart(true);
|
||||
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
setSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
}
|
||||
}, [
|
||||
saving,
|
||||
serverNeedsRestart,
|
||||
selectedConfiguration,
|
||||
valuesObj,
|
||||
closeModalAndRefresh
|
||||
]);
|
||||
|
||||
//Fetch Actions
|
||||
const submitForm = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
setSaving(true);
|
||||
};
|
||||
|
||||
const onValueChange = useCallback(
|
||||
newValue => {
|
||||
setValueObj(newValue);
|
||||
},
|
||||
[setValueObj]
|
||||
);
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={closeModalAndRefresh}
|
||||
title={selectedConfiguration.configuration_label}
|
||||
>
|
||||
<React.Fragment>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<form noValidate onSubmit={submitForm}>
|
||||
<ConfTargetGeneric
|
||||
fields={
|
||||
fieldsConfigurations[selectedConfiguration.configuration_id]
|
||||
}
|
||||
onChange={onValueChange}
|
||||
/>
|
||||
<Grid item xs={3} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={saving}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9} />
|
||||
</form>
|
||||
</React.Fragment>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const connector = connect(null, { serverNeedsRestart });
|
||||
|
||||
export default connector(withStyles(styles)(EditConfiguration));
|
||||
51
portal-ui/src/screens/Console/Configurations/types.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { SelectorTypes } from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
|
||||
export type KVFieldType =
|
||||
| "string"
|
||||
| "number"
|
||||
| "on|off"
|
||||
| "enum"
|
||||
| "path"
|
||||
| "url"
|
||||
| "address"
|
||||
| "duration"
|
||||
| "uri"
|
||||
| "sentence"
|
||||
| "csv";
|
||||
|
||||
export interface KVField {
|
||||
name: string;
|
||||
label: string;
|
||||
tooltip: string;
|
||||
required?: boolean;
|
||||
type: KVFieldType;
|
||||
options?: SelectorTypes[];
|
||||
multiline?: boolean;
|
||||
}
|
||||
|
||||
export interface IConfigurationElement {
|
||||
configuration_id: string;
|
||||
configuration_label: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface IElementValue {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
771
portal-ui/src/screens/Console/Configurations/utils.ts
Normal file
@@ -0,0 +1,771 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { IConfigurationElement, IElementValue } from "./types";
|
||||
|
||||
export const notifyPostgres = "notify_postgres";
|
||||
export const notifyMysql = "notify_mysql";
|
||||
export const notifyKafka = "notify_kafka";
|
||||
export const notifyAmqp = "notify_amqp";
|
||||
export const notifyMqtt = "notify_mqtt";
|
||||
export const notifyRedis = "notify_redis";
|
||||
export const notifyNats = "notify_nats";
|
||||
export const notifyElasticsearch = "notify_elasticsearch";
|
||||
export const notifyWebhooks = "notify_webhooks";
|
||||
export const notifyNsq = "notify_nsq";
|
||||
|
||||
export const configurationElements: IConfigurationElement[] = [
|
||||
{ configuration_id: "region", configuration_label: "Region Configuration" },
|
||||
{ configuration_id: "cache", configuration_label: "Cache Configuration" },
|
||||
{
|
||||
configuration_id: "compression",
|
||||
configuration_label: "Compression Configuration"
|
||||
},
|
||||
{ configuration_id: "etcd", configuration_label: "Etcd Configuration" },
|
||||
{
|
||||
configuration_id: "identity_openid",
|
||||
configuration_label: "Identity Openid Configuration"
|
||||
},
|
||||
{
|
||||
configuration_id: "identity_ldap",
|
||||
configuration_label: "Identity LDAP Configuration"
|
||||
},
|
||||
{
|
||||
configuration_id: "policy_opa",
|
||||
configuration_label: "Policy OPA Configuration"
|
||||
},
|
||||
{
|
||||
configuration_id: "kms_vault",
|
||||
configuration_label: "KMS Vault Configuration"
|
||||
},
|
||||
{ configuration_id: "kms_kes", configuration_label: "KMS KES Configuration" },
|
||||
{
|
||||
configuration_id: "logger_webhook",
|
||||
configuration_label: "Logger Webhook Configuration",
|
||||
url: "/webhook/logger"
|
||||
},
|
||||
{
|
||||
configuration_id: "audit_webhook",
|
||||
configuration_label: "Audit Webhook Configuration",
|
||||
url: "/webhook/audit"
|
||||
}
|
||||
];
|
||||
|
||||
export const fieldsConfigurations: any = {
|
||||
region: [
|
||||
{
|
||||
name: "name",
|
||||
required: true,
|
||||
label: "name",
|
||||
tooltip: 'Name of the location of the server e.g. "us-west-rack2"',
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
required: false,
|
||||
label: "comment",
|
||||
tooltip: "You can add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
],
|
||||
cache: [
|
||||
{
|
||||
name: "drives",
|
||||
required: true,
|
||||
label: "Drives",
|
||||
tooltip:
|
||||
'Mountpoints e.g. "/optane1" or "/optane2", you can write one per field',
|
||||
type: "csv"
|
||||
},
|
||||
{
|
||||
name: "expiry",
|
||||
required: false,
|
||||
label: "Expiry",
|
||||
tooltip: 'Cache expiry duration in days e.g. "90"',
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
name: "quota",
|
||||
required: false,
|
||||
label: "Quota",
|
||||
tooltip: 'Limit cache drive usage in percentage e.g. "90"',
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
name: "exclude",
|
||||
required: false,
|
||||
label: "Exclude",
|
||||
tooltip:
|
||||
'Wildcard exclusion patterns e.g. "bucket/*.tmp" or "*.exe", you can write one per field',
|
||||
type: "csv"
|
||||
},
|
||||
{
|
||||
name: "after",
|
||||
required: false,
|
||||
label: "After",
|
||||
tooltip: "Minimum number of access before caching an object",
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
required: false,
|
||||
label: "Comment",
|
||||
tooltip: "You can add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
],
|
||||
compression: [
|
||||
{
|
||||
name: "minio_compress",
|
||||
required: true,
|
||||
label: "MinIO Compress",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "extensions",
|
||||
required: false,
|
||||
label: "Extensions",
|
||||
tooltip:
|
||||
'Extensions to compress e.g. ".txt",".log" or ".csv", you can write one per field',
|
||||
type: "csv"
|
||||
},
|
||||
{
|
||||
name: "mime_types",
|
||||
required: false,
|
||||
label: "Mime Types",
|
||||
tooltip:
|
||||
'Mime types e.g. "text/*","application/json" or "application/xml", you can write one per field',
|
||||
type: "csv"
|
||||
}
|
||||
],
|
||||
etcd: [
|
||||
{
|
||||
name: "endpoints",
|
||||
required: true,
|
||||
label: "Endpoints",
|
||||
tooltip:
|
||||
'List of etcd endpoints e.g. "http://localhost:2379", you can write one per field',
|
||||
type: "csv"
|
||||
},
|
||||
{
|
||||
name: "path_prefix",
|
||||
required: false,
|
||||
label: "Path Prefix",
|
||||
tooltip: 'namespace prefix to isolate tenants e.g. "customer1/"',
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "coredns_path",
|
||||
required: false,
|
||||
label: "Coredns Path",
|
||||
tooltip: 'Shared bucket DNS records, default is "/skydns"',
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "client_cert",
|
||||
required: false,
|
||||
label: "Client Cert",
|
||||
tooltip: "Client cert for mTLS authentication",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "client_cert_key",
|
||||
required: false,
|
||||
label: "Client Cert Key",
|
||||
tooltip: "Client cert key for mTLS authentication",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
required: false,
|
||||
label: "Comment",
|
||||
tooltip: "You can add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
],
|
||||
identity_openid: [
|
||||
{
|
||||
name: "client_id",
|
||||
required: false,
|
||||
label: "Client ID",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "config_url",
|
||||
required: false,
|
||||
label: "Config URL",
|
||||
tooltip: "Config URL for Client ID configuration",
|
||||
type: "string"
|
||||
}
|
||||
],
|
||||
identity_ldap: [
|
||||
{
|
||||
name: "server_addr",
|
||||
required: true,
|
||||
label: "Server ADDR",
|
||||
tooltip: 'AD/LDAP server address e.g. "myldapserver.com:636"',
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "username_format",
|
||||
required: true,
|
||||
label: "Username Format",
|
||||
tooltip:
|
||||
'List of username bind DNs e.g. "uid=%s","cn=accounts","dc=myldapserver" or "dc=com", you can write one per field',
|
||||
type: "csv"
|
||||
},
|
||||
{
|
||||
name: "username_search_filter",
|
||||
required: true,
|
||||
label: "Username Search Filter",
|
||||
tooltip:
|
||||
'User search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"',
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "group_search_filter",
|
||||
required: true,
|
||||
label: "Group Search Filter",
|
||||
tooltip:
|
||||
'Search filter for groups e.g. "(&(objectclass=groupOfNames)(memberUid=%s))"',
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "username_search_base_dn",
|
||||
required: false,
|
||||
label: "Username Search Base DN",
|
||||
tooltip: "List of username search DNs, you can write one per field",
|
||||
type: "csv"
|
||||
},
|
||||
{
|
||||
name: "group_name_attribute",
|
||||
required: false,
|
||||
label: "Group Name Attribute",
|
||||
tooltip: 'Search attribute for group name e.g. "cn"',
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "sts_expiry",
|
||||
required: false,
|
||||
label: "STS Expiry",
|
||||
tooltip:
|
||||
'temporary credentials validity duration in s,m,h,d. Default is "1h"',
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
required: false,
|
||||
label: "TLS Skip Verify",
|
||||
tooltip:
|
||||
'Trust server TLS without verification, defaults to "off" (verify)',
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "server_insecure",
|
||||
required: false,
|
||||
label: "Server Insecure",
|
||||
tooltip:
|
||||
'Allow plain text connection to AD/LDAP server, defaults to "off"',
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
required: false,
|
||||
label: "Comment",
|
||||
tooltip: "Optionally add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
],
|
||||
policy_opa: [
|
||||
{
|
||||
name: "opa_url",
|
||||
required: true,
|
||||
label: "OPA URL",
|
||||
type: "string"
|
||||
}
|
||||
],
|
||||
kms_vault: [],
|
||||
kms_kes: [],
|
||||
logger_webhook: [
|
||||
{
|
||||
name: "name",
|
||||
required: true,
|
||||
label: "Name",
|
||||
tooltip: "Name of the webhook",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
required: true,
|
||||
label: "Auth Token",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "endpoint",
|
||||
required: true,
|
||||
label: "Endpoint",
|
||||
type: "string"
|
||||
}
|
||||
],
|
||||
audit_webhook: [
|
||||
{
|
||||
name: "name",
|
||||
required: true,
|
||||
label: "Name",
|
||||
tooltip: "Name of the webhook",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
required: true,
|
||||
label: "Auth Token",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "endpoint",
|
||||
required: true,
|
||||
label: "Endpoint",
|
||||
type: "string"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const commonFields = [
|
||||
{
|
||||
name: "queue-dir",
|
||||
label: "Queue Dir",
|
||||
required: true,
|
||||
|
||||
tooltip: "staging dir for undelivered messages e.g. '/home/events'",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "queue-limit",
|
||||
label: "Queue Limit",
|
||||
required: false,
|
||||
|
||||
tooltip: "maximum limit for undelivered messages, defaults to '10000'",
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
label: "Comment",
|
||||
required: false,
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
];
|
||||
|
||||
export const notificationEndpointsFields: any = {
|
||||
[notifyKafka]: [
|
||||
{
|
||||
name: "brokers",
|
||||
label: "Brokers",
|
||||
required: true,
|
||||
|
||||
tooltip: "comma separated list of Kafka broker addresses",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "topic",
|
||||
label: "Topic",
|
||||
tooltip: "Kafka topic used for bucket notifications",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "sasl_username",
|
||||
label: "SASL Username",
|
||||
tooltip: "username for SASL/PLAIN or SASL/SCRAM authentication",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "sasl_password",
|
||||
label: "SASL Password",
|
||||
tooltip: "password for SASL/PLAIN or SASL/SCRAM authentication",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "sasl_mechanism",
|
||||
label: "SASL Mechanism",
|
||||
tooltip: "sasl authentication mechanism, default 'PLAIN'",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "tls_client_auth",
|
||||
label: "TLS Client Auth",
|
||||
tooltip:
|
||||
"clientAuth determines the Kafka server's policy for TLS client auth",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "sasl",
|
||||
label: "SASL",
|
||||
tooltip: "set to 'on' to enable SASL authentication",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "tls",
|
||||
label: "TLS",
|
||||
tooltip: "set to 'on' to enable TLS",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
label: "TLS skip verify",
|
||||
tooltip:
|
||||
'trust server TLS without verification, defaults to "on" (verify)',
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "client_tls_cert",
|
||||
label: "client TLS cert",
|
||||
tooltip: "path to client certificate for mTLS auth",
|
||||
type: "path"
|
||||
},
|
||||
{
|
||||
name: "client_tls_key",
|
||||
label: "client TLS key",
|
||||
tooltip: "path to client key for mTLS auth",
|
||||
type: "path"
|
||||
},
|
||||
{
|
||||
name: "version",
|
||||
label: "Version",
|
||||
tooltip: "specify the version of the Kafka cluster e.g '2.2.0'",
|
||||
type: "string"
|
||||
},
|
||||
...commonFields
|
||||
],
|
||||
[notifyAmqp]: [
|
||||
{
|
||||
name: "url",
|
||||
required: true,
|
||||
label: "url",
|
||||
tooltip:
|
||||
"AMQP server endpoint e.g. `amqp://myuser:mypassword@localhost:5672`",
|
||||
type: "url"
|
||||
},
|
||||
{
|
||||
name: "exchange",
|
||||
label: "exchange",
|
||||
tooltip: "name of the AMQP exchange",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "exchange_type",
|
||||
label: "exchange_type",
|
||||
tooltip: "AMQP exchange type",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "routing_key",
|
||||
label: "routing_key",
|
||||
tooltip: "routing key for publishing",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "mandatory",
|
||||
label: "mandatory",
|
||||
tooltip:
|
||||
"quietly ignore undelivered messages when set to 'off', default is 'on'",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "durable",
|
||||
label: "durable",
|
||||
tooltip:
|
||||
"persist queue across broker restarts when set to 'on', default is 'off'",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "no_wait",
|
||||
label: "no_wait",
|
||||
tooltip:
|
||||
"non-blocking message delivery when set to 'on', default is 'off'",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "internal",
|
||||
label: "internal",
|
||||
tooltip:
|
||||
"set to 'on' for exchange to be not used directly by publishers, but only when bound to other exchanges",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "auto_deleted",
|
||||
label: "auto_deleted",
|
||||
tooltip:
|
||||
"auto delete queue when set to 'on', when there are no consumers",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "delivery_mode",
|
||||
label: "delivery_mode",
|
||||
tooltip: "set to '1' for non-persistent or '2' for persistent queue",
|
||||
type: "number"
|
||||
},
|
||||
...commonFields
|
||||
],
|
||||
[notifyRedis]: [
|
||||
{
|
||||
name: "address",
|
||||
required: true,
|
||||
label: "address",
|
||||
tooltip: "Redis server's address. For example: `localhost:6379`",
|
||||
type: "address"
|
||||
},
|
||||
{
|
||||
name: "key",
|
||||
required: true,
|
||||
label: "key",
|
||||
tooltip: "Redis key to store/update events, key is auto-created",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
label: "password",
|
||||
tooltip: "Redis server password",
|
||||
type: "string"
|
||||
},
|
||||
...commonFields
|
||||
],
|
||||
[notifyMqtt]: [
|
||||
{
|
||||
name: "broker",
|
||||
required: true,
|
||||
label: "broker",
|
||||
tooltip: "MQTT server endpoint e.g. `tcp://localhost:1883`",
|
||||
type: "uri"
|
||||
},
|
||||
{
|
||||
name: "topic",
|
||||
required: true,
|
||||
label: "topic",
|
||||
tooltip: "name of the MQTT topic to publish",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "username",
|
||||
label: "username",
|
||||
tooltip: "MQTT username",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
label: "password",
|
||||
tooltip: "MQTT password",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "qos",
|
||||
label: "qos",
|
||||
tooltip: "set the quality of service priority, defaults to '0'",
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
name: "keep_alive_interval",
|
||||
label: "keep_alive_interval",
|
||||
tooltip: "keep-alive interval for MQTT connections in s,m,h,d",
|
||||
type: "duration"
|
||||
},
|
||||
{
|
||||
name: "reconnect_interval",
|
||||
label: "reconnect_interval",
|
||||
tooltip: "reconnect interval for MQTT connections in s,m,h,d",
|
||||
type: "duration"
|
||||
},
|
||||
...commonFields
|
||||
],
|
||||
[notifyNats]: [
|
||||
{
|
||||
name: "address",
|
||||
required: true,
|
||||
label: "address",
|
||||
tooltip: "NATS server address e.g. '0.0.0.0:4222'",
|
||||
type: "address"
|
||||
},
|
||||
{
|
||||
name: "subject",
|
||||
required: true,
|
||||
label: "subject",
|
||||
tooltip: "NATS subscription subject",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "username",
|
||||
label: "username",
|
||||
tooltip: "NATS username",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
label: "password",
|
||||
tooltip: "NATS password",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "token",
|
||||
label: "token",
|
||||
tooltip: "NATS token",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "tls",
|
||||
label: "tls",
|
||||
tooltip: "set to 'on' to enable TLS",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
label: "tls_skip_verify",
|
||||
tooltip:
|
||||
'trust server TLS without verification, defaults to "on" (verify)',
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "ping_interval",
|
||||
label: "ping_interval",
|
||||
tooltip: "client ping commands interval in s,m,h,d. Disabled by default",
|
||||
type: "duration"
|
||||
},
|
||||
{
|
||||
name: "streaming",
|
||||
label: "streaming",
|
||||
tooltip: "set to 'on', to use streaming NATS server",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "streaming_async",
|
||||
label: "streaming_async",
|
||||
tooltip: "set to 'on', to enable asynchronous publish",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "streaming_max_pub_acks_in_flight",
|
||||
label: "streaming_max_pub_acks_in_flight",
|
||||
tooltip: "number of messages to publish without waiting for ACKs",
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
name: "streaming_cluster_id",
|
||||
label: "streaming_cluster_id",
|
||||
tooltip: "unique ID for NATS streaming cluster",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "cert_authority",
|
||||
label: "cert_authority",
|
||||
tooltip: "path to certificate chain of the target NATS server",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "client_cert",
|
||||
label: "client_cert",
|
||||
tooltip: "client cert for NATS mTLS auth",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "client_key",
|
||||
label: "client_key",
|
||||
tooltip: "client cert key for NATS mTLS auth",
|
||||
type: "string"
|
||||
},
|
||||
...commonFields
|
||||
],
|
||||
[notifyElasticsearch]: [
|
||||
{
|
||||
name: "url",
|
||||
required: true,
|
||||
label: "url",
|
||||
tooltip:
|
||||
"Elasticsearch server's address, with optional authentication info",
|
||||
type: "url"
|
||||
},
|
||||
{
|
||||
name: "index",
|
||||
required: true,
|
||||
label: "index",
|
||||
tooltip:
|
||||
"Elasticsearch index to store/update events, index is auto-created",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "format",
|
||||
required: true,
|
||||
label: "format",
|
||||
tooltip:
|
||||
"'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'",
|
||||
type: "enum"
|
||||
},
|
||||
...commonFields
|
||||
],
|
||||
[notifyWebhooks]: [
|
||||
{
|
||||
name: "endpoint",
|
||||
required: true,
|
||||
label: "endpoint",
|
||||
tooltip:
|
||||
"webhook server endpoint e.g. http://localhost:8080/minio/events",
|
||||
type: "url"
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
label: "auth_token",
|
||||
tooltip: "opaque string or JWT authorization token",
|
||||
type: "string"
|
||||
},
|
||||
...commonFields
|
||||
],
|
||||
[notifyNsq]: [
|
||||
{
|
||||
name: "nsqd_address",
|
||||
required: true,
|
||||
label: "nsqd_address",
|
||||
tooltip: "NSQ server address e.g. '127.0.0.1:4150'",
|
||||
type: "address"
|
||||
},
|
||||
{
|
||||
name: "topic",
|
||||
required: true,
|
||||
label: "topic",
|
||||
tooltip: "NSQ topic",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "tls",
|
||||
label: "tls",
|
||||
tooltip: "set to 'on' to enable TLS",
|
||||
type: "on|off"
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
label: "tls_skip_verify",
|
||||
tooltip:
|
||||
'trust server TLS without verification, defaults to "on" (verify)',
|
||||
type: "on|off"
|
||||
},
|
||||
...commonFields
|
||||
]
|
||||
};
|
||||
|
||||
export const removeEmptyFields = (formFields: IElementValue[]) => {
|
||||
const nonEmptyFields = formFields.filter(field => field.value !== "");
|
||||
|
||||
return nonEmptyFields;
|
||||
};
|
||||
@@ -40,7 +40,11 @@ import {
|
||||
} from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
import { AppState } from "../../store";
|
||||
import { setMenuOpen } from "../../actions";
|
||||
import {
|
||||
serverIsLoading,
|
||||
serverNeedsRestart,
|
||||
setMenuOpen
|
||||
} from "../../actions";
|
||||
import { ThemedComponentProps } from "@material-ui/core/styles/withTheme";
|
||||
import Buckets from "./Buckets/Buckets";
|
||||
import Policies from "./Policies/Policies";
|
||||
@@ -53,6 +57,11 @@ import NotFoundPage from "../NotFoundPage";
|
||||
import ServiceAccounts from "./ServiceAccounts/ServiceAccounts";
|
||||
import Users from "./Users/Users";
|
||||
import Groups from "./Groups/Groups";
|
||||
import ListNotificationEndpoints from "./NotificationEndopoints/ListNotificationEndpoints";
|
||||
import ConfigurationsList from "./Configurations/ConfigurationPanels/ConfigurationsList";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import WebhookPanel from "./Configurations/ConfigurationPanels/WebhookPanel";
|
||||
import Trace from "./Trace/Trace";
|
||||
|
||||
function Copyright() {
|
||||
return (
|
||||
@@ -150,27 +159,44 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
fixedHeight: {
|
||||
minHeight: 240
|
||||
},
|
||||
warningBar: {
|
||||
background: theme.palette.primary.main,
|
||||
color: "white",
|
||||
heigh: "60px",
|
||||
widht: "100%",
|
||||
lineHeight: "60px",
|
||||
textAlign: "center"
|
||||
}
|
||||
});
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
open: state.system.sidebarOpen
|
||||
open: state.system.sidebarOpen,
|
||||
needsRestart: state.system.serverNeedsRestart,
|
||||
isServerLoading: state.system.serverIsLoading
|
||||
});
|
||||
|
||||
const connector = connect(mapState, { setMenuOpen });
|
||||
const connector = connect(mapState, {
|
||||
setMenuOpen,
|
||||
serverNeedsRestart,
|
||||
serverIsLoading
|
||||
});
|
||||
|
||||
interface ConsoleProps {
|
||||
interface IConsoleProps {
|
||||
open: boolean;
|
||||
needsRestart: boolean;
|
||||
isServerLoading: boolean;
|
||||
title: string;
|
||||
classes: any;
|
||||
setMenuOpen: typeof setMenuOpen;
|
||||
serverNeedsRestart: typeof serverNeedsRestart;
|
||||
serverIsLoading: typeof serverIsLoading;
|
||||
}
|
||||
|
||||
class Console extends React.Component<
|
||||
ConsoleProps & RouteComponentProps & StyledProps & ThemedComponentProps
|
||||
IConsoleProps & RouteComponentProps & StyledProps & ThemedComponentProps
|
||||
> {
|
||||
componentDidMount(): void {
|
||||
//TODO: verify the session is still valid
|
||||
api
|
||||
.invoke("GET", `/api/v1/session`)
|
||||
.then(res => {
|
||||
@@ -182,8 +208,25 @@ class Console extends React.Component<
|
||||
});
|
||||
}
|
||||
|
||||
restartServer() {
|
||||
this.props.serverIsLoading(true);
|
||||
api
|
||||
.invoke("POST", "/api/v1/service/restart", {})
|
||||
.then(res => {
|
||||
console.log("success restarting service");
|
||||
console.log(res);
|
||||
this.props.serverIsLoading(false);
|
||||
this.props.serverNeedsRestart(false);
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.serverIsLoading(false);
|
||||
console.log("failure restarting service");
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, open } = this.props;
|
||||
const { classes, open, needsRestart, isServerLoading } = this.props;
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<CssBaseline />
|
||||
@@ -209,6 +252,30 @@ class Console extends React.Component<
|
||||
</Drawer>
|
||||
|
||||
<main className={classes.content}>
|
||||
{needsRestart && (
|
||||
<div className={classes.warningBar}>
|
||||
{isServerLoading ? (
|
||||
<React.Fragment>
|
||||
The server is restarting.
|
||||
<LinearProgress />
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
The instance needs to be restarted for configuration changes
|
||||
to take effect.{" "}
|
||||
<Button
|
||||
color="secondary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
this.restartServer();
|
||||
}}
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={classes.appBarSpacer} />
|
||||
<Container maxWidth="lg" className={classes.container}>
|
||||
<Router history={history}>
|
||||
@@ -224,6 +291,19 @@ class Console extends React.Component<
|
||||
<Route exact path="/users" component={Users} />
|
||||
<Route exact path="/dashboard" component={Dashboard} />
|
||||
<Route exct path="/groups" component={Groups} />
|
||||
<Route
|
||||
exact
|
||||
path="/notification-endpoints"
|
||||
component={ListNotificationEndpoints}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/configurations-list"
|
||||
component={ConfigurationsList}
|
||||
/>
|
||||
<Route exact path="/webhook/logger" component={WebhookPanel} />
|
||||
<Route exact path="/webhook/audit" component={WebhookPanel} />
|
||||
<Route exct path="/trace" component={Trace} />
|
||||
<Route exact path="/">
|
||||
<Redirect to="/dashboard" />
|
||||
</Route>
|
||||
|
||||
@@ -25,6 +25,7 @@ import PieChartIcon from "@material-ui/icons/PieChart";
|
||||
import ViewHeadlineIcon from "@material-ui/icons/ViewHeadline";
|
||||
import { Usage } from "./types";
|
||||
import api from "../../../common/api";
|
||||
import { niceBytes } from "../../../common/utils";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -137,26 +138,15 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
});
|
||||
};
|
||||
const prettyUsage = (usage: string | undefined) => {
|
||||
if (usage == undefined) {
|
||||
if (usage === undefined) {
|
||||
return "0";
|
||||
}
|
||||
return niceBytes(usage);
|
||||
};
|
||||
const units = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
|
||||
const niceBytes = (x: string) => {
|
||||
let l = 0,
|
||||
n = parseInt(x, 10) || 0;
|
||||
|
||||
while (n >= 1024 && ++l) {
|
||||
n = n / 1024;
|
||||
}
|
||||
//include a decimal point and a tenths-place digit if presenting
|
||||
//less than ten of KB or greater units
|
||||
return n.toFixed(n < 10 && l > 0 ? 1 : 0) + " " + units[l];
|
||||
};
|
||||
const prettyNumber = (usage: number | undefined) => {
|
||||
if (usage == undefined) {
|
||||
if (usage === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -165,11 +155,12 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid container xs={12}>
|
||||
<Grid container xs={12} spacing={3} className={classes.container}>
|
||||
<Grid container xs={12}>
|
||||
<Grid container>
|
||||
<Grid container spacing={3} className={classes.container}>
|
||||
<Grid container>
|
||||
<Typography variant="h2">MinIO Console</Typography>
|
||||
</Grid>
|
||||
{error !== "" && <Grid container>{error}</Grid>}
|
||||
<Grid item xs={12} md={4} lg={4}>
|
||||
<Paper className={fixedHeightPaper}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
|
||||
@@ -14,26 +14,16 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
TextField
|
||||
} from "@material-ui/core";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import api from "../../../common/api";
|
||||
import UsersSelectors from "./UsersSelectors";
|
||||
import { GroupsList } from "./types";
|
||||
import { groupsSort } from "../../../utils/sortFunctions";
|
||||
import Title from "../../../common/Title";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
|
||||
interface IGroupProps {
|
||||
open: boolean;
|
||||
@@ -58,6 +48,9 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -87,15 +80,69 @@ const AddGroup = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (saving) {
|
||||
const saveRecord = () => {
|
||||
if (selectedGroup !== null) {
|
||||
api
|
||||
.invoke("PUT", `/api/v1/groups/${groupName}`, {
|
||||
group: groupName,
|
||||
members: selectedUsers,
|
||||
status: groupEnabled
|
||||
})
|
||||
.then(res => {
|
||||
isSaving(false);
|
||||
setError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
isSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
} else {
|
||||
api
|
||||
.invoke("POST", "/api/v1/groups", {
|
||||
group: groupName,
|
||||
members: selectedUsers
|
||||
})
|
||||
.then(res => {
|
||||
isSaving(false);
|
||||
setError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
isSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
saveRecord();
|
||||
}
|
||||
}, [saving]);
|
||||
}, [
|
||||
saving,
|
||||
groupName,
|
||||
selectedUsers,
|
||||
groupEnabled,
|
||||
selectedGroup,
|
||||
closeModalAndRefresh
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedGroup && loadingGroup) {
|
||||
const fetchGroupInfo = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/groups/${selectedGroup}`)
|
||||
.then((res: MainGroupProps) => {
|
||||
setGroupEnabled(res.status);
|
||||
setGroupName(res.name);
|
||||
setSelectedUsers(res.members);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoadingGroup(false);
|
||||
});
|
||||
};
|
||||
fetchGroupInfo();
|
||||
}
|
||||
}, [loadingGroup]);
|
||||
}, [loadingGroup, selectedGroup]);
|
||||
|
||||
//Fetch Actions
|
||||
const setSaving = (event: React.FormEvent) => {
|
||||
@@ -104,152 +151,89 @@ const AddGroup = ({
|
||||
isSaving(true);
|
||||
};
|
||||
|
||||
const saveRecord = () => {
|
||||
if (selectedGroup !== null) {
|
||||
api
|
||||
.invoke("PUT", `/api/v1/groups/${groupName}`, {
|
||||
group: groupName,
|
||||
members: selectedUsers,
|
||||
status: groupEnabled
|
||||
})
|
||||
.then(res => {
|
||||
isSaving(false);
|
||||
setError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
isSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
} else {
|
||||
api
|
||||
.invoke("POST", "/api/v1/groups", {
|
||||
group: groupName,
|
||||
members: selectedUsers
|
||||
})
|
||||
.then(res => {
|
||||
isSaving(false);
|
||||
setError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
isSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const fetchGroupInfo = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/groups/${selectedGroup}`)
|
||||
.then((res: MainGroupProps) => {
|
||||
setGroupEnabled(res.status);
|
||||
setGroupName(res.name);
|
||||
setSelectedUsers(res.members);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoadingGroup(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={closeModalAndRefresh}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
title={selectedGroup !== null ? `Group Edit - ${groupName}` : "Add Group"}
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{selectedGroup !== null ? `Group Edit - ${groupName}` : "Add Group"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<form noValidate autoComplete="off" onSubmit={setSaving}>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{selectedGroup !== null ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Title>Status</Title>
|
||||
<RadioGroup
|
||||
aria-label="status"
|
||||
name="status"
|
||||
value={groupEnabled}
|
||||
onChange={e => {
|
||||
setGroupEnabled(e.target.value);
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="enabled"
|
||||
control={<Radio color={"primary"} />}
|
||||
label="Enabled"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="disabled"
|
||||
control={<Radio color={"primary"} />}
|
||||
label="Disabled"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
fullWidth
|
||||
label="Name"
|
||||
value={groupName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setGroupName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<form noValidate autoComplete="off" onSubmit={setSaving}>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<UsersSelectors
|
||||
selectedUsers={selectedUsers}
|
||||
setSelectedUsers={setSelectedUsers}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={saving}
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{saving && (
|
||||
)}
|
||||
|
||||
{selectedGroup !== null ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
<RadioGroupSelector
|
||||
currentSelection={groupEnabled}
|
||||
id="group-status"
|
||||
name="group-status"
|
||||
label="Status"
|
||||
onChange={e => {
|
||||
setGroupEnabled(e.target.value);
|
||||
}}
|
||||
selectorOptions={[
|
||||
{ label: "Enabled", value: "enabled" },
|
||||
{ label: "Disabled", value: "disabled" }
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="group-name"
|
||||
name="group-name"
|
||||
label="Name"
|
||||
value={groupName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setGroupName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Grid item xs={12}>
|
||||
<UsersSelectors
|
||||
selectedUsers={selectedUsers}
|
||||
setSelectedUsers={setSelectedUsers}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={saving}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{saving && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -54,28 +54,27 @@ const DeleteGroup = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (isDeleting) {
|
||||
const removeRecord = () => {
|
||||
if (!selectedGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/groups/${selectedGroup}`)
|
||||
.then((res: UsersList) => {
|
||||
setDeleteLoading(false);
|
||||
setError("");
|
||||
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch(err => {
|
||||
setDeleteLoading(false);
|
||||
setError(err);
|
||||
});
|
||||
};
|
||||
removeRecord();
|
||||
}
|
||||
}, [isDeleting]);
|
||||
|
||||
const removeRecord = () => {
|
||||
if (!selectedGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/groups/${selectedGroup}`)
|
||||
.then((res: UsersList) => {
|
||||
setDeleteLoading(false);
|
||||
setError("");
|
||||
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch(err => {
|
||||
setDeleteLoading(false);
|
||||
setError(err);
|
||||
});
|
||||
};
|
||||
}, [isDeleting, selectedGroup, closeDeleteModalAndRefresh]);
|
||||
|
||||
const closeNoAction = () => {
|
||||
setError("");
|
||||
|
||||
@@ -14,37 +14,22 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
} from "@material-ui/core";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import ViewIcon from "@material-ui/icons/Visibility";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import { Button } from "@material-ui/core";
|
||||
import { CreateIcon } from "../../../icons";
|
||||
import api from "../../../common/api";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
import { GroupsList } from "./types";
|
||||
import { groupsSort, usersSort } from "../../../utils/sortFunctions";
|
||||
import { UsersList } from "../Users/types";
|
||||
import { groupsSort } from "../../../utils/sortFunctions";
|
||||
import AddGroup from "../Groups/AddGroup";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
|
||||
interface IGroupsProps {
|
||||
classes: any;
|
||||
@@ -134,31 +119,35 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
const fetchRecords = () => {
|
||||
const offset = page * rowsPerPage;
|
||||
api
|
||||
.invoke("GET", `/api/v1/groups?offset=${offset}&limit=${rowsPerPage}`)
|
||||
.then((res: GroupsList) => {
|
||||
let resGroups: string[] = [];
|
||||
if (res.groups !== null) {
|
||||
resGroups = res.groups.sort(groupsSort);
|
||||
}
|
||||
setRecords(resGroups);
|
||||
const total = !res.total ? 0 : res.total;
|
||||
setTotalRecords(total);
|
||||
setError("");
|
||||
isLoading(false);
|
||||
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if ((!res.groups || res.groups.length === 0) && page > 0) {
|
||||
const newPage = page - 1;
|
||||
setPage(newPage);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
fetchRecords();
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
const fetchRecords = () => {
|
||||
const offset = page * rowsPerPage;
|
||||
api
|
||||
.invoke("GET", `/api/v1/groups?offset=${offset}&limit=${rowsPerPage}`)
|
||||
.then((res: GroupsList) => {
|
||||
setRecords(res.groups.sort(groupsSort));
|
||||
setTotalRecords(res.total);
|
||||
setError("");
|
||||
isLoading(false);
|
||||
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if ((!res.groups || res.groups.length === 0) && page > 0) {
|
||||
const newPage = page - 1;
|
||||
setPage(newPage);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
}, [loading, page, rowsPerPage]);
|
||||
|
||||
const closeAddModalAndRefresh = () => {
|
||||
setGroupOpen(false);
|
||||
@@ -177,6 +166,21 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
elementItem.includes(filter)
|
||||
);
|
||||
|
||||
const viewAction = (group: any) => {
|
||||
setGroupOpen(true);
|
||||
setSelectedGroup(group);
|
||||
};
|
||||
|
||||
const deleteAction = (group: any) => {
|
||||
setDeleteOpen(true);
|
||||
setSelectedGroup(group);
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: viewAction },
|
||||
{ type: "delete", onClick: deleteAction }
|
||||
];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addGroupOpen && (
|
||||
@@ -200,6 +204,7 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
{error !== "" ? <Grid container>{error}</Grid> : <React.Fragment />}
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Groups"
|
||||
@@ -235,68 +240,28 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{filteredRecords.map(group => (
|
||||
<TableRow key={`user-${group}`}>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{group}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
aria-label="view"
|
||||
onClick={() => {
|
||||
setGroupOpen(true);
|
||||
setSelectedGroup(group);
|
||||
}}
|
||||
>
|
||||
<ViewIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
setDeleteOpen(true);
|
||||
setSelectedGroup(group);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
colSpan={3}
|
||||
count={totalRecords}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
SelectProps={{
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
}}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
ActionsComponent={MinTablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
) : (
|
||||
<div>No Groups Available</div>
|
||||
)}
|
||||
</Paper>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[{ label: "Name", elementKey: "" }]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="Groups"
|
||||
idField=""
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -15,24 +15,19 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import get from "lodash/get";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { LinearProgress } from "@material-ui/core";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Title from "../../../common/Title";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import { UsersList } from "../Users/types";
|
||||
import { usersSort } from "../../../utils/sortFunctions";
|
||||
import api from "../../../common/api";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
|
||||
interface IGroupsProps {
|
||||
classes: any;
|
||||
@@ -86,7 +81,8 @@ const styles = (theme: Theme) =>
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
width: "100%"
|
||||
width: "100%",
|
||||
zIndex: 500
|
||||
},
|
||||
noFound: {
|
||||
textAlign: "center",
|
||||
@@ -96,7 +92,7 @@ const styles = (theme: Theme) =>
|
||||
maxHeight: 250
|
||||
},
|
||||
stickyHeader: {
|
||||
backgroundColor: "transparent"
|
||||
backgroundColor: "#fff"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -122,13 +118,15 @@ const UsersSelectors = ({
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
const selUsers = !selectedUsers ? [] : selectedUsers;
|
||||
|
||||
//Fetch Actions
|
||||
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const targetD = e.target;
|
||||
const value = targetD.value;
|
||||
const checked = targetD.checked;
|
||||
|
||||
let elements: string[] = [...selectedUsers]; // We clone the selectedGroups array
|
||||
let elements: string[] = [...selUsers]; // We clone the selectedGroups array
|
||||
|
||||
if (checked) {
|
||||
// If the user has checked this field we need to push this to selectedGroupsList
|
||||
@@ -146,7 +144,13 @@ const UsersSelectors = ({
|
||||
api
|
||||
.invoke("GET", `/api/v1/users`)
|
||||
.then((res: UsersList) => {
|
||||
setRecords(res.users.sort(usersSort));
|
||||
let users = get(res, "users", []);
|
||||
|
||||
if (!users) {
|
||||
users = [];
|
||||
}
|
||||
|
||||
setRecords(users.sort(usersSort));
|
||||
setError("");
|
||||
isLoading(false);
|
||||
})
|
||||
@@ -163,6 +167,7 @@ const UsersSelectors = ({
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title>Members</Title>
|
||||
{error !== "" ? <div>{error}</div> : <React.Fragment />}
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
@@ -187,40 +192,17 @@ const UsersSelectors = ({
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<TableContainer className={classes.tableContainer}>
|
||||
<Table size="small" stickyHeader aria-label="sticky table">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell className={classes.stickyHeader}>
|
||||
Select
|
||||
</TableCell>
|
||||
<TableCell className={classes.stickyHeader}>
|
||||
Access Key
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{filteredRecords.map(row => (
|
||||
<TableRow key={`group-${row.accessKey}`}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value={row.accessKey}
|
||||
color="primary"
|
||||
inputProps={{
|
||||
"aria-label": "secondary checkbox"
|
||||
}}
|
||||
onChange={selectionChanged}
|
||||
checked={selectedUsers.includes(row.accessKey)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.accessKey}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
columns={[{ label: "Access Key", elementKey: "accessKey" }]}
|
||||
onSelect={selectionChanged}
|
||||
selectedItems={selUsers}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="Users"
|
||||
idField="accessKey"
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<div className={classes.noFound}>No Users Available</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { Divider, withStyles } from "@material-ui/core";
|
||||
import { Divider, Typography, withStyles } from "@material-ui/core";
|
||||
import { ExitToApp } from "@material-ui/icons";
|
||||
import { AppState } from "../../store";
|
||||
import { connect } from "react-redux";
|
||||
@@ -27,7 +27,7 @@ import { userLoggedIn } from "../../actions";
|
||||
import List from "@material-ui/core/List";
|
||||
import storage from "local-storage-fallback";
|
||||
import history from "../../history";
|
||||
import logo from "../../icons/mkube_logo_temp.svg";
|
||||
import logo from "../../icons/minio_console_logo.svg";
|
||||
import {
|
||||
BucketsIcon,
|
||||
DashboardIcon,
|
||||
@@ -36,6 +36,10 @@ import {
|
||||
} from "../../icons";
|
||||
import { createStyles, Theme } from "@material-ui/core/styles";
|
||||
import PersonIcon from "@material-ui/icons/Person";
|
||||
import api from "../../common/api";
|
||||
import NotificationsIcon from "@material-ui/icons/Notifications";
|
||||
import ListAltIcon from "@material-ui/icons/ListAlt";
|
||||
import LoopIcon from "@material-ui/icons/Loop";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -44,7 +48,7 @@ const styles = (theme: Theme) =>
|
||||
marginBottom: "20px",
|
||||
textAlign: "center",
|
||||
"& img": {
|
||||
width: "76px"
|
||||
width: "120px"
|
||||
}
|
||||
},
|
||||
menuList: {
|
||||
@@ -91,9 +95,20 @@ interface MenuProps {
|
||||
|
||||
class Menu extends React.Component<MenuProps> {
|
||||
logout() {
|
||||
storage.removeItem("token");
|
||||
this.props.userLoggedIn(false);
|
||||
history.push("/");
|
||||
const deleteSession = () => {
|
||||
storage.removeItem("token");
|
||||
this.props.userLoggedIn(false);
|
||||
history.push("/");
|
||||
};
|
||||
api
|
||||
.invoke("POST", `/api/v1/logout`)
|
||||
.then(() => {
|
||||
deleteSession();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log(err);
|
||||
deleteSession();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -116,12 +131,8 @@ class Menu extends React.Component<MenuProps> {
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Buckets" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/policies">
|
||||
<ListItemIcon>
|
||||
<PermissionIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="IAM Policies" />
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem component={Typography}>Admin</ListItem>
|
||||
<ListItem button component={NavLink} to="/users">
|
||||
<ListItemIcon>
|
||||
<PersonIcon />
|
||||
@@ -134,6 +145,31 @@ class Menu extends React.Component<MenuProps> {
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Groups" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/policies">
|
||||
<ListItemIcon>
|
||||
<PermissionIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="IAM Policies" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/trace">
|
||||
<ListItemIcon>
|
||||
<LoopIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Trace" />
|
||||
</ListItem>
|
||||
<ListItem component={Typography}>Configuration</ListItem>
|
||||
<ListItem button component={NavLink} to="/notification-endpoints">
|
||||
<ListItemIcon>
|
||||
<NotificationsIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Lambda Notifications" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/configurations-list">
|
||||
<ListItemIcon>
|
||||
<ListAltIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Configurations List" />
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem
|
||||
button
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import get from "lodash/get";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import ConfPostgres from "../Configurations/CustomForms/ConfPostgres";
|
||||
import api from "../../../common/api";
|
||||
import { serverNeedsRestart } from "../../../actions";
|
||||
import { connect } from "react-redux";
|
||||
import ConfMySql from "../Configurations/CustomForms/ConfMySql";
|
||||
import ConfTargetGeneric from "../Configurations/ConfTargetGeneric";
|
||||
import {
|
||||
notificationEndpointsFields,
|
||||
notifyPostgres,
|
||||
notifyMysql,
|
||||
notifyKafka,
|
||||
notifyAmqp,
|
||||
notifyMqtt,
|
||||
notifyRedis,
|
||||
notifyNats,
|
||||
notifyElasticsearch,
|
||||
notifyWebhooks,
|
||||
notifyNsq,
|
||||
removeEmptyFields
|
||||
} from "../Configurations/utils";
|
||||
import { IElementValue } from "../Configurations/types";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
},
|
||||
logoButton: {
|
||||
height: "80px"
|
||||
}
|
||||
});
|
||||
|
||||
interface IAddNotificationEndpointProps {
|
||||
open: boolean;
|
||||
closeModalAndRefresh: any;
|
||||
serverNeedsRestart: typeof serverNeedsRestart;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const AddNotificationEndpoint = ({
|
||||
open,
|
||||
closeModalAndRefresh,
|
||||
serverNeedsRestart,
|
||||
classes
|
||||
}: IAddNotificationEndpointProps) => {
|
||||
//Local States
|
||||
const [service, setService] = useState<string>("");
|
||||
const [valuesArr, setValueArr] = useState<IElementValue[]>([]);
|
||||
const [saving, setSaving] = useState<boolean>(false);
|
||||
const [addError, setError] = useState<string>("");
|
||||
|
||||
//Effects
|
||||
|
||||
useEffect(() => {
|
||||
if (saving) {
|
||||
const payload = {
|
||||
key_values: removeEmptyFields(valuesArr)
|
||||
};
|
||||
api
|
||||
.invoke("PUT", `/api/v1/configs/${service}`, payload)
|
||||
.then(res => {
|
||||
setSaving(false);
|
||||
setError("");
|
||||
serverNeedsRestart(true);
|
||||
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
setSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
}
|
||||
}, [saving, serverNeedsRestart, service, valuesArr, closeModalAndRefresh]);
|
||||
|
||||
//Fetch Actions
|
||||
const submitForm = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
setSaving(true);
|
||||
};
|
||||
|
||||
const onValueChange = useCallback(
|
||||
newValue => {
|
||||
setValueArr(newValue);
|
||||
},
|
||||
[setValueArr]
|
||||
);
|
||||
|
||||
let srvComponent = <React.Fragment />;
|
||||
switch (service) {
|
||||
case notifyPostgres: {
|
||||
srvComponent = <ConfPostgres onChange={onValueChange} />;
|
||||
break;
|
||||
}
|
||||
case notifyMysql: {
|
||||
srvComponent = <ConfMySql onChange={onValueChange} />;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const fields = get(notificationEndpointsFields, service, []);
|
||||
|
||||
srvComponent = (
|
||||
<ConfTargetGeneric fields={fields} onChange={onValueChange} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let targetTitle = "";
|
||||
switch (service) {
|
||||
case notifyNsq:
|
||||
targetTitle = "NSQ";
|
||||
break;
|
||||
case notifyWebhooks:
|
||||
targetTitle = "Webhooks";
|
||||
break;
|
||||
case notifyElasticsearch:
|
||||
targetTitle = "Elastic Search";
|
||||
break;
|
||||
case notifyNats:
|
||||
targetTitle = "NATS";
|
||||
break;
|
||||
case notifyMqtt:
|
||||
targetTitle = "MQTT";
|
||||
break;
|
||||
case notifyRedis:
|
||||
targetTitle = "Redis";
|
||||
break;
|
||||
case notifyKafka:
|
||||
targetTitle = "Kafka";
|
||||
break;
|
||||
case notifyPostgres:
|
||||
targetTitle = "Postgres";
|
||||
break;
|
||||
case notifyMysql:
|
||||
targetTitle = "Mysql";
|
||||
break;
|
||||
case notifyAmqp:
|
||||
targetTitle = "AMQP";
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={closeModalAndRefresh}
|
||||
title={`Add Lambda Notification Target ${targetTitle}`}
|
||||
>
|
||||
{service === "" && (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<p>Pick a supported service:</p>
|
||||
<table className={classes.chooseTable} style={{ width: "100%" }}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyPostgres);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/postgres.png"
|
||||
className={classes.logoButton}
|
||||
alt="postgres"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyKafka);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/kafka.png"
|
||||
className={classes.logoButton}
|
||||
alt="kafka"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyAmqp);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/amqp.png"
|
||||
className={classes.logoButton}
|
||||
alt="amqp"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyMqtt);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/mqtt.png"
|
||||
className={classes.logoButton}
|
||||
alt="mqtt"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyRedis);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/redis.png"
|
||||
className={classes.logoButton}
|
||||
alt="redis"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyNats);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/nats.png"
|
||||
className={classes.logoButton}
|
||||
alt="nats"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyMysql);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/mysql.png"
|
||||
className={classes.logoButton}
|
||||
alt="mysql"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyElasticsearch);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/elasticsearch.png"
|
||||
className={classes.logoButton}
|
||||
alt="elasticsearch"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyWebhooks);
|
||||
}}
|
||||
>
|
||||
Webhook
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyNsq);
|
||||
}}
|
||||
>
|
||||
NSQ
|
||||
</Button>
|
||||
</td>
|
||||
<td />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
|
||||
{saving && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
{service !== "" && (
|
||||
<React.Fragment>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<form noValidate onSubmit={submitForm}>
|
||||
{srvComponent}
|
||||
|
||||
<Grid item xs={3} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={saving}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9} />
|
||||
</form>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const connector = connect(null, { serverNeedsRestart });
|
||||
|
||||
export default connector(withStyles(styles)(AddNotificationEndpoint));
|
||||
@@ -0,0 +1,244 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { TextField } from "@material-ui/core";
|
||||
import { red } from "@material-ui/core/colors";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
import {
|
||||
NotificationEndpointItem,
|
||||
NotificationEndpointsList,
|
||||
TransformedEndpointItem
|
||||
} from "./types";
|
||||
import { notificationTransform } from "./utils";
|
||||
import { CreateIcon } from "../../../icons";
|
||||
import api from "../../../common/api";
|
||||
import FiberManualRecordIcon from "@material-ui/icons/FiberManualRecord";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import AddNotificationEndpoint from "./AddNotificationEndpoint";
|
||||
|
||||
interface IListNotificationEndpoints {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
},
|
||||
iconText: {
|
||||
lineHeight: "24px"
|
||||
}
|
||||
});
|
||||
|
||||
const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
|
||||
//Local States
|
||||
const [records, setRecords] = useState<TransformedEndpointItem[]>([]);
|
||||
const [totalRecords, setTotalRecords] = useState<number>(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const [error, setError] = useState<string>("");
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
|
||||
|
||||
//Effects
|
||||
// load records on mount
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
const fetchRecords = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/admin/notification_endpoints`)
|
||||
.then((res: NotificationEndpointsList) => {
|
||||
let resNotEndList: NotificationEndpointItem[] = [];
|
||||
if (res.notification_endpoints !== null) {
|
||||
resNotEndList = res.notification_endpoints;
|
||||
}
|
||||
setRecords(notificationTransform(resNotEndList));
|
||||
setTotalRecords(resNotEndList.length);
|
||||
setError("");
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
fetchRecords();
|
||||
}
|
||||
}, [isLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
}, []);
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", to: "/notification-endpoints", sendOnlyId: true },
|
||||
{
|
||||
type: "delete",
|
||||
onClick: (row: any) => {
|
||||
//confirmDeleteBucket(row.name);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const filteredRecords = records.filter((b: TransformedEndpointItem) => {
|
||||
if (filter === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.service_name.indexOf(filter) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const statusDisplay = (status: string) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<FiberManualRecordIcon
|
||||
style={status === "Offline" ? { color: red[500] } : {}}
|
||||
/>
|
||||
{status}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addScreenOpen && (
|
||||
<AddNotificationEndpoint
|
||||
open={addScreenOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
setIsLoading(true);
|
||||
setAddScreenOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Lambda Notification Targets</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
{error !== "" && <Grid container>{error}</Grid>}
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Filter"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={event => {
|
||||
setFilter(event.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setAddScreenOpen(true);
|
||||
}}
|
||||
>
|
||||
Add Notification Target
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Service", elementKey: "service_name" },
|
||||
{
|
||||
label: "Status",
|
||||
elementKey: "status",
|
||||
renderFunction: statusDisplay
|
||||
}
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
records={filteredRecords}
|
||||
entityName="Notification Endpoints"
|
||||
idField="service_name"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
},
|
||||
onChangePage: (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
},
|
||||
onChangeRowsPerPage: (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const rPP = parseInt(event.target.value, 10);
|
||||
setRowsPerPage(rPP);
|
||||
},
|
||||
ActionsComponent: MinTablePaginationActions
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ListNotificationEndpoints);
|
||||
@@ -0,0 +1,36 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export interface NotificationEndpoint {
|
||||
service: string;
|
||||
account_id: string;
|
||||
properties: Map<string, string>;
|
||||
}
|
||||
|
||||
export interface NotificationEndpointItem {
|
||||
service: string;
|
||||
account_id: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface TransformedEndpointItem {
|
||||
service_name: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface NotificationEndpointsList {
|
||||
notification_endpoints: NotificationEndpointItem[];
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { NotificationEndpointItem } from "./types";
|
||||
|
||||
export const notificationTransform = (
|
||||
notificationElements: NotificationEndpointItem[]
|
||||
) => {
|
||||
return notificationElements.map(element => {
|
||||
return {
|
||||
service_name: `${element.service}:${element.account_id}`,
|
||||
status: element.status
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -483,7 +483,6 @@ class AddPermissionContent extends React.Component<
|
||||
count={buckets.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
labelRowsPerPage={null}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
@@ -521,28 +520,28 @@ class AddPermissionContent extends React.Component<
|
||||
label="Write Only"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="trace"
|
||||
control={<Radio />}
|
||||
label="Trace"
|
||||
value="trace"
|
||||
control={<Radio />}
|
||||
label="Trace"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
{action === 'trace' && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
Trace displays tracing information for all buckets.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
{action === "trace" && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
Trace displays tracing information for all buckets.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
|
||||
@@ -40,13 +40,11 @@ import { MinTablePaginationActions } from "../../../common/MinTablePaginationAct
|
||||
import EditIcon from "@material-ui/icons/Edit";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import { CreateIcon } from "../../../icons";
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import InputBase from '@material-ui/core/InputBase';
|
||||
import SearchIcon from '@material-ui/icons/Search';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import PlayArrowRoundedIcon from "@material-ui/icons/PlayArrowRounded";
|
||||
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
@@ -84,14 +82,14 @@ const styles = (theme: Theme) =>
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
marginLeft: 10
|
||||
}
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -241,7 +239,6 @@ class Permissions extends React.Component<
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
||||
<AddPermission
|
||||
open={addScreenOpen}
|
||||
selectedPermission={selectedPermission}
|
||||
@@ -269,7 +266,7 @@ class Permissions extends React.Component<
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
@@ -324,7 +321,7 @@ class Permissions extends React.Component<
|
||||
<Checkbox
|
||||
value="secondary"
|
||||
color="primary"
|
||||
inputProps={{ 'aria-label': 'secondary checkbox' }}
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
|
||||