Compare commits

..

31 Commits

Author SHA1 Message Date
Minio Trusted
419e94ccec update to v0.4.5 2020-11-13 11:10:13 -08:00
Alex
12bc5265b8 Fixed issue with object browser icons & long names (#389)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-11-12 14:33:56 -08:00
Kaan Kabalak
32898f0c57 Edit Searchbar styling based on mockups (#385)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-11-12 12:05:40 -08:00
Kaan Kabalak
125c9abf56 Adjust Modal form clear button font based on mockups (#384)
The font of the clear button for Modal forms were 'sans-serif' instead
of being 'Lato' as specified in the mockups.

Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-11-12 10:28:49 -08:00
Alex
bc27db4a69 Migrated tablewrapper to use react-virtualized (#387)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-11-11 20:14:48 -08:00
Kaan Kabalak
dd8e2b13d3 Add disabled functionality and fix styling for RadioGroupSelector (#383)
* Adjust RadioGroupSelector label styling based on mockups

* Add disabled support for Radio Group Selector options
2020-11-10 18:59:21 -08:00
Alex
005e3b941c Fixed issue with checkbox selection in table wrapper (#380)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-11-10 00:08:45 -08:00
Alex
efa614c773 Fixed default value for nulls in browse buckets (#379)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-11-06 16:47:07 -08:00
Minio Trusted
63350e5492 update to v0.4.4 2020-11-05 18:07:46 -08:00
Alex
255c128b67 Fixed pagination un buckets lists (#371)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-11-05 10:16:03 -08:00
Cesar N
06f333395e Move trace and logs UI to Operator Console (#375)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-11-04 21:45:48 -08:00
Alex
3cd024ea2c Changed sizes for main container & table paper (#377)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-11-04 16:36:51 -08:00
Alex
9c0a407db6 Fixed page refresh on buckets creation (#372)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-11-04 15:25:36 -08:00
Alex
dc3c619f3f Added animation & disabled button / fields on sending (#369)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-11-03 11:12:41 -08:00
Alex
5000aafba6 Added functionality for create folder & replaced icons (#368)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-11-02 21:45:47 -08:00
Cesar N
b9f2a39d50 Fix tenant creation (#363)
Memory size request was not being sent, hence the tenant was not being created.
2020-11-02 11:03:40 -08:00
Daniel Valdivia
df321191f4 Build assets inside the container (#357) 2020-11-01 07:34:37 -08:00
Alex
547eb41e96 Added navigation to object browser (#358)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-31 00:22:46 -07:00
Lenin Alevski
afbb83e081 certs-dir value was ignored because of wrong variable assigning (#362) 2020-10-30 22:36:52 -07:00
Lenin Alevski
b599968570 SNI support for Console (#352)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-29 22:26:48 -07:00
Cesar N
24cc60f34e Add put object api and add list object improvements (#356) 2020-10-28 16:08:26 -07:00
Alex
f967058409 Updated Heal section (#351)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-27 21:06:24 -07:00
Alex
078e09ba76 Fixed inconsistences in create tenant modal (#349)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-27 12:43:12 -07:00
Lenin Alevski
d6f97841d4 return generic login error when invalid credentials (#350) 2020-10-27 10:05:16 -07:00
Lenin Alevski
619ac124b3 Bucket encryption checkbox and endpoints for Console (#343)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-25 12:56:23 -07:00
Alex
a2180e123d Removed warnings on console (#345)
For Operator modules this will be removed after upgrading component to use redux/formik

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-24 00:05:26 -07:00
Cesar N
0325bb7e2d Add put object retention api (#340) 2020-10-23 15:04:02 -07:00
Minio Trusted
fce361e5bd update to v0.4.3 2020-10-23 02:15:25 -07:00
Alex
ed6d6e8b9d Fixed audit issues (#342)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-23 02:03:49 -07:00
Alex
406709f66b Updated Watch view to have console consistent styles (#341)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-22 17:15:40 -07:00
Cesar N
3ac45a2211 Add Set object's legalhold status api (#339)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-22 16:23:29 -07:00
152 changed files with 9397 additions and 5204 deletions

View File

@@ -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 }}

View File

@@ -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:
-

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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

View File

@@ -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

View 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
}

View 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
}

View 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
}

View File

@@ -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"`

View File

@@ -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"`

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View File

@@ -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

View File

@@ -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
View 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
View 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

View File

@@ -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",

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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";

View File

@@ -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

View File

@@ -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;

View 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;

View File

@@ -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";

View File

@@ -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"

View File

@@ -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>

View File

@@ -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));

View File

@@ -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)));

View File

@@ -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";

View File

@@ -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);

View File

@@ -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

View File

@@ -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"}&nbsp;</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>

View File

@@ -19,6 +19,11 @@ export interface Bucket {
creation_date: Date;
}
export interface BucketEncryptionInfo {
algorithm: string;
kmsMasterKeyID: string;
}
export interface BucketInfo {
name: string;
access: string;

View File

@@ -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

View File

@@ -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>
);

View File

@@ -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 ? "*" : ""}

View File

@@ -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 !== "" && (

View File

@@ -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";

View File

@@ -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 ? "*" : ""}

View File

@@ -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,
})}
/>
);
})}

View File

@@ -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,
},
};

View File

@@ -30,7 +30,7 @@ const styles = (theme: Theme) =>
minWidth: 180,
marginRight: 10,
"& ul": {
padding: 15,
padding: "0 15px 0 0",
"& li": {
listStyle: "lower-roman",

View File

@@ -51,6 +51,7 @@ const WizardPage = ({ classes, page, pageChange }: IWizardPage) => {
break;
case "to":
pageChange(btn.toPage || 0);
break;
default:
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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>

View File

@@ -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>
);
};

View File

@@ -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(() => {

View File

@@ -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"

View File

@@ -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"

View File

@@ -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";

View File

@@ -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 (

View File

@@ -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

View File

@@ -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";

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)));

View File

@@ -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));

View File

@@ -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>

View 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,
};
};

View 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;
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"}

View File

@@ -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";

View File

@@ -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>

View File

@@ -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}>

View File

@@ -69,6 +69,7 @@ const DeleteTenant = ({
setDeleteError(err);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [deleteLoading]);
const removeRecord = () => {

View File

@@ -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>

View File

@@ -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";

View File

@@ -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>
);
};

View File

@@ -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>
);

View File

@@ -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>
);
};

View File

@@ -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 {

View File

@@ -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