Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63350e5492 | ||
|
|
255c128b67 | ||
|
|
06f333395e | ||
|
|
3cd024ea2c | ||
|
|
9c0a407db6 | ||
|
|
dc3c619f3f | ||
|
|
5000aafba6 | ||
|
|
b9f2a39d50 | ||
|
|
df321191f4 | ||
|
|
547eb41e96 | ||
|
|
afbb83e081 | ||
|
|
b599968570 | ||
|
|
24cc60f34e | ||
|
|
f967058409 | ||
|
|
078e09ba76 | ||
|
|
d6f97841d4 | ||
|
|
619ac124b3 | ||
|
|
a2180e123d | ||
|
|
0325bb7e2d | ||
|
|
fce361e5bd | ||
|
|
ed6d6e8b9d | ||
|
|
406709f66b | ||
|
|
3ac45a2211 | ||
|
|
716f886780 | ||
|
|
4ef498f0c3 | ||
|
|
5e764e61ba | ||
|
|
1466632fd6 | ||
|
|
0c43e5c3f4 | ||
|
|
7e9d581277 | ||
|
|
c928972137 | ||
|
|
78884e3806 | ||
|
|
f6ac7e047e | ||
|
|
e1fdf3fb28 | ||
|
|
e4510cbc18 | ||
|
|
2c14142e19 | ||
|
|
1caa3f2ce8 | ||
|
|
6501a4b13f | ||
|
|
2f51621e69 | ||
|
|
7e6e64c729 | ||
|
|
9007c7dd14 | ||
|
|
850fd3e371 | ||
|
|
6d8f1c439e | ||
|
|
7166717688 | ||
|
|
f91346dc5b | ||
|
|
dccdfb5533 | ||
|
|
4f065bdedf |
2
.github/workflows/go.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.14.x]
|
||||
go-version: [1.15.x]
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
||||
|
||||
@@ -12,6 +12,10 @@ before:
|
||||
hooks:
|
||||
# you may remove this if you don't use vgo
|
||||
- go mod tidy
|
||||
- docker build -f Dockerfile.assets -t consoleassets .
|
||||
- docker create --name extract consoleassets
|
||||
- docker cp extract:/app/bindata_assetfs.go ./portal-ui/
|
||||
- docker rm extract
|
||||
|
||||
builds:
|
||||
-
|
||||
|
||||
29
Dockerfile
@@ -1,4 +1,25 @@
|
||||
FROM golang:1.13
|
||||
FROM golang:1.15 as binlayer
|
||||
|
||||
RUN go get github.com/go-bindata/go-bindata/... && go get github.com/elazarl/go-bindata-assetfs/...
|
||||
|
||||
FROM node:10 as uilayer
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=binlayer /go/bin/go-bindata-assetfs /bin/
|
||||
COPY --from=binlayer /go/bin/go-bindata /bin/
|
||||
|
||||
COPY ./portal-ui/package.json ./
|
||||
COPY ./portal-ui/yarn.lock ./
|
||||
RUN yarn install
|
||||
|
||||
COPY ./portal-ui .
|
||||
|
||||
RUN yarn install && make build-static
|
||||
|
||||
USER node
|
||||
|
||||
FROM golang:1.15 as golayer
|
||||
|
||||
RUN apt-get update -y && apt-get install -y ca-certificates
|
||||
|
||||
@@ -12,6 +33,8 @@ RUN go mod download
|
||||
ADD . /go/src/github.com/minio/console/
|
||||
WORKDIR /go/src/github.com/minio/console/
|
||||
|
||||
COPY --from=uilayer /app/bindata_assetfs.go /go/src/github.com/minio/console/portal-ui/
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
RUN go build -ldflags "-w -s" -a -o console ./cmd/console
|
||||
@@ -20,7 +43,7 @@ FROM scratch
|
||||
MAINTAINER MinIO Development "dev@min.io"
|
||||
EXPOSE 9090
|
||||
|
||||
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=0 /go/src/github.com/minio/console/console .
|
||||
COPY --from=golayer /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=golayer /go/src/github.com/minio/console/console .
|
||||
|
||||
ENTRYPOINT ["/console"]
|
||||
|
||||
20
Dockerfile.assets
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM golang:1.15 as binlayer
|
||||
|
||||
RUN go get github.com/go-bindata/go-bindata/... && go get github.com/elazarl/go-bindata-assetfs/...
|
||||
|
||||
FROM node:10 as uilayer
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=binlayer /go/bin/go-bindata-assetfs /bin/
|
||||
COPY --from=binlayer /go/bin/go-bindata /bin/
|
||||
|
||||
COPY ./portal-ui/package.json ./
|
||||
COPY ./portal-ui/yarn.lock ./
|
||||
RUN yarn install
|
||||
|
||||
COPY ./portal-ui .
|
||||
|
||||
RUN yarn install && make build-static
|
||||
|
||||
USER node
|
||||
38
README.md
@@ -101,8 +101,6 @@ Additionally, you can create policies to limit the privileges for `console` user
|
||||
To run the server:
|
||||
|
||||
```
|
||||
export CONSOLE_HMAC_JWT_SECRET=YOURJWTSIGNINGSECRET
|
||||
|
||||
#required to encrypt jwet payload
|
||||
export CONSOLE_PBKDF_PASSPHRASE=SECRET
|
||||
|
||||
@@ -115,11 +113,41 @@ export CONSOLE_MINIO_SERVER=http://localhost:9000
|
||||
./console server
|
||||
```
|
||||
|
||||
## Connect Console to a Minio using TLS and a self-signed certificate
|
||||
## Run Console with TLS enable
|
||||
|
||||
Copy your `public.crt` and `private.key` to `~/.console/certs`, then:
|
||||
|
||||
```bash
|
||||
./console server
|
||||
```
|
||||
|
||||
Additionally, `Console` has support for multiple certificates, clients can request them using `SNI`. It expects the following structure:
|
||||
|
||||
```bash
|
||||
certs/
|
||||
│
|
||||
├─ public.crt
|
||||
├─ private.key
|
||||
│
|
||||
├─ example.com/
|
||||
│ │
|
||||
│ ├─ public.crt
|
||||
│ └─ private.key
|
||||
└─ foobar.org/
|
||||
│
|
||||
├─ public.crt
|
||||
└─ private.key
|
||||
...
|
||||
|
||||
```
|
||||
...
|
||||
export CONSOLE_MINIO_SERVER_TLS_ROOT_CAS=<certificate_file_name>
|
||||
|
||||
Therefore, we read all filenames in the cert directory and check
|
||||
for each directory whether it contains a public.crt and private.key.
|
||||
|
||||
## Connect Console to a Minio using TLS and a self-signed certificate
|
||||
|
||||
Copy the MinIO `ca.crt` under `~/.console/certs/CAs`, then:
|
||||
```
|
||||
export CONSOLE_MINIO_SERVER=https://localhost:9000
|
||||
./console server
|
||||
```
|
||||
|
||||
@@ -20,12 +20,16 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/console/pkg/certs"
|
||||
"github.com/minio/console/restapi"
|
||||
"github.com/minio/console/restapi/operations"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
certsx "github.com/minio/minio/pkg/certs"
|
||||
)
|
||||
|
||||
// starts the server
|
||||
@@ -56,14 +60,9 @@ var serverCmd = cli.Command{
|
||||
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",
|
||||
Name: "certs-dir",
|
||||
Value: certs.GlobalCertsCADir.Get(),
|
||||
Usage: "path to certs directory",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -82,7 +81,9 @@ func startServer(ctx *cli.Context) error {
|
||||
parser := flags.NewParser(server, flags.Default)
|
||||
parser.ShortDescription = "MinIO Console Server"
|
||||
parser.LongDescription = swaggerSpec.Spec().Info.Description
|
||||
|
||||
server.ConfigureFlags()
|
||||
|
||||
for _, optsGroup := range api.CommandLineOptionsGroups {
|
||||
_, err := parser.AddGroup(optsGroup.ShortDescription, optsGroup.LongDescription, optsGroup.Options)
|
||||
if err != nil {
|
||||
@@ -106,12 +107,19 @@ func startServer(ctx *cli.Context) error {
|
||||
restapi.Hostname = ctx.String("host")
|
||||
restapi.Port = fmt.Sprintf("%v", ctx.Int("port"))
|
||||
|
||||
tlsCertificatePath := ctx.String("tls-certificate")
|
||||
tlsCertificateKeyPath := ctx.String("tls-key")
|
||||
// Set all certs and CAs directories.
|
||||
certs.GlobalCertsDir, _ = certs.NewConfigDirFromCtx(ctx, "certs-dir", certs.DefaultCertsDir.Get)
|
||||
certs.GlobalCertsCADir = &certs.ConfigDir{Path: filepath.Join(certs.GlobalCertsDir.Get(), certs.CertsCADir)}
|
||||
logger.FatalIf(certs.MkdirAllIgnorePerm(certs.GlobalCertsCADir.Get()), "Unable to create certs CA directory at %s", certs.GlobalCertsCADir.Get())
|
||||
|
||||
if tlsCertificatePath != "" && tlsCertificateKeyPath != "" {
|
||||
server.TLSCertificate = flags.Filename(tlsCertificatePath)
|
||||
server.TLSCertificateKey = flags.Filename(tlsCertificateKeyPath)
|
||||
// load all CAs from ~/.console/certs/CAs
|
||||
restapi.GlobalRootCAs, err = certsx.GetRootCAs(certs.GlobalCertsCADir.Get())
|
||||
logger.FatalIf(err, "Failed to read root CAs (%v)", err)
|
||||
// load all certs from ~/.console/certs
|
||||
restapi.GlobalPublicCerts, restapi.GlobalTLSCertsManager, err = certs.GetTLSConfig()
|
||||
logger.FatalIf(err, "Unable to load the TLS configuration")
|
||||
|
||||
if len(restapi.GlobalPublicCerts) > 0 && restapi.GlobalRootCAs != nil {
|
||||
// If TLS certificates are provided enforce the HTTPS schema, meaning console will redirect
|
||||
// plain HTTP connections to HTTPS server
|
||||
server.EnabledListeners = []string{"http", "https"}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
`Console` will authenticate against `Kubernetes`using bearer tokens via HTTP `Authorization` header. The user will provide this token once
|
||||
in the login form, Console will validate it against Kubernetes (list apis) and if valid will generate and return a new Console sessions
|
||||
with encrypted claims (the user Service account token will be inside the JWT in the data field)
|
||||
with encrypted claims (the user Service account token will be inside the session encrypted token
|
||||
|
||||
# Kubernetes
|
||||
|
||||
|
||||
5
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/minio/console
|
||||
|
||||
go 1.13
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
@@ -19,7 +19,8 @@ require (
|
||||
github.com/minio/mc v0.0.0-20201001165056-7f2df96e4821
|
||||
github.com/minio/minio v0.0.0-20200927172404-27d9bd04e544
|
||||
github.com/minio/minio-go/v7 v7.0.6-0.20200923173112-bc846cb9b089
|
||||
github.com/minio/operator v0.0.0-20200930213302-ab2bbdfae96c
|
||||
github.com/minio/operator v0.0.0-20201022162018-527e5c32132b
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/secure-io/sio-go v0.3.1
|
||||
github.com/stretchr/testify v1.6.1
|
||||
|
||||
11
go.sum
@@ -60,10 +60,12 @@ github.com/Azure/azure-storage-blob-go v0.10.0/go.mod h1:ep1edmW+kNQx4UfWM9heESN
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest v14.1.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
|
||||
github.com/Azure/go-autorest/autorest v0.10.2 h1:NuSF3gXetiHyUbVdneJMEVyPUYAe5wh+aN08JYAf1tI=
|
||||
github.com/Azure/go-autorest/autorest v0.10.2/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||
@@ -72,11 +74,13 @@ github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMl
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.1 h1:xjPqigMQe2+0DAJ5A6MLUPp5D2r2Io8qHCuCMMI/yJU=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.1/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
@@ -86,8 +90,10 @@ github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocm
|
||||
github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI=
|
||||
github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
@@ -540,6 +546,7 @@ github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV
|
||||
github.com/googleapis/gnostic v0.2.2 h1:DcFegQ7+ECdmkJMfVwWlC+89I4esJ7p8nkGt9ainGDk=
|
||||
github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
|
||||
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@@ -798,8 +805,8 @@ github.com/minio/minio-go/v7 v7.0.2/go.mod h1:dJ80Mv2HeGkYLH1sqS/ksz07ON6csH3S6J
|
||||
github.com/minio/minio-go/v7 v7.0.5-0.20200811211821-14ed05478889/go.mod h1:CSt2ETZNs+bIIhWTse0mcZKZWMGrFU7Er7RR0TmkDYk=
|
||||
github.com/minio/minio-go/v7 v7.0.6-0.20200923173112-bc846cb9b089 h1:9DDs/Gc3fNHOQxQmwIFWs7YDLMTBh59r2XQ6RqEUA1I=
|
||||
github.com/minio/minio-go/v7 v7.0.6-0.20200923173112-bc846cb9b089/go.mod h1:CSt2ETZNs+bIIhWTse0mcZKZWMGrFU7Er7RR0TmkDYk=
|
||||
github.com/minio/operator v0.0.0-20200930213302-ab2bbdfae96c h1:OIKdzEJDFmUokbJ1rIdlr3kcfsBfXelYgSCTN/+Ppec=
|
||||
github.com/minio/operator v0.0.0-20200930213302-ab2bbdfae96c/go.mod h1:6lavbNo2YuJWeQR5bZYsEWdbpRCO2KrTyfQ0PtC/AN4=
|
||||
github.com/minio/operator v0.0.0-20201022162018-527e5c32132b h1:ggfD6V3nodTzhHJHCYIT1F897gscrz+hHsan0a2Wtqw=
|
||||
github.com/minio/operator v0.0.0-20201022162018-527e5c32132b/go.mod h1:At+++4/6W5BEXK11tN5DKIvkJKhYBZybbb5zmxb0LQI=
|
||||
github.com/minio/selfupdate v0.3.1 h1:BWEFSNnrZVMUWXbXIgLDNDjbejkmpAmZvy/nCz1HlEs=
|
||||
github.com/minio/selfupdate v0.3.1/go.mod h1:b8ThJzzH7u2MkF6PcIra7KaXO9Khf6alWPvMSyTDCFM=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
|
||||
@@ -15,7 +15,7 @@ spec:
|
||||
serviceAccountName: console-sa
|
||||
containers:
|
||||
- name: console
|
||||
image: minio/console:v0.4.0
|
||||
image: minio/console:v0.4.4
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
args:
|
||||
- server
|
||||
|
||||
@@ -15,7 +15,7 @@ spec:
|
||||
serviceAccountName: console-sa
|
||||
containers:
|
||||
- name: console
|
||||
image: minio/console:v0.4.0
|
||||
image: minio/console:v0.4.4
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
env:
|
||||
- name: CONSOLE_OPERATOR_MODE
|
||||
|
||||
63
models/bucket_encryption_info.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"
|
||||
)
|
||||
|
||||
// BucketEncryptionInfo bucket encryption info
|
||||
//
|
||||
// swagger:model bucketEncryptionInfo
|
||||
type BucketEncryptionInfo struct {
|
||||
|
||||
// algorithm
|
||||
Algorithm string `json:"algorithm,omitempty"`
|
||||
|
||||
// kms master key ID
|
||||
KmsMasterKeyID string `json:"kmsMasterKeyID,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this bucket encryption info
|
||||
func (m *BucketEncryptionInfo) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *BucketEncryptionInfo) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *BucketEncryptionInfo) UnmarshalBinary(b []byte) error {
|
||||
var res BucketEncryptionInfo
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
89
models/bucket_encryption_request.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// BucketEncryptionRequest bucket encryption request
|
||||
//
|
||||
// swagger:model bucketEncryptionRequest
|
||||
type BucketEncryptionRequest struct {
|
||||
|
||||
// enc type
|
||||
EncType BucketEncryptionType `json:"encType,omitempty"`
|
||||
|
||||
// kms key ID
|
||||
KmsKeyID string `json:"kmsKeyID,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this bucket encryption request
|
||||
func (m *BucketEncryptionRequest) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateEncType(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *BucketEncryptionRequest) validateEncType(formats strfmt.Registry) error {
|
||||
|
||||
if swag.IsZero(m.EncType) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := m.EncType.Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("encType")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *BucketEncryptionRequest) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *BucketEncryptionRequest) UnmarshalBinary(b []byte) error {
|
||||
var res BucketEncryptionRequest
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
80
models/bucket_encryption_type.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// BucketEncryptionType bucket encryption type
|
||||
//
|
||||
// swagger:model bucketEncryptionType
|
||||
type BucketEncryptionType string
|
||||
|
||||
const (
|
||||
|
||||
// BucketEncryptionTypeSseS3 captures enum value "sse-s3"
|
||||
BucketEncryptionTypeSseS3 BucketEncryptionType = "sse-s3"
|
||||
|
||||
// BucketEncryptionTypeSseKms captures enum value "sse-kms"
|
||||
BucketEncryptionTypeSseKms BucketEncryptionType = "sse-kms"
|
||||
)
|
||||
|
||||
// for schema
|
||||
var bucketEncryptionTypeEnum []interface{}
|
||||
|
||||
func init() {
|
||||
var res []BucketEncryptionType
|
||||
if err := json.Unmarshal([]byte(`["sse-s3","sse-kms"]`), &res); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range res {
|
||||
bucketEncryptionTypeEnum = append(bucketEncryptionTypeEnum, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (m BucketEncryptionType) validateBucketEncryptionTypeEnum(path, location string, value BucketEncryptionType) error {
|
||||
if err := validate.EnumCase(path, location, value, bucketEncryptionTypeEnum, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates this bucket encryption type
|
||||
func (m BucketEncryptionType) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
// value enum
|
||||
if err := m.validateBucketEncryptionTypeEnum("", "body", m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -35,14 +35,44 @@ type BucketObject struct {
|
||||
// content type
|
||||
ContentType string `json:"content_type,omitempty"`
|
||||
|
||||
// expiration
|
||||
Expiration string `json:"expiration,omitempty"`
|
||||
|
||||
// expiration rule id
|
||||
ExpirationRuleID string `json:"expiration_rule_id,omitempty"`
|
||||
|
||||
// is delete marker
|
||||
IsDeleteMarker bool `json:"is_delete_marker,omitempty"`
|
||||
|
||||
// is latest
|
||||
IsLatest bool `json:"is_latest,omitempty"`
|
||||
|
||||
// last modified
|
||||
LastModified string `json:"last_modified,omitempty"`
|
||||
|
||||
// legal hold status
|
||||
LegalHoldStatus string `json:"legal_hold_status,omitempty"`
|
||||
|
||||
// name
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// retention mode
|
||||
RetentionMode string `json:"retention_mode,omitempty"`
|
||||
|
||||
// retention until date
|
||||
RetentionUntilDate string `json:"retention_until_date,omitempty"`
|
||||
|
||||
// size
|
||||
Size int64 `json:"size,omitempty"`
|
||||
|
||||
// tags
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
|
||||
// user tags
|
||||
UserTags map[string]string `json:"user_tags,omitempty"`
|
||||
|
||||
// version id
|
||||
VersionID string `json:"version_id,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this bucket object
|
||||
|
||||
@@ -93,9 +93,6 @@ type CreateTenantRequest struct {
|
||||
// secret key
|
||||
SecretKey string `json:"secret_key,omitempty"`
|
||||
|
||||
// service name
|
||||
ServiceName string `json:"service_name,omitempty"`
|
||||
|
||||
// tls
|
||||
TLS *TLSConfiguration `json:"tls,omitempty"`
|
||||
|
||||
|
||||
80
models/object_legal_hold_status.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// ObjectLegalHoldStatus object legal hold status
|
||||
//
|
||||
// swagger:model objectLegalHoldStatus
|
||||
type ObjectLegalHoldStatus string
|
||||
|
||||
const (
|
||||
|
||||
// ObjectLegalHoldStatusEnabled captures enum value "enabled"
|
||||
ObjectLegalHoldStatusEnabled ObjectLegalHoldStatus = "enabled"
|
||||
|
||||
// ObjectLegalHoldStatusDisabled captures enum value "disabled"
|
||||
ObjectLegalHoldStatusDisabled ObjectLegalHoldStatus = "disabled"
|
||||
)
|
||||
|
||||
// for schema
|
||||
var objectLegalHoldStatusEnum []interface{}
|
||||
|
||||
func init() {
|
||||
var res []ObjectLegalHoldStatus
|
||||
if err := json.Unmarshal([]byte(`["enabled","disabled"]`), &res); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range res {
|
||||
objectLegalHoldStatusEnum = append(objectLegalHoldStatusEnum, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (m ObjectLegalHoldStatus) validateObjectLegalHoldStatusEnum(path, location string, value ObjectLegalHoldStatus) error {
|
||||
if err := validate.EnumCase(path, location, value, objectLegalHoldStatusEnum, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates this object legal hold status
|
||||
func (m ObjectLegalHoldStatus) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
// value enum
|
||||
if err := m.validateObjectLegalHoldStatusEnum("", "body", m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
80
models/object_retention_mode.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// ObjectRetentionMode object retention mode
|
||||
//
|
||||
// swagger:model objectRetentionMode
|
||||
type ObjectRetentionMode string
|
||||
|
||||
const (
|
||||
|
||||
// ObjectRetentionModeGovernance captures enum value "governance"
|
||||
ObjectRetentionModeGovernance ObjectRetentionMode = "governance"
|
||||
|
||||
// ObjectRetentionModeCompliance captures enum value "compliance"
|
||||
ObjectRetentionModeCompliance ObjectRetentionMode = "compliance"
|
||||
)
|
||||
|
||||
// for schema
|
||||
var objectRetentionModeEnum []interface{}
|
||||
|
||||
func init() {
|
||||
var res []ObjectRetentionMode
|
||||
if err := json.Unmarshal([]byte(`["governance","compliance"]`), &res); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range res {
|
||||
objectRetentionModeEnum = append(objectRetentionModeEnum, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (m ObjectRetentionMode) validateObjectRetentionModeEnum(path, location string, value ObjectRetentionMode) error {
|
||||
if err := validate.EnumCase(path, location, value, objectRetentionModeEnum, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates this object retention mode
|
||||
func (m ObjectRetentionMode) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
// value enum
|
||||
if err := m.validateObjectRetentionModeEnum("", "body", m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
83
models/put_object_legal_hold_request.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// PutObjectLegalHoldRequest put object legal hold request
|
||||
//
|
||||
// swagger:model putObjectLegalHoldRequest
|
||||
type PutObjectLegalHoldRequest struct {
|
||||
|
||||
// status
|
||||
// Required: true
|
||||
Status ObjectLegalHoldStatus `json:"status"`
|
||||
}
|
||||
|
||||
// Validate validates this put object legal hold request
|
||||
func (m *PutObjectLegalHoldRequest) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateStatus(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PutObjectLegalHoldRequest) validateStatus(formats strfmt.Registry) error {
|
||||
|
||||
if err := m.Status.Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("status")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *PutObjectLegalHoldRequest) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *PutObjectLegalHoldRequest) UnmarshalBinary(b []byte) error {
|
||||
var res PutObjectLegalHoldRequest
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
104
models/put_object_retention_request.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 (
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// PutObjectRetentionRequest put object retention request
|
||||
//
|
||||
// swagger:model putObjectRetentionRequest
|
||||
type PutObjectRetentionRequest struct {
|
||||
|
||||
// expires
|
||||
// Required: true
|
||||
Expires *string `json:"expires"`
|
||||
|
||||
// governance bypass
|
||||
GovernanceBypass bool `json:"governance_bypass,omitempty"`
|
||||
|
||||
// mode
|
||||
// Required: true
|
||||
Mode ObjectRetentionMode `json:"mode"`
|
||||
}
|
||||
|
||||
// Validate validates this put object retention request
|
||||
func (m *PutObjectRetentionRequest) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateExpires(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateMode(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PutObjectRetentionRequest) validateExpires(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("expires", "body", m.Expires); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PutObjectRetentionRequest) validateMode(formats strfmt.Registry) error {
|
||||
|
||||
if err := m.Mode.Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("mode")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *PutObjectRetentionRequest) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *PutObjectRetentionRequest) UnmarshalBinary(b []byte) error {
|
||||
var res PutObjectRetentionRequest
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
60
models/put_object_tags_request.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// PutObjectTagsRequest put object tags request
|
||||
//
|
||||
// swagger:model putObjectTagsRequest
|
||||
type PutObjectTagsRequest struct {
|
||||
|
||||
// tags
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this put object tags request
|
||||
func (m *PutObjectTagsRequest) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *PutObjectTagsRequest) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *PutObjectTagsRequest) UnmarshalBinary(b []byte) error {
|
||||
var res PutObjectTagsRequest
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
@@ -22,26 +22,26 @@ import (
|
||||
|
||||
// endpoints definition
|
||||
var (
|
||||
configuration = "/configurations-list"
|
||||
users = "/users"
|
||||
groups = "/groups"
|
||||
iamPolicies = "/policies"
|
||||
dashboard = "/dashboard"
|
||||
profiling = "/profiling"
|
||||
trace = "/trace"
|
||||
logs = "/logs"
|
||||
watch = "/watch"
|
||||
notifications = "/notification-endpoints"
|
||||
buckets = "/buckets"
|
||||
bucketsDetail = "/buckets/:bucketName"
|
||||
serviceAccounts = "/service-accounts"
|
||||
tenants = "/tenants"
|
||||
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
|
||||
heal = "/heal"
|
||||
remoteBuckets = "/remote-buckets"
|
||||
replication = "/replication"
|
||||
objectBrowser = "/object-browser/:bucket?"
|
||||
mainObjectBrowser = "/object-browser"
|
||||
configuration = "/configurations-list"
|
||||
users = "/users"
|
||||
groups = "/groups"
|
||||
iamPolicies = "/policies"
|
||||
dashboard = "/dashboard"
|
||||
profiling = "/profiling"
|
||||
watch = "/watch"
|
||||
notifications = "/notification-endpoints"
|
||||
buckets = "/buckets"
|
||||
bucketsDetail = "/buckets/:bucketName"
|
||||
serviceAccounts = "/service-accounts"
|
||||
tenants = "/tenants"
|
||||
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
|
||||
heal = "/heal"
|
||||
remoteBuckets = "/remote-buckets"
|
||||
replication = "/replication"
|
||||
objectBrowser = "/object-browser/:bucket/*"
|
||||
objectBrowserBucket = "/object-browser/:bucket"
|
||||
mainObjectBrowser = "/object-browser"
|
||||
license = "/license"
|
||||
)
|
||||
|
||||
type ConfigurationActionSet struct {
|
||||
@@ -59,16 +59,6 @@ var configurationActionSet = ConfigurationActionSet{
|
||||
),
|
||||
}
|
||||
|
||||
// logsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var logsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ConsoleLogAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// dashboardActionSet contains the list of admin actions required for this endpoint to work
|
||||
var dashboardActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
@@ -117,16 +107,6 @@ var profilingActionSet = ConfigurationActionSet{
|
||||
),
|
||||
}
|
||||
|
||||
// traceActionSet contains the list of admin actions required for this endpoint to work
|
||||
var traceActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.TraceAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// usersActionSet contains the list of admin actions required for this endpoint to work
|
||||
var usersActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
@@ -236,26 +216,32 @@ var objectBrowserActionSet = ConfigurationActionSet{
|
||||
actions: iampolicy.NewActionSet(),
|
||||
}
|
||||
|
||||
// licenseActionSet no actions needed for this module to work
|
||||
var licenseActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(),
|
||||
actions: iampolicy.NewActionSet(),
|
||||
}
|
||||
|
||||
// endpointRules contains the mapping between endpoints and ActionSets, additional rules can be added here
|
||||
var endpointRules = map[string]ConfigurationActionSet{
|
||||
configuration: configurationActionSet,
|
||||
users: usersActionSet,
|
||||
groups: groupsActionSet,
|
||||
iamPolicies: iamPoliciesActionSet,
|
||||
dashboard: dashboardActionSet,
|
||||
profiling: profilingActionSet,
|
||||
trace: traceActionSet,
|
||||
logs: logsActionSet,
|
||||
watch: watchActionSet,
|
||||
notifications: notificationsActionSet,
|
||||
buckets: bucketsActionSet,
|
||||
bucketsDetail: bucketsActionSet,
|
||||
serviceAccounts: serviceAccountsActionSet,
|
||||
heal: healActionSet,
|
||||
remoteBuckets: remoteBucketsActionSet,
|
||||
replication: replicationActionSet,
|
||||
objectBrowser: objectBrowserActionSet,
|
||||
mainObjectBrowser: objectBrowserActionSet,
|
||||
configuration: configurationActionSet,
|
||||
users: usersActionSet,
|
||||
groups: groupsActionSet,
|
||||
iamPolicies: iamPoliciesActionSet,
|
||||
dashboard: dashboardActionSet,
|
||||
profiling: profilingActionSet,
|
||||
watch: watchActionSet,
|
||||
notifications: notificationsActionSet,
|
||||
buckets: bucketsActionSet,
|
||||
bucketsDetail: bucketsActionSet,
|
||||
serviceAccounts: serviceAccountsActionSet,
|
||||
heal: healActionSet,
|
||||
remoteBuckets: remoteBucketsActionSet,
|
||||
replication: replicationActionSet,
|
||||
objectBrowser: objectBrowserActionSet,
|
||||
mainObjectBrowser: objectBrowserActionSet,
|
||||
objectBrowserBucket: objectBrowserActionSet,
|
||||
license: licenseActionSet,
|
||||
}
|
||||
|
||||
// operatorRules contains the mapping between endpoints and ActionSets for operator only mode
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
args: args{
|
||||
[]string{"admin:ServerInfo"},
|
||||
},
|
||||
want: 4,
|
||||
want: 6,
|
||||
},
|
||||
{
|
||||
name: "policies endpoint",
|
||||
@@ -63,7 +63,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"admin:ListUserPolicies",
|
||||
},
|
||||
},
|
||||
want: 4,
|
||||
want: 6,
|
||||
},
|
||||
{
|
||||
name: "all admin endpoints",
|
||||
@@ -81,7 +81,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 6,
|
||||
want: 8,
|
||||
},
|
||||
{
|
||||
name: "all admin and s3 endpoints",
|
||||
|
||||
@@ -23,10 +23,9 @@ import (
|
||||
"github.com/minio/minio/pkg/env"
|
||||
)
|
||||
|
||||
// ConsoleSTSAndJWTDurationSeconds 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 GetConsoleSTSAndJWTDurationInSeconds() int {
|
||||
duration, err := strconv.Atoi(env.Get(ConsoleSTSAndJWTDurationSeconds, "3600"))
|
||||
// ConsoleSTSDurationSeconds returns the default session duration for the STS requested tokens.
|
||||
func GetConsoleSTSDurationInSeconds() int {
|
||||
duration, err := strconv.Atoi(env.Get(ConsoleSTSDurationSeconds, "3600"))
|
||||
if err != nil {
|
||||
duration = 3600
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package token
|
||||
|
||||
const (
|
||||
ConsoleSTSAndJWTDurationSeconds = "CONSOLE_STS_AND_JWT_DURATION_SECONDS"
|
||||
ConsolePBKDFPassphrase = "CONSOLE_PBKDF_PASSPHRASE"
|
||||
ConsolePBKDFSalt = "CONSOLE_PBKDF_SALT"
|
||||
ConsoleSTSDurationSeconds = "CONSOLE_STS_DURATION_SECONDS"
|
||||
ConsolePBKDFPassphrase = "CONSOLE_PBKDF_PASSPHRASE"
|
||||
ConsolePBKDFSalt = "CONSOLE_PBKDF_SALT"
|
||||
)
|
||||
|
||||
@@ -60,17 +60,17 @@ func TestJWTAuthenticate(t *testing.T) {
|
||||
funcAssert.Equal(claims.SecretAccessKey, creds.SecretAccessKey)
|
||||
funcAssert.Equal(claims.SessionToken, creds.SessionToken)
|
||||
}
|
||||
// Test-2 : SessionTokenAuthenticate() return an error because of a tampered jwt
|
||||
// Test-2 : SessionTokenAuthenticate() return an error because of a tampered token
|
||||
if _, err := SessionTokenAuthenticate(badToken); err != nil {
|
||||
funcAssert.Equal("session token internal data is malformed", err.Error())
|
||||
}
|
||||
// Test-3 : SessionTokenAuthenticate() return an error because of an empty jwt
|
||||
// Test-3 : SessionTokenAuthenticate() return an error because of an empty token
|
||||
if _, err := SessionTokenAuthenticate(""); err != nil {
|
||||
funcAssert.Equal("session token missing", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJWTValid(t *testing.T) {
|
||||
func TestSessionTokenValid(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : SessionTokenAuthenticate() provided token is valid
|
||||
funcAssert.Equal(true, IsSessionTokenValid(goodToken))
|
||||
|
||||
222
pkg/certs/certs.go
Normal file
@@ -0,0 +1,222 @@
|
||||
// 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 certs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/certs"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
type GetCertificateFunc = certs.GetCertificateFunc
|
||||
|
||||
// ConfigDir - points to a user set directory.
|
||||
type ConfigDir struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// Get - returns current directory.
|
||||
func (dir *ConfigDir) Get() string {
|
||||
return dir.Path
|
||||
}
|
||||
|
||||
func getDefaultConfigDir() string {
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(homeDir, DefaultConsoleConfigDir)
|
||||
}
|
||||
|
||||
func getDefaultCertsDir() string {
|
||||
return filepath.Join(getDefaultConfigDir(), CertsDir)
|
||||
}
|
||||
|
||||
func getDefaultCertsCADir() string {
|
||||
return filepath.Join(getDefaultCertsDir(), CertsCADir)
|
||||
}
|
||||
|
||||
// isFile - returns whether given Path is a file or not.
|
||||
func isFile(path string) bool {
|
||||
if fi, err := os.Stat(path); err == nil {
|
||||
return fi.Mode().IsRegular()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultCertsDir certs directory.
|
||||
DefaultCertsDir = &ConfigDir{Path: getDefaultCertsDir()}
|
||||
// DefaultCertsCADir CA directory.
|
||||
DefaultCertsCADir = &ConfigDir{Path: getDefaultCertsCADir()}
|
||||
// GlobalCertsDir points to current certs directory set by user with --certs-dir
|
||||
GlobalCertsDir = DefaultCertsDir
|
||||
// GlobalCertsCADir points to relative Path to certs directory and is <value-of-certs-dir>/CAs
|
||||
GlobalCertsCADir = DefaultCertsCADir
|
||||
)
|
||||
|
||||
// MkdirAllIgnorePerm attempts to create all directories, ignores any permission denied errors.
|
||||
func MkdirAllIgnorePerm(path string) error {
|
||||
err := os.MkdirAll(path, 0700)
|
||||
if err != nil {
|
||||
// It is possible in kubernetes like deployments this directory
|
||||
// is already mounted and is not writable, ignore any write errors.
|
||||
if os.IsPermission(err) {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func NewConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() string) (*ConfigDir, bool) {
|
||||
var dir string
|
||||
var dirSet bool
|
||||
|
||||
switch {
|
||||
case ctx.IsSet(option):
|
||||
dir = ctx.String(option)
|
||||
dirSet = true
|
||||
case ctx.GlobalIsSet(option):
|
||||
dir = ctx.GlobalString(option)
|
||||
dirSet = true
|
||||
// cli package does not expose parent's option option. Below code is workaround.
|
||||
if dir == "" || dir == getDefaultDir() {
|
||||
dirSet = false // Unset to false since GlobalIsSet() true is a false positive.
|
||||
if ctx.Parent().GlobalIsSet(option) {
|
||||
dir = ctx.Parent().GlobalString(option)
|
||||
dirSet = true
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Neither local nor global option is provided. In this case, try to use
|
||||
// default directory.
|
||||
dir = getDefaultDir()
|
||||
if dir == "" {
|
||||
logger.FatalIf(errors.New("invalid arguments specified"), "%s option must be provided", option)
|
||||
}
|
||||
}
|
||||
|
||||
if dir == "" {
|
||||
logger.FatalIf(errors.New("empty directory"), "%s directory cannot be empty", option)
|
||||
}
|
||||
|
||||
// Disallow relative paths, figure out absolute paths.
|
||||
dirAbs, err := filepath.Abs(dir)
|
||||
logger.FatalIf(err, "Unable to fetch absolute path for %s=%s", option, dir)
|
||||
logger.FatalIf(MkdirAllIgnorePerm(dirAbs), "Unable to create directory specified %s=%s", option, dir)
|
||||
|
||||
return &ConfigDir{Path: dirAbs}, dirSet
|
||||
}
|
||||
|
||||
func getPublicCertFile() string {
|
||||
return filepath.Join(GlobalCertsDir.Get(), PublicCertFile)
|
||||
}
|
||||
|
||||
func getPrivateKeyFile() string {
|
||||
return filepath.Join(GlobalCertsDir.Get(), PrivateKeyFile)
|
||||
}
|
||||
|
||||
func GetTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, err error) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if !(isFile(getPublicCertFile()) && isFile(getPrivateKeyFile())) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if x509Certs, err = config.ParsePublicCertFile(getPublicCertFile()); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
manager, err = certs.NewManager(ctx, getPublicCertFile(), getPrivateKeyFile(), config.LoadX509KeyPair)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
//Console has support for multiple certificates. It expects the following structure:
|
||||
// certs/
|
||||
// │
|
||||
// ├─ public.crt
|
||||
// ├─ private.key
|
||||
// │
|
||||
// ├─ example.com/
|
||||
// │ │
|
||||
// │ ├─ public.crt
|
||||
// │ └─ private.key
|
||||
// └─ foobar.org/
|
||||
// │
|
||||
// ├─ public.crt
|
||||
// └─ private.key
|
||||
// ...
|
||||
//
|
||||
//Therefore, we read all filenames in the cert directory and check
|
||||
//for each directory whether it contains a public.crt and private.key.
|
||||
// If so, we try to add it to certificate manager.
|
||||
root, err := os.Open(GlobalCertsDir.Get())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer root.Close()
|
||||
|
||||
files, err := root.Readdir(-1)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, file := range files {
|
||||
// Ignore all
|
||||
// - regular files
|
||||
// - "CAs" directory
|
||||
// - any directory which starts with ".."
|
||||
if file.Mode().IsRegular() || file.Name() == "CAs" || strings.HasPrefix(file.Name(), "..") {
|
||||
continue
|
||||
}
|
||||
if file.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
file, err = os.Stat(filepath.Join(root.Name(), file.Name()))
|
||||
if err != nil {
|
||||
// not accessible ignore
|
||||
continue
|
||||
}
|
||||
if !file.IsDir() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
certFile = filepath.Join(root.Name(), file.Name(), PublicCertFile)
|
||||
keyFile = filepath.Join(root.Name(), file.Name(), PrivateKeyFile)
|
||||
)
|
||||
if !isFile(certFile) || !isFile(keyFile) {
|
||||
continue
|
||||
}
|
||||
if err = manager.AddCertificate(certFile, keyFile); err != nil {
|
||||
err = fmt.Errorf("unable to load TLS certificate '%s,%s': %w", certFile, keyFile, err)
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
}
|
||||
}
|
||||
return x509Certs, manager, nil
|
||||
}
|
||||
34
pkg/certs/const.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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 certs
|
||||
|
||||
const (
|
||||
// Default minio configuration directory where below configuration files/directories are stored.
|
||||
DefaultConsoleConfigDir = ".console"
|
||||
|
||||
// Directory contains below files/directories for HTTPS configuration.
|
||||
CertsDir = "certs"
|
||||
|
||||
// Directory contains all CA certificates other than system defaults for HTTPS.
|
||||
CertsCADir = "CAs"
|
||||
|
||||
// Public certificate file for HTTPS.
|
||||
PublicCertFile = "public.crt"
|
||||
|
||||
// Private key file for HTTPS.
|
||||
PrivateKeyFile = "private.key"
|
||||
)
|
||||
@@ -28,7 +28,6 @@
|
||||
"local-storage-fallback": "^4.1.1",
|
||||
"lodash": "^4.17.19",
|
||||
"moment": "^2.24.0",
|
||||
"npm": "^6.14.4",
|
||||
"react": "^16.13.1",
|
||||
"react-app-rewire-hot-loader": "^2.0.1",
|
||||
"react-app-rewired": "^2.1.6",
|
||||
@@ -40,7 +39,7 @@
|
||||
"react-moment": "^0.9.7",
|
||||
"react-redux": "^7.1.3",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.4.1",
|
||||
"react-scripts": "3.4.4",
|
||||
"recharts": "^1.8.5",
|
||||
"redux": "^4.0.4",
|
||||
"redux-thunk": "^2.3.0",
|
||||
|
||||
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 7.8 KiB |
67
portal-ui/public/images/BG_IllustrationDarker.svg
Normal file
@@ -0,0 +1,67 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 368.999 192.934">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1{opacity:0.35;}.cls-12,.cls-15,.cls-16,.cls-17,.cls-2,.cls-5,.cls-6,.cls-7,.cls-8{opacity:0.5;}.cls-10,.cls-11,.cls-12,.cls-13,.cls-14,.cls-15,.cls-16,.cls-17,.cls-3,.cls-4,.cls-5,.cls-6,.cls-7,.cls-9{fill:none;stroke:#707070;stroke-miterlimit:10;}.cls-4{stroke-width:1px;}.cls-10,.cls-11,.cls-5,.cls-9{stroke-width:1.2px;}.cls-5{stroke-dasharray:2.619
|
||||
2.182;}.cls-12,.cls-15,.cls-16,.cls-17,.cls-5,.cls-6,.cls-7,.cls-8{isolation:isolate;}.cls-6{stroke-width:1.6px;stroke-dasharray:2.144
|
||||
1.786;}.cls-7{stroke-width:1.6px;stroke-dasharray:2.23 1.858;}.cls-10{stroke-dasharray:2.646
|
||||
2.204;}.cls-11{stroke-dasharray:2.585 2.154;}.cls-12{stroke-width:1.8px;stroke-dasharray:2.484
|
||||
2.07;}.cls-13{stroke-dasharray:2.984 2.487;}.cls-14{stroke-dasharray:2.773
|
||||
2.311;}.cls-16{stroke-width:1.8px;}.cls-17{stroke-width:1.8px;}
|
||||
</style>
|
||||
</defs>
|
||||
<title>BG_Illustration</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<g id="BG_Illustration" data-name="BG Illustration" class="cls-1">
|
||||
<g id="Group_118" data-name="Group 118" class="cls-2">
|
||||
<path id="Path_56" data-name="Path 56" class="cls-3"
|
||||
d="M211.5,140.678l-52.726,29.078L79.687,126.139V29.652L132.411.571,211.5,44.188Z"/>
|
||||
<path id="Path_58" data-name="Path 58" class="cls-3"
|
||||
d="M158.776,169.756V73.271L211.5,44.193,158.776,73.271,79.688,29.654"/>
|
||||
<path id="Path_59" data-name="Path 59" class="cls-4" d="M84.681,41l69.1,38.11v79.3l-69.1-38.11Z"/>
|
||||
<line id="Line_37" data-name="Line 37" class="cls-4" x1="106.25" y1="52.782" x2="106.25"
|
||||
y2="132.086"/>
|
||||
<line id="Line_38" data-name="Line 38" class="cls-4" x1="153.783" y1="92.327" x2="106.25"
|
||||
y2="65.999"/>
|
||||
<line id="Line_39" data-name="Line 39" class="cls-4" x1="153.783" y1="105.545" x2="106.25"
|
||||
y2="79.217"/>
|
||||
<line id="Line_40" data-name="Line 40" class="cls-4" x1="153.783" y1="118.762" x2="106.25"
|
||||
y2="92.434"/>
|
||||
<line id="Line_41" data-name="Line 41" class="cls-4" x1="153.783" y1="131.979" x2="106.25"
|
||||
y2="105.651"/>
|
||||
<line id="Line_42" data-name="Line 42" class="cls-4" x1="153.783" y1="145.197" x2="106.25"
|
||||
y2="118.869"/>
|
||||
<path id="Path_60" data-name="Path 60" class="cls-4"
|
||||
d="M166.723,151.031l38.8-22.487V62.916L166.723,85.4Z"/>
|
||||
</g>
|
||||
<path id="Path_62" data-name="Path 62" class="cls-5" d="M117.106,148.062l-76.18,43.33"/>
|
||||
<path id="Path_63" data-name="Path 63" class="cls-6" d="M271.394,167.271l-44.483,25.3"/>
|
||||
<path id="Path_64" data-name="Path 64" class="cls-7" d="M190.722,155.708l61.951,36.031"/>
|
||||
<path id="Path_65" data-name="Path 65" class="cls-5" d="M237.7,36.385l28.182,17.229"/>
|
||||
<g id="Path_66" data-name="Path 66" class="cls-8">
|
||||
<line class="cls-9" x1="362.563" y1="69.327" x2="361.42" y2="68.688"/>
|
||||
<line class="cls-10" x1="359.496" y1="67.613" x2="305.418" y2="37.39"/>
|
||||
<polyline class="cls-9" points="304.456 36.852 303.313 36.213 302.158 36.83"/>
|
||||
<line class="cls-11" x1="300.258" y1="37.844" x2="213.418" y2="84.213"/>
|
||||
<line class="cls-9" x1="212.468" y1="84.72" x2="211.313" y2="85.337"/>
|
||||
</g>
|
||||
<path id="Path_67" data-name="Path 67" class="cls-12"
|
||||
d="M79.648,192.571,31.786,166.344h-.868l-23.579,14.2"/>
|
||||
<g id="Path_68" data-name="Path 68" class="cls-8">
|
||||
<line class="cls-3" x1="22.871" y1="84.641" x2="24.156" y2="83.867"/>
|
||||
<line class="cls-13" x1="26.286" y1="82.584" x2="48.654" y2="69.113"/>
|
||||
<polyline class="cls-3" points="49.719 68.471 51.004 67.698 52.307 68.441"/>
|
||||
<line class="cls-14" x1="54.315" y1="69.585" x2="75.395" y2="81.606"/>
|
||||
<line class="cls-3" x1="76.399" y1="82.178" x2="77.702" y2="82.921"/>
|
||||
</g>
|
||||
<circle id="Ellipse_11" data-name="Ellipse 11" class="cls-15" cx="4.092" cy="183.59" r="3.592"/>
|
||||
<circle id="Ellipse_12" data-name="Ellipse 12" class="cls-15" cx="274.986" cy="165.477" r="3.592"/>
|
||||
<ellipse id="Ellipse_13" data-name="Ellipse 13" class="cls-16" cx="364.957" cy="71.922" rx="3.592"
|
||||
ry="2.904"/>
|
||||
<circle id="Ellipse_14" data-name="Ellipse 14" class="cls-15" cx="19.279" cy="87.681" r="3.592"/>
|
||||
<ellipse id="Ellipse_15" data-name="Ellipse 15" class="cls-17" cx="234.106" cy="32.58" rx="3.592"
|
||||
ry="2.649"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
6
portal-ui/public/images/ob_bucket_clear.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="11.174" height="11" viewBox="0 0 11.174 11">
|
||||
<defs>
|
||||
<style>.a{fill:none;stroke:#081c42;stroke-linecap:round;}</style>
|
||||
</defs>
|
||||
<path class="a" d="M8.392,10H1.608L0,0H10Z" transform="translate(0.587 0.5)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 279 B |
6
portal-ui/public/images/ob_bucket_filled.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="11.174" height="11" viewBox="0 0 11.174 11">
|
||||
<defs>
|
||||
<style>.a{fill:#081c42;stroke:#081c42;stroke-linecap:round;}</style>
|
||||
</defs>
|
||||
<path class="a" d="M8.392,10H1.608L0,0H10Z" transform="translate(0.587 0.5)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 282 B |
10
portal-ui/public/images/ob_file_clear.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="11.442" height="15.302" viewBox="0 0 11.442 15.302">
|
||||
<defs>
|
||||
<style>.a,.b{fill:none;stroke:#081c42;}.b{stroke-linejoin:round;}</style>
|
||||
</defs>
|
||||
<g transform="translate(0.5 0.5)">
|
||||
<path class="a" d="M-12060-11667.842v14.261h10.442v-10.591l-3.671-3.67Z"
|
||||
transform="translate(12059.999 11667.883)"/>
|
||||
<path class="b" d="M-12051.353-11664.255v-3.639l3.528,3.639Z" transform="translate(12058.188 11667.894)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 515 B |
10
portal-ui/public/images/ob_file_filled.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="11.442" height="15.302" viewBox="0 0 11.442 15.302">
|
||||
<defs>
|
||||
<style>.a,.b{fill:#081c42;stroke:#081c42;}.b{stroke-linejoin:round;fill:#fff}</style>
|
||||
</defs>
|
||||
<g transform="translate(0.5 0.5)">
|
||||
<path class="a" d="M-12060-11667.842v14.261h10.442v-10.591l-3.671-3.67Z"
|
||||
transform="translate(12059.999 11667.883)"/>
|
||||
<path class="b" d="M-12051.353-11664.255v-3.639l3.528,3.639Z" transform="translate(12058.188 11667.894)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 527 B |
10
portal-ui/public/images/ob_folder_clear.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="15.999" height="13.999" viewBox="0 0 15.999 13.999">
|
||||
<defs>
|
||||
<style>.a{fill:none;stroke-linecap:square;}.b,.c{stroke:none;}.c{fill:#081c42;}</style>
|
||||
</defs>
|
||||
<g class="a" transform="translate(-0.001 0.001)">
|
||||
<path class="b" d="M0,14V0H8.572V2.411H16V14Z"/>
|
||||
<path class="c"
|
||||
d="M 15.00020027160645 12.99860000610352 L 15.00020027160645 3.411099910736084 L 8.571599960327148 3.411099910736084 L 7.571600437164307 3.411099910736084 L 7.571600437164307 2.411099910736084 L 7.571600437164307 0.9990998506546021 L 1.000900268554688 0.9990998506546021 L 1.000900268554688 2.411099910736084 L 1.000900268554688 12.99860000610352 L 15.00020027160645 12.99860000610352 M 16.00020027160645 13.99860000610352 L 0.0009002700680866838 13.99860000610352 L 0.0009002700680866838 2.411099910736084 L 0.0009002700680866838 -0.0009001312428154051 L 8.571599960327148 -0.0009001312428154051 L 8.571599960327148 2.411099910736084 L 16.00020027160645 2.411099910736084 L 16.00020027160645 13.99860000610352 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
10
portal-ui/public/images/ob_folder_filled.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="15.999" height="13.999" viewBox="0 0 15.999 13.999">
|
||||
<defs>
|
||||
<style>.a{fill:none;stroke-linecap:square;}.b,.c{stroke:none;fill:#081c42}.c{fill:#081c42;}</style>
|
||||
</defs>
|
||||
<g class="a" transform="translate(-0.001 0.001)">
|
||||
<path class="b" d="M0,14V0H8.572V2.411H16V14Z"/>
|
||||
<path class="c"
|
||||
d="M 15.00020027160645 12.99860000610352 L 15.00020027160645 3.411099910736084 L 8.571599960327148 3.411099910736084 L 7.571600437164307 3.411099910736084 L 7.571600437164307 2.411099910736084 L 7.571600437164307 0.9990998506546021 L 1.000900268554688 0.9990998506546021 L 1.000900268554688 2.411099910736084 L 1.000900268554688 12.99860000610352 L 15.00020027160645 12.99860000610352 M 16.00020027160645 13.99860000610352 L 0.0009002700680866838 13.99860000610352 L 0.0009002700680866838 2.411099910736084 L 0.0009002700680866838 -0.0009001312428154051 L 8.571599960327148 -0.0009001312428154051 L 8.571599960327148 2.411099910736084 L 16.00020027160645 2.411099910736084 L 16.00020027160645 13.99860000610352 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 47 KiB |
@@ -19,7 +19,6 @@ import { Redirect, Route, Router, Switch } from "react-router-dom";
|
||||
import history from "./history";
|
||||
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";
|
||||
@@ -27,6 +26,22 @@ import { userLoggedIn } from "./actions";
|
||||
import LoginCallback from "./screens/LoginPage/LoginCallback";
|
||||
import { hot } from "react-hot-loader/root";
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
loggedIn: boolean;
|
||||
component: any;
|
||||
}
|
||||
|
||||
export class ProtectedRoute extends React.Component<ProtectedRouteProps> {
|
||||
render() {
|
||||
const Component = this.props.component;
|
||||
return this.props.loggedIn ? (
|
||||
<Component />
|
||||
) : (
|
||||
<Redirect to={{ pathname: "/login" }} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const isLoggedIn = () => {
|
||||
return (
|
||||
storage.getItem("token") !== undefined &&
|
||||
@@ -47,29 +62,14 @@ interface RoutesProps {
|
||||
}
|
||||
|
||||
class Routes extends React.Component<RoutesProps> {
|
||||
componentDidMount(): void {
|
||||
if (isLoggedIn()) {
|
||||
this.props.userLoggedIn(true);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const loggedIn = isLoggedIn();
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route exact path="/oauth_callback" component={LoginCallback} />
|
||||
<Route exact path="/login" component={Login} />
|
||||
{this.props.loggedIn ? (
|
||||
<Switch>
|
||||
<Route path="/*" component={Console} />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
) : (
|
||||
<Switch>
|
||||
<Route exact path="/" component={Login} />
|
||||
<Redirect to="/" />
|
||||
</Switch>
|
||||
)}
|
||||
<ProtectedRoute component={Console} loggedIn={loggedIn} />
|
||||
</Switch>
|
||||
</Router>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import storage from "local-storage-fallback";
|
||||
import { ICapacity, IStorageType, IZoneModel } from "./types";
|
||||
import { ICapacity, IZoneModel } from "./types";
|
||||
|
||||
const minStReq = 1073741824; // Minimal Space required for MinIO
|
||||
const minMemReq = 2147483648; // Minimal Memory required for MinIO in bytes
|
||||
|
||||
34
portal-ui/src/icons/AddIcon.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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 { SvgIcon } from "@material-ui/core";
|
||||
class AddIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 12 12">
|
||||
<path
|
||||
fill="#081c42"
|
||||
className="a"
|
||||
d="M-13160.269,1885.114h-3.235v-4.381h-4.382V1877.5h4.382v-4.381h3.235v4.381h4.383v3.238h-4.383v4.38Z"
|
||||
transform="translate(13167.886 -1873.114)"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddIcon;
|
||||
75
portal-ui/src/icons/AllBucketsIcon.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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 { SvgIcon } from "@material-ui/core";
|
||||
class AllBucketsIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 15.834 17.375">
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="a"
|
||||
y1="0.5"
|
||||
x2="1"
|
||||
y2="0.5"
|
||||
gradientUnits="objectBoundingBox"
|
||||
>
|
||||
<stop offset="0.044" stopColor="#362585" />
|
||||
<stop offset="0.301" stopColor="#281b6f" />
|
||||
<stop offset="1" stopColor="#1e1560" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(0 0.375)">
|
||||
<circle
|
||||
style={{ opacity: 0.1, fill: "url(#a)" }}
|
||||
cx="6.625"
|
||||
cy="6.625"
|
||||
r="6.625"
|
||||
transform="translate(0 3.75)"
|
||||
/>
|
||||
<g transform="translate(3.092)">
|
||||
<ellipse
|
||||
style={{
|
||||
fill: "none",
|
||||
stroke: "#707070",
|
||||
strokeMiterlimit: 10,
|
||||
strokeWidth: "0.75px",
|
||||
}}
|
||||
cx="6.183"
|
||||
cy="1.244"
|
||||
rx="6.183"
|
||||
ry="1.244"
|
||||
transform="translate(0)"
|
||||
/>
|
||||
<path
|
||||
style={{
|
||||
fill: "none",
|
||||
stroke: "#707070",
|
||||
strokeMiterlimit: 10,
|
||||
strokeWidth: "0.75px",
|
||||
}}
|
||||
d="M-3722.174,1225.225l-1.687,10.292a.858.858,0,0,1-.578.669,12.182,12.182,0,0,1-3.918.647,12.187,12.187,0,0,1-3.894-.639.878.878,0,0,1-.6-.678q-.843-5.145-1.687-10.291"
|
||||
transform="translate(3734.541 -1223.981)"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AllBucketsIcon;
|
||||
37
portal-ui/src/icons/ConsoleIcon.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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 { SvgIcon } from "@material-ui/core";
|
||||
class ConsoleIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
|
||||
<g transform="translate(-518 -361)">
|
||||
<path
|
||||
d="M-126,0V10h10V0Zm1.5,8.5V2.95h7V8.5Z"
|
||||
transform="translate(644 361)"
|
||||
/>
|
||||
<rect width="2" height="1" transform="translate(520.272 364.772)" />
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ConsoleIcon;
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
// 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
|
||||
@@ -15,21 +15,36 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import {SvgIcon} from "@material-ui/core";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class CreateIcon extends React.Component {
|
||||
render() {
|
||||
return (<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<title>ic_h_create-new_sl</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path className="cls-1"
|
||||
d="M0,0V16H16V0ZM11.886,9.048H9.048v2.838h-2.1V9.048H4.114v-2.1H6.952V4.114h2.1V6.952h2.838Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>)
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<g
|
||||
id="Group_55"
|
||||
data-name="Group 55"
|
||||
transform="translate(1002 -2555)"
|
||||
>
|
||||
<rect
|
||||
id="Rectangle_29"
|
||||
width="2"
|
||||
height="12"
|
||||
transform="translate(-997 2555)"
|
||||
fill="#fff"
|
||||
/>
|
||||
<rect
|
||||
id="Rectangle_30"
|
||||
width="2"
|
||||
height="12"
|
||||
transform="translate(-990 2560) rotate(90)"
|
||||
fill="#fff"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CreateIcon;
|
||||
|
||||
73
portal-ui/src/icons/EgressIcon.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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 { SvgIcon } from "@material-ui/core";
|
||||
class EgressIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 18.344 17.009">
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="a"
|
||||
y1="0.5"
|
||||
x2="1"
|
||||
y2="0.5"
|
||||
gradientUnits="objectBoundingBox"
|
||||
>
|
||||
<stop offset="0.044" stopColor="#362585" />
|
||||
<stop offset="0.301" stopColor="#281b6f" />
|
||||
<stop offset="1" stopColor="#1e1560" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(0 0.25)">
|
||||
<ellipse
|
||||
style={{ opacity: 0.1, fill: "url(#a)" }}
|
||||
cx="7.462"
|
||||
cy="7.462"
|
||||
rx="7.462"
|
||||
ry="7.462"
|
||||
transform="translate(0 1.835)"
|
||||
/>
|
||||
<rect
|
||||
style={{
|
||||
fill: "none",
|
||||
stroke: "#707070",
|
||||
strokeMiterlimit: 10,
|
||||
strokeWidth: "0.5px",
|
||||
}}
|
||||
width="9.323"
|
||||
height="9.323"
|
||||
transform="translate(4.083)"
|
||||
/>
|
||||
<rect
|
||||
style={{
|
||||
fill: "none",
|
||||
stroke: "#707070",
|
||||
strokeMiterlimit: 10,
|
||||
strokeWidth: "0.5px",
|
||||
}}
|
||||
width="8.223"
|
||||
height="8.223"
|
||||
transform="translate(9.871 5.307)"
|
||||
/>
|
||||
</g>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EgressIcon;
|
||||
53
portal-ui/src/icons/HealIcon.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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 { SvgIcon } from "@material-ui/core";
|
||||
class HealIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10.014 9.993">
|
||||
<path
|
||||
className="a"
|
||||
d="M9.162,5.971h0L8.192,5,9.346,3.846a2.257,2.257,0,0,0,0-3.192,2.311,2.311,0,0,0-3.192,0L5,1.808,4.029.837,3.846.654a2.311,2.311,0,0,0-3.192,0,2.257,2.257,0,0,0,0,3.192l.184.183h0L1.808,5,.654,6.154A2.257,2.257,0,0,0,3.846,9.346L5,8.192l.971.971.183.183A2.257,2.257,0,0,0,9.346,6.154Zm-2.29-4.6a1.27,1.27,0,0,1,1.757,0,1.242,1.242,0,0,1,0,1.757L7.475,4.283,5.717,2.525Zm-5.5,1.757A1.243,1.243,0,0,1,3.129,1.371l.183.183L1.555,3.312Zm1.757,5.5a1.27,1.27,0,0,1-1.757,0,1.242,1.242,0,0,1,0-1.757L2.525,5.717,4.283,7.475Zm2.843-.9-.254-.253L2.525,4.283l-.253-.254L4.029,2.272l.254.253L7.475,5.717l.253.254Zm2.657.9a1.271,1.271,0,0,1-1.757,0l-.183-.183L8.446,6.688l.183.183h0a1.241,1.241,0,0,1,0,1.757Z"
|
||||
transform="translate(0.007 -0.014)"
|
||||
/>
|
||||
<circle
|
||||
cx="0.5"
|
||||
cy="0.5"
|
||||
r="0.5"
|
||||
transform="translate(4.507 4.486)"
|
||||
/>
|
||||
<circle
|
||||
cx="0.5"
|
||||
cy="0.5"
|
||||
r="0.5"
|
||||
transform="translate(3.507 3.486)"
|
||||
/>
|
||||
<circle
|
||||
cx="0.5"
|
||||
cy="0.5"
|
||||
r="0.5"
|
||||
transform="translate(5.507 5.486)"
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HealIcon;
|
||||
47
portal-ui/src/icons/LicenseIcon.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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 { SvgIcon } from "@material-ui/core";
|
||||
|
||||
class LicenseIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 11">
|
||||
<path fill="#fff" d="M11 11H0V2h11v9zM2 8v1h7V8zm0-3v1h5V5z"></path>
|
||||
<g
|
||||
fill="#07274a"
|
||||
stroke="#fdfdfd"
|
||||
strokeWidth="0.5"
|
||||
transform="translate(7)"
|
||||
>
|
||||
<circle cx="3" cy="3" r="3" stroke="none"></circle>
|
||||
<circle cx="3" cy="3" r="2.75" fill="none"></circle>
|
||||
</g>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#fff"
|
||||
strokeWidth="0.5"
|
||||
d="M8.73 2.794l.954.953 1.471-1.471"
|
||||
></path>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LicenseIcon;
|
||||
47
portal-ui/src/icons/LogoutIcon.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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 { SvgIcon } from "@material-ui/core";
|
||||
class LogoutIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12.122 10.571">
|
||||
<g transform="translate(0 0.5)">
|
||||
<path
|
||||
style={{ fill: "none", stroke: "rgba(255,255,255,0.8)" }}
|
||||
d="M4816.27,3755.205v-2.939h8.539v9.571h-8.539v-2.932"
|
||||
transform="translate(-4813.187 -3752.266)"
|
||||
/>
|
||||
<path
|
||||
style={{ fill: "none", stroke: "rgba(255,255,255,0.8)" }}
|
||||
d="M4813.187,3757.052h8.081"
|
||||
transform="translate(-4813.187 -3752.266)"
|
||||
/>
|
||||
<path
|
||||
style={{ fill: "none", stroke: "rgba(255,255,255,0.8)" }}
|
||||
d="M4806.5,3756.511l2.265,2.063-2.265,2.063"
|
||||
transform="translate(-4800.808 -3753.863)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LogoutIcon;
|
||||
@@ -14,19 +14,20 @@
|
||||
// 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 IRemoteBucketsResponse {
|
||||
buckets: IRemoteBucket[];
|
||||
total: number;
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class RemoveIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 11.656 3.101">
|
||||
<path
|
||||
fill="#081c42"
|
||||
d="M-13157.172,1879.551h-11.656v-3.1h11.656v3.1Z"
|
||||
transform="translate(13168.828 -1876.449)"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IRemoteBucket {
|
||||
name: string;
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
sourceBucket: string;
|
||||
targetURL: string;
|
||||
targetBucket: string;
|
||||
remoteARN: string;
|
||||
status: string;
|
||||
service: string;
|
||||
}
|
||||
export default RemoveIcon;
|
||||
41
portal-ui/src/icons/UploadFile.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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 { SvgIcon } from "@material-ui/core";
|
||||
|
||||
class UploadFile extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 12.996">
|
||||
<g transform="translate(-63.686 -70.783)">
|
||||
<path
|
||||
className="a"
|
||||
d="M74.736,79.879v1.95h-9.1v-1.95h-1.95v3.9h13v-3.9Z"
|
||||
/>
|
||||
<path
|
||||
className="a"
|
||||
d="M69.211,80.533h1.95V73.861h1.525l-2.5-3.078-2.5,3.078h1.525Z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UploadFile;
|
||||
64
portal-ui/src/icons/UsageIcon.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
// 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 { SvgIcon } from "@material-ui/core";
|
||||
class UsageIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 16.172 17.187">
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="a"
|
||||
y1="0.5"
|
||||
x2="1"
|
||||
y2="0.5"
|
||||
gradientUnits="objectBoundingBox"
|
||||
>
|
||||
<stop offset="0.044" stopColor="#362585" />
|
||||
<stop offset="0.301" stopColor="#281b6f" />
|
||||
<stop offset="1" stopColor="#1e1560" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
style={{
|
||||
fill: "none",
|
||||
stroke: "#707070",
|
||||
strokeMiterlimit: 10,
|
||||
strokeWidth: "0.5px",
|
||||
}}
|
||||
d="M-4778.1,2239.582v6.425h6.425"
|
||||
transform="translate(4787.594 -2239.582)"
|
||||
/>
|
||||
<path
|
||||
fill={"#707070"}
|
||||
d="M-4784.238,2247.532v-.581c0-.027.009-.054.012-.081.039-.313.055-.632.121-.939a6.744,6.744,0,0,1,3.064-4.441,6.514,6.514,0,0,1,3.293-1.032,6.923,6.923,0,0,1,2.667.423,6.793,6.793,0,0,1,4.119,4.333,6.053,6.053,0,0,1,.279,1.337c.006.083.014.164.021.247v.86c-.011.131-.018.261-.032.392a6.494,6.494,0,0,1-.626,2.147,6.807,6.807,0,0,1-4.044,3.528,6.052,6.052,0,0,1-1.663.3,6.576,6.576,0,0,1-2.565-.325,6.73,6.73,0,0,1-3.947-3.451,6.627,6.627,0,0,1-.658-2.288C-4784.212,2247.816-4784.225,2247.674-4784.238,2247.532Zm13.025-.306c-.024-.309-.021-.661-.082-1a6.206,6.206,0,0,0-1.658-3.293,6.153,6.153,0,0,0-4.1-1.9,5.984,5.984,0,0,0-2.476.355,6.188,6.188,0,0,0-4.134,5.708,6.453,6.453,0,0,0,.228,1.881,6.127,6.127,0,0,0,1.984,3.052,6.046,6.046,0,0,0,3.806,1.445,6.043,6.043,0,0,0,1.235-.065,6.249,6.249,0,0,0,3.783-2.2,6.2,6.2,0,0,0,1.352-3.048C-4771.228,2247.863-4771.233,2247.563-4771.212,2247.226Z"
|
||||
transform="translate(4786.834 -2240.452)"
|
||||
/>
|
||||
<ellipse
|
||||
style={{ opacity: 0.1, fill: "url(#a)" }}
|
||||
cx="6.151"
|
||||
cy="6.151"
|
||||
rx="6.151"
|
||||
ry="6.151"
|
||||
transform="translate(0 4.886)"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UsageIcon;
|
||||
@@ -23,7 +23,6 @@ import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import api from "../../../../common/api";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import CheckboxWrapper from "../../Common/FormComponents/CheckboxWrapper/CheckboxWrapper";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import { factorForDropdown, getBytes } from "../../../../common/utils";
|
||||
import { AppState } from "../../../../store";
|
||||
@@ -38,6 +37,7 @@ import {
|
||||
} from "../actions";
|
||||
import { useDebounce } from "use-debounce";
|
||||
import { MakeBucketRequest } from "../types";
|
||||
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -52,7 +52,12 @@ const styles = (theme: Theme) =>
|
||||
alignItems: "center" as const,
|
||||
justifyContent: "flex-start" as const,
|
||||
},
|
||||
quotaSizeContainer: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
sizeFactorContainer: {
|
||||
flexGrow: 0,
|
||||
maxWidth: 80,
|
||||
marginLeft: 8,
|
||||
alignSelf: "flex-start" as const,
|
||||
},
|
||||
@@ -62,7 +67,7 @@ const styles = (theme: Theme) =>
|
||||
interface IAddBucketProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
closeModalAndRefresh: () => void;
|
||||
closeModalAndRefresh: (refresh: boolean) => void;
|
||||
addBucketName: typeof addBucketName;
|
||||
addBucketVersioned: typeof addBucketVersioned;
|
||||
addBucketQuota: typeof addBucketQuota;
|
||||
@@ -97,6 +102,7 @@ const AddBucket = ({
|
||||
const [bName, setBName] = useState<string>(bucketName);
|
||||
const [addLoading, setAddLoading] = useState<boolean>(false);
|
||||
const [addError, setAddError] = useState<string>("");
|
||||
const [sendEnabled, setSendEnabled] = useState<boolean>(false);
|
||||
|
||||
const addRecord = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
@@ -124,7 +130,7 @@ const AddBucket = ({
|
||||
.then((res) => {
|
||||
setAddLoading(false);
|
||||
setAddError("");
|
||||
closeModalAndRefresh();
|
||||
closeModalAndRefresh(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddLoading(false);
|
||||
@@ -135,9 +141,33 @@ const AddBucket = ({
|
||||
const [value] = useDebounce(bName, 1000);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("called");
|
||||
addBucketName(value);
|
||||
}, [value]);
|
||||
}, [value, addBucketName]);
|
||||
|
||||
const resetForm = () => {
|
||||
setBName("");
|
||||
addBucketVersioned(false);
|
||||
addBucketQuota(false);
|
||||
addBucketQuotaType("hard");
|
||||
addBucketQuotaSize("1");
|
||||
addBucketQuotaUnit("TiB");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let valid = false;
|
||||
|
||||
if (bName.trim() !== "") {
|
||||
valid = true;
|
||||
}
|
||||
|
||||
if (enableQuota && valid) {
|
||||
if (quotaSize.trim() === "" || parseInt(quotaSize) === 0) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
setSendEnabled(valid);
|
||||
}, [bName, versioned, quotaType, quotaSize, quotaUnit, enableQuota]);
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
@@ -145,7 +175,7 @@ const AddBucket = ({
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
setAddError("");
|
||||
closeModalAndRefresh();
|
||||
closeModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
@@ -182,7 +212,7 @@ const AddBucket = ({
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<CheckboxWrapper
|
||||
<FormSwitchWrapper
|
||||
value="versioned"
|
||||
id="versioned"
|
||||
name="versioned"
|
||||
@@ -190,11 +220,12 @@ const AddBucket = ({
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
addBucketVersioned(event.target.checked);
|
||||
}}
|
||||
label={"Turn On Versioning"}
|
||||
label={"Versioning"}
|
||||
indicatorLabel={"On"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<CheckboxWrapper
|
||||
<FormSwitchWrapper
|
||||
value="bucket_quota"
|
||||
id="bucket_quota"
|
||||
name="bucket_quota"
|
||||
@@ -203,6 +234,7 @@ const AddBucket = ({
|
||||
addBucketQuota(event.target.checked);
|
||||
}}
|
||||
label={"Enable Bucket Quota"}
|
||||
indicatorLabel={"On"}
|
||||
/>
|
||||
</Grid>
|
||||
{enableQuota && (
|
||||
@@ -224,7 +256,7 @@ const AddBucket = ({
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.multiContainer}>
|
||||
<div>
|
||||
<div className={classes.quotaSizeContainer}>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
id="quota_size"
|
||||
@@ -232,7 +264,7 @@ const AddBucket = ({
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
addBucketQuotaSize(e.target.value);
|
||||
}}
|
||||
label="Size"
|
||||
label="Quota"
|
||||
value={quotaSize}
|
||||
required
|
||||
min="1"
|
||||
@@ -240,7 +272,7 @@ const AddBucket = ({
|
||||
</div>
|
||||
<div className={classes.sizeFactorContainer}>
|
||||
<SelectWrapper
|
||||
label=""
|
||||
label=" "
|
||||
id="quota_unit"
|
||||
name="quota_unit"
|
||||
value={quotaUnit}
|
||||
@@ -258,11 +290,19 @@ const AddBucket = ({
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<button
|
||||
type="button"
|
||||
color="primary"
|
||||
className={classes.clearButton}
|
||||
onClick={resetForm}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addLoading}
|
||||
disabled={addLoading || !sendEnabled}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
|
||||
@@ -18,7 +18,6 @@ import React, { useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { Button } from "@material-ui/core";
|
||||
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";
|
||||
@@ -33,9 +32,12 @@ import { CreateIcon } from "../../../../icons";
|
||||
import { niceBytes } from "../../../../common/utils";
|
||||
import { AppState } from "../../../../store";
|
||||
import { connect } from "react-redux";
|
||||
import { logMessageReceived, logResetMessages } from "../../Logs/actions";
|
||||
import { addBucketOpen, addBucketReset } from "../actions";
|
||||
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../../Common/PageHeader/PageHeader";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -68,18 +70,8 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
@@ -100,7 +92,6 @@ const ListBuckets = ({
|
||||
const [totalRecords, setTotalRecords] = useState<number>(0);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [deleteError, setDeleteError] = useState<string>("");
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
@@ -143,10 +134,13 @@ const ListBuckets = ({
|
||||
}
|
||||
}, [loading, page, rowsPerPage]);
|
||||
|
||||
const closeAddModalAndRefresh = () => {
|
||||
const closeAddModalAndRefresh = (refresh: boolean) => {
|
||||
addBucketOpen(false);
|
||||
addBucketReset();
|
||||
setLoading(true);
|
||||
|
||||
if (refresh) {
|
||||
setLoading(true);
|
||||
}
|
||||
};
|
||||
|
||||
const closeDeleteModalAndRefresh = (refresh: boolean) => {
|
||||
@@ -191,28 +185,26 @@ const ListBuckets = ({
|
||||
return <Moment>{date}</Moment>;
|
||||
};
|
||||
|
||||
const filteredRecords = records
|
||||
.filter((b: Bucket) => {
|
||||
if (filterBuckets === "") {
|
||||
const filteredRecords = records.filter((b: Bucket) => {
|
||||
if (filterBuckets === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterBuckets) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterBuckets) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.slice(offset, offset + rowsPerPage);
|
||||
}
|
||||
});
|
||||
|
||||
const showInPage = filteredRecords.slice(offset, offset + rowsPerPage);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addBucketModalOpen && (
|
||||
<AddBucket
|
||||
open={addBucketModalOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
closeAddModalAndRefresh();
|
||||
}}
|
||||
closeModalAndRefresh={closeAddModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
{deleteOpen && (
|
||||
@@ -226,6 +218,7 @@ const ListBuckets = ({
|
||||
)}
|
||||
<PageHeader label={"Buckets"} />
|
||||
<Grid container>
|
||||
{error !== "" && <span className={classes.error}>{error}</span>}
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
@@ -276,13 +269,13 @@ const ListBuckets = ({
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
records={showInPage}
|
||||
entityName="Buckets"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
count: filteredRecords.length,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// 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 ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper";
|
||||
import { Button, Grid, LinearProgress } from "@material-ui/core";
|
||||
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { connect } from "react-redux";
|
||||
import { createFolder } from "../../../../ObjectBrowser/actions";
|
||||
|
||||
interface ICreateFolder {
|
||||
classes: any;
|
||||
modalOpen: boolean;
|
||||
folderName: string;
|
||||
createFolder: (newFolder: string) => any;
|
||||
onClose: () => any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
pathLabel: {
|
||||
marginTop: 0,
|
||||
marginBottom: 32,
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const CreateFolderModal = ({
|
||||
modalOpen,
|
||||
folderName,
|
||||
onClose,
|
||||
createFolder,
|
||||
classes,
|
||||
}: ICreateFolder) => {
|
||||
const [pathUrl, setPathUrl] = useState("");
|
||||
|
||||
const resetForm = () => {
|
||||
setPathUrl("");
|
||||
};
|
||||
|
||||
const createProcess = () => {
|
||||
createFolder(pathUrl);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const folderTruncated = folderName.split("/").slice(2).join("/");
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ModalWrapper modalOpen={modalOpen} title="Add Folder" onClose={onClose}>
|
||||
<Grid container>
|
||||
<h3 className={classes.pathLabel}>
|
||||
Current Path: {folderTruncated}/
|
||||
</h3>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
value={pathUrl}
|
||||
label={"Folder Path"}
|
||||
id={"folderPath"}
|
||||
name={"folderPath"}
|
||||
placeholder={"Enter Folder Path"}
|
||||
onChange={(e) => {
|
||||
setPathUrl(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<button
|
||||
type="button"
|
||||
color="primary"
|
||||
className={classes.clearButton}
|
||||
onClick={resetForm}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={pathUrl.trim() === ""}
|
||||
onClick={createProcess}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ModalWrapper>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
createFolder,
|
||||
};
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
|
||||
export default connector(withStyles(styles)(CreateFolderModal));
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
LinearProgress,
|
||||
} from "@material-ui/core";
|
||||
import api from "../../../../../../common/api";
|
||||
import { BucketObjectsList } from "../ListObjects/types";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -68,13 +67,14 @@ class DeleteObject extends React.Component<
|
||||
if (selectedObject.endsWith("/")) {
|
||||
recursive = true;
|
||||
}
|
||||
|
||||
this.setState({ deleteLoading: true }, () => {
|
||||
api
|
||||
.invoke(
|
||||
"DELETE",
|
||||
`/api/v1/buckets/${selectedBucket}/objects?path=${selectedObject}&recursive=${recursive}`
|
||||
)
|
||||
.then((res: BucketObjectsList) => {
|
||||
.then((res: any) => {
|
||||
this.setState(
|
||||
{
|
||||
deleteLoading: false,
|
||||
@@ -142,10 +142,12 @@ class DeleteObject extends React.Component<
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.removeRecord();
|
||||
this.setState({ deleteError: "" }, () => {
|
||||
this.removeRecord();
|
||||
});
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
|
||||
@@ -14,23 +14,49 @@
|
||||
// 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { Button } from "@material-ui/core";
|
||||
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 { BucketObject, BucketObjectsList } from "./types";
|
||||
import api from "../../../../../../common/api";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import TableWrapper from "../../../../Common/TableWrapper/TableWrapper";
|
||||
import { MinTablePaginationActions } from "../../../../../../common/MinTablePaginationActions";
|
||||
import { CreateIcon } from "../../.././../../../icons";
|
||||
import { niceBytes } from "../../../../../../common/utils";
|
||||
import Moment from "react-moment";
|
||||
import DeleteObject from "./DeleteObject";
|
||||
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
objectBrowserCommon,
|
||||
searchField,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../../../../Common/PageHeader/PageHeader";
|
||||
import storage from "local-storage-fallback";
|
||||
import { isNullOrUndefined } from "util";
|
||||
import { Button, Input } from "@material-ui/core";
|
||||
import * as reactMoment from "react-moment";
|
||||
import { CreateIcon } from "../../../../../../icons";
|
||||
import Snackbar from "@material-ui/core/Snackbar";
|
||||
import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
|
||||
import get from "lodash/get";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { addRoute, setAllRoutes } from "../../../../ObjectBrowser/actions";
|
||||
import { connect } from "react-redux";
|
||||
import { ObjectBrowserState, Route } from "../../../../ObjectBrowser/reducers";
|
||||
import CreateFolderModal from "./CreateFolderModal";
|
||||
import { create } from "domain";
|
||||
import UploadFile from "../../../../../../icons/UploadFile";
|
||||
|
||||
const commonIcon = {
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "center center",
|
||||
width: 16,
|
||||
height: 40,
|
||||
marginRight: 10,
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
@@ -61,142 +87,364 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
fileName: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
iconFolder: {
|
||||
backgroundImage: "url(/images/ob_folder_clear.svg)",
|
||||
...commonIcon,
|
||||
},
|
||||
iconFile: {
|
||||
backgroundImage: "url(/images/ob_file_clear.svg)",
|
||||
...commonIcon,
|
||||
},
|
||||
buttonsContainer: {
|
||||
"& .MuiButtonBase-root": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
"@global": {
|
||||
".rowElementRaw:hover .iconFileElm": {
|
||||
backgroundImage: "url(/images/ob_file_filled.svg)",
|
||||
},
|
||||
".rowElementRaw:hover .iconFolderElm": {
|
||||
backgroundImage: "url(/images/ob_folder_filled.svg)",
|
||||
},
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...objectBrowserCommon,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
interface IListObjectsProps {
|
||||
classes: any;
|
||||
match: any;
|
||||
addRoute: (param1: string, param2: string) => any;
|
||||
setAllRoutes: (path: string) => any;
|
||||
routesList: Route[];
|
||||
}
|
||||
|
||||
interface IListObjectsState {
|
||||
records: BucketObject[];
|
||||
totalRecords: number;
|
||||
loading: boolean;
|
||||
error: string;
|
||||
deleteOpen: boolean;
|
||||
deleteError: string;
|
||||
selectedObject: string;
|
||||
selectedBucket: string;
|
||||
filterObjects: string;
|
||||
interface ObjectBrowserReducer {
|
||||
objectBrowser: ObjectBrowserState;
|
||||
}
|
||||
|
||||
class ListObjects extends React.Component<
|
||||
IListObjectsProps,
|
||||
IListObjectsState
|
||||
> {
|
||||
state: IListObjectsState = {
|
||||
records: [],
|
||||
totalRecords: 0,
|
||||
loading: false,
|
||||
error: "",
|
||||
deleteOpen: false,
|
||||
deleteError: "",
|
||||
selectedObject: "",
|
||||
selectedBucket: "",
|
||||
filterObjects: "",
|
||||
const ListObjects = ({
|
||||
classes,
|
||||
match,
|
||||
addRoute,
|
||||
setAllRoutes,
|
||||
routesList,
|
||||
}: IListObjectsProps) => {
|
||||
const [records, setRecords] = useState<BucketObject[]>([]);
|
||||
const [totalRecords, setTotalRecords] = useState<number>(0);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
|
||||
const [deleteError, setDeleteError] = useState<string>("");
|
||||
const [selectedObject, setSelectedObject] = useState<string>("");
|
||||
const [selectedBucket, setSelectedBucket] = useState<string>("");
|
||||
const [filterObjects, setFilterObjects] = useState<string>("");
|
||||
const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
|
||||
const [snackBarMessage, setSnackbarMessage] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
const bucketName = match.params["bucket"];
|
||||
const internalPaths = match.params[0];
|
||||
|
||||
let extraPath = "";
|
||||
if (internalPaths) {
|
||||
extraPath = `?prefix=${internalPaths}/`;
|
||||
}
|
||||
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/objects${extraPath}`)
|
||||
.then((res: BucketObjectsList) => {
|
||||
setLoading(false);
|
||||
setSelectedBucket(bucketName);
|
||||
setRecords(res.objects || []);
|
||||
setTotalRecords(!res.objects ? 0 : res.total);
|
||||
setError("");
|
||||
// TODO:
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
})
|
||||
.catch((err: any) => {
|
||||
setLoading(false);
|
||||
setError(err);
|
||||
});
|
||||
}, [loading, match]);
|
||||
|
||||
useEffect(() => {
|
||||
const url = get(match, "url", "/object-browser");
|
||||
if (url !== routesList[routesList.length - 1].route) {
|
||||
setAllRoutes(url);
|
||||
}
|
||||
}, [match, routesList, setAllRoutes]);
|
||||
|
||||
const closeDeleteModalAndRefresh = (refresh: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
if (refresh) {
|
||||
setLoading(true);
|
||||
}
|
||||
};
|
||||
|
||||
fetchRecords = () => {
|
||||
this.setState({ loading: true }, () => {
|
||||
const { match } = this.props;
|
||||
const bucketName = match.params["bucket"];
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/objects`)
|
||||
.then((res: BucketObjectsList) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
selectedBucket: bucketName,
|
||||
records: res.objects || [],
|
||||
totalRecords: !res.objects ? 0 : res.total,
|
||||
error: "",
|
||||
});
|
||||
// TODO:
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.setState({ loading: false, error: err });
|
||||
const closeAddFolderModal = () => {
|
||||
setCreateFolderOpen(false);
|
||||
};
|
||||
|
||||
const showSnackBarMessage = (text: string) => {
|
||||
setSnackbarMessage(text);
|
||||
setOpenSnackbar(true);
|
||||
};
|
||||
|
||||
const closeSnackBar = () => {
|
||||
setSnackbarMessage("");
|
||||
setOpenSnackbar(false);
|
||||
};
|
||||
|
||||
const upload = (e: any, bucketName: string, path: string) => {
|
||||
if (isNullOrUndefined(e) || isNullOrUndefined(e.target)) {
|
||||
return;
|
||||
}
|
||||
const token: string = storage.getItem("token")!;
|
||||
e.preventDefault();
|
||||
let file = e.target.files[0];
|
||||
const fileName = file.name;
|
||||
|
||||
const objectName = `${path}${fileName}`;
|
||||
let uploadUrl = `/api/v1/buckets/${bucketName}/objects/upload?prefix=${objectName}`;
|
||||
let xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open("POST", uploadUrl, true);
|
||||
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
|
||||
|
||||
xhr.withCredentials = false;
|
||||
xhr.onload = function (event) {
|
||||
// TODO: handle status
|
||||
if (xhr.status === 401 || xhr.status === 403) {
|
||||
showSnackBarMessage("An error occurred while uploading the file.");
|
||||
}
|
||||
if (xhr.status === 500) {
|
||||
showSnackBarMessage("An error occurred while uploading the file.");
|
||||
}
|
||||
if (xhr.status === 200) {
|
||||
showSnackBarMessage("Object uploaded successfully.");
|
||||
setLoading(true);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.upload.addEventListener("error", (event) => {
|
||||
// TODO: handle error
|
||||
showSnackBarMessage("An error occurred while uploading the file.");
|
||||
});
|
||||
|
||||
xhr.upload.addEventListener("progress", (event) => {
|
||||
// TODO: handle progress with event.loaded, event.total
|
||||
});
|
||||
|
||||
xhr.onerror = () => {
|
||||
showSnackBarMessage("An error occurred while uploading the file.");
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
const blobFile = new Blob([file]);
|
||||
|
||||
formData.append("upfile", blobFile);
|
||||
xhr.send(formData);
|
||||
e.target.value = null;
|
||||
};
|
||||
|
||||
const download = (bucketName: string, objectName: string) => {
|
||||
const anchor = document.createElement("a");
|
||||
document.body.appendChild(anchor);
|
||||
const token: string = storage.getItem("token")!;
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects/download?prefix=${objectName}`,
|
||||
true
|
||||
);
|
||||
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
|
||||
xhr.responseType = "blob";
|
||||
|
||||
xhr.onload = function (e) {
|
||||
if (this.status === 200) {
|
||||
const blob = new Blob([this.response], {
|
||||
type: "octet/stream",
|
||||
});
|
||||
});
|
||||
const blobUrl = window.URL.createObjectURL(blob);
|
||||
|
||||
anchor.href = blobUrl;
|
||||
anchor.download = objectName;
|
||||
|
||||
anchor.click();
|
||||
window.URL.revokeObjectURL(blobUrl);
|
||||
anchor.remove();
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.fetchRecords();
|
||||
}
|
||||
const displayParsedDate = (date: string) => {
|
||||
return <reactMoment.default>{date}</reactMoment.default>;
|
||||
};
|
||||
|
||||
closeDeleteModalAndRefresh(refresh: boolean) {
|
||||
this.setState({ deleteOpen: false }, () => {
|
||||
if (refresh) {
|
||||
this.fetchRecords();
|
||||
}
|
||||
});
|
||||
}
|
||||
const confirmDeleteObject = (object: string) => {
|
||||
setDeleteOpen(true);
|
||||
setSelectedObject(object);
|
||||
};
|
||||
|
||||
bucketFilter(): void {}
|
||||
const downloadObject = (object: string) => {
|
||||
download(selectedBucket, object);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const {
|
||||
records,
|
||||
loading,
|
||||
selectedObject,
|
||||
selectedBucket,
|
||||
deleteOpen,
|
||||
filterObjects,
|
||||
} = this.state;
|
||||
const displayParsedDate = (date: string) => {
|
||||
return <Moment>{date}</Moment>;
|
||||
};
|
||||
const openPath = (idElement: string) => {
|
||||
const currentPath = get(match, "url", "/object-browser");
|
||||
|
||||
const confirmDeleteObject = (object: string) => {
|
||||
this.setState({ deleteOpen: true, selectedObject: object });
|
||||
};
|
||||
// Element is a folder, we redirect to it
|
||||
if (idElement.endsWith("/")) {
|
||||
const idElementClean = idElement
|
||||
.substr(0, idElement.length - 1)
|
||||
.split("/");
|
||||
const lastIndex = idElementClean.length - 1;
|
||||
const newPath = `${currentPath}/${idElementClean[lastIndex]}`;
|
||||
|
||||
const tableActions = [
|
||||
{ type: "delete", onClick: confirmDeleteObject, sendOnlyId: true },
|
||||
];
|
||||
addRoute(newPath, idElementClean[lastIndex]);
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredRecords = records.filter((b: BucketObject) => {
|
||||
if (filterObjects === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterObjects) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
// Element is a file. we open details here
|
||||
// TODO: Add details open function here.
|
||||
//console.log("object", idElementClean);
|
||||
};
|
||||
|
||||
const uploadObject = (e: any): void => {
|
||||
// Handle of deeper routes.
|
||||
const currentPath = routesList[routesList.length - 1].route;
|
||||
const splitPaths = currentPath
|
||||
.split("/")
|
||||
.filter((item) => item.trim() !== "");
|
||||
|
||||
let path = "";
|
||||
|
||||
if (splitPaths.length > 2) {
|
||||
path = `${splitPaths.slice(2).join("/")}/`;
|
||||
}
|
||||
|
||||
let file = e.target.files[0];
|
||||
showSnackBarMessage(`Uploading: ${file.name}`);
|
||||
upload(e, selectedBucket, path);
|
||||
};
|
||||
|
||||
const snackBarAction = (
|
||||
<Button
|
||||
color="secondary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
closeSnackBar();
|
||||
}}
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
);
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: openPath, sendOnlyId: true },
|
||||
{ type: "download", onClick: downloadObject, sendOnlyId: true },
|
||||
{ type: "delete", onClick: confirmDeleteObject, sendOnlyId: true },
|
||||
];
|
||||
|
||||
const displayName = (element: string) => {
|
||||
let elementString = element;
|
||||
let icon = `${classes.iconFile} iconFileElm`;
|
||||
// Element is a folder
|
||||
if (element.endsWith("/")) {
|
||||
icon = `${classes.iconFolder} iconFolderElm`;
|
||||
elementString = element.substr(0, element.length - 1);
|
||||
}
|
||||
|
||||
const splitItem = elementString.split("/");
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{deleteOpen && (
|
||||
<DeleteObject
|
||||
deleteOpen={deleteOpen}
|
||||
selectedBucket={selectedBucket}
|
||||
selectedObject={selectedObject}
|
||||
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
||||
this.closeDeleteModalAndRefresh(refresh);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Objects</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
<div className={classes.fileName}>
|
||||
<div className={icon} />
|
||||
<span>{splitItem[splitItem.length - 1]}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter((b: BucketObject) => {
|
||||
if (filterObjects === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterObjects) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{deleteOpen && (
|
||||
<DeleteObject
|
||||
deleteOpen={deleteOpen}
|
||||
selectedBucket={selectedBucket}
|
||||
selectedObject={selectedObject}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
{createFolderOpen && (
|
||||
<CreateFolderModal
|
||||
modalOpen={createFolderOpen}
|
||||
folderName={routesList[routesList.length - 1].route}
|
||||
onClose={closeAddFolderModal}
|
||||
/>
|
||||
)}
|
||||
<Snackbar
|
||||
open={openSnackbar}
|
||||
message={snackBarMessage}
|
||||
action={snackBarAction}
|
||||
/>
|
||||
<PageHeader label="Object Browser" />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid item xs={12} className={classes.obTitleSection}>
|
||||
<div>
|
||||
<BrowserBreadcrumbs />
|
||||
</div>
|
||||
<div className={classes.buttonsContainer}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
component="label"
|
||||
onClick={() => {
|
||||
setCreateFolderOpen(true);
|
||||
}}
|
||||
>
|
||||
Create Folder
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<UploadFile />}
|
||||
component="label"
|
||||
>
|
||||
File
|
||||
<Input
|
||||
type="file"
|
||||
onChange={(e) => uploadObject(e)}
|
||||
id="file-input"
|
||||
style={{ display: "none" }}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
@@ -205,9 +453,7 @@ class ListObjects extends React.Component<
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
this.setState({
|
||||
filterObjects: val.target.value,
|
||||
});
|
||||
setFilterObjects(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
@@ -226,7 +472,11 @@ class ListObjects extends React.Component<
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: displayName,
|
||||
},
|
||||
{
|
||||
label: "Last Modified",
|
||||
elementKey: "last_modified",
|
||||
@@ -245,9 +495,20 @@ class ListObjects extends React.Component<
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ListObjects);
|
||||
const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({
|
||||
routesList: get(objectBrowser, "routesList", []),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
addRoute,
|
||||
setAllRoutes,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export default withRouter(connector(withStyles(styles)(ListObjects)));
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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, { useState } from "react";
|
||||
import get from "lodash/get";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
@@ -22,14 +22,9 @@ import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import api from "../../../../common/api";
|
||||
import {
|
||||
IRemoteBucket,
|
||||
IRemoteBucketsResponse,
|
||||
} from "../../RemoteBuckets/types";
|
||||
import RemoteBucketsList from "../../RemoteBuckets/RemoteBuckets";
|
||||
import { IRemoteBucket } from "../types";
|
||||
|
||||
interface IReplicationModal {
|
||||
open: boolean;
|
||||
@@ -64,42 +59,61 @@ const AddReplicationModal = ({
|
||||
bucketName,
|
||||
}: IReplicationModal) => {
|
||||
const [addError, setAddError] = useState("");
|
||||
const [loadingForm, setLoadingForm] = useState(true);
|
||||
const [addLoading, setAddLoading] = useState(false);
|
||||
const [remoteURL, setRemoteURL] = useState("");
|
||||
const [source, setSource] = useState("");
|
||||
const [target, setTarget] = useState("");
|
||||
const [ARN, setARN] = useState("");
|
||||
const [arnValues, setARNValues] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (addLoading) {
|
||||
addRecord();
|
||||
}
|
||||
}, [addLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingForm) {
|
||||
getARNValues();
|
||||
}
|
||||
});
|
||||
const [accessKey, setAccessKey] = useState("");
|
||||
const [secretKey, setSecretKey] = useState("");
|
||||
const [targetURL, setTargetURL] = useState("");
|
||||
const [targetBucket, setTargetBucket] = useState("");
|
||||
const [region, setRegion] = useState("");
|
||||
|
||||
const addRecord = () => {
|
||||
const replicationInfo = {
|
||||
destination_bucket: target,
|
||||
arn: ARN,
|
||||
const remoteBucketInfo = {
|
||||
accessKey: accessKey,
|
||||
secretKey: secretKey,
|
||||
sourceBucket: bucketName,
|
||||
targetURL: targetURL,
|
||||
targetBucket: targetBucket,
|
||||
region: region,
|
||||
};
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"POST",
|
||||
`/api/v1/buckets/${bucketName}/replication`,
|
||||
replicationInfo
|
||||
)
|
||||
.then((res) => {
|
||||
setAddLoading(false);
|
||||
setAddError("");
|
||||
closeModalAndRefresh();
|
||||
.invoke("POST", "/api/v1/remote-buckets", remoteBucketInfo)
|
||||
.then(() => {
|
||||
api
|
||||
.invoke("GET", "/api/v1/remote-buckets")
|
||||
.then((res: any) => {
|
||||
const remoteBuckets = get(res, "buckets", []);
|
||||
const remoteBucket = remoteBuckets.find(
|
||||
(itemRemote: IRemoteBucket) => {
|
||||
return itemRemote.sourceBucket === bucketName;
|
||||
}
|
||||
);
|
||||
if (remoteBucket && remoteBucket.remoteARN) {
|
||||
const remoteARN = remoteBucket.remoteARN;
|
||||
const replicationInfo = {
|
||||
destination_bucket: targetBucket,
|
||||
arn: remoteARN,
|
||||
};
|
||||
api
|
||||
.invoke(
|
||||
"POST",
|
||||
`/api/v1/buckets/${bucketName}/replication`,
|
||||
replicationInfo
|
||||
)
|
||||
.then(() => {
|
||||
setAddLoading(false);
|
||||
setAddError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddLoading(false);
|
||||
setAddError(err);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddError(err);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddLoading(false);
|
||||
@@ -107,23 +121,6 @@ const AddReplicationModal = ({
|
||||
});
|
||||
};
|
||||
|
||||
const getARNValues = () => {
|
||||
api
|
||||
.invoke("GET", "/api/v1/remote-buckets")
|
||||
.then((res: any) => {
|
||||
const remoteBuckets = get(res, "buckets", []);
|
||||
|
||||
const remoteARNS = remoteBuckets.map((itemRemote: IRemoteBucket) => {
|
||||
return { label: itemRemote.remoteARN, value: itemRemote.remoteARN };
|
||||
});
|
||||
setLoadingForm(false);
|
||||
setARNValues(remoteARNS);
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoadingForm(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
@@ -139,71 +136,96 @@ const AddReplicationModal = ({
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setAddLoading(true);
|
||||
addRecord();
|
||||
}}
|
||||
>
|
||||
{loadingForm && (
|
||||
<Grid container>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="target"
|
||||
name="target"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAccessKey(e.target.value);
|
||||
}}
|
||||
label="Access Key"
|
||||
value={accessKey}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="target"
|
||||
name="target"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSecretKey(e.target.value);
|
||||
}}
|
||||
label="Secret Key"
|
||||
value={secretKey}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="target"
|
||||
name="target"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTargetURL(e.target.value);
|
||||
}}
|
||||
placeholder="https://play.min.io:9000"
|
||||
label="Target URL"
|
||||
value={targetURL}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="target"
|
||||
name="target"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTargetBucket(e.target.value);
|
||||
}}
|
||||
label="Target Bucket"
|
||||
value={targetBucket}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="target"
|
||||
name="target"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRegion(e.target.value);
|
||||
}}
|
||||
label="Region"
|
||||
value={region}
|
||||
/>
|
||||
</Grid>
|
||||
</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>
|
||||
)}
|
||||
{!loadingForm && (
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="target"
|
||||
name="target"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTarget(e.target.value);
|
||||
}}
|
||||
label="Destination Bucket"
|
||||
value={target}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
setARN(e.target.value as string);
|
||||
}}
|
||||
id="arn"
|
||||
name="arn"
|
||||
label={"ARN"}
|
||||
value={ARN}
|
||||
options={arnValues}
|
||||
/>
|
||||
</Grid>
|
||||
</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>
|
||||
)}
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// 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 Typography from "@material-ui/core/Typography";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import api from "../../../../common/api";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
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({
|
||||
errorBlock: {
|
||||
color: "red",
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
interface IEnableBucketEncryptionProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
selectedBucket: string;
|
||||
closeModalAndRefresh: () => void;
|
||||
}
|
||||
|
||||
interface IEnableBucketEncryptionState {
|
||||
loading: boolean;
|
||||
encryptionError: string;
|
||||
kmsKeyID: string;
|
||||
suffix: string;
|
||||
encryptionType: string;
|
||||
}
|
||||
|
||||
class EnableBucketEncryption extends React.Component<
|
||||
IEnableBucketEncryptionProps,
|
||||
IEnableBucketEncryptionState
|
||||
> {
|
||||
state: IEnableBucketEncryptionState = {
|
||||
loading: false,
|
||||
encryptionError: "",
|
||||
kmsKeyID: "",
|
||||
suffix: "",
|
||||
encryptionType: "sse-s3",
|
||||
};
|
||||
|
||||
enableBucketEncryption(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
const { kmsKeyID, loading, encryptionType } = this.state;
|
||||
const { selectedBucket } = this.props;
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
api
|
||||
.invoke("POST", `/api/v1/buckets/${selectedBucket}/encryption/enable`, {
|
||||
encType: encryptionType,
|
||||
kmsKeyID: kmsKeyID,
|
||||
})
|
||||
.then(() => {
|
||||
this.setState(
|
||||
{
|
||||
loading: false,
|
||||
encryptionError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.setState({ encryptionError: err });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, open } = this.props;
|
||||
const { loading, encryptionError, kmsKeyID, encryptionType } = this.state;
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
this.setState({ encryptionError: "" }, () => {
|
||||
this.props.closeModalAndRefresh();
|
||||
});
|
||||
}}
|
||||
title="Enable Bucket Encryption"
|
||||
>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
this.enableBucketEncryption(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
{encryptionError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{encryptionError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ encryptionType: e.target.value as string });
|
||||
}}
|
||||
id="select-encryption-type"
|
||||
name="select-encryption-type"
|
||||
label={"Encryption Type"}
|
||||
value={encryptionType}
|
||||
options={[
|
||||
{
|
||||
label: "SSE-S3",
|
||||
value: "sse-s3",
|
||||
},
|
||||
{
|
||||
label: "SSE-KMS",
|
||||
value: "sse-kms",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
{encryptionType === "sse-kms" && (
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="kms-key-id"
|
||||
name="kms-key-id"
|
||||
label="KMS Key ID"
|
||||
value={kmsKeyID}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ kmsKeyID: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{loading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(EnableBucketEncryption);
|
||||
@@ -94,7 +94,7 @@ class SetAccessPolicy extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, open, actualPolicy } = this.props;
|
||||
const { classes, open } = this.props;
|
||||
const { addLoading, addError, accessPolicy } = this.state;
|
||||
return (
|
||||
<ModalWrapper
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
BucketEvent,
|
||||
BucketEventList,
|
||||
BucketInfo,
|
||||
BucketEncryptionInfo,
|
||||
BucketList,
|
||||
BucketReplication,
|
||||
BucketReplicationDestination,
|
||||
@@ -34,8 +35,7 @@ import {
|
||||
BucketReplicationRuleDeleteMarker,
|
||||
BucketVersioning,
|
||||
} from "../types";
|
||||
import { Box, Button } from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Button } from "@material-ui/core";
|
||||
import SetAccessPolicy from "./SetAccessPolicy";
|
||||
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
|
||||
import { CreateIcon } from "../../../../icons";
|
||||
@@ -46,6 +46,8 @@ import { niceBytes } from "../../../../common/utils";
|
||||
import AddReplicationModal from "./AddReplicationModal";
|
||||
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../../Common/PageHeader/PageHeader";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import EnableBucketEncryption from "./EnableBucketEncryption";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -191,6 +193,7 @@ interface IViewBucketState {
|
||||
rowsPerPage: number;
|
||||
curTab: number;
|
||||
addScreenOpen: boolean;
|
||||
enableEncryptionScreenOpen: boolean;
|
||||
deleteOpen: boolean;
|
||||
selectedBucket: string;
|
||||
selectedEvent: BucketEvent | null;
|
||||
@@ -199,6 +202,7 @@ interface IViewBucketState {
|
||||
replicationSet: boolean;
|
||||
openSetReplication: boolean;
|
||||
isVersioned: boolean;
|
||||
encryptionEnabled: boolean;
|
||||
}
|
||||
|
||||
class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
@@ -218,6 +222,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
curTab: 0,
|
||||
rowsPerPage: 10,
|
||||
addScreenOpen: false,
|
||||
enableEncryptionScreenOpen: false,
|
||||
deleteOpen: false,
|
||||
selectedBucket: "",
|
||||
selectedEvent: null,
|
||||
@@ -226,6 +231,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
replicationSet: false,
|
||||
openSetReplication: false,
|
||||
isVersioned: false,
|
||||
encryptionEnabled: false,
|
||||
};
|
||||
|
||||
fetchEvents() {
|
||||
@@ -325,6 +331,21 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
});
|
||||
}
|
||||
|
||||
fetchBucketEncryptionInfo() {
|
||||
const { match } = this.props;
|
||||
const bucketName = match.params["bucketName"];
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/encryption/info`)
|
||||
.then((res: BucketEncryptionInfo) => {
|
||||
if (res.algorithm) {
|
||||
this.setState({ encryptionEnabled: true });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
closeAddModalAndRefresh() {
|
||||
this.setState({ setAccessPolicyScreenOpen: false }, () => {
|
||||
this.loadInfo();
|
||||
@@ -343,6 +364,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
this.loadInfo();
|
||||
this.fetchEvents();
|
||||
this.fetchBucketsSize();
|
||||
this.fetchBucketEncryptionInfo();
|
||||
}
|
||||
|
||||
bucketFilter(): void {}
|
||||
@@ -360,14 +382,15 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
addScreenOpen,
|
||||
enableEncryptionScreenOpen,
|
||||
selectedEvent,
|
||||
bucketSize,
|
||||
loadingSize,
|
||||
replicationSet,
|
||||
openSetReplication,
|
||||
isVersioned,
|
||||
replicationRules,
|
||||
curTab,
|
||||
encryptionEnabled,
|
||||
} = this.state;
|
||||
|
||||
const offset = page * rowsPerPage;
|
||||
@@ -415,6 +438,23 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
this.setState({ openSetReplication: open });
|
||||
};
|
||||
|
||||
const handleEncryptionCheckbox = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
if (event.target.checked) {
|
||||
this.setState({ enableEncryptionScreenOpen: true });
|
||||
} else {
|
||||
api
|
||||
.invoke("POST", `/api/v1/buckets/${bucketName}/encryption/disable`)
|
||||
.then(() => {
|
||||
this.setState({ encryptionEnabled: false });
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.setState({ error: err });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const tableActions = [{ type: "delete", onClick: confirmDeleteEvent }];
|
||||
|
||||
const filteredRecords = records.slice(offset, offset + rowsPerPage);
|
||||
@@ -432,6 +472,16 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{enableEncryptionScreenOpen && (
|
||||
<EnableBucketEncryption
|
||||
open={enableEncryptionScreenOpen}
|
||||
selectedBucket={bucketName}
|
||||
closeModalAndRefresh={() => {
|
||||
this.setState({ enableEncryptionScreenOpen: false });
|
||||
this.fetchBucketEncryptionInfo();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{setAccessPolicyScreenOpen && (
|
||||
<SetAccessPolicy
|
||||
bucketName={bucketName}
|
||||
@@ -490,6 +540,17 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
</div>
|
||||
<div>Versioning:</div>
|
||||
<div>{isVersioned ? "Yes" : "No"} </div>
|
||||
<div>Encryption:</div>
|
||||
<div>
|
||||
<Checkbox
|
||||
color="primary"
|
||||
inputProps={{
|
||||
"aria-label": "secondary checkbox",
|
||||
}}
|
||||
onChange={(event) => handleEncryptionCheckbox(event)}
|
||||
checked={encryptionEnabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
@@ -515,51 +576,53 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Tabs
|
||||
value={curTab}
|
||||
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
|
||||
this.setState({ curTab: newValue });
|
||||
}}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
aria-label="cluster-tabs"
|
||||
>
|
||||
<Tab label="Events" {...a11yProps(0)} />
|
||||
<Tab label="Replication" {...a11yProps(1)} />
|
||||
</Tabs>
|
||||
</Grid>
|
||||
<Grid item xs={6} className={classes.actionsTray}>
|
||||
{curTab === 0 && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
size="medium"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
});
|
||||
<Grid container item xs={12}>
|
||||
<Grid item xs={6}>
|
||||
<Tabs
|
||||
value={curTab}
|
||||
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
|
||||
this.setState({ curTab: newValue });
|
||||
}}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
aria-label="cluster-tabs"
|
||||
>
|
||||
Subscribe to Event
|
||||
</Button>
|
||||
)}
|
||||
{curTab === 1 && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
size="medium"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
openSetReplication: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Add Replication Rule
|
||||
</Button>
|
||||
)}
|
||||
<Tab label="Events" {...a11yProps(0)} />
|
||||
<Tab label="Replication" {...a11yProps(1)} />
|
||||
</Tabs>
|
||||
</Grid>
|
||||
<Grid item xs={6} className={classes.actionsTray}>
|
||||
{curTab === 0 && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
size="medium"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Subscribe to Event
|
||||
</Button>
|
||||
)}
|
||||
{curTab === 1 && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
size="medium"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
openSetReplication: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Add Replication Rule
|
||||
</Button>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TabPanel index={0} value={curTab}>
|
||||
|
||||
@@ -19,6 +19,11 @@ export interface Bucket {
|
||||
creation_date: Date;
|
||||
}
|
||||
|
||||
export interface BucketEncryptionInfo {
|
||||
algorithm: string;
|
||||
kmsMasterKeyID: string;
|
||||
}
|
||||
|
||||
export interface BucketInfo {
|
||||
name: string;
|
||||
access: string;
|
||||
@@ -79,3 +84,15 @@ export interface MakeBucketRequest {
|
||||
versioning: boolean;
|
||||
quota?: QuotaRequest;
|
||||
}
|
||||
|
||||
export interface IRemoteBucket {
|
||||
name: string;
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
sourceBucket: string;
|
||||
targetURL: string;
|
||||
targetBucket: string;
|
||||
remoteARN: string;
|
||||
status: string;
|
||||
service: string;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
// 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
|
||||
@@ -13,21 +13,32 @@
|
||||
//
|
||||
// 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 } from "react";
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
createRef,
|
||||
useLayoutEffect,
|
||||
ChangeEvent,
|
||||
useRef,
|
||||
} from "react";
|
||||
import get from "lodash/get";
|
||||
import debounce from "lodash/debounce";
|
||||
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 HelpIcon from "@material-ui/icons/Help";
|
||||
import { InputLabel, Tooltip } from "@material-ui/core";
|
||||
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
import InputBoxWrapper from "../InputBoxWrapper/InputBoxWrapper";
|
||||
import AddIcon from "../../../../../icons/AddIcon";
|
||||
|
||||
interface ICSVMultiSelector {
|
||||
elements: string;
|
||||
name: string;
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
commonPlaceholder?: string;
|
||||
classes: any;
|
||||
withBorder?: boolean;
|
||||
onChange: (elements: string) => void;
|
||||
}
|
||||
|
||||
@@ -35,16 +46,13 @@ const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
inputLabel: {
|
||||
...fieldBasic.inputLabel,
|
||||
width: 116,
|
||||
},
|
||||
inputContainer: {
|
||||
inputWithBorder: {
|
||||
border: "1px solid #EAEAEA",
|
||||
padding: 15,
|
||||
height: 150,
|
||||
overflowY: "auto",
|
||||
padding: 15,
|
||||
position: "relative",
|
||||
border: "1px solid #c4c4c4",
|
||||
marginTop: 15,
|
||||
},
|
||||
labelContainer: {
|
||||
display: "flex",
|
||||
@@ -56,7 +64,9 @@ const CSVMultiSelector = ({
|
||||
name,
|
||||
label,
|
||||
tooltip = "",
|
||||
commonPlaceholder = "",
|
||||
onChange,
|
||||
withBorder = false,
|
||||
classes,
|
||||
}: ICSVMultiSelector) => {
|
||||
const [currentElements, setCurrentElements] = useState<string[]>([""]);
|
||||
@@ -75,29 +85,37 @@ const CSVMultiSelector = ({
|
||||
|
||||
setCurrentElements(elementsSplit);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [elements, currentElements]);
|
||||
|
||||
// Use effect to send new values to onChange
|
||||
useEffect(() => {
|
||||
const elementsString = currentElements
|
||||
.filter((element) => element.trim() !== "")
|
||||
.join(",");
|
||||
onChange(elementsString);
|
||||
const refScroll = bottomList.current;
|
||||
if (refScroll) {
|
||||
refScroll.scrollIntoView(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentElements]);
|
||||
|
||||
// We avoid multiple re-renders / hang issue typing too fast
|
||||
const firstUpdate = useRef(true);
|
||||
useLayoutEffect(() => {
|
||||
if (firstUpdate.current) {
|
||||
firstUpdate.current = false;
|
||||
return;
|
||||
}
|
||||
debouncedOnChange();
|
||||
// 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);
|
||||
}
|
||||
const cpList = [...elementsUp];
|
||||
cpList.push("");
|
||||
setCurrentElements(cpList);
|
||||
}
|
||||
|
||||
return elementsUp;
|
||||
};
|
||||
|
||||
// Onchange function for input box, we get the dataset-index & only update that value in the array
|
||||
@@ -108,10 +126,18 @@ const CSVMultiSelector = ({
|
||||
const index = get(e.target, "dataset.index", 0);
|
||||
updatedElement[index] = e.target.value;
|
||||
|
||||
updatedElement = addEmptyLine(updatedElement);
|
||||
setCurrentElements(updatedElement);
|
||||
};
|
||||
|
||||
// Debounce for On Change
|
||||
const debouncedOnChange = debounce(() => {
|
||||
const elementsString = currentElements
|
||||
.filter((element) => element.trim() !== "")
|
||||
.join(",");
|
||||
|
||||
onChange(elementsString);
|
||||
}, 500);
|
||||
|
||||
const inputs = currentElements.map((element, index) => {
|
||||
return (
|
||||
<InputBoxWrapper
|
||||
@@ -122,6 +148,11 @@ const CSVMultiSelector = ({
|
||||
onChange={onChangeElement}
|
||||
index={index}
|
||||
key={`csv-${name}-${index.toString()}`}
|
||||
placeholder={commonPlaceholder}
|
||||
overlayIcon={index === currentElements.length - 1 ? <AddIcon /> : null}
|
||||
overlayAction={() => {
|
||||
addEmptyLine(currentElements);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -139,7 +170,11 @@ const CSVMultiSelector = ({
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
<Grid item xs={12} className={classes.inputContainer}>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={`${withBorder ? classes.inputWithBorder : ""}`}
|
||||
>
|
||||
{inputs}
|
||||
<div ref={bottomList} />
|
||||
</Grid>
|
||||
@@ -147,5 +182,4 @@ const CSVMultiSelector = ({
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(CSVMultiSelector);
|
||||
|
||||
@@ -14,21 +14,8 @@
|
||||
// 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 {
|
||||
Checkbox,
|
||||
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 { Checkbox, Grid, InputLabel, Tooltip } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
checkboxIcons,
|
||||
fieldBasic,
|
||||
@@ -54,8 +41,14 @@ const styles = (theme: Theme) =>
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
...checkboxIcons,
|
||||
labelContainer: {
|
||||
flexGrow: 1,
|
||||
fieldContainer: {
|
||||
...fieldBasic.fieldContainer,
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
margin: "15px 0",
|
||||
marginBottom: 0,
|
||||
flexBasis: "initial",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -73,19 +66,7 @@ const CheckboxWrapper = ({
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
)}
|
||||
<div className={classes.labelContainer}>
|
||||
<div>
|
||||
<Checkbox
|
||||
name={name}
|
||||
id={id}
|
||||
@@ -99,6 +80,18 @@ const CheckboxWrapper = ({
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// 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 HelpIcon from "@material-ui/icons/Help";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { Controlled as CodeMirror } from "react-codemirror2";
|
||||
import { InputLabel, Tooltip } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { fieldBasic } from "../common/styleLibrary";
|
||||
import "./ConsoleCodeMirror.css";
|
||||
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
|
||||
interface ICodeWrapper {
|
||||
value: string;
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
classes: any;
|
||||
onChange?: (editor: any, data: any, value: string) => any;
|
||||
onBeforeChange: (editor: any, data: any, value: string) => any;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
});
|
||||
|
||||
const CodeMirrorWrapper = ({
|
||||
value,
|
||||
label = "",
|
||||
tooltip = "",
|
||||
classes,
|
||||
onChange = () => {},
|
||||
onBeforeChange,
|
||||
readOnly = false,
|
||||
}: ICodeWrapper) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<InputLabel className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<CodeMirror
|
||||
value={value}
|
||||
options={{
|
||||
mode: "javascript",
|
||||
lineNumbers: true,
|
||||
readOnly,
|
||||
}}
|
||||
onBeforeChange={onBeforeChange}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(CodeMirrorWrapper);
|
||||
@@ -0,0 +1,353 @@
|
||||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: #fff;
|
||||
background: #081C42;
|
||||
direction: ltr;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: rgba(255,255,255,0.8); /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #ffffff80;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
white-space: nowrap;
|
||||
color: #000;
|
||||
font-size: 10px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid white;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
.cm-fat-cursor-mark {
|
||||
background-color: rgba(20, 255, 20, 0.5);
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
}
|
||||
.cm-animate-fat-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: -50px; bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0; bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: #fff;}
|
||||
.cm-s-default .cm-quote {color: #fff;}
|
||||
.cm-negative {color: #fff;}
|
||||
.cm-positive {color: #fff;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #fff;}
|
||||
.cm-s-default .cm-atom {color: #fff;}
|
||||
.cm-s-default .cm-number {color: #fff;}
|
||||
.cm-s-default .cm-def {color: #fff;}
|
||||
.cm-s-default .cm-variable,
|
||||
.cm-s-default .cm-punctuation,
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #fff;}
|
||||
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #fff;}
|
||||
.cm-s-default .cm-comment {color: #fff;}
|
||||
.cm-s-default .cm-string {color: #fff;}
|
||||
.cm-s-default .cm-string-2 {color: #fff;}
|
||||
.cm-s-default .cm-meta {color: #fff;}
|
||||
.cm-s-default .cm-qualifier {color: #fff;}
|
||||
.cm-s-default .cm-builtin {color: #fff;}
|
||||
.cm-s-default .cm-bracket {color: #fff;}
|
||||
.cm-s-default .cm-tag {color: #fff;}
|
||||
.cm-s-default .cm-attribute {color: #fff;}
|
||||
.cm-s-default .cm-hr {color: #fff;}
|
||||
.cm-s-default .cm-link {color: #fff;}
|
||||
|
||||
.cm-s-default .cm-error {color: #fff;}
|
||||
.cm-invalidchar {color: #fff;}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #fff;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #fff;}
|
||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 50px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -50px; margin-right: -50px;
|
||||
padding-bottom: 50px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 50px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -50px;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
.CodeMirror-wrap pre.CodeMirror-line,
|
||||
.CodeMirror-wrap pre.CodeMirror-line-like {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-rtl pre { direction: rtl; }
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background-color: #ffa;
|
||||
background-color: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after { content: ''; }
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext { background: none; }
|
||||
@@ -0,0 +1,141 @@
|
||||
// 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, Tooltip } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
interface CommentBoxProps {
|
||||
label: string;
|
||||
classes: any;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
value: string | boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
index?: number;
|
||||
error?: string;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
inputLabel: {
|
||||
...fieldBasic.inputLabel,
|
||||
marginBottom: 16,
|
||||
fontSize: 14,
|
||||
},
|
||||
textBoxContainer: {
|
||||
flexGrow: 1,
|
||||
position: "relative",
|
||||
},
|
||||
errorState: {
|
||||
color: "#b53b4b",
|
||||
fontSize: 14,
|
||||
position: "absolute",
|
||||
top: 7,
|
||||
right: 7,
|
||||
},
|
||||
cssOutlinedInput: {
|
||||
borderColor: "#9C9C9C",
|
||||
padding: 16,
|
||||
},
|
||||
rootContainer: {
|
||||
"& .MuiOutlinedInput-inputMultiline": {
|
||||
...fieldBasic.inputLabel,
|
||||
fontSize: 13,
|
||||
minHeight: 150,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const CommentBoxWrapper = ({
|
||||
label,
|
||||
onChange,
|
||||
value,
|
||||
id,
|
||||
name,
|
||||
disabled = false,
|
||||
tooltip = "",
|
||||
index = 0,
|
||||
error = "",
|
||||
required = false,
|
||||
placeholder = "",
|
||||
classes,
|
||||
}: CommentBoxProps) => {
|
||||
let inputProps: any = { "data-index": index };
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={`${classes.fieldContainer} ${
|
||||
error !== "" ? classes.errorInField : ""
|
||||
}`}
|
||||
>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>
|
||||
{label}
|
||||
{required ? "*" : ""}
|
||||
</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
)}
|
||||
|
||||
<div className={classes.textBoxContainer}>
|
||||
<TextField
|
||||
id={id}
|
||||
name={name}
|
||||
fullWidth
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
multiline
|
||||
inputProps={inputProps}
|
||||
error={error !== ""}
|
||||
helperText={error}
|
||||
placeholder={placeholder}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.cssOutlinedInput,
|
||||
root: classes.rootContainer,
|
||||
},
|
||||
}}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(CommentBoxWrapper);
|
||||
@@ -76,6 +76,12 @@ const styles = (theme: Theme) =>
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
fieldBottom: {
|
||||
borderBottom: "#9c9c9c 1px solid",
|
||||
},
|
||||
fileInputField: {
|
||||
margin: "13px 0",
|
||||
},
|
||||
});
|
||||
|
||||
const FileSelector = ({
|
||||
@@ -98,7 +104,7 @@ const FileSelector = ({
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={`${classes.fieldContainer} ${
|
||||
className={`${classes.fieldBottom} ${classes.fieldContainer} ${
|
||||
error !== "" ? classes.errorInField : ""
|
||||
}`}
|
||||
>
|
||||
@@ -137,6 +143,7 @@ const FileSelector = ({
|
||||
accept={accept}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
className={classes.fileInputField}
|
||||
/>
|
||||
|
||||
{value !== "" && (
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
// 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { InputLabel, Switch, Tooltip } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { actionsTray, fieldBasic } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
interface IFormSwitch {
|
||||
label?: string;
|
||||
classes: any;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
value: string | boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
index?: number;
|
||||
indicatorLabel?: string;
|
||||
checked: boolean;
|
||||
switchOnly?: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
paper: {
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column",
|
||||
paddingTop: 15,
|
||||
boxShadow: "none",
|
||||
},
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px",
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red",
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0),
|
||||
},
|
||||
wrapCell: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
noFound: {
|
||||
textAlign: "center",
|
||||
padding: "10px 0",
|
||||
},
|
||||
tableContainer: {
|
||||
maxHeight: 200,
|
||||
},
|
||||
stickyHeader: {
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
actionsTitle: {
|
||||
fontWeight: 600,
|
||||
color: "#081C42",
|
||||
fontSize: 16,
|
||||
alignSelf: "center",
|
||||
},
|
||||
tableBlock: {
|
||||
marginTop: 15,
|
||||
},
|
||||
filterField: {
|
||||
width: 375,
|
||||
fontWeight: 600,
|
||||
"& .input": {
|
||||
"&::placeholder": {
|
||||
fontWeight: 600,
|
||||
color: "#081C42",
|
||||
},
|
||||
},
|
||||
},
|
||||
wrapperContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
borderBottom: "#9c9c9c 1px solid",
|
||||
paddingBottom: 14,
|
||||
marginBottom: 20,
|
||||
},
|
||||
indicatorLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
color: "#081C42",
|
||||
margin: "0 8px 0 10px",
|
||||
},
|
||||
switchContainer: {
|
||||
display: "flex",
|
||||
},
|
||||
...actionsTray,
|
||||
...fieldBasic,
|
||||
});
|
||||
|
||||
const StyledSwitch = withStyles({
|
||||
root: {
|
||||
alignItems: "flex-start",
|
||||
height: 18,
|
||||
padding: "0 12px",
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
},
|
||||
switchBase: {
|
||||
color: "#fff",
|
||||
padding: 0,
|
||||
top: "initial",
|
||||
"&$checked": {
|
||||
color: "#fff",
|
||||
},
|
||||
"&$checked + $track": {
|
||||
backgroundColor: "#081C42",
|
||||
opacity: 1,
|
||||
height: 15,
|
||||
},
|
||||
},
|
||||
checked: {},
|
||||
track: {
|
||||
height: 15,
|
||||
backgroundColor: "#081C42",
|
||||
opacity: 1,
|
||||
padding: 0,
|
||||
marginTop: 1.5,
|
||||
},
|
||||
thumb: {
|
||||
backgroundColor: "#fff",
|
||||
border: "#081C42 1px solid",
|
||||
boxShadow: "none",
|
||||
width: 18,
|
||||
height: 18,
|
||||
padding: 0,
|
||||
marginLeft: 10,
|
||||
},
|
||||
})(Switch);
|
||||
|
||||
const FormSwitchWrapper = ({
|
||||
label = "",
|
||||
onChange,
|
||||
value,
|
||||
id,
|
||||
name,
|
||||
checked = false,
|
||||
disabled = false,
|
||||
switchOnly = false,
|
||||
tooltip = "",
|
||||
indicatorLabel = "",
|
||||
classes,
|
||||
}: IFormSwitch) => {
|
||||
const switchComponent = (
|
||||
<React.Fragment>
|
||||
<div className={classes.switchContainer}>
|
||||
<StyledSwitch
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
color="primary"
|
||||
name={name}
|
||||
inputProps={{ "aria-label": "primary checkbox" }}
|
||||
disabled={disabled}
|
||||
disableRipple
|
||||
disableFocusRipple
|
||||
disableTouchRipple
|
||||
value={value}
|
||||
/>
|
||||
{indicatorLabel !== "" && (
|
||||
<span className={classes.indicatorLabel}>{indicatorLabel}</span>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
if (switchOnly) {
|
||||
return switchComponent;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={`${classes.wrapperContainer}`}>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
)}
|
||||
{switchComponent}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(FormSwitchWrapper);
|
||||
@@ -16,6 +16,7 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Grid,
|
||||
IconButton,
|
||||
InputLabel,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
@@ -49,6 +50,8 @@ interface InputBoxProps {
|
||||
placeholder?: string;
|
||||
min?: string;
|
||||
max?: string;
|
||||
overlayIcon?: any;
|
||||
overlayAction?: () => void;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -57,7 +60,10 @@ const styles = (theme: Theme) =>
|
||||
...tooltipHelper,
|
||||
textBoxContainer: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
textBoxWithIcon: {
|
||||
position: "relative",
|
||||
paddingRight: 25,
|
||||
},
|
||||
errorState: {
|
||||
color: "#b53b4b",
|
||||
@@ -66,22 +72,43 @@ const styles = (theme: Theme) =>
|
||||
top: 7,
|
||||
right: 7,
|
||||
},
|
||||
overlayAction: {
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 15,
|
||||
"& svg": {
|
||||
maxWidth: 15,
|
||||
maxHeight: 15,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const inputStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
borderColor: "#393939",
|
||||
borderRadius: 0,
|
||||
"&::before": {
|
||||
borderColor: "#9c9c9c",
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
"&.MuiInput-underline::before": {
|
||||
borderColor: "#eaeaea",
|
||||
borderBottomStyle: "solid",
|
||||
},
|
||||
},
|
||||
input: {
|
||||
padding: "11px 20px",
|
||||
padding: "15px 30px 10px 5px",
|
||||
color: "#393939",
|
||||
fontSize: 14,
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
"&:placeholder": {
|
||||
color: "#393939",
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
error: {
|
||||
color: "#b53b4b",
|
||||
boxShadow: "inset 0px 0px 1px 1px #b53b4b",
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -114,6 +141,8 @@ const InputBoxWrapper = ({
|
||||
placeholder = "",
|
||||
min,
|
||||
max,
|
||||
overlayIcon = null,
|
||||
overlayAction,
|
||||
classes,
|
||||
}: InputBoxProps) => {
|
||||
let inputProps: any = { "data-index": index };
|
||||
@@ -136,12 +165,7 @@ const InputBoxWrapper = ({
|
||||
}`}
|
||||
>
|
||||
{label !== "" && (
|
||||
<InputLabel
|
||||
htmlFor={id}
|
||||
className={`${error !== "" ? classes.fieldLabelError : ""} ${
|
||||
classes.inputLabel
|
||||
}`}
|
||||
>
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>
|
||||
{label}
|
||||
{required ? "*" : ""}
|
||||
@@ -160,7 +184,6 @@ const InputBoxWrapper = ({
|
||||
<InputField
|
||||
id={id}
|
||||
name={name}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
@@ -172,8 +195,28 @@ const InputBoxWrapper = ({
|
||||
error={error !== ""}
|
||||
helperText={error}
|
||||
placeholder={placeholder}
|
||||
className={classes.inputRebase}
|
||||
/>
|
||||
</div>
|
||||
{overlayIcon && (
|
||||
<div className={classes.overlayAction}>
|
||||
<IconButton
|
||||
onClick={
|
||||
overlayAction
|
||||
? () => {
|
||||
overlayAction();
|
||||
}
|
||||
: () => null
|
||||
}
|
||||
size={"small"}
|
||||
disableFocusRipple={false}
|
||||
disableRipple={false}
|
||||
disableTouchRipple={false}
|
||||
>
|
||||
{overlayIcon}
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
withStyles,
|
||||
makeStyles,
|
||||
} from "@material-ui/core/styles";
|
||||
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
||||
import { fieldBasic, radioIcons, tooltipHelper } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
export interface SelectorTypes {
|
||||
@@ -49,8 +49,20 @@ const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
radioBoxContainer: {
|
||||
flexGrow: 1,
|
||||
radioBoxContainer: {},
|
||||
fieldContainer: {
|
||||
...fieldBasic.fieldContainer,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
borderBottom: "#9c9c9c 1px solid",
|
||||
paddingBottom: 10,
|
||||
marginTop: 11,
|
||||
},
|
||||
checkedOption: {
|
||||
"& .MuiFormControlLabel-label": {
|
||||
color: "#000",
|
||||
fontWeight: 700,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -60,31 +72,7 @@ const radioStyles = makeStyles({
|
||||
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,
|
||||
},
|
||||
},
|
||||
...radioIcons,
|
||||
});
|
||||
|
||||
const RadioButton = (props: RadioProps) => {
|
||||
@@ -95,8 +83,8 @@ const RadioButton = (props: RadioProps) => {
|
||||
className={classes.root}
|
||||
disableRipple
|
||||
color="default"
|
||||
checkedIcon={<span className={classes.checkedIcon} />}
|
||||
icon={<span className={classes.icon} />}
|
||||
checkedIcon={<span className={classes.radioSelectedIcon} />}
|
||||
icon={<span className={classes.radioUnselectedIcon} />}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -143,6 +131,11 @@ export const RadioGroupSelector = ({
|
||||
value={selectorOption.value}
|
||||
control={<RadioButton />}
|
||||
label={selectorOption.label}
|
||||
className={
|
||||
selectorOption.value === currentSelection
|
||||
? classes.checkedOption
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -50,26 +50,25 @@ const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
inputLabel: {
|
||||
...fieldBasic.inputLabel,
|
||||
width: 215,
|
||||
},
|
||||
});
|
||||
|
||||
const SelectStyled = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
lineHeight: 1,
|
||||
"label + &": {
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
},
|
||||
input: {
|
||||
borderRadius: 0,
|
||||
position: "relative",
|
||||
color: "#393939",
|
||||
fontSize: 14,
|
||||
padding: "11px 20px",
|
||||
border: "1px solid #c4c4c4",
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
padding: "15px 20px 10px 10px",
|
||||
borderBottom: "1px solid #9c9c9c",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"&:hover": {
|
||||
borderColor: "#393939",
|
||||
},
|
||||
@@ -106,7 +105,7 @@ const SelectWrapper = ({
|
||||
)}
|
||||
</InputLabel>
|
||||
)}
|
||||
<FormControl variant="outlined" fullWidth>
|
||||
<FormControl fullWidth>
|
||||
<Select
|
||||
id={id}
|
||||
name={name}
|
||||
|
||||
@@ -18,28 +18,24 @@
|
||||
|
||||
export const fieldBasic = {
|
||||
inputLabel: {
|
||||
fontWeight: 500,
|
||||
fontWeight: 600,
|
||||
marginRight: 10,
|
||||
width: 160,
|
||||
fontSize: 14,
|
||||
color: "#393939",
|
||||
textAlign: "right" as const,
|
||||
display: "flex",
|
||||
textOverflow: "ellipsis",
|
||||
fontSize: 15,
|
||||
color: "#000",
|
||||
textAlign: "left" as const,
|
||||
overflow: "hidden",
|
||||
justifyContent: "flex-end",
|
||||
"& span": {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
display: "flex",
|
||||
},
|
||||
fieldLabelError: {
|
||||
paddingBottom: 22,
|
||||
},
|
||||
fieldContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: 10,
|
||||
marginBottom: 20,
|
||||
position: "relative" as const,
|
||||
},
|
||||
tooltipContainer: {
|
||||
marginLeft: 5,
|
||||
@@ -57,6 +53,30 @@ export const modalBasic = {
|
||||
formSlider: {
|
||||
marginLeft: 0,
|
||||
},
|
||||
clearButton: {
|
||||
border: "0",
|
||||
backgroundColor: "transparent",
|
||||
color: "#393939",
|
||||
fontWeight: 600,
|
||||
fontSize: 14,
|
||||
marginRight: 10,
|
||||
outline: "0",
|
||||
padding: "16px 25px 16px 25px",
|
||||
cursor: "pointer",
|
||||
},
|
||||
floatingEnabled: {
|
||||
position: "absolute" as const,
|
||||
right: 58,
|
||||
zIndex: 1000,
|
||||
marginTop: -38,
|
||||
},
|
||||
configureString: {
|
||||
border: "#EAEAEA 1px solid",
|
||||
borderRadius: 4,
|
||||
padding: "24px 50px",
|
||||
overflowY: "auto" as const,
|
||||
height: 170,
|
||||
},
|
||||
};
|
||||
|
||||
export const tooltipHelper = {
|
||||
@@ -66,17 +86,32 @@ export const tooltipHelper = {
|
||||
};
|
||||
|
||||
const checkBoxBasic = {
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 3,
|
||||
width: 14,
|
||||
height: 14,
|
||||
borderRadius: 2,
|
||||
};
|
||||
|
||||
export const checkboxIcons = {
|
||||
unCheckedIcon: { ...checkBoxBasic, border: "1px solid #d0d0d0" },
|
||||
unCheckedIcon: { ...checkBoxBasic, border: "1px solid #c3c3c3" },
|
||||
checkedIcon: {
|
||||
...checkBoxBasic,
|
||||
border: "1px solid #201763",
|
||||
backgroundColor: "#201763",
|
||||
border: "1px solid #081C42",
|
||||
backgroundColor: "#081C42",
|
||||
},
|
||||
};
|
||||
|
||||
const radioBasic = {
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: "100%",
|
||||
};
|
||||
|
||||
export const radioIcons = {
|
||||
radioUnselectedIcon: { ...radioBasic, border: "1px solid #000" },
|
||||
radioSelectedIcon: {
|
||||
...radioBasic,
|
||||
border: "1px solid #000",
|
||||
backgroundColor: "#000",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -89,9 +124,86 @@ export const containerForHeader = (bottomSpacing: any) => ({
|
||||
fontSize: 14,
|
||||
},
|
||||
"& p": {
|
||||
"& span": {
|
||||
"& span:not(*[class*='smallUnit'])": {
|
||||
fontSize: 16,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const actionsTray = {
|
||||
actionsTray: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
"& button": {
|
||||
flexGrow: 0,
|
||||
marginLeft: 15,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const searchField = {
|
||||
searchField: {
|
||||
flexGrow: 1,
|
||||
height: 40,
|
||||
background: "#FFFFFF",
|
||||
borderRadius: 5,
|
||||
border: "#EAEDEE 1px solid",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
padding: "0 16px",
|
||||
"& input": {
|
||||
fontSize: 14,
|
||||
color: "#000",
|
||||
"&::placeholder": {
|
||||
color: "#393939",
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const predefinedList = {
|
||||
predefinedTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: "#000",
|
||||
margin: "10px 0",
|
||||
},
|
||||
predefinedList: {
|
||||
backgroundColor: "#eaeaea",
|
||||
padding: "12px 10px",
|
||||
color: "#393939",
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
minHeight: 41,
|
||||
},
|
||||
};
|
||||
|
||||
export const objectBrowserCommon = {
|
||||
obTitleSection: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "flex-start",
|
||||
marginBottom: 20,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 22,
|
||||
color: "#000",
|
||||
fontWeight: 600,
|
||||
height: 40,
|
||||
lineHeight: "40px",
|
||||
},
|
||||
breadcrumbs: {
|
||||
fontSize: 10,
|
||||
color: "#000",
|
||||
marginTop: 2,
|
||||
"& a": {
|
||||
textDecoration: "none",
|
||||
color: "#000",
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ const styles = (theme: Theme) =>
|
||||
minWidth: 180,
|
||||
marginRight: 10,
|
||||
"& ul": {
|
||||
padding: 15,
|
||||
padding: "0 15px 0 0",
|
||||
|
||||
"& li": {
|
||||
listStyle: "lower-roman",
|
||||
|
||||
@@ -51,6 +51,7 @@ const WizardPage = ({ classes, page, pageChange }: IWizardPage) => {
|
||||
break;
|
||||
case "to":
|
||||
pageChange(btn.toPage || 0);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
|
||||
@@ -24,23 +24,24 @@ interface IModalProps {
|
||||
modalOpen: boolean;
|
||||
title: string;
|
||||
children: any;
|
||||
wideLimit?: boolean;
|
||||
}
|
||||
|
||||
const baseCloseLine = {
|
||||
content: '" "',
|
||||
borderLeft: "2px solid #707070",
|
||||
borderLeft: "2px solid #9C9C9C",
|
||||
height: 33,
|
||||
width: 1,
|
||||
position: "absolute"
|
||||
position: "absolute",
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
dialogContainer: {
|
||||
padding: "12px 26px 22px"
|
||||
padding: "8px 15px 22px",
|
||||
},
|
||||
closeContainer: {
|
||||
textAlign: "right"
|
||||
textAlign: "right",
|
||||
},
|
||||
closeButton: {
|
||||
width: 45,
|
||||
@@ -48,40 +49,52 @@ const styles = (theme: Theme) =>
|
||||
padding: 0,
|
||||
backgroundColor: "initial",
|
||||
"&:hover": {
|
||||
backgroundColor: "initial"
|
||||
backgroundColor: "initial",
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: "initial"
|
||||
}
|
||||
backgroundColor: "initial",
|
||||
},
|
||||
},
|
||||
modalCloseIcon: {
|
||||
fontSize: 35,
|
||||
color: "#707070",
|
||||
color: "#9C9C9C",
|
||||
fontWeight: 300,
|
||||
"&:hover": {
|
||||
color: "#000"
|
||||
}
|
||||
color: "#9C9C9C",
|
||||
},
|
||||
},
|
||||
closeIcon: {
|
||||
"&::before": {
|
||||
...baseCloseLine,
|
||||
transform: "rotate(45deg)"
|
||||
transform: "rotate(45deg)",
|
||||
},
|
||||
"&::after": {
|
||||
...baseCloseLine,
|
||||
transform: "rotate(-45deg)"
|
||||
transform: "rotate(-45deg)",
|
||||
},
|
||||
"&:hover::before, &:hover::after": {
|
||||
borderColor: "#000"
|
||||
borderColor: "#9C9C9C",
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
display: "block",
|
||||
position: "relative"
|
||||
position: "relative",
|
||||
},
|
||||
titleClass: {
|
||||
padding: "0px 24px 12px"
|
||||
}
|
||||
padding: "0px 50px 12px",
|
||||
"& h2": {
|
||||
fontWeight: 600,
|
||||
color: "#000",
|
||||
fontSize: 22,
|
||||
},
|
||||
},
|
||||
modalContent: {
|
||||
padding: "0 50px",
|
||||
},
|
||||
customDialogSize: {
|
||||
width: "100%",
|
||||
maxWidth: 765,
|
||||
},
|
||||
});
|
||||
|
||||
const ModalWrapper = ({
|
||||
@@ -89,16 +102,23 @@ const ModalWrapper = ({
|
||||
modalOpen,
|
||||
title,
|
||||
children,
|
||||
classes
|
||||
classes,
|
||||
wideLimit = true,
|
||||
}: IModalProps) => {
|
||||
const customSize = wideLimit
|
||||
? {
|
||||
classes: {
|
||||
paper: classes.customDialogSize,
|
||||
},
|
||||
}
|
||||
: { maxWidth: "md" as const, fullWidth: true };
|
||||
return (
|
||||
<Dialog
|
||||
open={modalOpen}
|
||||
onClose={onClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
maxWidth={"md"}
|
||||
fullWidth
|
||||
{...customSize}
|
||||
>
|
||||
<div className={classes.dialogContainer}>
|
||||
<div className={classes.closeContainer}>
|
||||
@@ -114,7 +134,9 @@ const ModalWrapper = ({
|
||||
<DialogTitle id="alert-dialog-title" className={classes.titleClass}>
|
||||
{title}
|
||||
</DialogTitle>
|
||||
<DialogContent>{children}</DialogContent>
|
||||
<DialogContent className={classes.modalContent}>
|
||||
{children}
|
||||
</DialogContent>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -22,6 +22,8 @@ import DeleteIcon from "./TableActionIcons/DeleteIcon";
|
||||
import DescriptionIcon from "./TableActionIcons/DescriptionIcon";
|
||||
import CloudIcon from "./TableActionIcons/CloudIcon";
|
||||
import ConsoleIcon from "./TableActionIcons/ConsoleIcon";
|
||||
import GetAppIcon from "@material-ui/icons/GetApp";
|
||||
import SvgIcon from "@material-ui/core/SvgIcon";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
interface IActionButton {
|
||||
@@ -48,6 +50,10 @@ const defineIcon = (type: string, selected: boolean) => {
|
||||
return <CloudIcon active={selected} />;
|
||||
case "console":
|
||||
return <ConsoleIcon active={selected} />;
|
||||
case "download":
|
||||
return (
|
||||
<SvgIcon component={GetAppIcon} fontSize="small" color="primary" />
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -7,13 +7,17 @@ const DeleteIcon = ({ active = false }: IIcon) => {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
viewBox="0 0 10.402 13"
|
||||
>
|
||||
<g transform="translate(-1225 -657)">
|
||||
<g transform="translate(0.004 -28.959)">
|
||||
<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)"
|
||||
d="M6.757,29.959v-1H3.636v1H0v1H10.4v-1Z"
|
||||
/>
|
||||
<path
|
||||
fill={active ? selected : unSelected}
|
||||
d="M0,31.957l1.672,10H8.724l1.673-10ZM3.412,40.2,2.86,33.722h.653l.553,6.472Zm3.569,0H6.328l.551-6.472h.654Z"
|
||||
transform="translate(0 0)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
@@ -7,12 +7,13 @@ const PencilIcon = ({ active = false }: IIcon) => {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
viewBox="0 0 10 11.429"
|
||||
>
|
||||
<path
|
||||
fill={active ? selected : unSelected}
|
||||
d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"
|
||||
></path>
|
||||
d="M-43.375,11.429-48.35,8.664l-5.025,2.764V0h10Z"
|
||||
transform="translate(53.375)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,5 +2,5 @@ export interface IIcon {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export const unSelected = "#adadad";
|
||||
export const selected = "#201763";
|
||||
export const unSelected = "#081C42";
|
||||
export const selected = "#081C42";
|
||||
|
||||
@@ -33,7 +33,10 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { TablePaginationActionsProps } from "@material-ui/core/TablePagination/TablePaginationActions";
|
||||
import TableActionButton from "./TableActionButton";
|
||||
import history from "../../../../history";
|
||||
import { checkboxIcons } from "../FormComponents/common/styleLibrary";
|
||||
import {
|
||||
checkboxIcons,
|
||||
radioIcons,
|
||||
} from "../FormComponents/common/styleLibrary";
|
||||
|
||||
//Interfaces for table Items
|
||||
|
||||
@@ -49,7 +52,9 @@ interface IColumns {
|
||||
elementKey: string;
|
||||
sortable?: boolean;
|
||||
renderFunction?: (input: any) => any;
|
||||
renderFullObject?: boolean;
|
||||
globalClass?: any;
|
||||
rowClass?: any;
|
||||
}
|
||||
|
||||
interface IPaginatorConfig {
|
||||
@@ -80,15 +85,23 @@ interface TableWrapperProps {
|
||||
entityName: string;
|
||||
selectedItems?: string[];
|
||||
stickyHeader?: boolean;
|
||||
radioSelection?: boolean;
|
||||
customEmptyMessage?: string;
|
||||
paginatorConfig?: IPaginatorConfig;
|
||||
}
|
||||
|
||||
const borderColor = "#eaeaea";
|
||||
const borderColor = "#9c9c9c80";
|
||||
|
||||
const rowText = {
|
||||
fontWeight: 400,
|
||||
fontSize: 14,
|
||||
borderColor: borderColor,
|
||||
borderWidth: "0.5px",
|
||||
height: 40,
|
||||
transitionDuration: "0.3s",
|
||||
padding: "initial",
|
||||
paddingRight: 6,
|
||||
paddingLeft: 6,
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -101,7 +114,18 @@ const styles = (theme: Theme) =>
|
||||
overflow: "auto",
|
||||
flexDirection: "column",
|
||||
padding: "19px 38px",
|
||||
minHeight: "200px",
|
||||
boxShadow: "none",
|
||||
border: "#EAEDEE 1px solid",
|
||||
borderRadius: 3,
|
||||
minHeight: "calc(100vh - 340px)",
|
||||
},
|
||||
allTableSettings: {
|
||||
"& .MuiTableCell-sizeSmall:last-child": {
|
||||
paddingRight: "initial",
|
||||
},
|
||||
"& .MuiTableCell-body.MuiTableCell-sizeSmall:last-child": {
|
||||
paddingRight: 6,
|
||||
},
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
@@ -109,17 +133,20 @@ const styles = (theme: Theme) =>
|
||||
"& th": {
|
||||
fontWeight: 700,
|
||||
fontSize: 14,
|
||||
paddingBottom: 15,
|
||||
borderColor: borderColor,
|
||||
borderColor: "#39393980",
|
||||
borderWidth: "0.5px",
|
||||
padding: "6px 0 10px",
|
||||
},
|
||||
},
|
||||
},
|
||||
rowUnselected: {
|
||||
...rowText,
|
||||
color: "#393939",
|
||||
},
|
||||
rowSelected: {
|
||||
...rowText,
|
||||
color: "#201763",
|
||||
color: "#081C42",
|
||||
fontWeight: 600,
|
||||
},
|
||||
paginatorContainer: {
|
||||
display: "flex",
|
||||
@@ -127,8 +154,12 @@ const styles = (theme: Theme) =>
|
||||
padding: "5px 38px",
|
||||
},
|
||||
checkBoxHeader: {
|
||||
width: 50,
|
||||
textAlign: "left",
|
||||
paddingRight: 10,
|
||||
"&.MuiTableCell-paddingCheckbox": {
|
||||
paddingBottom: 9,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 0,
|
||||
},
|
||||
},
|
||||
actionsContainer: {
|
||||
@@ -140,6 +171,7 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
checkBoxRow: {
|
||||
borderColor: borderColor,
|
||||
padding: "0 10px 0 0",
|
||||
},
|
||||
loadingBox: {
|
||||
paddingTop: "100px",
|
||||
@@ -150,12 +182,17 @@ const styles = (theme: Theme) =>
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: "#ececec",
|
||||
|
||||
"& td": {
|
||||
fontWeight: 600,
|
||||
},
|
||||
},
|
||||
},
|
||||
rowClickable: {
|
||||
cursor: "pointer",
|
||||
},
|
||||
...checkboxIcons,
|
||||
...radioIcons,
|
||||
});
|
||||
|
||||
// Function that renders Title Columns
|
||||
@@ -183,13 +220,17 @@ const rowColumnsMap = (
|
||||
const itemElement = isString(itemData)
|
||||
? itemData
|
||||
: get(itemData, column.elementKey, null); // If the element is just a string, we render it as it is
|
||||
const renderConst = column.renderFullObject ? itemData : itemElement;
|
||||
|
||||
const renderElement = column.renderFunction
|
||||
? column.renderFunction(itemElement)
|
||||
: itemElement; // If render function is set, we send the value to the function.
|
||||
? column.renderFunction(renderConst)
|
||||
: renderConst; // 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}
|
||||
className={`${column.rowClass} ${
|
||||
isSelected ? classes.rowSelected : classes.rowUnselected
|
||||
}`}
|
||||
>
|
||||
{renderElement}
|
||||
</TableCell>
|
||||
@@ -236,6 +277,8 @@ const TableWrapper = ({
|
||||
idField,
|
||||
classes,
|
||||
stickyHeader = false,
|
||||
radioSelection = false,
|
||||
customEmptyMessage = "",
|
||||
paginatorConfig,
|
||||
}: TableWrapperProps) => {
|
||||
const findView = itemActions
|
||||
@@ -270,15 +313,15 @@ const TableWrapper = ({
|
||||
</Grid>
|
||||
)}
|
||||
{records && !isLoading && records.length > 0 ? (
|
||||
<Table size="small" stickyHeader={stickyHeader}>
|
||||
<Table
|
||||
size="small"
|
||||
stickyHeader={stickyHeader}
|
||||
className={classes.allTableSettings}
|
||||
>
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
{onSelect && selectedItems && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
align="center"
|
||||
className={classes.checkBoxHeader}
|
||||
>
|
||||
<TableCell align="center" className={classes.checkBoxHeader}>
|
||||
Select
|
||||
</TableCell>
|
||||
)}
|
||||
@@ -309,17 +352,13 @@ const TableWrapper = ({
|
||||
key={`tb-${entityName}-${index.toString()}`}
|
||||
className={`${findView ? classes.rowClickable : ""} ${
|
||||
classes.rowElement
|
||||
}`}
|
||||
} rowElementRaw`}
|
||||
onClick={() => {
|
||||
clickAction(record);
|
||||
}}
|
||||
>
|
||||
{onSelect && selectedItems && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
align="center"
|
||||
className={classes.checkBoxRow}
|
||||
>
|
||||
<TableCell align="center" className={classes.checkBoxRow}>
|
||||
<Checkbox
|
||||
value={isString(record) ? record : record[idField]}
|
||||
color="primary"
|
||||
@@ -330,8 +369,24 @@ const TableWrapper = ({
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
checkedIcon={<span className={classes.checkedIcon} />}
|
||||
icon={<span className={classes.unCheckedIcon} />}
|
||||
checkedIcon={
|
||||
<span
|
||||
className={
|
||||
radioSelection
|
||||
? classes.radioSelectedIcon
|
||||
: classes.checkedIcon
|
||||
}
|
||||
/>
|
||||
}
|
||||
icon={
|
||||
<span
|
||||
className={
|
||||
radioSelection
|
||||
? classes.radioUnselectedIcon
|
||||
: classes.unCheckedIcon
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
@@ -359,7 +414,13 @@ const TableWrapper = ({
|
||||
</Table>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{!isLoading && <div>{`There are no ${entityName} yet.`}</div>}
|
||||
{!isLoading && (
|
||||
<div>
|
||||
{customEmptyMessage !== ""
|
||||
? customEmptyMessage
|
||||
: `There are no ${entityName} yet.`}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
@@ -20,8 +20,9 @@ import Grid from "@material-ui/core/Grid";
|
||||
import { IElementValue, KVField } from "./types";
|
||||
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
import CSVMultiSelector from "../Common/FormComponents/CSVMultiSelector/CSVMultiSelector";
|
||||
import CommentBoxWrapper from "../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
|
||||
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
interface IConfGenericProps {
|
||||
onChange: (newValue: IElementValue[]) => void;
|
||||
@@ -77,6 +78,7 @@ const ConfTargetGeneric = ({
|
||||
});
|
||||
|
||||
setValueHolder(values);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fields, defaultVals]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -94,20 +96,21 @@ const ConfTargetGeneric = ({
|
||||
const fieldDefinition = (field: KVField, item: number) => {
|
||||
switch (field.type) {
|
||||
case "on|off":
|
||||
const value = valueHolder[item] ? valueHolder[item].value : "false";
|
||||
|
||||
return (
|
||||
<RadioGroupSelector
|
||||
currentSelection={valueHolder[item] ? valueHolder[item].value : ""}
|
||||
<FormSwitchWrapper
|
||||
indicatorLabel="On"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.checked ? "true" : "false";
|
||||
setValueElement(field.name, value, item);
|
||||
}}
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
value={"switch_on"}
|
||||
tooltip={field.tooltip}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValueElement(field.name, e.target.value, item)
|
||||
}
|
||||
selectorOptions={[
|
||||
{ label: "On", value: "true" },
|
||||
{ label: "Off", value: "false" },
|
||||
]}
|
||||
checked={value === "true"}
|
||||
/>
|
||||
);
|
||||
case "csv":
|
||||
@@ -120,6 +123,22 @@ const ConfTargetGeneric = ({
|
||||
setValueElement(field.name, value, item)
|
||||
}
|
||||
tooltip={field.tooltip}
|
||||
commonPlaceholder={field.placeholder}
|
||||
withBorder={!!field.withBorder}
|
||||
/>
|
||||
);
|
||||
case "comment":
|
||||
return (
|
||||
<CommentBoxWrapper
|
||||
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)
|
||||
}
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
@@ -134,6 +153,7 @@ const ConfTargetGeneric = ({
|
||||
setValueElement(field.name, e.target.value, item)
|
||||
}
|
||||
multiline={!!field.multiline}
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ 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";
|
||||
@@ -27,7 +26,11 @@ import TableWrapper from "../../Common/TableWrapper/TableWrapper";
|
||||
import { configurationElements } from "../utils";
|
||||
import { IConfigurationElement } from "../types";
|
||||
import EditConfiguration from "../CustomForms/EditConfiguration";
|
||||
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../../Common/PageHeader/PageHeader";
|
||||
|
||||
interface IListConfiguration {
|
||||
@@ -45,21 +48,11 @@ const styles = (theme: Theme) =>
|
||||
keyName: {
|
||||
marginLeft: 5,
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
iconText: {
|
||||
lineHeight: "24px",
|
||||
},
|
||||
...searchField,
|
||||
...actionsTray,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
@@ -69,7 +62,6 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
configuration_id: "",
|
||||
configuration_label: "",
|
||||
});
|
||||
const [error, setError] = useState("");
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
const tableActions = [
|
||||
@@ -109,7 +101,6 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
<PageHeader label="Configurations List" />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
{error !== "" && <Grid container>{error}</Grid>}
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Filter"
|
||||
|
||||
@@ -94,10 +94,9 @@ const panels = {
|
||||
|
||||
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 [webhooks, setWebhooks] = useState<IWebhook[]>([]);
|
||||
|
||||
const pathIn = get(match, "path", "");
|
||||
const panelToDisplay = pathIn.split("/");
|
||||
@@ -107,6 +106,8 @@ const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const webhooks: IWebhook[] = [];
|
||||
|
||||
const filteredRecords: IWebhook[] = webhooks.filter((elementItem) =>
|
||||
elementItem.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())
|
||||
);
|
||||
@@ -137,7 +138,6 @@ const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
{error !== "" && <Grid container>{error}</Grid>}
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Filter"
|
||||
|
||||
@@ -16,12 +16,16 @@
|
||||
|
||||
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";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
modalBasic,
|
||||
predefinedList,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import CommentBoxWrapper from "../../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
|
||||
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
interface IConfMySqlProps {
|
||||
onChange: (newValue: IElementValue[]) => void;
|
||||
@@ -31,6 +35,7 @@ interface IConfMySqlProps {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...modalBasic,
|
||||
...predefinedList,
|
||||
});
|
||||
|
||||
const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
@@ -104,44 +109,41 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
setDsnString(cs);
|
||||
}, [user, dbName, password, port, host, setDsnString, configToDsnString]);
|
||||
|
||||
const switcherChangeEvt = (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);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container className={classes.formScrollable}>
|
||||
<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"
|
||||
className={classes.formSlider}
|
||||
<FormSwitchWrapper
|
||||
label={"Enter DNS String"}
|
||||
checked={useDsnString}
|
||||
id="checkedB"
|
||||
name="checkedB"
|
||||
onChange={switcherChangeEvt}
|
||||
value={"dnsString"}
|
||||
indicatorLabel={"On"}
|
||||
/>
|
||||
</Grid>
|
||||
{useDsnString ? (
|
||||
@@ -160,62 +162,78 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
</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} className={classes.configureString}>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="host"
|
||||
name="host"
|
||||
label=""
|
||||
placeholder="Enter 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=""
|
||||
placeholder="Enter DB Name"
|
||||
value={dbName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDbName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="port"
|
||||
name="port"
|
||||
label=""
|
||||
placeholder="Enter 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 item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="user"
|
||||
name="user"
|
||||
label=""
|
||||
placeholder="Enter User"
|
||||
value={user}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUser(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="password"
|
||||
name="password"
|
||||
label=""
|
||||
placeholder="Enter Password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.predefinedTitle}>
|
||||
Connection String
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.predefinedList}>
|
||||
{dsnString}
|
||||
</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);
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
@@ -224,6 +242,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
id="table"
|
||||
name="table"
|
||||
label="Table"
|
||||
placeholder="Enter Table Name"
|
||||
value={table}
|
||||
tooltip="DB table name to store/update events, table is auto-created"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -252,6 +271,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
id="queue-dir"
|
||||
name="queue_dir"
|
||||
label="Queue Dir"
|
||||
placeholder="Enter Queue Dir"
|
||||
value={queueDir}
|
||||
tooltip="staging dir for undelivered messages e.g. '/home/events'"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -264,6 +284,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
id="queue-limit"
|
||||
name="queue_limit"
|
||||
label="Queue Limit"
|
||||
placeholder="Enter Queue Limit"
|
||||
type="number"
|
||||
value={queueLimit}
|
||||
tooltip="maximum limit for undelivered messages, defaults to '10000'"
|
||||
@@ -273,11 +294,11 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
<CommentBoxWrapper
|
||||
id="comment"
|
||||
name="comment"
|
||||
label="Comment"
|
||||
multiline={true}
|
||||
placeholder="Enter Comment"
|
||||
value={comment}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setComment(e.target.value);
|
||||
|
||||
@@ -16,13 +16,17 @@
|
||||
|
||||
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";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
modalBasic,
|
||||
predefinedList,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import CommentBoxWrapper from "../../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
|
||||
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
interface IConfPostgresProps {
|
||||
onChange: (newValue: IElementValue[]) => void;
|
||||
@@ -32,6 +36,7 @@ interface IConfPostgresProps {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...modalBasic,
|
||||
...predefinedList,
|
||||
});
|
||||
|
||||
const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
@@ -45,7 +50,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
const [port, setPort] = useState<string>("");
|
||||
const [user, setUser] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [sslMode, setSslMode] = useState<string>("require");
|
||||
const [sslMode, setSslMode] = useState<string>(" ");
|
||||
|
||||
const [table, setTable] = useState<string>("");
|
||||
const [format, setFormat] = useState<string>("namespace");
|
||||
@@ -126,8 +131,11 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
if (port !== "") {
|
||||
strValue = `${strValue} port=${port}`;
|
||||
}
|
||||
if (sslMode !== " ") {
|
||||
strValue = `${strValue} sslmode=${sslMode}`;
|
||||
}
|
||||
|
||||
strValue = `${strValue} sslmode=${sslMode}`;
|
||||
strValue = `${strValue} `;
|
||||
|
||||
return strValue.trim();
|
||||
}, [host, dbName, user, password, port, sslMode]);
|
||||
@@ -169,48 +177,46 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
configToString,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (useConnectionString) {
|
||||
// build connection_string
|
||||
const cs = configToString();
|
||||
setConnectionString(cs);
|
||||
|
||||
return;
|
||||
}
|
||||
// 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") + "" : " ");
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [useConnectionString]);
|
||||
|
||||
return (
|
||||
<Grid container className={classes.formScrollable}>
|
||||
<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"
|
||||
className={classes.formSlider}
|
||||
<FormSwitchWrapper
|
||||
label={"Manually Configure String"}
|
||||
checked={useConnectionString}
|
||||
id="manualString"
|
||||
name="manualString"
|
||||
onChange={(e) => {
|
||||
setUseConnectionString(e.target.checked);
|
||||
}}
|
||||
value={"manualString"}
|
||||
indicatorLabel={"On"}
|
||||
/>
|
||||
</Grid>
|
||||
{useConnectionString ? (
|
||||
@@ -229,81 +235,97 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
</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 item xs={12} className={classes.configureString}>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="host"
|
||||
name="host"
|
||||
label=""
|
||||
placeholder="Enter 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=""
|
||||
placeholder="Enter DB Name"
|
||||
value={dbName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDbName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="port"
|
||||
name="port"
|
||||
label=""
|
||||
placeholder="Enter Port"
|
||||
value={port}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPort(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
value={sslMode}
|
||||
label=""
|
||||
id="sslmode"
|
||||
name="sslmode"
|
||||
onChange={(e): void => {
|
||||
if (e.target.value !== undefined) {
|
||||
setSslMode(e.target.value + "");
|
||||
}
|
||||
}}
|
||||
options={[
|
||||
{ label: "Enter SSL Mode", value: " " },
|
||||
{ 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=""
|
||||
placeholder="Enter User"
|
||||
value={user}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUser(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="password"
|
||||
name="password"
|
||||
label=""
|
||||
type="password"
|
||||
placeholder="Enter Password"
|
||||
value={password}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.predefinedTitle}>
|
||||
Connection String
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.predefinedList}>
|
||||
{connectionString}
|
||||
</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);
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
@@ -312,6 +334,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
id="table"
|
||||
name="table"
|
||||
label="Table"
|
||||
placeholder={"Enter Table Name"}
|
||||
value={table}
|
||||
tooltip="DB table name to store/update events, table is auto-created"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -340,6 +363,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
id="queue-dir"
|
||||
name="queue_dir"
|
||||
label="Queue Dir"
|
||||
placeholder="Enter Queue Directory"
|
||||
value={queueDir}
|
||||
tooltip="staging dir for undelivered messages e.g. '/home/events'"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -352,6 +376,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
id="queue-limit"
|
||||
name="queue_limit"
|
||||
label="Queue Limit"
|
||||
placeholder="Enter Queue Limit"
|
||||
type="number"
|
||||
value={queueLimit}
|
||||
tooltip="maximum limit for undelivered messages, defaults to '10000'"
|
||||
@@ -361,11 +386,11 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
<CommentBoxWrapper
|
||||
id="comment"
|
||||
name="comment"
|
||||
label="Comment"
|
||||
multiline={true}
|
||||
placeholder="Enter Comment"
|
||||
value={comment}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setComment(e.target.value);
|
||||
|
||||
@@ -27,7 +27,9 @@ export type KVFieldType =
|
||||
| "duration"
|
||||
| "uri"
|
||||
| "sentence"
|
||||
| "csv";
|
||||
| "csv"
|
||||
| "comment"
|
||||
| "switch";
|
||||
|
||||
export interface KVField {
|
||||
name: string;
|
||||
@@ -37,6 +39,8 @@ export interface KVField {
|
||||
type: KVFieldType;
|
||||
options?: SelectorTypes[];
|
||||
multiline?: boolean;
|
||||
placeholder?: string;
|
||||
withBorder?: boolean;
|
||||
}
|
||||
|
||||
export interface IConfigurationElement {
|
||||
|
||||
@@ -16,27 +16,12 @@
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
createStyles,
|
||||
StyledProps,
|
||||
Theme,
|
||||
withStyles,
|
||||
} from "@material-ui/core/styles";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||
import Drawer from "@material-ui/core/Drawer";
|
||||
import Box from "@material-ui/core/Box";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Container from "@material-ui/core/Container";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import history from "../../history";
|
||||
import {
|
||||
Redirect,
|
||||
Route,
|
||||
RouteComponentProps,
|
||||
Router,
|
||||
Switch,
|
||||
withRouter,
|
||||
} from "react-router-dom";
|
||||
import { Redirect, Route, Router, Switch } from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
import { AppState } from "../../store";
|
||||
import {
|
||||
@@ -44,7 +29,6 @@ import {
|
||||
serverNeedsRestart,
|
||||
setMenuOpen,
|
||||
} from "../../actions";
|
||||
import { ThemedComponentProps } from "@material-ui/core/styles/withTheme";
|
||||
import Buckets from "./Buckets/Buckets";
|
||||
import Policies from "./Policies/Policies";
|
||||
import Permissions from "./Permissions/Permissions";
|
||||
@@ -58,8 +42,6 @@ import ListNotificationEndpoints from "./NotificationEndopoints/ListNotification
|
||||
import ConfigurationsList from "./Configurations/ConfigurationPanels/ConfigurationsList";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import WebhookPanel from "./Configurations/ConfigurationPanels/WebhookPanel";
|
||||
import Trace from "./Trace/Trace";
|
||||
import Logs from "./Logs/Logs";
|
||||
import Heal from "./Heal/Heal";
|
||||
import Watch from "./Watch/Watch";
|
||||
import ListTenants from "./Tenants/ListTenants/ListTenants";
|
||||
@@ -67,22 +49,9 @@ import { ISessionResponse } from "./types";
|
||||
import { saveSessionResponse } from "./actions";
|
||||
import TenantDetails from "./Tenants/TenantDetails/TenantDetails";
|
||||
import { clearSession } from "../../common/utils";
|
||||
import RemoteBuckets from "./RemoteBuckets/RemoteBuckets";
|
||||
import ObjectBrowser from "./ObjectBrowser/ObjectBrowser";
|
||||
import ListObjects from "./Buckets/ListBuckets/Objects/ListObjects/ListObjects";
|
||||
|
||||
function Copyright() {
|
||||
return (
|
||||
<Typography variant="body2" color="textSecondary" align="center">
|
||||
{"Copyright © "}
|
||||
<Link color="inherit" href="https://material-ui.com/">
|
||||
MinIO
|
||||
</Link>{" "}
|
||||
{new Date().getFullYear()}
|
||||
{"."}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
import License from "./License/License";
|
||||
|
||||
const drawerWidth = 245;
|
||||
|
||||
@@ -160,6 +129,8 @@ const styles = (theme: Theme) =>
|
||||
container: {
|
||||
paddingBottom: theme.spacing(4),
|
||||
margin: 0,
|
||||
width: "100%",
|
||||
maxWidth: "initial",
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(2),
|
||||
@@ -261,7 +232,11 @@ const Console = ({
|
||||
},
|
||||
{
|
||||
component: ListObjects,
|
||||
path: "/object-browser/:bucket?",
|
||||
path: "/object-browser/:bucket",
|
||||
},
|
||||
{
|
||||
component: ListObjects,
|
||||
path: "/object-browser/:bucket/*",
|
||||
},
|
||||
{
|
||||
component: Watch,
|
||||
@@ -279,18 +254,6 @@ const Console = ({
|
||||
component: Policies,
|
||||
path: "/policies",
|
||||
},
|
||||
{
|
||||
component: RemoteBuckets,
|
||||
path: "/remote-buckets",
|
||||
},
|
||||
{
|
||||
component: Trace,
|
||||
path: "/trace",
|
||||
},
|
||||
{
|
||||
component: Logs,
|
||||
path: "/logs",
|
||||
},
|
||||
{
|
||||
component: Heal,
|
||||
path: "/heal",
|
||||
@@ -327,12 +290,16 @@ const Console = ({
|
||||
component: TenantDetails,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName",
|
||||
},
|
||||
{
|
||||
component: License,
|
||||
path: "/license",
|
||||
},
|
||||
];
|
||||
const allowedRoutes = routes.filter((route: any) => allowedPages[route.path]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{session.status == "ok" ? (
|
||||
{session.status === "ok" ? (
|
||||
<div className={classes.root}>
|
||||
<CssBaseline />
|
||||
<Drawer
|
||||
@@ -385,9 +352,7 @@ const Console = ({
|
||||
/>
|
||||
))}
|
||||
{allowedRoutes.length > 0 ? (
|
||||
<Route exact path="/">
|
||||
<Redirect to={allowedRoutes[0].path} />
|
||||
</Route>
|
||||
<Redirect to={allowedRoutes[0].path} />
|
||||
) : null}
|
||||
</Switch>
|
||||
</Router>
|
||||
|
||||
@@ -20,15 +20,15 @@ import clsx from "clsx";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import NetworkCheckIcon from "@material-ui/icons/NetworkCheck";
|
||||
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";
|
||||
import { LinearProgress } from "@material-ui/core";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
|
||||
import AllBucketsIcon from "../../../icons/AllBucketsIcon";
|
||||
import UsageIcon from "../../../icons/UsageIcon";
|
||||
import EgressIcon from "../../../icons/EgressIcon";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -90,9 +90,13 @@ const styles = (theme: Theme) =>
|
||||
boxShadow: "none",
|
||||
},
|
||||
fixedHeight: {
|
||||
minHeight: 165,
|
||||
height: 165,
|
||||
minWidth: 247,
|
||||
marginRight: 20,
|
||||
padding: "25px 28px",
|
||||
"& svg": {
|
||||
maxHeight: 18,
|
||||
},
|
||||
},
|
||||
consumptionValue: {
|
||||
color: "#000000",
|
||||
@@ -106,6 +110,32 @@ const styles = (theme: Theme) =>
|
||||
notationContainer: {
|
||||
display: "flex",
|
||||
},
|
||||
dashboardBG: {
|
||||
width: 390,
|
||||
height: 255,
|
||||
zIndex: 500,
|
||||
position: "absolute",
|
||||
backgroundSize: "fill",
|
||||
|
||||
backgroundImage: "url(/images/BG_IllustrationDarker.svg)",
|
||||
backgroundPosition: "right bottom",
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundRepeat: "no-repeat",
|
||||
},
|
||||
dashboardContainer: {
|
||||
zIndex: 600,
|
||||
position: "absolute",
|
||||
},
|
||||
elementTitle: {
|
||||
fontWeight: 500,
|
||||
color: "#777777",
|
||||
fontSize: 14,
|
||||
marginTop: -9,
|
||||
},
|
||||
smallUnit: {
|
||||
fontSize: 20,
|
||||
},
|
||||
});
|
||||
|
||||
interface IDashboardProps {
|
||||
@@ -141,7 +171,19 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
if (usage === undefined) {
|
||||
return "0";
|
||||
}
|
||||
return niceBytes(usage);
|
||||
|
||||
const niceBytesUsage = niceBytes(usage).split(" ");
|
||||
|
||||
if (niceBytesUsage.length !== 2) {
|
||||
return niceBytesUsage.join(" ");
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{niceBytesUsage[0]}
|
||||
<span className={classes.smallUnit}>{niceBytesUsage[1]}</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const prettyNumber = (usage: number | undefined) => {
|
||||
@@ -155,7 +197,8 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<PageHeader label="Dashboard" />
|
||||
<Grid container>
|
||||
<div className={classes.dashboardBG} />
|
||||
<Grid container className={classes.dashboardContainer}>
|
||||
<Grid container spacing={3} className={classes.container}>
|
||||
{error !== "" && <Grid container>{error}</Grid>}
|
||||
{loading ? (
|
||||
@@ -168,10 +211,12 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
<Paper className={fixedHeightPaper}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid item className={classes.icon}>
|
||||
<ViewHeadlineIcon />
|
||||
<AllBucketsIcon />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h6">All Buckets</Typography>
|
||||
<Typography className={classes.elementTitle}>
|
||||
All buckets
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Typography className={classes.consumptionValue}>
|
||||
@@ -181,10 +226,12 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
<Paper className={fixedHeightPaper}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid item className={classes.icon}>
|
||||
<PieChartIcon />
|
||||
<UsageIcon />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h6">Usage</Typography>
|
||||
<Typography className={classes.elementTitle}>
|
||||
Usage
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Typography className={classes.consumptionValue}>
|
||||
@@ -194,10 +241,13 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
<Paper className={fixedHeightPaper}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid item className={classes.icon}>
|
||||
<NetworkCheckIcon />
|
||||
<EgressIcon />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h6"> Total Objects</Typography>
|
||||
<Typography className={classes.elementTitle}>
|
||||
{" "}
|
||||
Total Objects
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Typography className={classes.consumptionValue}>
|
||||
|
||||
@@ -19,12 +19,15 @@ 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 { modalBasic } from "../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
modalBasic,
|
||||
predefinedList,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import api from "../../../common/api";
|
||||
import UsersSelectors from "./UsersSelectors";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
interface IGroupProps {
|
||||
open: boolean;
|
||||
@@ -54,6 +57,7 @@ const styles = (theme: Theme) =>
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
...predefinedList,
|
||||
});
|
||||
|
||||
const AddGroup = ({
|
||||
@@ -64,11 +68,12 @@ const AddGroup = ({
|
||||
}: IGroupProps) => {
|
||||
//Local States
|
||||
const [groupName, setGroupName] = useState<string>("");
|
||||
const [groupEnabled, setGroupEnabled] = useState<string>("");
|
||||
const [groupEnabled, setGroupEnabled] = useState<boolean>(false);
|
||||
const [saving, isSaving] = useState<boolean>(false);
|
||||
const [addError, setError] = useState<string>("");
|
||||
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
|
||||
const [loadingGroup, isLoadingGroup] = useState<boolean>(false);
|
||||
const [validGroup, setValidGroup] = useState<boolean>(false);
|
||||
|
||||
//Effects
|
||||
useEffect(() => {
|
||||
@@ -80,6 +85,10 @@ const AddGroup = ({
|
||||
}
|
||||
}, [selectedGroup]);
|
||||
|
||||
useEffect(() => {
|
||||
setValidGroup(groupName.trim() !== "");
|
||||
}, [groupName, selectedUsers]);
|
||||
|
||||
useEffect(() => {
|
||||
if (saving) {
|
||||
const saveRecord = () => {
|
||||
@@ -88,7 +97,7 @@ const AddGroup = ({
|
||||
.invoke("PUT", `/api/v1/groups/${groupName}`, {
|
||||
group: groupName,
|
||||
members: selectedUsers,
|
||||
status: groupEnabled,
|
||||
status: groupEnabled ? "enabled" : "disabled",
|
||||
})
|
||||
.then((res) => {
|
||||
isSaving(false);
|
||||
@@ -133,7 +142,7 @@ const AddGroup = ({
|
||||
api
|
||||
.invoke("GET", `/api/v1/groups/${selectedGroup}`)
|
||||
.then((res: MainGroupProps) => {
|
||||
setGroupEnabled(res.status);
|
||||
setGroupEnabled(res.status === "enabled");
|
||||
setGroupName(res.name);
|
||||
setSelectedUsers(res.members);
|
||||
})
|
||||
@@ -153,12 +162,35 @@ const AddGroup = ({
|
||||
isSaving(true);
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
if (selectedGroup === null) {
|
||||
setGroupName("");
|
||||
}
|
||||
|
||||
setSelectedUsers([]);
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={closeModalAndRefresh}
|
||||
title={selectedGroup !== null ? `Group Edit - ${groupName}` : "Add Group"}
|
||||
title={selectedGroup !== null ? `Edit Group` : "Create Group"}
|
||||
>
|
||||
{selectedGroup !== null && (
|
||||
<div className={classes.floatingEnabled}>
|
||||
<FormSwitchWrapper
|
||||
indicatorLabel={"Enabled"}
|
||||
checked={groupEnabled}
|
||||
value={"group_enabled"}
|
||||
id="group-status"
|
||||
name="group-status"
|
||||
onChange={(e) => {
|
||||
setGroupEnabled(e.target.checked);
|
||||
}}
|
||||
switchOnly
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<form noValidate autoComplete="off" onSubmit={setSaving}>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
@@ -174,31 +206,13 @@ const AddGroup = ({
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{selectedGroup !== null ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<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>
|
||||
) : (
|
||||
{selectedGroup === null ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="group-name"
|
||||
name="group-name"
|
||||
label="Name"
|
||||
label="Group Name"
|
||||
value={groupName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setGroupName(e.target.value);
|
||||
@@ -206,23 +220,38 @@ const AddGroup = ({
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.predefinedTitle}>
|
||||
Group Name
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.predefinedList}>
|
||||
{selectedGroup}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<UsersSelectors
|
||||
selectedUsers={selectedUsers}
|
||||
setSelectedUsers={setSelectedUsers}
|
||||
editMode={selectedGroup !== null}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<button
|
||||
type="button"
|
||||
color="primary"
|
||||
className={classes.clearButton}
|
||||
onClick={resetForm}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={saving}
|
||||
disabled={saving || !validGroup}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
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";
|
||||
@@ -31,7 +30,11 @@ import AddGroup from "../Groups/AddGroup";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import SetPolicy from "../Policies/SetPolicy";
|
||||
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
|
||||
interface IGroupsProps {
|
||||
@@ -74,18 +77,8 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { LinearProgress } from "@material-ui/core";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Title from "../../../common/Title";
|
||||
import { UsersList } from "../Users/types";
|
||||
import { usersSort } from "../../../utils/sortFunctions";
|
||||
import api from "../../../common/api";
|
||||
@@ -28,11 +27,13 @@ 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";
|
||||
import { actionsTray } from "../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
interface IGroupsProps {
|
||||
classes: any;
|
||||
selectedUsers: string[];
|
||||
setSelectedUsers: any;
|
||||
editMode?: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -41,10 +42,11 @@ const styles = (theme: Theme) =>
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
paper: {
|
||||
// padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column",
|
||||
paddingTop: 15,
|
||||
boxShadow: "none",
|
||||
},
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
@@ -70,36 +72,43 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "left",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
filterField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
width: "100%",
|
||||
zIndex: 500,
|
||||
},
|
||||
noFound: {
|
||||
textAlign: "center",
|
||||
padding: "10px 0",
|
||||
},
|
||||
tableContainer: {
|
||||
maxHeight: 250,
|
||||
maxHeight: 200,
|
||||
},
|
||||
stickyHeader: {
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
actionsTitle: {
|
||||
fontWeight: 600,
|
||||
color: "#000",
|
||||
fontSize: 16,
|
||||
alignSelf: "center",
|
||||
},
|
||||
tableBlock: {
|
||||
marginTop: 15,
|
||||
},
|
||||
filterField: {
|
||||
width: 375,
|
||||
fontWeight: 600,
|
||||
"& .input": {
|
||||
"&::placeholder": {
|
||||
fontWeight: 600,
|
||||
color: "#000",
|
||||
},
|
||||
},
|
||||
},
|
||||
...actionsTray,
|
||||
});
|
||||
|
||||
const UsersSelectors = ({
|
||||
classes,
|
||||
selectedUsers,
|
||||
setSelectedUsers,
|
||||
editMode = false,
|
||||
}: IGroupsProps) => {
|
||||
//Local States
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
@@ -166,21 +175,22 @@ const UsersSelectors = ({
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title>Members</Title>
|
||||
{error !== "" ? <div>{error}</div> : <React.Fragment />}
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{error !== "" ? <div>{error}</div> : <React.Fragment />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<span className={classes.actionsTitle}>
|
||||
{editMode ? "Edit" : "Assign"} Members
|
||||
</span>
|
||||
<TextField
|
||||
placeholder="Filter Groups"
|
||||
className={classes.filterField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
@@ -192,7 +202,7 @@ const UsersSelectors = ({
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
<TableWrapper
|
||||
columns={[{ label: "Access Key", elementKey: "accessKey" }]}
|
||||
onSelect={selectionChanged}
|
||||
|
||||