Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
419e94ccec | ||
|
|
12bc5265b8 | ||
|
|
32898f0c57 | ||
|
|
125c9abf56 | ||
|
|
bc27db4a69 | ||
|
|
dd8e2b13d3 | ||
|
|
005e3b941c | ||
|
|
efa614c773 | ||
|
|
63350e5492 | ||
|
|
255c128b67 | ||
|
|
06f333395e | ||
|
|
3cd024ea2c | ||
|
|
9c0a407db6 | ||
|
|
dc3c619f3f | ||
|
|
5000aafba6 | ||
|
|
b9f2a39d50 | ||
|
|
df321191f4 | ||
|
|
547eb41e96 | ||
|
|
afbb83e081 | ||
|
|
b599968570 | ||
|
|
24cc60f34e | ||
|
|
f967058409 | ||
|
|
078e09ba76 | ||
|
|
d6f97841d4 | ||
|
|
619ac124b3 | ||
|
|
a2180e123d | ||
|
|
0325bb7e2d | ||
|
|
fce361e5bd | ||
|
|
ed6d6e8b9d | ||
|
|
406709f66b | ||
|
|
3ac45a2211 |
2
.github/workflows/go.yml
vendored
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
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
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
|
||||
36
README.md
36
README.md
@@ -113,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
|
||||
```
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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"}
|
||||
|
||||
5
go.mod
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
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.2
|
||||
image: minio/console:v0.4.5
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
args:
|
||||
- server
|
||||
|
||||
@@ -15,7 +15,7 @@ spec:
|
||||
serviceAccountName: console-sa
|
||||
containers:
|
||||
- name: console
|
||||
image: minio/console:v0.4.2
|
||||
image: minio/console:v0.4.5
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
env:
|
||||
- name: CONSOLE_OPERATOR_MODE
|
||||
|
||||
63
models/bucket_encryption_info.go
Normal file
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
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
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
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
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
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
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
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",
|
||||
|
||||
222
pkg/certs/certs.go
Normal file
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
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"
|
||||
)
|
||||
File diff suppressed because one or more lines are too long
@@ -17,6 +17,7 @@
|
||||
"@types/react-redux": "^7.1.5",
|
||||
"@types/react-router": "^5.1.3",
|
||||
"@types/react-router-dom": "^5.1.2",
|
||||
"@types/react-virtualized": "^9.21.10",
|
||||
"@types/recharts": "^1.8.9",
|
||||
"@types/superagent": "^4.1.4",
|
||||
"@types/webpack-env": "^1.14.1",
|
||||
@@ -28,7 +29,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 +40,9 @@
|
||||
"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",
|
||||
"react-virtualized": "^9.22.2",
|
||||
"react-window-infinite-loader": "^1.0.5",
|
||||
"recharts": "^1.8.5",
|
||||
"redux": "^4.0.4",
|
||||
"redux-thunk": "^2.3.0",
|
||||
|
||||
6
portal-ui/public/images/ob_bucket_clear.svg
Normal file
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
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
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
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
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
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 |
@@ -15,17 +15,10 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
Redirect,
|
||||
Route,
|
||||
Router,
|
||||
Switch,
|
||||
BrowserRouter,
|
||||
} from "react-router-dom";
|
||||
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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
41
portal-ui/src/icons/UploadFile.tsx
Normal file
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;
|
||||
@@ -21,6 +21,7 @@ import Routes from "./Routes";
|
||||
import configureStore from "./store";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
import { ThemeProvider, withStyles } from "@material-ui/core/styles";
|
||||
import "react-virtualized/styles.css";
|
||||
|
||||
import "./index.css";
|
||||
import theme from "./theme/main";
|
||||
|
||||
@@ -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;
|
||||
@@ -131,7 +130,7 @@ const AddBucket = ({
|
||||
.then((res) => {
|
||||
setAddLoading(false);
|
||||
setAddError("");
|
||||
closeModalAndRefresh();
|
||||
closeModalAndRefresh(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddLoading(false);
|
||||
@@ -143,7 +142,7 @@ const AddBucket = ({
|
||||
|
||||
useEffect(() => {
|
||||
addBucketName(value);
|
||||
}, [value]);
|
||||
}, [value, addBucketName]);
|
||||
|
||||
const resetForm = () => {
|
||||
setBName("");
|
||||
@@ -176,7 +175,7 @@ const AddBucket = ({
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
setAddError("");
|
||||
closeModalAndRefresh();
|
||||
closeModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
|
||||
@@ -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;
|
||||
|
||||
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
|
||||
@@ -266,26 +266,14 @@ const ListBuckets = ({
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: niceBytes,
|
||||
width: 60,
|
||||
contentTextAlign: "right",
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
records={showInPage}
|
||||
entityName="Buckets"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -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 } 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,6 +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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
@@ -21,7 +22,6 @@ 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 from "react";
|
||||
import TableWrapper from "../../../../Common/TableWrapper/TableWrapper";
|
||||
import { niceBytes } from "../../../../../../common/utils";
|
||||
import DeleteObject from "./DeleteObject";
|
||||
@@ -29,6 +29,7 @@ import DeleteObject from "./DeleteObject";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
objectBrowserCommon,
|
||||
searchField,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../../../../Common/PageHeader/PageHeader";
|
||||
@@ -38,6 +39,23 @@ 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 UploadFile from "../../../../../../icons/UploadFile";
|
||||
|
||||
const commonIcon = {
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "center center",
|
||||
width: 16,
|
||||
minWidth: 16,
|
||||
height: 40,
|
||||
marginRight: 10,
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -69,93 +87,133 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
},
|
||||
fileName: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
fileNameText: {
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
iconFolder: {
|
||||
backgroundImage: "url(/images/ob_folder_clear.svg)",
|
||||
...commonIcon,
|
||||
},
|
||||
iconFile: {
|
||||
backgroundImage: "url(/images/ob_file_clear.svg)",
|
||||
...commonIcon,
|
||||
},
|
||||
buttonsContainer: {
|
||||
"& .MuiButtonBase-root": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
browsePaper: {
|
||||
height: "calc(100vh - 280px)",
|
||||
},
|
||||
"@global": {
|
||||
".rowLine:hover .iconFileElm": {
|
||||
backgroundImage: "url(/images/ob_file_filled.svg)",
|
||||
},
|
||||
".rowLine: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;
|
||||
openSnackbar: boolean;
|
||||
snackBarMessage: 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: "",
|
||||
openSnackbar: false,
|
||||
snackBarMessage: "",
|
||||
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);
|
||||
};
|
||||
|
||||
showSnackBarMessage(text: string) {
|
||||
this.setState({ openSnackbar: true, snackBarMessage: text });
|
||||
}
|
||||
|
||||
closeSnackBar() {
|
||||
this.setState({ openSnackbar: false, snackBarMessage: `` });
|
||||
}
|
||||
|
||||
upload(e: any, bucketName: string, path: string) {
|
||||
let listObjects = this;
|
||||
const upload = (e: any, bucketName: string, path: string) => {
|
||||
if (isNullOrUndefined(e) || isNullOrUndefined(e.target)) {
|
||||
return;
|
||||
}
|
||||
@@ -174,25 +232,21 @@ class ListObjects extends React.Component<
|
||||
xhr.withCredentials = false;
|
||||
xhr.onload = function (event) {
|
||||
// TODO: handle status
|
||||
if (xhr.status == 401 || xhr.status == 403) {
|
||||
listObjects.showSnackBarMessage(
|
||||
"An error occurred while uploading the file."
|
||||
);
|
||||
if (xhr.status === 401 || xhr.status === 403) {
|
||||
showSnackBarMessage("An error occurred while uploading the file.");
|
||||
}
|
||||
if (xhr.status == 500) {
|
||||
listObjects.showSnackBarMessage(
|
||||
"An error occurred while uploading the file."
|
||||
);
|
||||
if (xhr.status === 500) {
|
||||
showSnackBarMessage("An error occurred while uploading the file.");
|
||||
}
|
||||
if (xhr.status == 200) {
|
||||
listObjects.showSnackBarMessage("Object uploaded successfully.");
|
||||
listObjects.fetchRecords();
|
||||
if (xhr.status === 200) {
|
||||
showSnackBarMessage("Object uploaded successfully.");
|
||||
setLoading(true);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.upload.addEventListener("error", (event) => {
|
||||
// TODO: handle error
|
||||
this.showSnackBarMessage("An error occurred while uploading the file.");
|
||||
showSnackBarMessage("An error occurred while uploading the file.");
|
||||
});
|
||||
|
||||
xhr.upload.addEventListener("progress", (event) => {
|
||||
@@ -200,24 +254,22 @@ class ListObjects extends React.Component<
|
||||
});
|
||||
|
||||
xhr.onerror = () => {
|
||||
listObjects.showSnackBarMessage(
|
||||
"An error occurred while uploading the file."
|
||||
);
|
||||
showSnackBarMessage("An error occurred while uploading the file.");
|
||||
};
|
||||
|
||||
var formData = new FormData();
|
||||
var blobFile = new Blob([file]);
|
||||
const formData = new FormData();
|
||||
const blobFile = new Blob([file]);
|
||||
|
||||
formData.append("upfile", blobFile);
|
||||
xhr.send(formData);
|
||||
e.target.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
download(bucketName: string, objectName: string) {
|
||||
var anchor = document.createElement("a");
|
||||
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",
|
||||
@@ -228,11 +280,11 @@ class ListObjects extends React.Component<
|
||||
xhr.responseType = "blob";
|
||||
|
||||
xhr.onload = function (e) {
|
||||
if (this.status == 200) {
|
||||
var blob = new Blob([this.response], {
|
||||
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;
|
||||
@@ -243,158 +295,233 @@ 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,
|
||||
snackBarMessage,
|
||||
openSnackbar,
|
||||
} = this.state;
|
||||
const displayParsedDate = (date: string) => {
|
||||
return <reactMoment.default>{date}</reactMoment.default>;
|
||||
};
|
||||
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 uploadObject = (e: any): void => {
|
||||
// TODO: handle deeper paths/folders
|
||||
let file = e.target.files[0];
|
||||
this.showSnackBarMessage(`Uploading: ${file.name}`);
|
||||
this.upload(e, selectedBucket, "");
|
||||
};
|
||||
// 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 snackBarAction = (
|
||||
<Button
|
||||
color="secondary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
this.closeSnackBar();
|
||||
}}
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
);
|
||||
addRoute(newPath, idElementClean[lastIndex]);
|
||||
return;
|
||||
}
|
||||
|
||||
const tableActions = [
|
||||
{ type: "download", onClick: downloadObject, sendOnlyId: true },
|
||||
{ type: "delete", onClick: confirmDeleteObject, sendOnlyId: true },
|
||||
];
|
||||
// Element is a file. we open details here
|
||||
// TODO: Add details open function here.
|
||||
//console.log("object", idElementClean);
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter((b: BucketObject) => {
|
||||
if (filterObjects === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterObjects) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Snackbar
|
||||
open={openSnackbar}
|
||||
message={snackBarMessage}
|
||||
action={snackBarAction}
|
||||
/>
|
||||
<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,
|
||||
});
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<div className={classes.fileName}>
|
||||
<div className={icon} />
|
||||
<span className={classes.fileNameText}>
|
||||
{splitItem[splitItem.length - 1]}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
component="label"
|
||||
>
|
||||
Upload Object
|
||||
<Input
|
||||
type="file"
|
||||
onChange={(e) => uploadObject(e)}
|
||||
id="file-input"
|
||||
style={{ display: "none" }}
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
</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>
|
||||
const filteredRecords = records.filter((b: BucketObject) => {
|
||||
if (filterObjects === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterObjects) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{deleteOpen && (
|
||||
<DeleteObject
|
||||
deleteOpen={deleteOpen}
|
||||
selectedBucket={selectedBucket}
|
||||
selectedObject={selectedObject}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
{createFolderOpen && (
|
||||
<CreateFolderModal
|
||||
modalOpen={createFolderOpen}
|
||||
folderName={routesList[routesList.length - 1].route}
|
||||
onClose={closeAddFolderModal}
|
||||
/>
|
||||
)}
|
||||
<Snackbar
|
||||
open={openSnackbar}
|
||||
message={snackBarMessage}
|
||||
action={snackBarAction}
|
||||
/>
|
||||
<PageHeader label="Object Browser" />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid item xs={12} className={classes.obTitleSection}>
|
||||
<div>
|
||||
<BrowserBreadcrumbs />
|
||||
</div>
|
||||
<div className={classes.buttonsContainer}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
component="label"
|
||||
onClick={() => {
|
||||
setCreateFolderOpen(true);
|
||||
}}
|
||||
>
|
||||
Create Folder
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<UploadFile />}
|
||||
component="label"
|
||||
>
|
||||
File
|
||||
<Input
|
||||
type="file"
|
||||
onChange={(e) => uploadObject(e)}
|
||||
id="file-input"
|
||||
style={{ display: "none" }}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
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,
|
||||
width: 60,
|
||||
contentTextAlign: "right",
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
entityName="Objects"
|
||||
idField="name"
|
||||
records={filteredRecords}
|
||||
customPaperHeight={classes.browsePaper}
|
||||
/>
|
||||
</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>
|
||||
@@ -581,20 +642,6 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
records={filteredRecords}
|
||||
entityName="Events"
|
||||
idField="id"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel index={1} value={curTab}>
|
||||
@@ -622,20 +669,6 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
records={filteredRules}
|
||||
entityName="Replication Rules"
|
||||
idField="id"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -19,6 +19,11 @@ export interface Bucket {
|
||||
creation_date: Date;
|
||||
}
|
||||
|
||||
export interface BucketEncryptionInfo {
|
||||
algorithm: string;
|
||||
kmsMasterKeyID: string;
|
||||
}
|
||||
|
||||
export interface BucketInfo {
|
||||
name: string;
|
||||
access: string;
|
||||
|
||||
@@ -105,8 +105,8 @@ const CSVMultiSelector = ({
|
||||
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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -14,20 +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 {
|
||||
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 { 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";
|
||||
|
||||
@@ -105,12 +93,7 @@ const CommentBoxWrapper = ({
|
||||
}`}
|
||||
>
|
||||
{label !== "" && (
|
||||
<InputLabel
|
||||
htmlFor={id}
|
||||
className={`${error !== "" ? classes.fieldLabelError : ""} ${
|
||||
classes.inputLabel
|
||||
}`}
|
||||
>
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>
|
||||
{label}
|
||||
{required ? "*" : ""}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -109,7 +109,6 @@ const inputStyles = makeStyles((theme: Theme) =>
|
||||
},
|
||||
error: {
|
||||
color: "#b53b4b",
|
||||
boxShadow: "inset 0px 0px 1px 1px #b53b4b",
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -166,12 +165,7 @@ const InputBoxWrapper = ({
|
||||
}`}
|
||||
>
|
||||
{label !== "" && (
|
||||
<InputLabel
|
||||
htmlFor={id}
|
||||
className={`${error !== "" ? classes.fieldLabelError : ""} ${
|
||||
classes.inputLabel
|
||||
}`}
|
||||
>
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>
|
||||
{label}
|
||||
{required ? "*" : ""}
|
||||
|
||||
@@ -14,6 +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 from "react";
|
||||
import clsx from "clsx";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
@@ -40,6 +41,7 @@ interface RadioGroupProps {
|
||||
id: string;
|
||||
name: string;
|
||||
tooltip?: string;
|
||||
disableOptions?: boolean;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
classes: any;
|
||||
displayInColumn?: boolean;
|
||||
@@ -58,8 +60,23 @@ const styles = (theme: Theme) =>
|
||||
paddingBottom: 10,
|
||||
marginTop: 11,
|
||||
},
|
||||
optionLabel: {
|
||||
"&.Mui-disabled": {
|
||||
"& .MuiFormControlLabel-label": {
|
||||
color: "#9c9c9c",
|
||||
},
|
||||
},
|
||||
"&:last-child": {
|
||||
marginRight: 0,
|
||||
},
|
||||
"& .MuiFormControlLabel-label": {
|
||||
fontSize: 12,
|
||||
color: "#000",
|
||||
},
|
||||
},
|
||||
checkedOption: {
|
||||
"& .MuiFormControlLabel-label": {
|
||||
fontSize: 12,
|
||||
color: "#000",
|
||||
fontWeight: 700,
|
||||
},
|
||||
@@ -98,6 +115,7 @@ export const RadioGroupSelector = ({
|
||||
name,
|
||||
onChange,
|
||||
tooltip = "",
|
||||
disableOptions = false,
|
||||
classes,
|
||||
displayInColumn = false,
|
||||
}: RadioGroupProps) => {
|
||||
@@ -128,14 +146,14 @@ export const RadioGroupSelector = ({
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={`rd-${name}-${selectorOption.value}`}
|
||||
value={selectorOption.value}
|
||||
value={disableOptions ? "disabled" : selectorOption.value}
|
||||
control={<RadioButton />}
|
||||
label={selectorOption.label}
|
||||
className={
|
||||
selectorOption.value === currentSelection
|
||||
? classes.checkedOption
|
||||
: ""
|
||||
}
|
||||
disabled={disableOptions}
|
||||
className={clsx(classes.optionLabel, {
|
||||
[classes.checkedOption]:
|
||||
selectorOption.value === currentSelection,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -54,6 +54,7 @@ export const modalBasic = {
|
||||
marginLeft: 0,
|
||||
},
|
||||
clearButton: {
|
||||
fontFamily: "Lato, sans-serif",
|
||||
border: "0",
|
||||
backgroundColor: "transparent",
|
||||
color: "#393939",
|
||||
@@ -104,6 +105,9 @@ const radioBasic = {
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: "100%",
|
||||
"input:disabled ~ &": {
|
||||
border: "1px solid #9C9C9C",
|
||||
},
|
||||
};
|
||||
|
||||
export const radioIcons = {
|
||||
@@ -117,8 +121,7 @@ export const radioIcons = {
|
||||
|
||||
export const containerForHeader = (bottomSpacing: any) => ({
|
||||
container: {
|
||||
padding: "110px 33px 30px",
|
||||
paddingBottom: bottomSpacing,
|
||||
padding: "110px 33px 0",
|
||||
"& h6": {
|
||||
color: "#777777",
|
||||
fontSize: 14,
|
||||
@@ -145,6 +148,7 @@ export const actionsTray = {
|
||||
export const searchField = {
|
||||
searchField: {
|
||||
flexGrow: 1,
|
||||
height: 40,
|
||||
background: "#FFFFFF",
|
||||
borderRadius: 5,
|
||||
border: "#EAEDEE 1px solid",
|
||||
@@ -152,13 +156,17 @@ export const searchField = {
|
||||
justifyContent: "center",
|
||||
padding: "0 16px",
|
||||
"& input": {
|
||||
fontSize: 14,
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
color: "#000",
|
||||
"&::placeholder": {
|
||||
color: "#393939",
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
"&:hover": {
|
||||
borderColor: "#000",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -178,3 +186,37 @@ export const predefinedList = {
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const selectorsCommon = {
|
||||
multiSelectTable: {
|
||||
height: 200,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -25,6 +25,14 @@ import ConsoleIcon from "./TableActionIcons/ConsoleIcon";
|
||||
import GetAppIcon from "@material-ui/icons/GetApp";
|
||||
import SvgIcon from "@material-ui/core/SvgIcon";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStyles, withStyles } from "@material-ui/core/styles";
|
||||
|
||||
const styles = () =>
|
||||
createStyles({
|
||||
spacing: {
|
||||
margin: "0 8px",
|
||||
},
|
||||
});
|
||||
|
||||
interface IActionButton {
|
||||
type: string;
|
||||
@@ -34,6 +42,7 @@ interface IActionButton {
|
||||
selected: boolean;
|
||||
sendOnlyId?: boolean;
|
||||
idField: string;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const defineIcon = (type: string, selected: boolean) => {
|
||||
@@ -67,6 +76,7 @@ const TableActionButton = ({
|
||||
selected,
|
||||
to,
|
||||
sendOnlyId = false,
|
||||
classes,
|
||||
}: IActionButton) => {
|
||||
const valueClick = sendOnlyId ? valueToSend[idField] : valueToSend;
|
||||
|
||||
@@ -82,6 +92,7 @@ const TableActionButton = ({
|
||||
}
|
||||
: () => null
|
||||
}
|
||||
className={classes.spacing}
|
||||
>
|
||||
{defineIcon(type, selected)}
|
||||
</IconButton>
|
||||
@@ -107,4 +118,4 @@ const TableActionButton = ({
|
||||
return null;
|
||||
};
|
||||
|
||||
export default TableActionButton;
|
||||
export default withStyles(styles)(TableActionButton);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -18,19 +18,13 @@ import get from "lodash/get";
|
||||
import isString from "lodash/isString";
|
||||
import {
|
||||
LinearProgress,
|
||||
TablePagination,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Grid,
|
||||
Checkbox,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { TablePaginationActionsProps } from "@material-ui/core/TablePagination/TablePaginationActions";
|
||||
import { Table, Column, AutoSizer } from "react-virtualized";
|
||||
import { createStyles, withStyles } from "@material-ui/core/styles";
|
||||
import TableActionButton from "./TableActionButton";
|
||||
import history from "../../../../history";
|
||||
import {
|
||||
@@ -50,27 +44,13 @@ interface ItemActions {
|
||||
interface IColumns {
|
||||
label: string;
|
||||
elementKey: string;
|
||||
sortable?: boolean;
|
||||
renderFunction?: (input: any) => any;
|
||||
renderFullObject?: boolean;
|
||||
globalClass?: any;
|
||||
}
|
||||
|
||||
interface IPaginatorConfig {
|
||||
rowsPerPageOptions: number[];
|
||||
colSpan: number;
|
||||
count: number;
|
||||
rowsPerPage: number;
|
||||
page: number;
|
||||
SelectProps: any;
|
||||
onChangePage: (
|
||||
event: React.MouseEvent<HTMLButtonElement> | null,
|
||||
page: number
|
||||
) => void;
|
||||
onChangeRowsPerPage?: React.ChangeEventHandler<
|
||||
HTMLTextAreaElement | HTMLInputElement
|
||||
>;
|
||||
ActionsComponent?: React.ElementType<TablePaginationActionsProps>;
|
||||
rowClass?: any;
|
||||
width?: number;
|
||||
headerTextAlign?: string;
|
||||
contentTextAlign?: string;
|
||||
}
|
||||
|
||||
interface TableWrapperProps {
|
||||
@@ -83,21 +63,26 @@ interface TableWrapperProps {
|
||||
classes: any;
|
||||
entityName: string;
|
||||
selectedItems?: string[];
|
||||
stickyHeader?: boolean;
|
||||
radioSelection?: boolean;
|
||||
customEmptyMessage?: string;
|
||||
paginatorConfig?: IPaginatorConfig;
|
||||
customPaperHeight?: string;
|
||||
}
|
||||
|
||||
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) =>
|
||||
const styles = () =>
|
||||
createStyles({
|
||||
dialogContainer: {
|
||||
padding: "12px 26px 22px",
|
||||
@@ -107,10 +92,27 @@ const styles = (theme: Theme) =>
|
||||
overflow: "auto",
|
||||
flexDirection: "column",
|
||||
padding: "19px 38px",
|
||||
minHeight: "200px",
|
||||
boxShadow: "none",
|
||||
border: "#EAEDEE 1px solid",
|
||||
borderRadius: 3,
|
||||
minHeight: 200,
|
||||
overflowY: "scroll",
|
||||
|
||||
"&::-webkit-scrollbar": {
|
||||
width: 3,
|
||||
height: 3,
|
||||
},
|
||||
},
|
||||
defaultPaperHeight: {
|
||||
height: "calc(100vh - 205px)",
|
||||
},
|
||||
allTableSettings: {
|
||||
"& .MuiTableCell-sizeSmall:last-child": {
|
||||
paddingRight: "initial",
|
||||
},
|
||||
"& .MuiTableCell-body.MuiTableCell-sizeSmall:last-child": {
|
||||
paddingRight: 6,
|
||||
},
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
@@ -118,13 +120,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,
|
||||
@@ -137,8 +141,12 @@ const styles = (theme: Theme) =>
|
||||
padding: "5px 38px",
|
||||
},
|
||||
checkBoxHeader: {
|
||||
width: 50,
|
||||
textAlign: "left",
|
||||
paddingRight: 10,
|
||||
"&.MuiTableCell-paddingCheckbox": {
|
||||
paddingBottom: 9,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 0,
|
||||
},
|
||||
},
|
||||
actionsContainer: {
|
||||
@@ -150,62 +158,149 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
checkBoxRow: {
|
||||
borderColor: borderColor,
|
||||
padding: "0 10px 0 0",
|
||||
},
|
||||
loadingBox: {
|
||||
paddingTop: "100px",
|
||||
paddingBottom: "100px",
|
||||
},
|
||||
rowElement: {
|
||||
userSelect: "none",
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: "#ececec",
|
||||
"@global": {
|
||||
".rowLine": {
|
||||
borderBottom: `1px solid ${borderColor}`,
|
||||
height: 40,
|
||||
color: "#393939",
|
||||
fontSize: 14,
|
||||
transitionDuration: 0.3,
|
||||
"&:focus": {
|
||||
outline: "initial",
|
||||
},
|
||||
"&:hover:not(.ReactVirtualized__Table__headerRow)": {
|
||||
userSelect: "none",
|
||||
backgroundColor: "#ececec",
|
||||
fontWeight: 600,
|
||||
"&.canClick": {
|
||||
cursor: "pointer",
|
||||
},
|
||||
},
|
||||
"& .selected": {
|
||||
color: "#081C42",
|
||||
fontWeight: 600,
|
||||
},
|
||||
},
|
||||
".headerItem": {
|
||||
userSelect: "none",
|
||||
fontWeight: 700,
|
||||
fontSize: 14,
|
||||
fontStyle: "initial",
|
||||
},
|
||||
".ReactVirtualized__Table__headerRow": {
|
||||
fontWeight: 700,
|
||||
fontSize: 14,
|
||||
borderColor: "#39393980",
|
||||
textTransform: "initial",
|
||||
},
|
||||
".optionsAlignment": {
|
||||
textAlign: "center",
|
||||
},
|
||||
".text-center": {
|
||||
textAlign: "center",
|
||||
},
|
||||
".text-right": {
|
||||
textAlign: "right",
|
||||
},
|
||||
},
|
||||
rowClickable: {
|
||||
cursor: "pointer",
|
||||
},
|
||||
...checkboxIcons,
|
||||
...radioIcons,
|
||||
});
|
||||
|
||||
// Function that renders Title Columns
|
||||
const titleColumnsMap = (columns: IColumns[]) => {
|
||||
return columns.map((column: IColumns, index: number) => {
|
||||
return (
|
||||
<TableCell
|
||||
key={`tbCT-${column.elementKey}-${index}`}
|
||||
className={column.globalClass}
|
||||
>
|
||||
{column.label}
|
||||
</TableCell>
|
||||
);
|
||||
});
|
||||
};
|
||||
const selectWidth = 45;
|
||||
|
||||
// Function that renders Rows
|
||||
const rowColumnsMap = (
|
||||
columns: IColumns[],
|
||||
itemData: any,
|
||||
classes: any,
|
||||
// Function to render elements in table
|
||||
const subRenderFunction = (
|
||||
rowData: any,
|
||||
column: IColumns,
|
||||
isSelected: boolean
|
||||
) => {
|
||||
return columns.map((column: IColumns, index: number) => {
|
||||
const itemElement = isString(itemData)
|
||||
? itemData
|
||||
: get(itemData, column.elementKey, null); // If the element is just a string, we render it as it is
|
||||
const renderConst = column.renderFullObject ? itemData : itemElement;
|
||||
const itemElement = isString(rowData)
|
||||
? rowData
|
||||
: get(rowData, column.elementKey, null); // If the element is just a string, we render it as it is
|
||||
const renderConst = column.renderFullObject ? rowData : itemElement;
|
||||
|
||||
const renderElement = column.renderFunction
|
||||
? column.renderFunction(renderConst)
|
||||
: renderConst; // If render function is set, we send the value to the function.
|
||||
const renderElement = column.renderFunction
|
||||
? column.renderFunction(renderConst)
|
||||
: renderConst; // If render function is set, we send the value to the function.
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span className={isSelected ? "selected" : ""}>{renderElement}</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
// Function to calculate common column width for elements with no with size
|
||||
const calculateColumnRest = (
|
||||
columns: IColumns[],
|
||||
containerWidth: number,
|
||||
actionsWidth: number,
|
||||
hasSelect: boolean,
|
||||
hasActions: boolean
|
||||
) => {
|
||||
let initialValue = containerWidth;
|
||||
|
||||
if (hasSelect) {
|
||||
initialValue -= selectWidth;
|
||||
}
|
||||
|
||||
if (hasActions) {
|
||||
initialValue -= actionsWidth;
|
||||
}
|
||||
|
||||
let freeSpacing = columns.reduce((total, currValue) => {
|
||||
return currValue.width ? total - currValue.width : total;
|
||||
}, initialValue);
|
||||
|
||||
return freeSpacing / columns.filter((el) => !el.width).length;
|
||||
};
|
||||
|
||||
// Function that renders Columns in table
|
||||
const generateColumnsMap = (
|
||||
columns: IColumns[],
|
||||
containerWidth: number,
|
||||
actionsWidth: number,
|
||||
hasSelect: boolean,
|
||||
hasActions: boolean,
|
||||
selectedItems: string[],
|
||||
idField: string
|
||||
) => {
|
||||
const commonRestWidth = calculateColumnRest(
|
||||
columns,
|
||||
containerWidth,
|
||||
actionsWidth,
|
||||
hasSelect,
|
||||
hasActions
|
||||
);
|
||||
return columns.map((column: IColumns, index: number) => {
|
||||
return (
|
||||
<TableCell
|
||||
key={`tbRE-${column.elementKey}-${index}`}
|
||||
className={isSelected ? classes.rowSelected : classes.rowUnselected}
|
||||
>
|
||||
{renderElement}
|
||||
</TableCell>
|
||||
<Column
|
||||
key={`col-tb-${index.toString()}`}
|
||||
dataKey={column.elementKey}
|
||||
headerClassName={`titleHeader ${
|
||||
column.headerTextAlign ? `text-${column.headerTextAlign}` : ""
|
||||
}`}
|
||||
headerRenderer={() => <React.Fragment>{column.label}</React.Fragment>}
|
||||
className={
|
||||
column.contentTextAlign ? `text-${column.contentTextAlign}` : ""
|
||||
}
|
||||
cellRenderer={({ rowData }) => {
|
||||
const isSelected = selectedItems
|
||||
? selectedItems.includes(
|
||||
isString(rowData) ? rowData : rowData[idField]
|
||||
)
|
||||
: false;
|
||||
return subRenderFunction(rowData, column, isSelected);
|
||||
}}
|
||||
width={column.width || commonRestWidth}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -237,6 +332,22 @@ const elementActions = (
|
||||
});
|
||||
};
|
||||
|
||||
// Function to calculate the options column width according elements inside
|
||||
const calculateOptionsSize = (containerWidth: number, totalOptions: number) => {
|
||||
const minContainerSize = 80;
|
||||
const sizeOptions = totalOptions * 45;
|
||||
|
||||
if (sizeOptions < minContainerSize) {
|
||||
return minContainerSize;
|
||||
}
|
||||
|
||||
if (sizeOptions > containerWidth) {
|
||||
return containerWidth;
|
||||
}
|
||||
|
||||
return sizeOptions;
|
||||
};
|
||||
|
||||
// Main function to render the Table Wrapper
|
||||
const TableWrapper = ({
|
||||
itemActions,
|
||||
@@ -248,10 +359,9 @@ const TableWrapper = ({
|
||||
selectedItems,
|
||||
idField,
|
||||
classes,
|
||||
stickyHeader = false,
|
||||
radioSelection = false,
|
||||
customEmptyMessage = "",
|
||||
paginatorConfig,
|
||||
customPaperHeight = "",
|
||||
}: TableWrapperProps) => {
|
||||
const findView = itemActions
|
||||
? itemActions.find((el) => el.type === "view")
|
||||
@@ -273,7 +383,13 @@ const TableWrapper = ({
|
||||
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
<Paper
|
||||
className={`${classes.paper} ${
|
||||
customPaperHeight !== ""
|
||||
? customPaperHeight
|
||||
: classes.defaultPaperHeight
|
||||
}`}
|
||||
>
|
||||
{isLoading && (
|
||||
<Grid container className={classes.loadingBox}>
|
||||
<Grid item xs={12} style={{ textAlign: "center" }}>
|
||||
@@ -285,109 +401,133 @@ const TableWrapper = ({
|
||||
</Grid>
|
||||
)}
|
||||
{records && !isLoading && records.length > 0 ? (
|
||||
<Table size="small" stickyHeader={stickyHeader}>
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
{onSelect && selectedItems && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
align="center"
|
||||
className={classes.checkBoxHeader}
|
||||
>
|
||||
Select
|
||||
</TableCell>
|
||||
)}
|
||||
{titleColumnsMap(columns)}
|
||||
{((itemActions && itemActions.length > 1) ||
|
||||
(itemActions &&
|
||||
itemActions.length === 1 &&
|
||||
itemActions[0].type !== "view")) && (
|
||||
<TableCell
|
||||
align="center"
|
||||
className={classes.actionsContainer}
|
||||
>
|
||||
Actions
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.map((record: any, index: number) => {
|
||||
const isSelected = selectedItems
|
||||
? selectedItems.includes(
|
||||
isString(record) ? record : record[idField]
|
||||
)
|
||||
: false;
|
||||
<AutoSizer>
|
||||
{({ width, height }: any) => {
|
||||
const optionsWidth = calculateOptionsSize(
|
||||
width,
|
||||
itemActions
|
||||
? itemActions.filter((el) => el.type !== "view").length
|
||||
: 0
|
||||
);
|
||||
const hasSelect: boolean = !!(onSelect && selectedItems);
|
||||
const hasOptions: boolean = !!(
|
||||
(itemActions && itemActions.length > 1) ||
|
||||
(itemActions &&
|
||||
itemActions.length === 1 &&
|
||||
itemActions[0].type !== "view")
|
||||
);
|
||||
return (
|
||||
<Table
|
||||
ref="Table"
|
||||
disableHeader={false}
|
||||
headerClassName={"headerItem"}
|
||||
headerHeight={40}
|
||||
height={height}
|
||||
noRowsRenderer={() => (
|
||||
<React.Fragment>
|
||||
{customEmptyMessage !== ""
|
||||
? customEmptyMessage
|
||||
: `There are no ${entityName} yet.`}
|
||||
</React.Fragment>
|
||||
)}
|
||||
overscanRowCount={10}
|
||||
rowHeight={40}
|
||||
width={width}
|
||||
rowCount={records.length}
|
||||
rowGetter={({ index }) => records[index]}
|
||||
onRowClick={({ rowData }) => {
|
||||
clickAction(rowData);
|
||||
}}
|
||||
rowClassName={`rowLine ${findView ? "canClick" : ""}`}
|
||||
>
|
||||
{hasSelect && (
|
||||
<Column
|
||||
headerRenderer={() => (
|
||||
<React.Fragment>Select</React.Fragment>
|
||||
)}
|
||||
dataKey={idField}
|
||||
width={selectWidth}
|
||||
cellRenderer={({ rowData }) => {
|
||||
const isSelected = selectedItems
|
||||
? selectedItems.includes(
|
||||
isString(rowData) ? rowData : rowData[idField]
|
||||
)
|
||||
: false;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={`tb-${entityName}-${index.toString()}`}
|
||||
className={`${findView ? classes.rowClickable : ""} ${
|
||||
classes.rowElement
|
||||
}`}
|
||||
onClick={() => {
|
||||
clickAction(record);
|
||||
}}
|
||||
>
|
||||
{onSelect && selectedItems && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
align="center"
|
||||
className={classes.checkBoxRow}
|
||||
>
|
||||
<Checkbox
|
||||
value={isString(record) ? record : record[idField]}
|
||||
color="primary"
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
checked={isSelected}
|
||||
onChange={onSelect}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
checkedIcon={
|
||||
<span
|
||||
className={
|
||||
radioSelection
|
||||
? classes.radioSelectedIcon
|
||||
: classes.checkedIcon
|
||||
}
|
||||
/>
|
||||
}
|
||||
icon={
|
||||
<span
|
||||
className={
|
||||
radioSelection
|
||||
? classes.radioUnselectedIcon
|
||||
: classes.unCheckedIcon
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
{rowColumnsMap(columns, record, classes, isSelected)}
|
||||
{((itemActions && itemActions.length > 1) ||
|
||||
(itemActions &&
|
||||
itemActions.length === 1 &&
|
||||
itemActions[0].type !== "view")) && (
|
||||
<TableCell
|
||||
align="center"
|
||||
className={classes.actionsContainer}
|
||||
>
|
||||
{elementActions(
|
||||
itemActions,
|
||||
record,
|
||||
return (
|
||||
<Checkbox
|
||||
value={
|
||||
isString(rowData) ? rowData : rowData[idField]
|
||||
}
|
||||
color="primary"
|
||||
inputProps={{
|
||||
"aria-label": "secondary checkbox",
|
||||
}}
|
||||
checked={isSelected}
|
||||
onChange={onSelect}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
checkedIcon={
|
||||
<span
|
||||
className={
|
||||
radioSelection
|
||||
? classes.radioSelectedIcon
|
||||
: classes.checkedIcon
|
||||
}
|
||||
/>
|
||||
}
|
||||
icon={
|
||||
<span
|
||||
className={
|
||||
radioSelection
|
||||
? classes.radioUnselectedIcon
|
||||
: classes.unCheckedIcon
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{generateColumnsMap(
|
||||
columns,
|
||||
width,
|
||||
optionsWidth,
|
||||
hasSelect,
|
||||
hasOptions,
|
||||
selectedItems || [],
|
||||
idField
|
||||
)}
|
||||
{hasOptions && (
|
||||
<Column
|
||||
headerRenderer={() => (
|
||||
<React.Fragment>Options</React.Fragment>
|
||||
)}
|
||||
dataKey={idField}
|
||||
width={optionsWidth}
|
||||
headerClassName="optionsAlignment"
|
||||
className="optionsAlignment"
|
||||
cellRenderer={({ rowData }) => {
|
||||
const isSelected = selectedItems
|
||||
? selectedItems.includes(
|
||||
isString(rowData) ? rowData : rowData[idField]
|
||||
)
|
||||
: false;
|
||||
return elementActions(
|
||||
itemActions || [],
|
||||
rowData,
|
||||
isSelected,
|
||||
idField
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Table>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{!isLoading && (
|
||||
@@ -400,20 +540,6 @@ const TableWrapper = ({
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Paper>
|
||||
{paginatorConfig && (
|
||||
<Grid item xs={12} className={classes.paginatorContainer}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
{...paginatorConfig}
|
||||
className={classes.paginatorComponent}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,7 +20,6 @@ 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";
|
||||
@@ -79,6 +78,7 @@ const ConfTargetGeneric = ({
|
||||
});
|
||||
|
||||
setValueHolder(values);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fields, defaultVals]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -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,7 +16,6 @@
|
||||
|
||||
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";
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
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";
|
||||
@@ -201,6 +200,8 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
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 (
|
||||
|
||||
@@ -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";
|
||||
@@ -147,6 +129,8 @@ const styles = (theme: Theme) =>
|
||||
container: {
|
||||
paddingBottom: theme.spacing(4),
|
||||
margin: 0,
|
||||
width: "100%",
|
||||
maxWidth: "initial",
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(2),
|
||||
@@ -248,7 +232,11 @@ const Console = ({
|
||||
},
|
||||
{
|
||||
component: ListObjects,
|
||||
path: "/object-browser/:bucket?",
|
||||
path: "/object-browser/:bucket",
|
||||
},
|
||||
{
|
||||
component: ListObjects,
|
||||
path: "/object-browser/:bucket/*",
|
||||
},
|
||||
{
|
||||
component: Watch,
|
||||
@@ -266,14 +254,6 @@ const Console = ({
|
||||
component: Policies,
|
||||
path: "/policies",
|
||||
},
|
||||
{
|
||||
component: Trace,
|
||||
path: "/trace",
|
||||
},
|
||||
{
|
||||
component: Logs,
|
||||
path: "/logs",
|
||||
},
|
||||
{
|
||||
component: Heal,
|
||||
path: "/heal",
|
||||
@@ -319,7 +299,7 @@ const Console = ({
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{session.status == "ok" ? (
|
||||
{session.status === "ok" ? (
|
||||
<div className={classes.root}>
|
||||
<CssBaseline />
|
||||
<Drawer
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -27,7 +27,6 @@ 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 {
|
||||
|
||||
@@ -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";
|
||||
@@ -258,20 +257,6 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
records={filteredRecords}
|
||||
entityName="Groups"
|
||||
idField=""
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -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";
|
||||
@@ -30,7 +29,7 @@ import SearchIcon from "@material-ui/icons/Search";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import {
|
||||
actionsTray,
|
||||
predefinedList,
|
||||
selectorsCommon,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
interface IGroupsProps {
|
||||
@@ -106,6 +105,7 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
...actionsTray,
|
||||
...selectorsCommon,
|
||||
});
|
||||
|
||||
const UsersSelectors = ({
|
||||
@@ -215,6 +215,7 @@ const UsersSelectors = ({
|
||||
records={filteredRecords}
|
||||
entityName="Users"
|
||||
idField="accessKey"
|
||||
customPaperHeight={classes.multiSelectTable}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -62,6 +62,7 @@ const styles = (theme: Theme) =>
|
||||
height: "80px",
|
||||
},
|
||||
lambdaNotif: {
|
||||
backgroundColor: "#fff",
|
||||
border: "#393939 1px solid",
|
||||
borderRadius: 5,
|
||||
width: 101,
|
||||
@@ -289,7 +290,7 @@ const AddNotificationEndpoint = ({
|
||||
<div className={classes.iconContainer}>
|
||||
{withLogos.map((item) => {
|
||||
return (
|
||||
<a
|
||||
<button
|
||||
key={`icon-${item.targetTitle}`}
|
||||
className={classes.lambdaNotif}
|
||||
onClick={() => {
|
||||
@@ -301,7 +302,7 @@ const AddNotificationEndpoint = ({
|
||||
className={classes.logoButton}
|
||||
alt={item.targetTitle}
|
||||
/>
|
||||
</a>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -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";
|
||||
@@ -206,27 +205,6 @@ const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
|
||||
records={filteredRecords}
|
||||
entityName="Notification Endpoints"
|
||||
idField="service_name"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
},
|
||||
onChangeRowsPerPage: (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const rPP = parseInt(event.target.value, 10);
|
||||
setRowsPerPage(rPP);
|
||||
},
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -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,50 @@ 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": {
|
||||
".rowLine:hover .iconBucketElm": {
|
||||
backgroundImage: "url(/images/ob_bucket_filled.svg)",
|
||||
},
|
||||
},
|
||||
browsePaper: {
|
||||
height: "calc(100vh - 280px)",
|
||||
},
|
||||
...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,15 +125,17 @@ const BrowseBuckets = ({ classes }: IBrowseBucketsProps) => {
|
||||
|
||||
const offset = page * rowsPerPage;
|
||||
|
||||
useEffect(() => {
|
||||
resetRoutesList(true);
|
||||
}, [match]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets?offset=${offset}&limit=${rowsPerPage}`)
|
||||
.then((res: BucketList) => {
|
||||
const buckets = get(res, "buckets", []);
|
||||
|
||||
setLoading(false);
|
||||
setRecords(buckets);
|
||||
setRecords(res.buckets || []);
|
||||
setError("");
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if (
|
||||
@@ -119,21 +154,22 @@ 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 handleChangePage = (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
@@ -147,6 +183,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 +208,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 +243,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,35 +251,34 @@ 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,
|
||||
width: 100,
|
||||
contentTextAlign: "right",
|
||||
headerTextAlign: "right",
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="Buckets"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: filteredRecords.length,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
customPaperHeight={classes.browsePaper}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -231,4 +286,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
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
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;
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
@@ -264,20 +257,6 @@ const Policies = ({ classes }: IPoliciesProps) => {
|
||||
records={paginatedRecords}
|
||||
entityName="Policies"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: filteredRecords.length,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -25,7 +25,10 @@ import TextField from "@material-ui/core/TextField";
|
||||
import api from "../../../common/api";
|
||||
import { policySort } from "../../../utils/sortFunctions";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import { actionsTray } from "../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
actionsTray,
|
||||
selectorsCommon,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { PolicyList } from "./types";
|
||||
|
||||
interface ISelectPolicyProps {
|
||||
@@ -100,6 +103,7 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
...actionsTray,
|
||||
...selectorsCommon,
|
||||
});
|
||||
|
||||
const PolicySelectors = ({
|
||||
@@ -188,6 +192,7 @@ const PolicySelectors = ({
|
||||
records={filteredRecords}
|
||||
entityName="Policies"
|
||||
idField="name"
|
||||
customPaperHeight={classes.multiSelectTable}
|
||||
radioSelection
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -14,20 +14,10 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import get from "lodash/get";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
Button,
|
||||
LinearProgress,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@material-ui/core";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import {
|
||||
modalBasic,
|
||||
@@ -35,10 +25,7 @@ import {
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { User } from "../Users/types";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import { Policy, PolicyList } from "./types";
|
||||
import api from "../../../common/api";
|
||||
import { policySort } from "../../../utils/sortFunctions";
|
||||
import { Group } from "../Groups/types";
|
||||
import PolicySelectors from "./PolicySelectors";
|
||||
|
||||
interface ISetPolicyProps {
|
||||
@@ -132,7 +119,8 @@ const SetPolicy = ({
|
||||
setActualPolicy(userPolicy);
|
||||
setSelectedPolicy(userPolicy);
|
||||
}
|
||||
}, [open]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open, selectedGroup, selectedUser]);
|
||||
|
||||
const userName = get(selectedUser, "accessKey", "");
|
||||
|
||||
@@ -144,6 +132,7 @@ const SetPolicy = ({
|
||||
modalOpen={open}
|
||||
title="Set Policies"
|
||||
>
|
||||
{error !== "" && <span className={classes.error}>{error}</span>}
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} className={classes.predefinedTitle}>
|
||||
Selected {selectedGroup !== null ? "Group" : "User"}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Button, LinearProgress, Tooltip } from "@material-ui/core";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
|
||||
@@ -278,20 +278,6 @@ const ServiceAccounts = ({ classes }: IServiceAccountsProps) => {
|
||||
idField={""}
|
||||
columns={[{ label: "Service Account", elementKey: "" }]}
|
||||
itemActions={tableActions}
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 4,
|
||||
count: records.length,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -100,6 +100,13 @@ const styles = (theme: Theme) =>
|
||||
color: "#dc1f2e",
|
||||
fontSize: "0.75rem",
|
||||
},
|
||||
h3Section: {
|
||||
marginTop: 0,
|
||||
},
|
||||
descriptionText: {
|
||||
fontSize: 13,
|
||||
color: "#777777",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
@@ -265,6 +272,8 @@ const AddTenant = ({
|
||||
/*Calculate Allocation*/
|
||||
useEffect(() => {
|
||||
validateClusterSize();
|
||||
setECParityChoices([]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [nodes, volumeSize, sizeFactor]);
|
||||
|
||||
const validateClusterSize = () => {
|
||||
@@ -660,6 +669,7 @@ const AddTenant = ({
|
||||
setEncryptionValid(Object.keys(commonVal).length === 0);
|
||||
|
||||
setValidationErrors(commonVal);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
enableEncryption,
|
||||
encryptionType,
|
||||
@@ -744,7 +754,7 @@ const AddTenant = ({
|
||||
},
|
||||
resources: {
|
||||
requests: {
|
||||
memory: parseInt(getBytes(memoryNode, "Gi")),
|
||||
memory: parseInt(getBytes(memoryNode, "GiB")),
|
||||
},
|
||||
},
|
||||
affinity: hardCodedAffinity,
|
||||
@@ -920,6 +930,7 @@ const AddTenant = ({
|
||||
setAddError(err);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [addSending]);
|
||||
|
||||
const storeCertInObject = (certName: string, certValue: string) => {
|
||||
@@ -945,8 +956,10 @@ const AddTenant = ({
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Name Tenant</h3>
|
||||
<span>How would you like to name this new tenant?</span>
|
||||
<h3 className={classes.h3Section}>Name Tenant</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
How would you like to name this new tenant?
|
||||
</span>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
@@ -981,7 +994,6 @@ const AddTenant = ({
|
||||
id="storage_class"
|
||||
name="storage_class"
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
console.log(e.target.value as string);
|
||||
setSelectedStorageClass(e.target.value as string);
|
||||
}}
|
||||
label="Storage Class"
|
||||
@@ -992,7 +1004,7 @@ const AddTenant = ({
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
<span>
|
||||
<span className={classes.descriptionText}>
|
||||
Check 'Advanced Mode' for additional configuration options, such
|
||||
as IDP, Disk Encryption, and customized TLS/SSL Certificates.
|
||||
<br />
|
||||
@@ -1028,8 +1040,10 @@ const AddTenant = ({
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Configure</h3>
|
||||
<span>Basic configurations for tenant management</span>
|
||||
<h3 className={classes.h3Section}>Configure</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Basic configurations for tenant management
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Grid item xs={12}>
|
||||
@@ -1111,8 +1125,8 @@ const AddTenant = ({
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>IDP</h3>
|
||||
<span>
|
||||
<h3 className={classes.h3Section}>IDP</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Access to the tenant can be controlled via an external Identity
|
||||
Manager.
|
||||
</span>
|
||||
@@ -1299,7 +1313,7 @@ const AddTenant = ({
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Security</h3>
|
||||
<h3 className={classes.h3Section}>Security</h3>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
@@ -1421,8 +1435,10 @@ const AddTenant = ({
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Encryption</h3>
|
||||
<span>How would you like to encrypt the information at rest.</span>
|
||||
<h3 className={classes.h3Section}>Encryption</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
How would you like to encrypt the information at rest.
|
||||
</span>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
@@ -1883,8 +1899,10 @@ const AddTenant = ({
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Tenant Size</h3>
|
||||
<span>Please select the desired capacity</span>
|
||||
<h3 className={classes.h3Section}>Tenant Size</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Please select the desired capacity
|
||||
</span>
|
||||
</div>
|
||||
<span className={classes.error}>{distribution.error}</span>
|
||||
<Grid item xs={12}>
|
||||
@@ -1923,7 +1941,7 @@ const AddTenant = ({
|
||||
</div>
|
||||
<div className={classes.sizeFactorContainer}>
|
||||
<SelectWrapper
|
||||
label=""
|
||||
label={"Unit"}
|
||||
id="size_factor"
|
||||
name="size_factor"
|
||||
value={sizeFactor}
|
||||
@@ -1963,7 +1981,7 @@ const AddTenant = ({
|
||||
value={ecParity}
|
||||
options={ecParityChoices}
|
||||
/>
|
||||
<span>
|
||||
<span className={classes.descriptionText}>
|
||||
Please select the desired parity. This setting will change the
|
||||
max usable capacity in the cluster
|
||||
</span>
|
||||
@@ -2011,8 +2029,10 @@ const AddTenant = ({
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Review</h3>
|
||||
<span>Review the details of the new tenant</span>
|
||||
<h3 className={classes.h3Section}>Review</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Review the details of the new tenant
|
||||
</span>
|
||||
</div>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
|
||||
@@ -69,6 +69,7 @@ const DeleteTenant = ({
|
||||
setDeleteError(err);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [deleteLoading]);
|
||||
|
||||
const removeRecord = () => {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
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";
|
||||
@@ -34,7 +33,11 @@ import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
|
||||
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
|
||||
import history from "../../../../history";
|
||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../../Common/PageHeader/PageHeader";
|
||||
|
||||
interface ITenantsList {
|
||||
@@ -71,18 +74,8 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
@@ -93,7 +86,6 @@ const ListTenants = ({ classes }: ITenantsList) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [filterTenants, setFilterTenants] = useState<string>("");
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
const [offset, setOffset] = useState<number>(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [error, setError] = useState<string>("");
|
||||
@@ -154,17 +146,15 @@ const ListTenants = ({ classes }: ITenantsList) => {
|
||||
setRowsPerPage(rPP);
|
||||
};
|
||||
|
||||
const openLink = (link: string) => {
|
||||
window.open(link, "_blank");
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: redirectToTenantDetails },
|
||||
{ type: "delete", onClick: confirmDeleteTenant },
|
||||
];
|
||||
|
||||
const globalOffset = 0;
|
||||
|
||||
const filteredRecords = records
|
||||
.slice(offset, offset + rowsPerPage)
|
||||
.slice(globalOffset, globalOffset + rowsPerPage)
|
||||
.filter((b: any) => {
|
||||
if (filterTenants === "") {
|
||||
return true;
|
||||
@@ -252,17 +242,6 @@ const ListTenants = ({ classes }: ITenantsList) => {
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Refresh Tenant List"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
setIsLoading(true);
|
||||
}}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
|
||||
<TextField
|
||||
placeholder="Search Tenants"
|
||||
className={classes.searchField}
|
||||
@@ -280,6 +259,16 @@ const ListTenants = ({ classes }: ITenantsList) => {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Refresh Tenant List"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
setIsLoading(true);
|
||||
}}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
@@ -291,6 +280,11 @@ const ListTenants = ({ classes }: ITenantsList) => {
|
||||
Create Tenant
|
||||
</Button>
|
||||
</Grid>
|
||||
{error !== "" && (
|
||||
<Grid item xs={12}>
|
||||
{error}
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
@@ -307,20 +301,6 @@ const ListTenants = ({ classes }: ITenantsList) => {
|
||||
records={filteredRecords}
|
||||
entityName="Tenants"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: filteredRecords.length,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -3,13 +3,8 @@ import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import {
|
||||
factorForDropdown,
|
||||
getTotalSize,
|
||||
niceBytes,
|
||||
} from "../../../../common/utils";
|
||||
import { niceBytes } from "../../../../common/utils";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import api from "../../../../common/api";
|
||||
import { IAddZoneRequest, ITenant } from "../ListTenants/types";
|
||||
|
||||
@@ -15,24 +15,22 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
|
||||
import { AppState } from "../../../store";
|
||||
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 { wsProtocol } from "../../../utils/wsUtils";
|
||||
import { timeFromDate } from "../../../../../common/utils";
|
||||
import { wsProtocol } from "../../../../../utils/wsUtils";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { Button, Grid } from "@material-ui/core";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import { CreateIcon } from "../../../icons";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -74,6 +72,8 @@ interface ILogs {
|
||||
logMessageReceived: typeof logMessageReceived;
|
||||
logResetMessages: typeof logResetMessages;
|
||||
messages: LogMessage[];
|
||||
namespace: string;
|
||||
tenant: string;
|
||||
}
|
||||
|
||||
const Logs = ({
|
||||
@@ -81,6 +81,8 @@ const Logs = ({
|
||||
logMessageReceived,
|
||||
logResetMessages,
|
||||
messages,
|
||||
namespace,
|
||||
tenant,
|
||||
}: ILogs) => {
|
||||
const [highlight, setHighlight] = useState("");
|
||||
|
||||
@@ -93,7 +95,7 @@ const Logs = ({
|
||||
const wsProt = wsProtocol(url.protocol);
|
||||
|
||||
const c = new W3CWebSocket(
|
||||
`${wsProt}://${url.hostname}:${port}/ws/console`
|
||||
`${wsProt}://${url.hostname}:${port}/ws/console/${namespace}/${tenant}`
|
||||
);
|
||||
|
||||
let interval: any | null = null;
|
||||
@@ -122,20 +124,7 @@ const Logs = ({
|
||||
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;
|
||||
};
|
||||
}, [logMessageReceived, logResetMessages]);
|
||||
|
||||
const renderError = (logElement: LogMessage) => {
|
||||
let errorElems = [];
|
||||
@@ -335,38 +324,33 @@ const Logs = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<PageHeader label="Logs" />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Highlight Line"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setHighlight(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.logList}>{renderLines}</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Highlight Line"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setHighlight(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.logList}>{renderLines}</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -31,8 +31,9 @@ import AddZoneModal from "./AddZoneModal";
|
||||
import AddBucket from "../../Buckets/ListBuckets/AddBucket";
|
||||
import ReplicationSetup from "./ReplicationSetup";
|
||||
import api from "../../../../common/api";
|
||||
import { BucketInfo } from "../../Buckets/types";
|
||||
import { ITenant, IZone } from "../ListTenants/types";
|
||||
import Logs from "./Logs/Logs";
|
||||
import Trace from "./Trace/Trace";
|
||||
|
||||
interface ITenantDetailsProps {
|
||||
classes: any;
|
||||
@@ -94,8 +95,6 @@ const styles = (theme: Theme) =>
|
||||
const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
|
||||
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||
const [capacity, setCapacity] = useState<number>(0);
|
||||
const [externalIDP, setExternalIDP] = useState<boolean>(false);
|
||||
const [externalKMS, setExternalKMS] = useState<boolean>(false);
|
||||
const [zoneCount, setZoneCount] = useState<number>(0);
|
||||
const [zones, setZones] = useState<IZone[]>([]);
|
||||
const [instances, setInstances] = useState<number>(0);
|
||||
@@ -103,7 +102,6 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
|
||||
const [addZoneOpen, setAddZone] = useState<boolean>(false);
|
||||
const [addBucketOpen, setAddBucketOpen] = useState<boolean>(false);
|
||||
const [addReplicationOpen, setAddReplicationOpen] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [tenant, setTenant] = useState<ITenant | null>(null);
|
||||
|
||||
@@ -131,8 +129,6 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
|
||||
const tenantName = match.params["tenantName"];
|
||||
const tenantNamespace = match.params["tenantNamespace"];
|
||||
|
||||
setLoading(true);
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
@@ -140,7 +136,7 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
|
||||
)
|
||||
.then((res: ITenant) => {
|
||||
const resZones = !res.zones ? [] : res.zones;
|
||||
const total = res.volume_count * res.volume_size;
|
||||
|
||||
let totalInstances = 0;
|
||||
let totalVolumes = 0;
|
||||
let count = 1;
|
||||
@@ -165,16 +161,15 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
|
||||
|
||||
setTenant(res);
|
||||
setError("");
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadInfo();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -204,6 +199,11 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
|
||||
{`Tenant > ${match.params["tenantName"]}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{error !== "" && (
|
||||
<Grid item xs={12}>
|
||||
{error}
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
@@ -239,6 +239,8 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
|
||||
aria-label="tenant-tabs"
|
||||
>
|
||||
<Tab label="Zones" />
|
||||
<Tab label="Logs" />
|
||||
<Tab label="Trace" />
|
||||
</Tabs>
|
||||
</Grid>
|
||||
<Grid item xs={6} className={classes.actionsTray}>
|
||||
@@ -257,42 +259,36 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={[
|
||||
{
|
||||
type: "delete",
|
||||
onClick: (element) => {
|
||||
console.log(element);
|
||||
{selectedTab === 0 && (
|
||||
<TableWrapper
|
||||
itemActions={[
|
||||
{
|
||||
type: "delete",
|
||||
onClick: (element) => {
|
||||
console.log(element);
|
||||
},
|
||||
sendOnlyId: true,
|
||||
},
|
||||
sendOnlyId: true,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Capacity", elementKey: "capacity" },
|
||||
{ label: "# of Instances", elementKey: "servers" },
|
||||
{ label: "# of Drives", elementKey: "volumes" },
|
||||
]}
|
||||
isLoading={false}
|
||||
records={zones}
|
||||
entityName="Zones"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: zoneCount,
|
||||
rowsPerPage: 10,
|
||||
page: 0,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
onChangePage: () => {},
|
||||
onChangeRowsPerPage: () => {},
|
||||
}}
|
||||
/>
|
||||
]}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Capacity", elementKey: "capacity" },
|
||||
{ label: "# of Instances", elementKey: "servers" },
|
||||
{ label: "# of Drives", elementKey: "volumes" },
|
||||
]}
|
||||
isLoading={false}
|
||||
records={zones}
|
||||
entityName="Zones"
|
||||
idField="name"
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
{selectedTab === 1 && tenant !== null && (
|
||||
<Logs namespace={tenant.namespace} tenant={tenant.name} />
|
||||
)}
|
||||
{selectedTab === 2 && tenant !== null && (
|
||||
<Trace namespace={tenant.namespace} tenant={tenant.name} />
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -16,17 +16,16 @@
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
|
||||
import { AppState } from "../../../store";
|
||||
import { AppState } from "../../../../../store";
|
||||
import { connect } from "react-redux";
|
||||
import { traceMessageReceived, traceResetMessages } from "./actions";
|
||||
import { TraceMessage } from "./types";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { niceBytes, timeFromDate } from "../../../common/utils";
|
||||
import { wsProtocol } from "../../../utils/wsUtils";
|
||||
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import { niceBytes, timeFromDate } from "../../../../../common/utils";
|
||||
import { wsProtocol } from "../../../../../utils/wsUtils";
|
||||
import { containerForHeader } from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import TableWrapper from "../../../Common/TableWrapper/TableWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -45,6 +44,12 @@ const styles = (theme: Theme) =>
|
||||
borderBottom: "1px solid #dedede",
|
||||
},
|
||||
},
|
||||
sizeItem: {
|
||||
width: 150,
|
||||
},
|
||||
timeItem: {
|
||||
width: 100,
|
||||
},
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
@@ -53,6 +58,8 @@ interface ITrace {
|
||||
traceMessageReceived: typeof traceMessageReceived;
|
||||
traceResetMessages: typeof traceResetMessages;
|
||||
messages: TraceMessage[];
|
||||
namespace: string;
|
||||
tenant: string;
|
||||
}
|
||||
|
||||
const Trace = ({
|
||||
@@ -60,6 +67,8 @@ const Trace = ({
|
||||
traceMessageReceived,
|
||||
traceResetMessages,
|
||||
messages,
|
||||
namespace,
|
||||
tenant,
|
||||
}: ITrace) => {
|
||||
useEffect(() => {
|
||||
traceResetMessages();
|
||||
@@ -68,7 +77,9 @@ const Trace = ({
|
||||
const port = isDev ? "9090" : url.port;
|
||||
|
||||
const wsProt = wsProtocol(url.protocol);
|
||||
const c = new W3CWebSocket(`${wsProt}://${url.hostname}:${port}/ws/trace`);
|
||||
const c = new W3CWebSocket(
|
||||
`${wsProt}://${url.hostname}:${port}/ws/trace/${namespace}/${tenant}`
|
||||
);
|
||||
|
||||
let interval: any | null = null;
|
||||
if (c !== null) {
|
||||
@@ -95,60 +106,62 @@ const Trace = ({
|
||||
console.log("closing websockets");
|
||||
};
|
||||
}
|
||||
}, [traceMessageReceived]);
|
||||
}, [traceMessageReceived, traceResetMessages]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<PageHeader label={"Trace"} />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<TableWrapper
|
||||
itemActions={[]}
|
||||
columns={[
|
||||
{
|
||||
label: "Time",
|
||||
elementKey: "time",
|
||||
renderFunction: (time: Date) => {
|
||||
const timeParse = new Date(time);
|
||||
return timeFromDate(timeParse);
|
||||
},
|
||||
},
|
||||
{ label: "Name", elementKey: "api" },
|
||||
{
|
||||
label: "Status",
|
||||
elementKey: "",
|
||||
renderFunction: (fullElement: TraceMessage) =>
|
||||
`${fullElement.statusCode} ${fullElement.statusMsg}`,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Location",
|
||||
elementKey: "configuration_id",
|
||||
renderFunction: (fullElement: TraceMessage) =>
|
||||
`${fullElement.host} ${fullElement.client}`,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{ label: "Load Time", elementKey: "callStats.duration" },
|
||||
{
|
||||
label: "Upload",
|
||||
elementKey: "callStats.rx",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
{
|
||||
label: "Download",
|
||||
elementKey: "callStats.tx",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
]}
|
||||
isLoading={false}
|
||||
records={messages}
|
||||
entityName="Traces"
|
||||
idField="api"
|
||||
customEmptyMessage="There are no traced Elements yet"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={[]}
|
||||
columns={[
|
||||
{
|
||||
label: "Time",
|
||||
elementKey: "time",
|
||||
renderFunction: (time: Date) => {
|
||||
const timeParse = new Date(time);
|
||||
return timeFromDate(timeParse);
|
||||
},
|
||||
globalClass: classes.timeItem,
|
||||
},
|
||||
{ label: "Name", elementKey: "api" },
|
||||
{
|
||||
label: "Status",
|
||||
elementKey: "",
|
||||
renderFunction: (fullElement: TraceMessage) =>
|
||||
`${fullElement.statusCode} ${fullElement.statusMsg}`,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Location",
|
||||
elementKey: "configuration_id",
|
||||
renderFunction: (fullElement: TraceMessage) =>
|
||||
`${fullElement.host} ${fullElement.client}`,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Load Time",
|
||||
elementKey: "callStats.duration",
|
||||
globalClass: classes.timeItem,
|
||||
},
|
||||
{
|
||||
label: "Upload",
|
||||
elementKey: "callStats.rx",
|
||||
renderFunction: niceBytes,
|
||||
globalClass: classes.sizeItem,
|
||||
},
|
||||
{
|
||||
label: "Download",
|
||||
elementKey: "callStats.tx",
|
||||
renderFunction: niceBytes,
|
||||
globalClass: classes.sizeItem,
|
||||
},
|
||||
]}
|
||||
isLoading={false}
|
||||
records={messages}
|
||||
entityName="Traces"
|
||||
idField="api"
|
||||
customEmptyMessage="There are no traced Elements yet"
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import api from "../../../common/api";
|
||||
import GroupsSelectors from "./GroupsSelectors";
|
||||
import Title from "../../../common/Title";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
|
||||
interface IAddToGroup {
|
||||
|
||||
@@ -28,7 +28,6 @@ import api from "../../../common/api";
|
||||
import GroupsSelectors from "./GroupsSelectors";
|
||||
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";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user