Compare commits
34 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 |
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.1
|
||||
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.1
|
||||
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
|
||||
}
|
||||
@@ -65,6 +65,9 @@ type BucketObject struct {
|
||||
// size
|
||||
Size int64 `json:"size,omitempty"`
|
||||
|
||||
// tags
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
|
||||
// user tags
|
||||
UserTags map[string]string `json:"user_tags,omitempty"`
|
||||
|
||||
|
||||
@@ -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,27 +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"
|
||||
license = "/license"
|
||||
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 {
|
||||
@@ -60,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(
|
||||
@@ -118,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(
|
||||
@@ -245,25 +224,24 @@ var licenseActionSet = ConfigurationActionSet{
|
||||
|
||||
// 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,
|
||||
license: licenseActionSet,
|
||||
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: 5,
|
||||
want: 6,
|
||||
},
|
||||
{
|
||||
name: "policies endpoint",
|
||||
@@ -63,7 +63,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"admin:ListUserPolicies",
|
||||
},
|
||||
},
|
||||
want: 5,
|
||||
want: 6,
|
||||
},
|
||||
{
|
||||
name: "all admin endpoints",
|
||||
@@ -72,7 +72,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"admin:*",
|
||||
},
|
||||
},
|
||||
want: 16,
|
||||
want: 15,
|
||||
},
|
||||
{
|
||||
name: "all s3 endpoints",
|
||||
@@ -81,7 +81,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 7,
|
||||
want: 8,
|
||||
},
|
||||
{
|
||||
name: "all admin and s3 endpoints",
|
||||
@@ -91,7 +91,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 19,
|
||||
want: 18,
|
||||
},
|
||||
{
|
||||
name: "no 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 |
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;
|
||||
@@ -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;
|
||||
|
||||
33
portal-ui/src/icons/RemoveIcon.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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 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 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;
|
||||
@@ -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";
|
||||
@@ -68,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;
|
||||
@@ -103,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();
|
||||
@@ -130,7 +130,7 @@ const AddBucket = ({
|
||||
.then((res) => {
|
||||
setAddLoading(false);
|
||||
setAddError("");
|
||||
closeModalAndRefresh();
|
||||
closeModalAndRefresh(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddLoading(false);
|
||||
@@ -141,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
|
||||
@@ -151,7 +175,7 @@ const AddBucket = ({
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
setAddError("");
|
||||
closeModalAndRefresh();
|
||||
closeModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
@@ -196,7 +220,8 @@ const AddBucket = ({
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
addBucketVersioned(event.target.checked);
|
||||
}}
|
||||
label={"Turn On Versioning"}
|
||||
label={"Versioning"}
|
||||
indicatorLabel={"On"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
@@ -209,6 +234,7 @@ const AddBucket = ({
|
||||
addBucketQuota(event.target.checked);
|
||||
}}
|
||||
label={"Enable Bucket Quota"}
|
||||
indicatorLabel={"On"}
|
||||
/>
|
||||
</Grid>
|
||||
{enableQuota && (
|
||||
@@ -264,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";
|
||||
@@ -93,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);
|
||||
@@ -136,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) => {
|
||||
@@ -184,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 && (
|
||||
@@ -219,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
|
||||
@@ -269,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));
|
||||
@@ -14,29 +14,48 @@
|
||||
// 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({
|
||||
@@ -68,84 +87,181 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
"@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);
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.fetchRecords();
|
||||
}
|
||||
const showSnackBarMessage = (text: string) => {
|
||||
setSnackbarMessage(text);
|
||||
setOpenSnackbar(true);
|
||||
};
|
||||
|
||||
closeDeleteModalAndRefresh(refresh: boolean) {
|
||||
this.setState({ deleteOpen: false }, () => {
|
||||
if (refresh) {
|
||||
this.fetchRecords();
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
download(bucketName: string, objectName: string) {
|
||||
var anchor = document.createElement("a");
|
||||
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")!;
|
||||
var xhr = new XMLHttpRequest();
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open(
|
||||
"GET",
|
||||
@@ -155,12 +271,12 @@ class ListObjects extends React.Component<
|
||||
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
|
||||
xhr.responseType = "blob";
|
||||
|
||||
xhr.onload = function(e) {
|
||||
if (this.status == 200) {
|
||||
var blob = new Blob([this.response], {
|
||||
xhr.onload = function (e) {
|
||||
if (this.status === 200) {
|
||||
const blob = new Blob([this.response], {
|
||||
type: "octet/stream",
|
||||
});
|
||||
var blobUrl = window.URL.createObjectURL(blob);
|
||||
const blobUrl = window.URL.createObjectURL(blob);
|
||||
|
||||
anchor.href = blobUrl;
|
||||
anchor.download = objectName;
|
||||
@@ -171,115 +287,228 @@ class ListObjects extends React.Component<
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
};
|
||||
|
||||
bucketFilter(): void {}
|
||||
const displayParsedDate = (date: string) => {
|
||||
return <reactMoment.default>{date}</reactMoment.default>;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const {
|
||||
records,
|
||||
loading,
|
||||
selectedObject,
|
||||
selectedBucket,
|
||||
deleteOpen,
|
||||
filterObjects,
|
||||
} = this.state;
|
||||
const displayParsedDate = (date: string) => {
|
||||
return <Moment>{date}</Moment>;
|
||||
};
|
||||
const confirmDeleteObject = (object: string) => {
|
||||
setDeleteOpen(true);
|
||||
setSelectedObject(object);
|
||||
};
|
||||
|
||||
const confirmDeleteObject = (object: string) => {
|
||||
this.setState({ deleteOpen: true, selectedObject: object });
|
||||
};
|
||||
const downloadObject = (object: string) => {
|
||||
download(selectedBucket, object);
|
||||
};
|
||||
|
||||
const downloadObject = (object: string) => {
|
||||
this.download(selectedBucket, object);
|
||||
};
|
||||
const openPath = (idElement: string) => {
|
||||
const currentPath = get(match, "url", "/object-browser");
|
||||
|
||||
const tableActions = [
|
||||
{ type: "download", onClick: downloadObject, sendOnlyId: true },
|
||||
{ type: "delete", onClick: confirmDeleteObject, sendOnlyId: true },
|
||||
];
|
||||
// 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 filteredRecords = records.filter((b: BucketObject) => {
|
||||
if (filterObjects === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterObjects) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
addRoute(newPath, idElementClean[lastIndex]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<PageHeader label="Objects" />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Objects"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
this.setState({
|
||||
filterObjects: val.target.value,
|
||||
});
|
||||
<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);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{
|
||||
label: "Last Modified",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
entityName="Objects"
|
||||
idField="name"
|
||||
records={filteredRecords}
|
||||
/>
|
||||
</Grid>
|
||||
>
|
||||
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
|
||||
placeholder="Search Objects"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterObjects(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: displayName,
|
||||
},
|
||||
{
|
||||
label: "Last Modified",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
entityName="Objects"
|
||||
idField="name"
|
||||
records={filteredRecords}
|
||||
/>
|
||||
</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,7 +22,6 @@ 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 } from "../types";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -19,6 +19,11 @@ export interface Bucket {
|
||||
creation_date: Date;
|
||||
}
|
||||
|
||||
export interface BucketEncryptionInfo {
|
||||
algorithm: string;
|
||||
kmsMasterKeyID: string;
|
||||
}
|
||||
|
||||
export interface BucketInfo {
|
||||
name: string;
|
||||
access: 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 !== "" && (
|
||||
|
||||
@@ -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, { useEffect, useState } from "react";
|
||||
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";
|
||||
@@ -22,7 +22,7 @@ import { actionsTray, fieldBasic } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
interface IFormSwitch {
|
||||
label: string;
|
||||
label?: string;
|
||||
classes: any;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
value: string | boolean;
|
||||
@@ -31,7 +31,9 @@ interface IFormSwitch {
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
index?: number;
|
||||
indicatorLabel?: string;
|
||||
checked: boolean;
|
||||
switchOnly?: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -82,7 +84,7 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
actionsTitle: {
|
||||
fontWeight: 600,
|
||||
color: "#000",
|
||||
color: "#081C42",
|
||||
fontSize: 16,
|
||||
alignSelf: "center",
|
||||
},
|
||||
@@ -95,7 +97,7 @@ const styles = (theme: Theme) =>
|
||||
"& .input": {
|
||||
"&::placeholder": {
|
||||
fontWeight: 600,
|
||||
color: "#000",
|
||||
color: "#081C42",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -107,6 +109,15 @@ const styles = (theme: Theme) =>
|
||||
paddingBottom: 14,
|
||||
marginBottom: 20,
|
||||
},
|
||||
indicatorLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
color: "#081C42",
|
||||
margin: "0 8px 0 10px",
|
||||
},
|
||||
switchContainer: {
|
||||
display: "flex",
|
||||
},
|
||||
...actionsTray,
|
||||
...fieldBasic,
|
||||
});
|
||||
@@ -127,7 +138,7 @@ const StyledSwitch = withStyles({
|
||||
color: "#fff",
|
||||
},
|
||||
"&$checked + $track": {
|
||||
backgroundColor: "#000",
|
||||
backgroundColor: "#081C42",
|
||||
opacity: 1,
|
||||
height: 15,
|
||||
},
|
||||
@@ -135,14 +146,14 @@ const StyledSwitch = withStyles({
|
||||
checked: {},
|
||||
track: {
|
||||
height: 15,
|
||||
backgroundColor: "#000",
|
||||
backgroundColor: "#081C42",
|
||||
opacity: 1,
|
||||
padding: 0,
|
||||
marginTop: 1.5,
|
||||
},
|
||||
thumb: {
|
||||
backgroundColor: "#fff",
|
||||
border: "#000 1px solid",
|
||||
border: "#081C42 1px solid",
|
||||
boxShadow: "none",
|
||||
width: 18,
|
||||
height: 18,
|
||||
@@ -152,16 +163,44 @@ const StyledSwitch = withStyles({
|
||||
})(Switch);
|
||||
|
||||
const FormSwitchWrapper = ({
|
||||
label,
|
||||
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}`}>
|
||||
@@ -177,21 +216,7 @@ const FormSwitchWrapper = ({
|
||||
)}
|
||||
</InputLabel>
|
||||
)}
|
||||
|
||||
<div className={classes.textBoxContainer}>
|
||||
<StyledSwitch
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
color="primary"
|
||||
name={name}
|
||||
inputProps={{ "aria-label": "primary checkbox" }}
|
||||
disabled={disabled}
|
||||
disableRipple
|
||||
disableFocusRipple
|
||||
disableTouchRipple
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
{switchComponent}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -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,6 +72,15 @@ 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) =>
|
||||
@@ -76,15 +91,24 @@ const inputStyles = makeStyles((theme: Theme) =>
|
||||
borderColor: "#9c9c9c",
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
"&.MuiInput-underline::before": {
|
||||
borderColor: "#eaeaea",
|
||||
borderBottomStyle: "solid",
|
||||
},
|
||||
},
|
||||
input: {
|
||||
padding: "15px 5px 10px",
|
||||
padding: "15px 30px 10px 5px",
|
||||
color: "#393939",
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
"&:placeholder": {
|
||||
color: "#393939",
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
error: {
|
||||
color: "#b53b4b",
|
||||
boxShadow: "inset 0px 0px 1px 1px #b53b4b",
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -117,6 +141,8 @@ const InputBoxWrapper = ({
|
||||
placeholder = "",
|
||||
min,
|
||||
max,
|
||||
overlayIcon = null,
|
||||
overlayAction,
|
||||
classes,
|
||||
}: InputBoxProps) => {
|
||||
let inputProps: any = { "data-index": index };
|
||||
@@ -139,12 +165,7 @@ const InputBoxWrapper = ({
|
||||
}`}
|
||||
>
|
||||
{label !== "" && (
|
||||
<InputLabel
|
||||
htmlFor={id}
|
||||
className={`${error !== "" ? classes.fieldLabelError : ""} ${
|
||||
classes.inputLabel
|
||||
}`}
|
||||
>
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>
|
||||
{label}
|
||||
{required ? "*" : ""}
|
||||
@@ -174,12 +195,28 @@ const InputBoxWrapper = ({
|
||||
error={error !== ""}
|
||||
helperText={error}
|
||||
placeholder={placeholder}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
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
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -20,7 +20,6 @@ export const fieldBasic = {
|
||||
inputLabel: {
|
||||
fontWeight: 600,
|
||||
marginRight: 10,
|
||||
width: 160,
|
||||
fontSize: 15,
|
||||
color: "#000",
|
||||
textAlign: "left" as const,
|
||||
@@ -36,6 +35,7 @@ export const fieldBasic = {
|
||||
},
|
||||
fieldContainer: {
|
||||
marginBottom: 20,
|
||||
position: "relative" as const,
|
||||
},
|
||||
tooltipContainer: {
|
||||
marginLeft: 5,
|
||||
@@ -53,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 = {
|
||||
@@ -76,6 +100,21 @@ export const checkboxIcons = {
|
||||
},
|
||||
};
|
||||
|
||||
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",
|
||||
},
|
||||
};
|
||||
|
||||
export const containerForHeader = (bottomSpacing: any) => ({
|
||||
container: {
|
||||
padding: "110px 33px 30px",
|
||||
@@ -106,7 +145,7 @@ export const actionsTray = {
|
||||
export const searchField = {
|
||||
searchField: {
|
||||
flexGrow: 1,
|
||||
marginRight: 30,
|
||||
height: 40,
|
||||
background: "#FFFFFF",
|
||||
borderRadius: 5,
|
||||
border: "#EAEDEE 1px solid",
|
||||
@@ -137,5 +176,34 @@ export const predefinedList = {
|
||||
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:
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,7 @@
|
||||
import React from "react";
|
||||
import { Dialog, DialogContent, DialogTitle } from "@material-ui/core";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import {
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
} from "@material-ui/core/styles";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
|
||||
interface IModalProps {
|
||||
classes: any;
|
||||
@@ -29,11 +24,12 @@ 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",
|
||||
@@ -61,10 +57,10 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
modalCloseIcon: {
|
||||
fontSize: 35,
|
||||
color: "#707070",
|
||||
color: "#9C9C9C",
|
||||
fontWeight: 300,
|
||||
"&:hover": {
|
||||
color: "#000",
|
||||
color: "#9C9C9C",
|
||||
},
|
||||
},
|
||||
closeIcon: {
|
||||
@@ -77,7 +73,7 @@ const styles = (theme: Theme) =>
|
||||
transform: "rotate(-45deg)",
|
||||
},
|
||||
"&:hover::before, &:hover::after": {
|
||||
borderColor: "#000",
|
||||
borderColor: "#9C9C9C",
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
@@ -95,6 +91,10 @@ const styles = (theme: Theme) =>
|
||||
modalContent: {
|
||||
padding: "0 50px",
|
||||
},
|
||||
customDialogSize: {
|
||||
width: "100%",
|
||||
maxWidth: 765,
|
||||
},
|
||||
});
|
||||
|
||||
const ModalWrapper = ({
|
||||
@@ -103,15 +103,22 @@ const ModalWrapper = ({
|
||||
title,
|
||||
children,
|
||||
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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,9 +114,18 @@ const styles = (theme: Theme) =>
|
||||
overflow: "auto",
|
||||
flexDirection: "column",
|
||||
padding: "19px 38px",
|
||||
minHeight: "200px",
|
||||
boxShadow: "none",
|
||||
border: "#e7e7e7 1px solid",
|
||||
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",
|
||||
@@ -111,13 +133,15 @@ 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,
|
||||
@@ -130,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: {
|
||||
@@ -143,6 +171,7 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
checkBoxRow: {
|
||||
borderColor: borderColor,
|
||||
padding: "0 10px 0 0",
|
||||
},
|
||||
loadingBox: {
|
||||
paddingTop: "100px",
|
||||
@@ -153,12 +182,17 @@ const styles = (theme: Theme) =>
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: "#ececec",
|
||||
|
||||
"& td": {
|
||||
fontWeight: 600,
|
||||
},
|
||||
},
|
||||
},
|
||||
rowClickable: {
|
||||
cursor: "pointer",
|
||||
},
|
||||
...checkboxIcons,
|
||||
...radioIcons,
|
||||
});
|
||||
|
||||
// Function that renders Title Columns
|
||||
@@ -186,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>
|
||||
@@ -239,6 +277,8 @@ const TableWrapper = ({
|
||||
idField,
|
||||
classes,
|
||||
stickyHeader = false,
|
||||
radioSelection = false,
|
||||
customEmptyMessage = "",
|
||||
paginatorConfig,
|
||||
}: TableWrapperProps) => {
|
||||
const findView = itemActions
|
||||
@@ -273,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>
|
||||
)}
|
||||
@@ -312,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"
|
||||
@@ -333,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>
|
||||
)}
|
||||
@@ -362,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";
|
||||
@@ -63,7 +62,6 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
configuration_id: "",
|
||||
configuration_label: "",
|
||||
});
|
||||
const [error, setError] = useState("");
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
const tableActions = [
|
||||
@@ -103,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";
|
||||
@@ -71,19 +53,6 @@ import ObjectBrowser from "./ObjectBrowser/ObjectBrowser";
|
||||
import ListObjects from "./Buckets/ListBuckets/Objects/ListObjects/ListObjects";
|
||||
import License from "./License/License";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
const drawerWidth = 245;
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -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,14 +254,6 @@ const Console = ({
|
||||
component: Policies,
|
||||
path: "/policies",
|
||||
},
|
||||
{
|
||||
component: Trace,
|
||||
path: "/trace",
|
||||
},
|
||||
{
|
||||
component: Logs,
|
||||
path: "/logs",
|
||||
},
|
||||
{
|
||||
component: Heal,
|
||||
path: "/heal",
|
||||
@@ -332,7 +299,7 @@ const Console = ({
|
||||
|
||||
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,9 +20,6 @@ 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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>Assign Users</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}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { HorizontalBar } from "react-chartjs-2";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Grid,
|
||||
Typography,
|
||||
TextField,
|
||||
Checkbox,
|
||||
} from "@material-ui/core";
|
||||
import { Button, Grid, TextField, InputBase } from "@material-ui/core";
|
||||
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { wsProtocol } from "../../../utils/wsUtils";
|
||||
@@ -15,8 +9,13 @@ import { FormControl, MenuItem, Select } from "@material-ui/core";
|
||||
import { BucketList, Bucket } from "../Watch/types";
|
||||
import { HealStatus, colorH } from "./types";
|
||||
import { niceBytes } from "../../../common/utils";
|
||||
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import CheckboxWrapper from "../Common/FormComponents/CheckboxWrapper/CheckboxWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -35,31 +34,27 @@ const styles = (theme: Theme) =>
|
||||
borderBottom: "1px solid #dedede",
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
graphContainer: {
|
||||
backgroundColor: "#fff",
|
||||
border: "#EAEDEE 1px solid",
|
||||
borderRadius: 3,
|
||||
padding: "19px 38px",
|
||||
},
|
||||
inputField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
marginLeft: 10,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
scanInfo: {
|
||||
marginTop: 20,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
fieldContainer: {
|
||||
background: "#FFFFFF",
|
||||
padding: 0,
|
||||
borderRadius: 5,
|
||||
marginLeft: 10,
|
||||
textAlign: "left",
|
||||
minWidth: "206",
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
scanData: {
|
||||
fontSize: 13,
|
||||
},
|
||||
lastElementWPadding: {
|
||||
paddingRight: "78",
|
||||
inlineCheckboxes: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
@@ -67,6 +62,26 @@ interface IHeal {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const SelectStyled = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
width: 450,
|
||||
lineHeight: 1,
|
||||
marginRight: 15,
|
||||
"label + &": {
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
"& .MuiSelect-select:focus": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
input: {
|
||||
fontSize: 13,
|
||||
lineHeight: 15,
|
||||
},
|
||||
})
|
||||
)(InputBase);
|
||||
|
||||
const Heal = ({ classes }: IHeal) => {
|
||||
const [start, setStart] = useState(false);
|
||||
const [bucketName, setBucketName] = useState("Select Bucket");
|
||||
@@ -171,7 +186,7 @@ const Heal = ({ classes }: IHeal) => {
|
||||
};
|
||||
}
|
||||
}
|
||||
}, [start]);
|
||||
}, [start, bucketName, forceStart, forceStop, prefix, recursive]);
|
||||
|
||||
let data = {
|
||||
labels: ["Green", "Yellow", "Red", "Grey"],
|
||||
@@ -210,8 +225,8 @@ const Heal = ({ classes }: IHeal) => {
|
||||
onChange={(e) => {
|
||||
setBucketName(e.target.value as string);
|
||||
}}
|
||||
className={classes.fieldContainer}
|
||||
disabled={false}
|
||||
className={classes.searchField}
|
||||
input={<SelectStyled />}
|
||||
>
|
||||
<MenuItem value="" key={`select-bucket-name-default`}>
|
||||
Select Bucket
|
||||
@@ -228,7 +243,7 @@ const Heal = ({ classes }: IHeal) => {
|
||||
</FormControl>
|
||||
<TextField
|
||||
placeholder="Prefix"
|
||||
className={classes.inputField}
|
||||
className={classes.searchField}
|
||||
id="prefix-resource"
|
||||
label=""
|
||||
disabled={false}
|
||||
@@ -248,76 +263,75 @@ const Heal = ({ classes }: IHeal) => {
|
||||
>
|
||||
Start
|
||||
</Button>
|
||||
<Grid item xs={12}>
|
||||
<span>{"Recursive"}</span>
|
||||
<Checkbox
|
||||
name="recursive"
|
||||
id="recursive"
|
||||
value="recursive"
|
||||
color="primary"
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
checked={recursive}
|
||||
onChange={(e) => {
|
||||
setRecursive(e.target.checked);
|
||||
}}
|
||||
disabled={false}
|
||||
/>
|
||||
<span>{"Force Start"}</span>
|
||||
<Checkbox
|
||||
name="recursive"
|
||||
id="recursive"
|
||||
value="recursive"
|
||||
color="primary"
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
checked={forceStart}
|
||||
onChange={(e) => {
|
||||
setForceStart(e.target.checked);
|
||||
}}
|
||||
disabled={false}
|
||||
/>
|
||||
<span>{"Force Stop"}</span>
|
||||
<Checkbox
|
||||
name="recursive"
|
||||
id="recursive"
|
||||
value="recursive"
|
||||
color="primary"
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
checked={forceStop}
|
||||
onChange={(e) => {
|
||||
setForceStop(e.target.checked);
|
||||
}}
|
||||
disabled={false}
|
||||
/>
|
||||
<span className={classes.lastElementWPadding}>{""}</span>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.inlineCheckboxes}>
|
||||
<CheckboxWrapper
|
||||
name="recursive"
|
||||
id="recursive"
|
||||
value="recursive"
|
||||
checked={recursive}
|
||||
onChange={(e) => {
|
||||
setRecursive(e.target.checked);
|
||||
}}
|
||||
disabled={false}
|
||||
label="Recursive"
|
||||
/>
|
||||
<CheckboxWrapper
|
||||
name="forceStart"
|
||||
id="forceStart"
|
||||
value="forceStart"
|
||||
checked={forceStart}
|
||||
onChange={(e) => {
|
||||
setForceStart(e.target.checked);
|
||||
}}
|
||||
disabled={false}
|
||||
label="Force Start"
|
||||
/>
|
||||
<CheckboxWrapper
|
||||
name="forceStop"
|
||||
id="forceStop"
|
||||
value="forceStop"
|
||||
checked={forceStop}
|
||||
onChange={(e) => {
|
||||
setForceStop(e.target.checked);
|
||||
}}
|
||||
disabled={false}
|
||||
label="Force Stop"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.graphContainer}>
|
||||
<HorizontalBar
|
||||
data={data}
|
||||
width={80}
|
||||
height={30}
|
||||
options={{
|
||||
title: {
|
||||
display: true,
|
||||
text: "Item's Health Status [%]",
|
||||
fontSize: 20,
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: "right",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Grid item xs={12} className={classes.scanInfo}>
|
||||
<div className={classes.scanData}>
|
||||
<strong>Size scanned:</strong> {hStatus.sizeScanned}
|
||||
</div>
|
||||
<div className={classes.scanData}>
|
||||
<strong>Objects healed:</strong> {hStatus.objectsHealed} /{" "}
|
||||
{hStatus.objectsScanned}
|
||||
</div>
|
||||
<div className={classes.scanData}>
|
||||
<strong>Healing time:</strong> {hStatus.healDuration}s
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<HorizontalBar
|
||||
data={data}
|
||||
width={100}
|
||||
height={30}
|
||||
options={{
|
||||
title: {
|
||||
display: true,
|
||||
text: "Item's Health Status [%]",
|
||||
fontSize: 20,
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: "right",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
Size scanned: {hStatus.sizeScanned}
|
||||
<br />
|
||||
Objects healed: {hStatus.objectsHealed} / {hStatus.objectsScanned}
|
||||
<br />
|
||||
Healing time: {hStatus.healDuration}s
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { useEffect } from "react";
|
||||
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
|
||||
import { AppState } from "../../../store";
|
||||
import { connect } from "react-redux";
|
||||
import { logMessageReceived, logResetMessages } from "./actions";
|
||||
import { LogMessage } from "./types";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { timeFromDate } from "../../../common/utils";
|
||||
import { isNullOrUndefined } from "util";
|
||||
import { wsProtocol } from "../../../utils/wsUtils";
|
||||
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
logList: {
|
||||
background: "white",
|
||||
maxHeight: "400px",
|
||||
overflow: "auto",
|
||||
"& ul": {
|
||||
margin: "4px",
|
||||
padding: "0px",
|
||||
},
|
||||
"& ul li": {
|
||||
listStyle: "none",
|
||||
margin: "0px",
|
||||
padding: "0px",
|
||||
borderBottom: "1px solid #dedede",
|
||||
},
|
||||
},
|
||||
tab: {
|
||||
padding: "25px",
|
||||
},
|
||||
logerror: {
|
||||
color: "#A52A2A",
|
||||
},
|
||||
logerror_tab: {
|
||||
color: "#A52A2A",
|
||||
padding: "25px",
|
||||
},
|
||||
ansidefault: {
|
||||
color: "black",
|
||||
},
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
interface ILogs {
|
||||
classes: any;
|
||||
logMessageReceived: typeof logMessageReceived;
|
||||
logResetMessages: typeof logResetMessages;
|
||||
messages: LogMessage[];
|
||||
}
|
||||
|
||||
const Logs = ({
|
||||
classes,
|
||||
logMessageReceived,
|
||||
logResetMessages,
|
||||
messages,
|
||||
}: ILogs) => {
|
||||
useEffect(() => {
|
||||
logResetMessages();
|
||||
const url = new URL(window.location.toString());
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
const port = isDev ? "9090" : url.port;
|
||||
|
||||
const wsProt = wsProtocol(url.protocol);
|
||||
|
||||
const c = new W3CWebSocket(
|
||||
`${wsProt}://${url.hostname}:${port}/ws/console`
|
||||
);
|
||||
|
||||
let interval: any | null = null;
|
||||
if (c !== null) {
|
||||
c.onopen = () => {
|
||||
console.log("WebSocket Client Connected");
|
||||
c.send("ok");
|
||||
interval = setInterval(() => {
|
||||
c.send("ok");
|
||||
}, 10 * 1000);
|
||||
};
|
||||
c.onmessage = (message: IMessageEvent) => {
|
||||
// console.log(message.data.toString())
|
||||
let m: LogMessage = JSON.parse(message.data.toString());
|
||||
m.time = new Date(m.time.toString());
|
||||
m.key = Math.random();
|
||||
logMessageReceived(m);
|
||||
};
|
||||
c.onclose = () => {
|
||||
clearInterval(interval);
|
||||
console.log("connection closed by server");
|
||||
};
|
||||
return () => {
|
||||
c.close(1000);
|
||||
clearInterval(interval);
|
||||
console.log("closing websockets");
|
||||
};
|
||||
}
|
||||
}, [logMessageReceived]);
|
||||
|
||||
// replaces a character of a string with other at a given index
|
||||
const replaceWeirdChar = (
|
||||
origString: string,
|
||||
replaceChar: string,
|
||||
index: number
|
||||
) => {
|
||||
let firstPart = origString.substr(0, index);
|
||||
let lastPart = origString.substr(index + 1);
|
||||
|
||||
let newString = firstPart + replaceChar + lastPart;
|
||||
return newString;
|
||||
};
|
||||
|
||||
const renderError = (logElement: LogMessage) => {
|
||||
let errorElems = [];
|
||||
if (!isNullOrUndefined(logElement.error)) {
|
||||
if (logElement.api && logElement.api.name) {
|
||||
errorElems.push(
|
||||
<li key={`api-${logElement.key}`}>
|
||||
<span className={classes.logerror}>API: {logElement.api.name}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
if (logElement.time) {
|
||||
errorElems.push(
|
||||
<li key={`time-${logElement.key}`}>
|
||||
<span className={classes.logerror}>
|
||||
Time: {timeFromDate(logElement.time)}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
if (logElement.deploymentid) {
|
||||
errorElems.push(
|
||||
<li key={`deploytmentid-${logElement.key}`}>
|
||||
<span className={classes.logerror}>
|
||||
DeploymentID: {logElement.deploymentid}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
if (logElement.requestID) {
|
||||
errorElems.push(
|
||||
<li key={`requestid-${logElement.key}`}>
|
||||
<span className={classes.logerror}>
|
||||
RequestID: {logElement.requestID}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
if (logElement.remotehost) {
|
||||
errorElems.push(
|
||||
<li key={`remotehost-${logElement.key}`}>
|
||||
<span className={classes.logerror}>
|
||||
RemoteHost: {logElement.remotehost}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
if (logElement.host) {
|
||||
errorElems.push(
|
||||
<li key={`host-${logElement.key}`}>
|
||||
<span className={classes.logerror}>Host: {logElement.host}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
if (logElement.userAgent) {
|
||||
errorElems.push(
|
||||
<li key={`useragent-${logElement.key}`}>
|
||||
<span className={classes.logerror}>
|
||||
UserAgent: {logElement.userAgent}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
if (logElement.error.message) {
|
||||
errorElems.push(
|
||||
<li key={`message-${logElement.key}`}>
|
||||
<span className={classes.logerror}>
|
||||
Error: {logElement.error.message}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
if (logElement.error.source) {
|
||||
// for all sources add padding
|
||||
for (let s in logElement.error.source) {
|
||||
errorElems.push(
|
||||
<li key={`source-${logElement.key}-${s}`}>
|
||||
<span className={classes.logerror_tab}>
|
||||
{logElement.error.source[s]}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorElems;
|
||||
};
|
||||
|
||||
const renderLog = (logElement: LogMessage) => {
|
||||
let logMessage = logElement.ConsoleMsg;
|
||||
// remove any non ascii characters, exclude any control codes
|
||||
logMessage = logMessage.replace(/([^\x20-\x7F])/g, "");
|
||||
|
||||
// regex for terminal colors like e.g. `[31;4m `
|
||||
const tColorRegex = /((\[[0-9;]+m))/g;
|
||||
|
||||
// get substring if there was a match for to split what
|
||||
// is going to be colored and what not, here we add color
|
||||
// only to the first match.
|
||||
let substr = logMessage.replace(tColorRegex, "");
|
||||
|
||||
// if starts with multiple spaces add padding
|
||||
if (substr.startsWith(" ")) {
|
||||
return (
|
||||
<li key={logElement.key}>
|
||||
<span className={classes.tab}>{substr}</span>
|
||||
</li>
|
||||
);
|
||||
} else if (!isNullOrUndefined(logElement.error)) {
|
||||
// list error message and all sources and error elems
|
||||
return renderError(logElement);
|
||||
} else {
|
||||
// for all remaining set default class
|
||||
return (
|
||||
<li key={logElement.key}>
|
||||
<span className={classes.ansidefault}>{substr}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<PageHeader label="Logs" />
|
||||
<Grid container>
|
||||
<Grid className={classes.container} item xs={12}>
|
||||
<div className={classes.logList}>
|
||||
<ul>
|
||||
{messages.map((m) => {
|
||||
return renderLog(m);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
messages: state.logs.messages,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
logMessageReceived: logMessageReceived,
|
||||
logResetMessages: logResetMessages,
|
||||
});
|
||||
|
||||
export default connector(withStyles(styles)(Logs));
|
||||
@@ -19,16 +19,11 @@ import { connect } from "react-redux";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import WebAssetIcon from "@material-ui/icons/WebAsset";
|
||||
import HealingIcon from "@material-ui/icons/Healing";
|
||||
import DescriptionIcon from "@material-ui/icons/Description";
|
||||
import FileCopyIcon from "@material-ui/icons/FileCopy";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import List from "@material-ui/core/List";
|
||||
import { Divider, Typography, withStyles } from "@material-ui/core";
|
||||
import { ExitToApp } from "@material-ui/icons";
|
||||
import storage from "local-storage-fallback";
|
||||
import { Divider, withStyles } from "@material-ui/core";
|
||||
import { createStyles, Theme } from "@material-ui/core/styles";
|
||||
import history from "../../../history";
|
||||
import logo from "../../../icons/minio_console_logo.svg";
|
||||
@@ -48,13 +43,11 @@ import {
|
||||
LambdaNotificationsIcon,
|
||||
MirroringIcon,
|
||||
ServiceAccountsIcon,
|
||||
TraceIcon,
|
||||
UsersIcon,
|
||||
WarpIcon,
|
||||
} from "../../../icons";
|
||||
import { clearSession } from "../../../common/utils";
|
||||
import HealIcon from "../../../icons/HealIcon";
|
||||
import ConsoleIcon from "../../../icons/ConsoleIcon";
|
||||
import LicenseIcon from "../../../icons/LicenseIcon";
|
||||
import LogoutIcon from "../../../icons/LogoutIcon";
|
||||
|
||||
@@ -247,14 +240,6 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
|
||||
name: "IAM Policies",
|
||||
icon: <IAMPoliciesIcon />,
|
||||
},
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/logs",
|
||||
name: "Logs",
|
||||
icon: <ConsoleIcon />,
|
||||
},
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
@@ -263,14 +248,6 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
|
||||
name: "Watch",
|
||||
icon: <WatchIcon />,
|
||||
},
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/trace",
|
||||
name: "Trace",
|
||||
icon: <TraceIcon />,
|
||||
},
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
@@ -362,7 +339,7 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
|
||||
(menuItem: any) => menuItem.type !== "title"
|
||||
);
|
||||
|
||||
if (countableElements.length == 0) {
|
||||
if (countableElements.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -429,6 +406,7 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
|
||||
);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
<Divider />
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
removeEmptyFields,
|
||||
} from "../Configurations/utils";
|
||||
import { IElementValue } from "../Configurations/types";
|
||||
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -60,6 +61,70 @@ const styles = (theme: Theme) =>
|
||||
logoButton: {
|
||||
height: "80px",
|
||||
},
|
||||
lambdaNotif: {
|
||||
backgroundColor: "#fff",
|
||||
border: "#393939 1px solid",
|
||||
borderRadius: 5,
|
||||
width: 101,
|
||||
height: 91,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginBottom: 16,
|
||||
cursor: "pointer",
|
||||
"& img": {
|
||||
maxWidth: 71,
|
||||
maxHeight: 71,
|
||||
},
|
||||
},
|
||||
iconContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: 455,
|
||||
justifyContent: "space-between",
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
nonIconContainer: {
|
||||
marginBottom: 16,
|
||||
"& button": {
|
||||
marginRight: 16,
|
||||
},
|
||||
},
|
||||
pickTitle: {
|
||||
fontWeight: 600,
|
||||
color: "#393939",
|
||||
fontSize: 14,
|
||||
marginBottom: 16,
|
||||
},
|
||||
lambdaFormIndicator: {
|
||||
display: "flex",
|
||||
marginBottom: 40,
|
||||
},
|
||||
lambdaName: {
|
||||
fontSize: 18,
|
||||
fontWeight: 700,
|
||||
color: "#000",
|
||||
marginBottom: 6,
|
||||
},
|
||||
lambdaSubname: {
|
||||
fontSize: 12,
|
||||
color: "#000",
|
||||
fontWeight: 600,
|
||||
},
|
||||
lambdaIcon: {
|
||||
borderRadius: 5,
|
||||
border: "#393939 1px solid",
|
||||
width: 53,
|
||||
height: 48,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginRight: 16,
|
||||
"& img": {
|
||||
width: 38,
|
||||
},
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
interface IAddNotificationEndpointProps {
|
||||
@@ -136,186 +201,111 @@ const AddNotificationEndpoint = ({
|
||||
}
|
||||
}
|
||||
|
||||
let targetTitle = "";
|
||||
switch (service) {
|
||||
case notifyNsq:
|
||||
targetTitle = "NSQ";
|
||||
break;
|
||||
case notifyWebhooks:
|
||||
targetTitle = "Webhooks";
|
||||
break;
|
||||
case notifyElasticsearch:
|
||||
targetTitle = "Elastic Search";
|
||||
break;
|
||||
case notifyNats:
|
||||
targetTitle = "NATS";
|
||||
break;
|
||||
case notifyMqtt:
|
||||
targetTitle = "MQTT";
|
||||
break;
|
||||
case notifyRedis:
|
||||
targetTitle = "Redis";
|
||||
break;
|
||||
case notifyKafka:
|
||||
targetTitle = "Kafka";
|
||||
break;
|
||||
case notifyPostgres:
|
||||
targetTitle = "Postgres";
|
||||
break;
|
||||
case notifyMysql:
|
||||
targetTitle = "Mysql";
|
||||
break;
|
||||
case notifyAmqp:
|
||||
targetTitle = "AMQP";
|
||||
break;
|
||||
}
|
||||
const servicesList = [
|
||||
{
|
||||
actionTrigger: notifyPostgres,
|
||||
targetTitle: "Postgres SQL",
|
||||
logo: "/postgres.png",
|
||||
},
|
||||
{
|
||||
actionTrigger: notifyKafka,
|
||||
targetTitle: "Kafka",
|
||||
logo: "/kafka.png",
|
||||
},
|
||||
{
|
||||
actionTrigger: notifyAmqp,
|
||||
targetTitle: "AMQP",
|
||||
logo: "/amqp.png",
|
||||
},
|
||||
{
|
||||
actionTrigger: notifyMqtt,
|
||||
targetTitle: "MQTT",
|
||||
logo: "/mqtt.png",
|
||||
},
|
||||
{
|
||||
actionTrigger: notifyRedis,
|
||||
targetTitle: "Redis",
|
||||
logo: "/redis.png",
|
||||
},
|
||||
{
|
||||
actionTrigger: notifyNats,
|
||||
targetTitle: "NATS",
|
||||
logo: "/nats.png",
|
||||
},
|
||||
{
|
||||
actionTrigger: notifyMysql,
|
||||
targetTitle: "Mysql",
|
||||
logo: "/mysql.png",
|
||||
},
|
||||
{
|
||||
actionTrigger: notifyElasticsearch,
|
||||
targetTitle: "Elastic Search",
|
||||
logo: "/elasticsearch.png",
|
||||
},
|
||||
{
|
||||
actionTrigger: notifyWebhooks,
|
||||
targetTitle: "Webhook",
|
||||
logo: "",
|
||||
},
|
||||
{
|
||||
actionTrigger: notifyNsq,
|
||||
targetTitle: "NSQ",
|
||||
logo: "",
|
||||
},
|
||||
];
|
||||
|
||||
const nonLogos = servicesList.filter((elService) => elService.logo === "");
|
||||
const withLogos = servicesList.filter((elService) => elService.logo !== "");
|
||||
|
||||
const targetElement = servicesList.find(
|
||||
(element) => element.actionTrigger === service
|
||||
);
|
||||
|
||||
const goBack = () => {
|
||||
setService("");
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={closeModalAndRefresh}
|
||||
title={`Add Lambda Notification Target ${targetTitle}`}
|
||||
>
|
||||
<ModalWrapper modalOpen={open} onClose={closeModalAndRefresh} title={""}>
|
||||
{service === "" && (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<p>Pick a supported service:</p>
|
||||
<table className={classes.chooseTable} style={{ width: "100%" }}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyPostgres);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/postgres.png"
|
||||
className={classes.logoButton}
|
||||
alt="postgres"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyKafka);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/kafka.png"
|
||||
className={classes.logoButton}
|
||||
alt="kafka"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyAmqp);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/amqp.png"
|
||||
className={classes.logoButton}
|
||||
alt="amqp"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyMqtt);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/mqtt.png"
|
||||
className={classes.logoButton}
|
||||
alt="mqtt"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyRedis);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/redis.png"
|
||||
className={classes.logoButton}
|
||||
alt="redis"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyNats);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/nats.png"
|
||||
className={classes.logoButton}
|
||||
alt="nats"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyMysql);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/mysql.png"
|
||||
className={classes.logoButton}
|
||||
alt="mysql"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyElasticsearch);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/elasticsearch.png"
|
||||
className={classes.logoButton}
|
||||
alt="elasticsearch"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyWebhooks);
|
||||
}}
|
||||
>
|
||||
Webhook
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyNsq);
|
||||
}}
|
||||
>
|
||||
NSQ
|
||||
</Button>
|
||||
</td>
|
||||
<td />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className={classes.pickTitle}>Pick a supported service:</div>
|
||||
<div className={classes.nonIconContainer}>
|
||||
{nonLogos.map((item) => {
|
||||
return (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
key={`non-icon-${item.targetTitle}`}
|
||||
onClick={() => {
|
||||
setService(item.actionTrigger);
|
||||
}}
|
||||
>
|
||||
{item.targetTitle.toUpperCase()}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className={classes.iconContainer}>
|
||||
{withLogos.map((item) => {
|
||||
return (
|
||||
<button
|
||||
key={`icon-${item.targetTitle}`}
|
||||
className={classes.lambdaNotif}
|
||||
onClick={() => {
|
||||
setService(item.actionTrigger);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={item.logo}
|
||||
className={classes.logoButton}
|
||||
alt={item.targetTitle}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
@@ -342,10 +332,37 @@ const AddNotificationEndpoint = ({
|
||||
</Grid>
|
||||
)}
|
||||
<form noValidate onSubmit={submitForm}>
|
||||
<Grid item xs={12} className={classes.lambdaFormIndicator}>
|
||||
{targetElement && targetElement.logo !== "" && (
|
||||
<div className={classes.lambdaIcon}>
|
||||
<img
|
||||
src={targetElement.logo}
|
||||
alt={targetElement.targetTitle}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={classes.lambdaTitle}>
|
||||
<div className={classes.lambdaName}>
|
||||
{targetElement ? targetElement.targetTitle : ""}
|
||||
</div>
|
||||
<div className={classes.lambdaSubname}>
|
||||
Add Lambda Notification Target
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
{srvComponent}
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<button
|
||||
type="button"
|
||||
color="primary"
|
||||
className={classes.clearButton}
|
||||
onClick={goBack}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
|
||||
@@ -20,7 +20,6 @@ import { TextField } from "@material-ui/core";
|
||||
import { red } from "@material-ui/core/colors";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
import get from "lodash/get";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import AddBucket from "../Buckets/ListBuckets/AddBucket";
|
||||
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";
|
||||
@@ -28,13 +28,16 @@ import { CreateIcon } from "../../../icons";
|
||||
import { niceBytes } from "../../../common/utils";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
import { Bucket, BucketList } from "../Buckets/types";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import api from "../../../common/api";
|
||||
import history from "../../../history";
|
||||
import {
|
||||
actionsTray,
|
||||
objectBrowserCommon,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { addRoute, resetRoutesList } from "./actions";
|
||||
import BrowserBreadcrumbs from "./BrowserBreadcrumbs";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import AddBucket from "../Buckets/ListBuckets/AddBucket";
|
||||
import api from "../../../common/api";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -68,20 +71,47 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
usedSpaceCol: {
|
||||
width: 150,
|
||||
textAlign: "right",
|
||||
},
|
||||
subTitleLabel: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
},
|
||||
bucketName: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
iconBucket: {
|
||||
backgroundImage: "url(/images/ob_bucket_clear.svg)",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "center center",
|
||||
width: 16,
|
||||
height: 40,
|
||||
marginRight: 10,
|
||||
},
|
||||
"@global": {
|
||||
".rowElementRaw:hover .iconBucketElm": {
|
||||
backgroundImage: "url(/images/ob_bucket_filled.svg)",
|
||||
},
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...objectBrowserCommon,
|
||||
});
|
||||
|
||||
interface IBrowseBucketsProps {
|
||||
classes: any;
|
||||
addRoute: (path: string, label: string) => any;
|
||||
resetRoutesList: (doVar: boolean) => any;
|
||||
match: any;
|
||||
}
|
||||
|
||||
const BrowseBuckets = ({ classes }: IBrowseBucketsProps) => {
|
||||
const BrowseBuckets = ({
|
||||
classes,
|
||||
match,
|
||||
addRoute,
|
||||
resetRoutesList,
|
||||
}: IBrowseBucketsProps) => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
|
||||
@@ -92,6 +122,10 @@ const BrowseBuckets = ({ classes }: IBrowseBucketsProps) => {
|
||||
|
||||
const offset = page * rowsPerPage;
|
||||
|
||||
useEffect(() => {
|
||||
resetRoutesList(true);
|
||||
}, [match]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
api
|
||||
@@ -119,21 +153,24 @@ const BrowseBuckets = ({ classes }: IBrowseBucketsProps) => {
|
||||
setError(err);
|
||||
});
|
||||
}
|
||||
}, [loading]);
|
||||
}, [loading, offset, rowsPerPage, page]);
|
||||
|
||||
const closeAddModalAndRefresh = () => {
|
||||
const closeAddModalAndRefresh = (refresh: boolean) => {
|
||||
setAddScreenOpen(false);
|
||||
setLoading(false);
|
||||
|
||||
if (refresh) {
|
||||
setLoading(true);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredRecords = records
|
||||
.filter((b: Bucket) => {
|
||||
if (filterBuckets === "") {
|
||||
return true;
|
||||
}
|
||||
return b.name.indexOf(filterBuckets) >= 0;
|
||||
})
|
||||
.slice(offset, offset + rowsPerPage);
|
||||
const filteredRecords = records.filter((b: Bucket) => {
|
||||
if (filterBuckets === "") {
|
||||
return true;
|
||||
}
|
||||
return b.name.indexOf(filterBuckets) >= 0;
|
||||
});
|
||||
|
||||
const showInPage = filteredRecords.slice(offset, offset + rowsPerPage);
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
@@ -147,6 +184,22 @@ const BrowseBuckets = ({ classes }: IBrowseBucketsProps) => {
|
||||
setRowsPerPage(rPP);
|
||||
};
|
||||
|
||||
const handleViewChange = (idElement: string) => {
|
||||
const currentPath = get(match, "url", "/object-browser");
|
||||
const newPath = `${currentPath}/${idElement}`;
|
||||
|
||||
addRoute(newPath, idElement);
|
||||
};
|
||||
|
||||
const renderBucket = (bucketName: string) => {
|
||||
return (
|
||||
<div className={classes.bucketName}>
|
||||
<div className={`${classes.iconBucket} iconBucketElm`} />
|
||||
<span>{bucketName}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addScreenOpen && (
|
||||
@@ -156,10 +209,24 @@ const BrowseBuckets = ({ classes }: IBrowseBucketsProps) => {
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={2} className={classes.subTitleLabel}>
|
||||
<Typography variant="h6">Buckets</Typography>
|
||||
<Grid item xs={12} className={classes.obTitleSection}>
|
||||
<div>
|
||||
<BrowserBreadcrumbs />
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setAddScreenOpen(true);
|
||||
}}
|
||||
>
|
||||
Create Bucket
|
||||
</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={10} className={classes.actionsTray}>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Buckets"
|
||||
className={classes.searchField}
|
||||
@@ -177,16 +244,6 @@ const BrowseBuckets = ({ classes }: IBrowseBucketsProps) => {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setAddScreenOpen(true);
|
||||
}}
|
||||
>
|
||||
Add Bucket
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
@@ -195,19 +252,28 @@ const BrowseBuckets = ({ classes }: IBrowseBucketsProps) => {
|
||||
{error !== "" && <span className={classes.errorBlock}>{error}</span>}
|
||||
<TableWrapper
|
||||
itemActions={[
|
||||
{ type: "view", to: `/object-browser`, sendOnlyId: true },
|
||||
{
|
||||
type: "view",
|
||||
sendOnlyId: true,
|
||||
onClick: handleViewChange,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: renderBucket,
|
||||
},
|
||||
{
|
||||
label: "Used Space",
|
||||
elementKey: "size",
|
||||
renderFunction: niceBytes,
|
||||
globalClass: classes.usedSpaceCol,
|
||||
rowClass: classes.usedSpaceCol,
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
records={showInPage}
|
||||
entityName="Buckets"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
@@ -231,4 +297,11 @@ const BrowseBuckets = ({ classes }: IBrowseBucketsProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(BrowseBuckets);
|
||||
const mapDispatchToProps = {
|
||||
addRoute,
|
||||
resetRoutesList,
|
||||
};
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
|
||||
export default withRouter(connector(withStyles(styles)(BrowseBuckets)));
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import get from "lodash/get";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { connect } from "react-redux";
|
||||
import { withStyles } from "@material-ui/core";
|
||||
import { createStyles, Theme } from "@material-ui/core/styles";
|
||||
import { removeRouteLevel } from "./actions";
|
||||
import { ObjectBrowserState, Route } from "./reducers";
|
||||
import { objectBrowserCommon } from "../Common/FormComponents/common/styleLibrary";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
interface ObjectBrowserReducer {
|
||||
objectBrowser: ObjectBrowserState;
|
||||
}
|
||||
|
||||
interface IObjectBrowser {
|
||||
classes: any;
|
||||
objectsList: Route[];
|
||||
removeRouteLevel: (path: string) => any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...objectBrowserCommon,
|
||||
});
|
||||
|
||||
const BrowserBreadcrumbs = ({
|
||||
classes,
|
||||
objectsList,
|
||||
removeRouteLevel,
|
||||
}: IObjectBrowser) => {
|
||||
const listBreadcrumbs = objectsList.map((objectItem, index) => {
|
||||
return (
|
||||
<React.Fragment key={`breadcrumbs-${index.toString()}`}>
|
||||
<Link
|
||||
to={objectItem.route}
|
||||
onClick={() => {
|
||||
removeRouteLevel(objectItem.route);
|
||||
}}
|
||||
>
|
||||
{objectItem.label}
|
||||
</Link>
|
||||
{index < objectsList.length - 1 && <span> / </span>}
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.sectionTitle}>
|
||||
{objectsList && objectsList.length > 0
|
||||
? objectsList.slice(-1)[0].label
|
||||
: ""}
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.breadcrumbs}>
|
||||
{listBreadcrumbs}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({
|
||||
objectsList: get(objectBrowser, "routesList", []),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
removeRouteLevel,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export default connector(withStyles(styles)(BrowserBreadcrumbs));
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from "react";
|
||||
import get from "lodash/get";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Grid, Typography } from "@material-ui/core";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import BrowseBuckets from "./BrowseBuckets";
|
||||
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
@@ -73,7 +73,7 @@ const styles = (theme: Theme) =>
|
||||
});
|
||||
|
||||
const ObjectBrowser = ({ match, classes }: IObjectBrowserProps) => {
|
||||
const pathIn = get(match, "path", "");
|
||||
const pathIn = get(match, "url", "");
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
||||
93
portal-ui/src/screens/Console/ObjectBrowser/actions.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export const OBJECT_BROWSER_ADD_ROUTE = "OBJECT_BROWSER/ADD_ROUTE";
|
||||
export const OBJECT_BROWSER_RESET_ROUTES_LIST =
|
||||
"OBJECT_BROWSER/RESET_ROUTES_LIST";
|
||||
export const OBJECT_BROWSER_REMOVE_ROUTE_LEVEL =
|
||||
"OBJECT_BROWSER/REMOVE_ROUTE_LEVEL";
|
||||
export const OBJECT_BROWSER_SET_ALL_ROUTES = "OBJECT_BROWSER/SET_ALL_ROUTES";
|
||||
export const OBJECT_BROWSER_CREATE_FOLDER = "OBJECT_BROWSER/CREATE_FOLDER";
|
||||
|
||||
interface AddRouteAction {
|
||||
type: typeof OBJECT_BROWSER_ADD_ROUTE;
|
||||
route: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface ResetRoutesList {
|
||||
type: typeof OBJECT_BROWSER_RESET_ROUTES_LIST;
|
||||
reset: boolean;
|
||||
}
|
||||
|
||||
interface RemoveRouteLevel {
|
||||
type: typeof OBJECT_BROWSER_REMOVE_ROUTE_LEVEL;
|
||||
toRoute: string;
|
||||
}
|
||||
|
||||
interface SetAllRoutes {
|
||||
type: typeof OBJECT_BROWSER_SET_ALL_ROUTES;
|
||||
currentRoute: string;
|
||||
}
|
||||
|
||||
interface CreateFolder {
|
||||
type: typeof OBJECT_BROWSER_CREATE_FOLDER;
|
||||
newRoute: string;
|
||||
}
|
||||
|
||||
export type ObjectBrowserActionTypes =
|
||||
| AddRouteAction
|
||||
| ResetRoutesList
|
||||
| RemoveRouteLevel
|
||||
| SetAllRoutes
|
||||
| CreateFolder;
|
||||
|
||||
export const addRoute = (route: string, label: string) => {
|
||||
return {
|
||||
type: OBJECT_BROWSER_ADD_ROUTE,
|
||||
route,
|
||||
label,
|
||||
};
|
||||
};
|
||||
|
||||
export const resetRoutesList = (reset: boolean) => {
|
||||
console.log("RESET");
|
||||
return {
|
||||
type: OBJECT_BROWSER_RESET_ROUTES_LIST,
|
||||
reset,
|
||||
};
|
||||
};
|
||||
|
||||
export const removeRouteLevel = (toRoute: string) => {
|
||||
return {
|
||||
type: OBJECT_BROWSER_REMOVE_ROUTE_LEVEL,
|
||||
toRoute,
|
||||
};
|
||||
};
|
||||
|
||||
export const setAllRoutes = (currentRoute: string) => {
|
||||
return {
|
||||
type: OBJECT_BROWSER_SET_ALL_ROUTES,
|
||||
currentRoute,
|
||||
};
|
||||
};
|
||||
|
||||
export const createFolder = (newRoute: string) => {
|
||||
return {
|
||||
type: OBJECT_BROWSER_CREATE_FOLDER,
|
||||
newRoute,
|
||||
};
|
||||
};
|
||||
114
portal-ui/src/screens/Console/ObjectBrowser/reducers.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
// 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 history from "../../../history";
|
||||
|
||||
import {
|
||||
OBJECT_BROWSER_ADD_ROUTE,
|
||||
OBJECT_BROWSER_CREATE_FOLDER,
|
||||
OBJECT_BROWSER_REMOVE_ROUTE_LEVEL,
|
||||
OBJECT_BROWSER_RESET_ROUTES_LIST,
|
||||
OBJECT_BROWSER_SET_ALL_ROUTES,
|
||||
ObjectBrowserActionTypes,
|
||||
} from "./actions";
|
||||
|
||||
export interface Route {
|
||||
route: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface ObjectBrowserState {
|
||||
routesList: Route[];
|
||||
}
|
||||
|
||||
const initialRoute = [{ route: "/object-browser", label: "All Buckets" }];
|
||||
|
||||
const initialState: ObjectBrowserState = {
|
||||
routesList: initialRoute,
|
||||
};
|
||||
|
||||
export function objectBrowserReducer(
|
||||
state = initialState,
|
||||
action: ObjectBrowserActionTypes
|
||||
): ObjectBrowserState {
|
||||
switch (action.type) {
|
||||
case OBJECT_BROWSER_ADD_ROUTE:
|
||||
const newRouteList = [
|
||||
...state.routesList,
|
||||
{ route: action.route, label: action.label },
|
||||
];
|
||||
history.push(action.route);
|
||||
|
||||
return { ...state, routesList: newRouteList };
|
||||
case OBJECT_BROWSER_RESET_ROUTES_LIST:
|
||||
return {
|
||||
...state,
|
||||
routesList: [...initialRoute],
|
||||
};
|
||||
case OBJECT_BROWSER_REMOVE_ROUTE_LEVEL:
|
||||
const indexOfTopPath =
|
||||
state.routesList.findIndex(
|
||||
(element) => element.route === action.toRoute
|
||||
) + 1;
|
||||
const newRouteLevels = state.routesList.slice(0, indexOfTopPath);
|
||||
|
||||
return {
|
||||
...state,
|
||||
routesList: newRouteLevels,
|
||||
};
|
||||
case OBJECT_BROWSER_SET_ALL_ROUTES:
|
||||
const splitRoutes = action.currentRoute.split("/");
|
||||
const routesArray: Route[] = [];
|
||||
let initRoute = initialRoute[0].route;
|
||||
|
||||
splitRoutes.forEach((route) => {
|
||||
if (route !== "" && route !== "object-browser") {
|
||||
initRoute = `${initRoute}/${route}`;
|
||||
routesArray.push({ route: initRoute, label: route });
|
||||
}
|
||||
});
|
||||
|
||||
const newSetOfRoutes = [...initialRoute, ...routesArray];
|
||||
|
||||
return {
|
||||
...state,
|
||||
routesList: newSetOfRoutes,
|
||||
};
|
||||
case OBJECT_BROWSER_CREATE_FOLDER:
|
||||
const newFoldersRoutes = [...state.routesList];
|
||||
let lastRoute = state.routesList[state.routesList.length - 1].route;
|
||||
|
||||
const splitElements = action.newRoute.split("/");
|
||||
|
||||
splitElements.forEach((element) => {
|
||||
const folderTrim = element.trim();
|
||||
if (folderTrim !== "") {
|
||||
lastRoute = `${lastRoute}/${folderTrim}`;
|
||||
|
||||
const newItem = { route: lastRoute, label: folderTrim };
|
||||
newFoldersRoutes.push(newItem);
|
||||
}
|
||||
});
|
||||
|
||||
history.push(lastRoute);
|
||||
|
||||
return {
|
||||
...state,
|
||||
routesList: newFoldersRoutes,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -16,19 +16,18 @@
|
||||
|
||||
import React from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { UnControlled as CodeMirror } from "react-codemirror2";
|
||||
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 "codemirror/lib/codemirror.css";
|
||||
import "codemirror/theme/material.css";
|
||||
import { Policy } from "./types";
|
||||
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
fieldBasic,
|
||||
modalBasic,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -39,26 +38,11 @@ const styles = (theme: Theme) =>
|
||||
minHeight: 400,
|
||||
width: "100%",
|
||||
},
|
||||
codeMirror: {
|
||||
fontSize: 14,
|
||||
"& .CodeMirror": {
|
||||
color: "#fff",
|
||||
backgroundColor: "#081C42",
|
||||
},
|
||||
"& .CodeMirror-gutter": {
|
||||
backgroundColor: "#081C4280",
|
||||
},
|
||||
"& .CodeMirror-linenumber": {
|
||||
color: "#000",
|
||||
fontSize: 10,
|
||||
height: 20,
|
||||
lineHeight: "20px",
|
||||
},
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
...fieldBasic,
|
||||
});
|
||||
|
||||
interface IAddPolicyProps {
|
||||
@@ -121,13 +105,26 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
if (policyEdit) {
|
||||
this.setState({
|
||||
policyName: policyEdit.name,
|
||||
policyDefinition: policyEdit
|
||||
? JSON.stringify(JSON.parse(policyEdit.policy), null, 4)
|
||||
: "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resetForm() {
|
||||
this.setState({
|
||||
policyName: "",
|
||||
policyDefinition: "",
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, open, policyEdit } = this.props;
|
||||
const { addLoading, addError, policyName } = this.state;
|
||||
const { addLoading, addError, policyName, policyDefinition } = this.state;
|
||||
|
||||
const validSave = policyName.trim() !== "";
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
@@ -163,6 +160,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
id="policy-name"
|
||||
name="policy-name"
|
||||
label="Policy Name"
|
||||
placeholder="Enter Policy Name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ policyName: e.target.value });
|
||||
}}
|
||||
@@ -173,31 +171,32 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<CodeMirror
|
||||
className={classes.codeMirror}
|
||||
value={
|
||||
policyEdit
|
||||
? JSON.stringify(JSON.parse(policyEdit.policy), null, 4)
|
||||
: ""
|
||||
}
|
||||
options={{
|
||||
mode: "javascript",
|
||||
lineNumbers: true,
|
||||
}}
|
||||
onChange={(editor, data, value) => {
|
||||
this.setState({ policyDefinition: value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<CodeMirrorWrapper
|
||||
label="Write Policy"
|
||||
value={policyDefinition}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
this.setState({ policyDefinition: value });
|
||||
}}
|
||||
readOnly={!!policyEdit}
|
||||
/>
|
||||
</Grid>
|
||||
{!policyEdit && (
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<button
|
||||
type="button"
|
||||
color="primary"
|
||||
className={classes.clearButton}
|
||||
onClick={() => {
|
||||
this.resetForm();
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addLoading}
|
||||
disabled={addLoading || !validSave}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress
|
||||
LinearProgress,
|
||||
} from "@material-ui/core";
|
||||
import api from "../../../common/api";
|
||||
import { PolicyList } from "./types";
|
||||
@@ -32,8 +32,8 @@ import Typography from "@material-ui/core/Typography";
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
|
||||
interface IDeletePolicyProps {
|
||||
@@ -54,7 +54,7 @@ class DeletePolicy extends React.Component<
|
||||
> {
|
||||
state: IDeletePolicyState = {
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
deleteError: "",
|
||||
};
|
||||
removeRecord() {
|
||||
const { deleteLoading } = this.state;
|
||||
@@ -69,17 +69,17 @@ class DeletePolicy extends React.Component<
|
||||
this.setState(
|
||||
{
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
deleteError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeDeleteModalAndRefresh(true);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
deleteLoading: false,
|
||||
deleteError: err
|
||||
deleteError: err,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -98,7 +98,7 @@ class DeletePolicy extends React.Component<
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Bucket</DialogTitle>
|
||||
<DialogTitle id="alert-dialog-title">Delete Policy</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
|
||||
@@ -19,7 +19,6 @@ import get from "lodash/get";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Button } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
@@ -78,7 +77,6 @@ interface IPoliciesProps {
|
||||
|
||||
const Policies = ({ classes }: IPoliciesProps) => {
|
||||
const [records, setRecords] = useState<Policy[]>([]);
|
||||
const [totalRecords, setTotalRecords] = useState<number>(0);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
|
||||
@@ -100,7 +98,6 @@ const Policies = ({ classes }: IPoliciesProps) => {
|
||||
.invoke("GET", `/api/v1/policies?offset=${offset}&limit=${rowsPerPage}`)
|
||||
.then((res: PolicyList) => {
|
||||
const policies = get(res, "policies", []);
|
||||
const total = get(res, "total", 0);
|
||||
|
||||
policies.sort((pa, pb) => {
|
||||
if (pa.name > pb.name) {
|
||||
@@ -116,7 +113,6 @@ const Policies = ({ classes }: IPoliciesProps) => {
|
||||
|
||||
setLoading(false);
|
||||
setRecords(policies);
|
||||
setTotalRecords(total);
|
||||
setError("");
|
||||
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
@@ -137,15 +133,7 @@ const Policies = ({ classes }: IPoliciesProps) => {
|
||||
setError(err);
|
||||
});
|
||||
}
|
||||
}, [
|
||||
loading,
|
||||
setLoading,
|
||||
setRecords,
|
||||
setTotalRecords,
|
||||
setError,
|
||||
setPage,
|
||||
setError,
|
||||
]);
|
||||
}, [loading, setLoading, setRecords, setError, setPage, page, rowsPerPage]);
|
||||
|
||||
const fetchRecords = () => {
|
||||
setLoading(true);
|
||||
@@ -256,6 +244,11 @@ const Policies = ({ classes }: IPoliciesProps) => {
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
{error && (
|
||||
<Grid item xs={12}>
|
||||
{error}
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
|
||||