Compare commits

...

31 Commits

Author SHA1 Message Date
Minio Trusted
2c14142e19 update to v0.4.1 2020-10-12 20:37:21 -07:00
Kaan Kabalak
1caa3f2ce8 Implement License page (#324)
* Implement License page

Fixes #320

* License Assets

* Fix endpoint tests

Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-12 11:56:15 -07:00
Alex
6501a4b13f First set of Modal style changes (#322)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-09 12:01:24 -07:00
Daniel Valdivia
2f51621e69 Get Tenant Secret From Tenant CR (#323)
We were assuming the Tenant Credentials Secret instead of reading it from it's .spec.credsSecret this commit addresses that
2020-10-09 11:51:02 -07:00
Cesar N
7e6e64c729 Add download objects api and integrate it with UI (#321) 2020-10-09 11:43:15 -07:00
Kaan Kabalak
9007c7dd14 Consolidate Remote Buckets and Replication modals (#317)
* Consolidate Remote Buckets and Replication modals

This commit consolidates Remote Buckets and Replication modals into a
single modal in the Add Replication Rule modal located in the Buckets
section

Fixes #301

* Remove Remote Buckets section

* Properly align tabs and button on Buckets page
2020-10-08 09:55:31 -07:00
Alex
850fd3e371 Changed buttons & search boxes styles (#318)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-06 21:37:13 -07:00
Daniel Valdivia
6d8f1c439e Handle Invalid User error coming from madmin (#314)
Right now we display `Internal Server Error` when invalid credentials are presneted, this makes it so we only present `Unauthorized`
2020-10-06 16:45:26 -07:00
Alex
7166717688 Changed styles for Login page (#316)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-06 16:37:25 -07:00
Cesar N
f91346dc5b Add retention mode and legal hold mode on list objects api (#312)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-06 16:07:33 -07:00
Alex
dccdfb5533 Customization of Dashboard page & improved some styles (#315)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-05 21:30:52 -07:00
Daniel Valdivia
4f065bdedf Change Menu Order. (#313) 2020-10-05 20:48:10 -07:00
Minio Trusted
4a02c5848b update to v0.4.0 2020-10-05 12:47:31 -07:00
Lenin Alevski
e16a926ef8 Add support for loading multiple TLS certificates (#304)
- update operator version to latest version
- create tenant endpoint now supports multiple TLS certificates for
  MinIO TLS configuration
- update certificates endpoint now support multiple TLS certificates

Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-10-05 12:09:34 -07:00
Alex
78f4978a9a Changed navbar & header styles (#311)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-05 11:33:27 -07:00
Cesar N
42d617caf9 Add list objects UI and integrate with listing and delete api (#310) 2020-10-02 17:37:08 -07:00
Daniel Valdivia
28eb8784a9 Set Bucket Quota on Creation (#308)
Introces the capability to set bucket quota on bucket creation and adds the API to set the bucket on it's own
2020-10-01 18:59:20 -07:00
Cesar N
fcf5d5c9f7 Add delete objects api (#303)
Supports single and multiple objects which needs to be defined by recursive flag.
An object to be deleted needs to be defined by a query parameter, path, since it can be
an object or a folder.
2020-10-01 17:00:32 -07:00
Alex
a42f1ff4ee Added buckets-object browser view (#307)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-10-01 13:21:55 -07:00
Alex
98f897ed5b Added object browser main paths (#302)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-09-30 17:46:07 -07:00
Cesar N
7afd608faa Add list objects api (#291)
This includes the basic information of an object
2020-09-29 14:34:51 -07:00
Alex
8313a62f17 Add support for Remote Buckets and Replication (#287)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-09-28 10:46:08 -07:00
Alex
459e2bf61c Added file name visualization in file select (#289)
* Added missing validations in add tenant modal

* Added file name visualization in file selector

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2020-09-28 10:36:31 -07:00
Minio Trusted
858d363e97 update to version v0.3.26 2020-09-23 08:52:43 -07:00
Lenin Alevski
47704189d1 fix kes empty configuration (#286)
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
2020-09-22 20:49:25 -07:00
Daniel Valdivia
b72d424ec9 UI: Tweaks to multiple elements (#284) 2020-09-22 20:31:00 -07:00
Lenin Alevski
86426e95f7 Added Annotations, Labels and NodeSelector fields (#285)
For Console/Encryption objects in the  CreateTenant Api
2020-09-22 15:50:37 -07:00
Daniel Valdivia
e5f7870f5e Parity API (#280) 2020-09-22 11:15:21 -07:00
Lenin Alevski
c0ee739624 IV generation for ChaCha20 poly auth scheme (#283)
Generate 16 bytes IV instead of an IV of 32 bytes (and then use half of it) when using ChaCha20 to
encrypt tokens, this is to prevent tokens to become malleable.
2020-09-22 10:49:34 -07:00
Minio Trusted
1dc99498d9 update v0.3.25 2020-09-21 22:07:11 -07:00
Cesar N
319d96c725 Use operator port variables (#282) 2020-09-21 21:31:30 -07:00
181 changed files with 18455 additions and 1980 deletions

View File

@@ -3,10 +3,10 @@ name: Go
on:
pull_request:
branches:
- master
- master
push:
branches:
- master
- master
jobs:
build:
@@ -14,7 +14,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [1.13.x, 1.14.x]
go-version: [1.14.x]
os: [ubuntu-latest]
steps:
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}

View File

@@ -76,21 +76,19 @@ func newApp(name string) *cli.App {
findClosestCommands := func(command string) []string {
var closestCommands []string
for _, value := range commandsTree.PrefixMatch(command) {
closestCommands = append(closestCommands, value.(string))
}
closestCommands = append(closestCommands, commandsTree.PrefixMatch(command)...)
sort.Strings(closestCommands)
// Suggest other close commands - allow missed, wrongly added and
// even transposed characters
for _, value := range commandsTree.Walk(commandsTree.Root()) {
if sort.SearchStrings(closestCommands, value.(string)) < len(closestCommands) {
if sort.SearchStrings(closestCommands, value) < len(closestCommands) {
continue
}
// 2 is arbitrary and represents the max
// allowed number of typed errors
if words.DamerauLevenshteinDistance(command, value.(string)) < 2 {
closestCommands = append(closestCommands, value.(string))
if words.DamerauLevenshteinDistance(command, value) < 2 {
closestCommands = append(closestCommands, value)
}
}

12
go.mod
View File

@@ -16,16 +16,16 @@ require (
github.com/jessevdk/go-flags v1.4.0
github.com/minio/cli v1.22.0
github.com/minio/kes v0.11.0
github.com/minio/mc v0.0.0-20200808005614-7e52c104bee1
github.com/minio/minio v0.0.0-20200808024306-2a9819aff876
github.com/minio/minio-go/v7 v7.0.5-0.20200807085956-d7db33ea7618
github.com/minio/operator v0.0.0-20200904194631-b8aa01dc5d70
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/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/secure-io/sio-go v0.3.1
github.com/stretchr/testify v1.6.1
github.com/unrolled/secure v1.0.7
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/net v0.0.0-20200707034311-ab3426394381
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/net v0.0.0-20200904194848-62affa334b73
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
gopkg.in/yaml.v2 v2.3.0
k8s.io/api v0.18.6

76
go.sum
View File

@@ -14,6 +14,7 @@ cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0 h1:EpMNVUorLiZIELdMZbCYX/ByTFCdoYopYAGxaGVz9ms=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
@@ -30,6 +31,7 @@ cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjp
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0 h1:86K1Gel7BQ9/WmNWn7dTKMvTLFzwtBe5FNqYbi9X35g=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
code.gitea.io/sdk/gitea v0.12.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
@@ -43,6 +45,7 @@ git.apache.org/thrift.git v0.13.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqbl
github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU=
github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZOMdj5HYo=
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY=
github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
@@ -52,6 +55,8 @@ github.com/Azure/azure-sdk-for-go v42.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0=
github.com/Azure/azure-storage-blob-go v0.8.0 h1:53qhf0Oxa0nOjgbDeeYPUeyiNmafAFEY95rZLK0Tj6o=
github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
github.com/Azure/azure-storage-blob-go v0.10.0 h1:evCwGreYo3XLeBV4vSxLbLiYb6e0SzsJiXQVRGsRXxs=
github.com/Azure/azure-storage-blob-go v0.10.0/go.mod h1:ep1edmW+kNQx4UfWM9heESNmQdijykocJ0YOxmMX8SE=
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=
@@ -67,6 +72,7 @@ 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/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=
@@ -145,6 +151,7 @@ github.com/aws/aws-sdk-go v1.20.21/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.26.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.29.11/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg=
github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2 h1:M+TYzBcNIRyzPRg66ndEqUMd7oWDmhvdQmaPC6EZNwM=
@@ -169,6 +176,7 @@ github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMS
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -520,9 +528,11 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s=
github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww=
github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI=
@@ -541,6 +551,8 @@ github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.5-0.20200711200521-98cb6bf42e08 h1:kPna6oIGlRXWmg/jkKfxbpvsl+0DHYnw1qQwN+6+gyA=
github.com/gorilla/mux v1.7.5-0.20200711200521-98cb6bf42e08/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
@@ -585,6 +597,7 @@ github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es
github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE=
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8=
@@ -711,6 +724,8 @@ github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -734,6 +749,8 @@ github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaa
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
@@ -748,6 +765,8 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
@@ -765,30 +784,30 @@ github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
github.com/minio/kes v0.11.0 h1:8ma6OCVSxKT50b1uYXLJro3m7PmZtCLxBaTddQexI5k=
github.com/minio/kes v0.11.0/go.mod h1:mTF1Bv8YVEtQqF/B7Felp4tLee44Pp+dgI0rhCvgNg8=
github.com/minio/mc v0.0.0-20200808005614-7e52c104bee1 h1:OrcFWsUIzKoXeIXVReZ7AryDtbPBLtkjDDOBnuU9RWY=
github.com/minio/mc v0.0.0-20200808005614-7e52c104bee1/go.mod h1:OGP9+cwQ174WKwZTgJOIFstVv19CH0wdSDZSG6NyTuE=
github.com/minio/mc v0.0.0-20201001165056-7f2df96e4821 h1:+09Ta2rY29df6U4bfSC85t7jxlNuYpUfl7lIVgTiDEA=
github.com/minio/mc v0.0.0-20201001165056-7f2df96e4821/go.mod h1:+OeGPfw7qpvO1x2Td65ejwAuFT0Cxzx0GFTJSDlPMZQ=
github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
github.com/minio/minio v0.0.0-20200723003940-b9be841fd222 h1:+XFGpEsqmA033nDX8LtjyPZy01Shivf6E2OL67WoGiE=
github.com/minio/minio v0.0.0-20200723003940-b9be841fd222/go.mod h1:Eu2KC2p+vW03rnYY/6R/D+QduPB7/j4kBaVA/EDLjWM=
github.com/minio/minio v0.0.0-20200807001021-adcaa6f9de88 h1:v2mCqNx6N02jcYHWjMPHdTN9+ogxEN9L+cCQJ+8j2AU=
github.com/minio/minio v0.0.0-20200807001021-adcaa6f9de88/go.mod h1:r+PkhkMRxudvboO0Wa7F7nMiDfI8Rz1HZSza0uIhtMU=
github.com/minio/minio v0.0.0-20200808024306-2a9819aff876 h1:e5114Mb8Evzt1QsA8b6PrXZ1KqBLts0CokpKeU1DV2U=
github.com/minio/minio v0.0.0-20200808024306-2a9819aff876/go.mod h1:r+PkhkMRxudvboO0Wa7F7nMiDfI8Rz1HZSza0uIhtMU=
github.com/minio/minio v0.0.0-20200927172404-27d9bd04e544 h1:G6M9uXdFShowoLG3rMkdCtHsx37ZYB1vc+7bu22r85I=
github.com/minio/minio v0.0.0-20200927172404-27d9bd04e544/go.mod h1:5uolst3SWpgvDNF+KJwfViU+j9WH7dB5i2YCi0nMNVo=
github.com/minio/minio-go/v7 v7.0.1/go.mod h1:dJ80Mv2HeGkYLH1sqS/ksz07ON6csH3S6JUMSQ2zAns=
github.com/minio/minio-go/v7 v7.0.2 h1:P/7wFd4KrRBHVo7AKdcqO+9ReoS+XpMjfRFoE5quH0E=
github.com/minio/minio-go/v7 v7.0.2/go.mod h1:dJ80Mv2HeGkYLH1sqS/ksz07ON6csH3S6JUMSQ2zAns=
github.com/minio/minio-go/v7 v7.0.3/go.mod h1:TA0CQCjJZHM5SJj9IjqR0NmpmQJ6bCbXifAJ3mUU6Hw=
github.com/minio/minio-go/v7 v7.0.5-0.20200807085956-d7db33ea7618 h1:8iTb0TFs6kDGAUnhI/s2QCZOYcSTtYmY9dF+Cbc0WJo=
github.com/minio/minio-go/v7 v7.0.5-0.20200807085956-d7db33ea7618/go.mod h1:CSt2ETZNs+bIIhWTse0mcZKZWMGrFU7Er7RR0TmkDYk=
github.com/minio/operator v0.0.0-20200904194631-b8aa01dc5d70 h1:FjyhnnrOHMzhJryqNoOISgp8p1dmmn1IMOlgBAaf8r4=
github.com/minio/operator v0.0.0-20200904194631-b8aa01dc5d70/go.mod h1:NVl1+c7TYxJB22opK/m2L5SkTvlEYd1ZFPuL6SX5fCg=
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/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=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/simdjson-go v0.1.5-0.20200303142138-b17fe061ea37 h1:pDeao6M5AEd8hwTtGmE0pVKomlL56JFRa5SiXDZAuJE=
github.com/minio/simdjson-go v0.1.5-0.20200303142138-b17fe061ea37/go.mod h1:oKURrZZEBtqObgJrSjN1Ln2n9MJj2icuBTkeJzZnvSI=
github.com/minio/simdjson-go v0.1.5 h1:6T5mHh7r3kUvgwhmFWQAjoPV5Yt5oD/VPjAI9ViH1kM=
github.com/minio/simdjson-go v0.1.5/go.mod h1:oKURrZZEBtqObgJrSjN1Ln2n9MJj2icuBTkeJzZnvSI=
github.com/minio/sio v0.2.0 h1:NCRCFLx0r5pRbXf65LVNjxbCGZgNQvNFQkgX3XF4BoA=
github.com/minio/sio v0.2.0/go.mod h1:nKM5GIWSrqbOZp0uhyj6M1iA0X6xQzSGtYSaTKSCut0=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@@ -811,6 +830,7 @@ github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mmcloughlin/avo v0.0.0-20200523190732-4439b6b2c061 h1:UCU8+cLbbvyxi0sQ9fSeoEhZgvrrD9HKMtX6Gmc1vk8=
github.com/mmcloughlin/avo v0.0.0-20200523190732-4439b6b2c061/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -855,6 +875,7 @@ github.com/nsqio/go-nsq v1.0.7/go.mod h1:XP5zaUs3pqf+Q71EqUJs3HYfBIqfK6G83WQMdNN
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olivere/elastic/v7 v7.0.12/go.mod h1:14rWX28Pnh3qCKYRVnSGXWLf9MbLonYS/4FDCY3LAPo=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -874,6 +895,7 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -986,12 +1008,15 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
github.com/smartystreets/gunit v1.1.3/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
@@ -1057,6 +1082,7 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
@@ -1077,6 +1103,8 @@ github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJb
github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/willf/bloom v2.0.3+incompatible h1:QDacWdqcAUI1MPOwIQZRy9kOR7yxfyEmxX8Wdm2/JPA=
github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8=
github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
@@ -1093,6 +1121,7 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
@@ -1112,6 +1141,7 @@ go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -1156,8 +1186,8 @@ golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1238,6 +1268,9 @@ golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1316,8 +1349,8 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200806125547-5acd03effb82 h1:6cBnXxYO+CiRVrChvCosSv7magqTPbyAgz1M8iOv5wM=
golang.org/x/sys v0.0.0-20200806125547-5acd03effb82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200915084602-288bc346aa39 h1:356XA7ITklAU2//sYkjFeco+dH1bCRD8XCJ9FIEsvo4=
golang.org/x/sys v0.0.0-20200915084602-288bc346aa39/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1407,14 +1440,18 @@ golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200724172932-b5fc9d354d99 h1:OHn441rq5CeM5r1xJ0OmY7lfdTvnedi6k+vQiI7G9b8=
golang.org/x/tools v0.0.0-20200724172932-b5fc9d354d99/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d h1:szSOL78iTCl0LF1AMjhSWJj8tIM0KixlUUnBtYXsmd8=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200814172026-c4923e618c08 h1:sfBQLM20fzeXhOixVQirwEbuW4PGStP773EXQpsBB6E=
golang.org/x/tools v0.0.0-20200814172026-c4923e618c08/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200929223013-bf155c11ec6f h1:7+Nz9MyPqt2qMCTvNiRy1G0zYfkB7UCa+ayT6uVvbyI=
golang.org/x/tools v0.0.0-20200929223013-bf155c11ec6f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
@@ -1435,6 +1472,7 @@ google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.25.0 h1:LodzhlzZEUfhXzNUMIfVlf9Gr6Ua5MMtoFWh7+f47qA=
google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1516,6 +1554,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@@ -1545,6 +1585,8 @@ gopkg.in/ldap.v3 v3.0.3/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/olivere/elastic.v5 v5.0.80 h1:AKjfcq3ZIAAqO4m8h/vJ3GP6nY8n9ft5mgf54fEqC60=
gopkg.in/olivere/elastic.v5 v5.0.80/go.mod h1:uhHoB4o3bvX5sorxBU29rPcmBQdV2Qfg0FBrx5D6pV0=
gopkg.in/olivere/elastic.v5 v5.0.86 h1:xFy6qRCGAmo5Wjx96srho9BitLhZl2fcnpuidPwduXM=
gopkg.in/olivere/elastic.v5 v5.0.86/go.mod h1:M3WNlsF+WhYn7api4D87NIflwTV/c0iVs8cqfWhK+68=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=

View File

@@ -7,7 +7,6 @@ rules:
- ""
resources:
- namespaces
- secrets
- pods
- services
- events
@@ -18,6 +17,18 @@ rules:
- create
- list
- patch
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- watch
- create
- list
- patch
- deletecollection
- delete
- apiGroups:
- "storage.k8s.io"
resources:

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:v0.3.24
image: minio/console:v0.4.1
imagePullPolicy: "IfNotPresent"
args:
- server

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:v0.3.24
image: minio/console:v0.4.1
imagePullPolicy: "IfNotPresent"
env:
- name: CONSOLE_OPERATOR_MODE

View File

@@ -8,4 +8,4 @@ resources:
- console-configmap.yaml
- console-service.yaml
- console-deployment.yaml
- https://github.com/minio/operator/?ref=v3.0.10
- https://github.com/minio/operator/?ref=v3.0.19

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"
)
// AddBucketReplication add bucket replication
//
// swagger:model addBucketReplication
type AddBucketReplication struct {
// arn
Arn string `json:"arn,omitempty"`
// destination bucket
DestinationBucket string `json:"destination_bucket,omitempty"`
}
// Validate validates this add bucket replication
func (m *AddBucketReplication) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *AddBucketReplication) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *AddBucketReplication) UnmarshalBinary(b []byte) error {
var res AddBucketReplication
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

96
models/bucket_object.go Normal file
View File

@@ -0,0 +1,96 @@
// 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"
)
// BucketObject bucket object
//
// swagger:model bucketObject
type BucketObject struct {
// content type
ContentType string `json:"content_type,omitempty"`
// expiration
Expiration string `json:"expiration,omitempty"`
// expiration rule id
ExpirationRuleID string `json:"expiration_rule_id,omitempty"`
// is delete marker
IsDeleteMarker bool `json:"is_delete_marker,omitempty"`
// is latest
IsLatest bool `json:"is_latest,omitempty"`
// last modified
LastModified string `json:"last_modified,omitempty"`
// legal hold status
LegalHoldStatus string `json:"legal_hold_status,omitempty"`
// name
Name string `json:"name,omitempty"`
// retention mode
RetentionMode string `json:"retention_mode,omitempty"`
// retention until date
RetentionUntilDate string `json:"retention_until_date,omitempty"`
// size
Size int64 `json:"size,omitempty"`
// user tags
UserTags map[string]string `json:"user_tags,omitempty"`
// version id
VersionID string `json:"version_id,omitempty"`
}
// Validate validates this bucket object
func (m *BucketObject) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *BucketObject) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *BucketObject) UnmarshalBinary(b []byte) error {
var res BucketObject
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"
)
// BucketReplicationDestination bucket replication destination
//
// swagger:model bucketReplicationDestination
type BucketReplicationDestination struct {
// bucket
Bucket string `json:"bucket,omitempty"`
}
// Validate validates this bucket replication destination
func (m *BucketReplicationDestination) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *BucketReplicationDestination) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *BucketReplicationDestination) UnmarshalBinary(b []byte) error {
var res BucketReplicationDestination
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,97 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// BucketReplicationResponse bucket replication response
//
// swagger:model bucketReplicationResponse
type BucketReplicationResponse struct {
// rules
Rules []*BucketReplicationRule `json:"rules"`
}
// Validate validates this bucket replication response
func (m *BucketReplicationResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateRules(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *BucketReplicationResponse) validateRules(formats strfmt.Registry) error {
if swag.IsZero(m.Rules) { // not required
return nil
}
for i := 0; i < len(m.Rules); i++ {
if swag.IsZero(m.Rules[i]) { // not required
continue
}
if m.Rules[i] != nil {
if err := m.Rules[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("rules" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *BucketReplicationResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *BucketReplicationResponse) UnmarshalBinary(b []byte) error {
var res BucketReplicationResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,173 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// BucketReplicationRule bucket replication rule
//
// swagger:model bucketReplicationRule
type BucketReplicationRule struct {
// delete marker replication
DeleteMarkerReplication *BucketReplicationRuleMarker `json:"delete_marker_replication,omitempty"`
// destination
Destination *BucketReplicationDestination `json:"destination,omitempty"`
// id
ID string `json:"id,omitempty"`
// priority
Priority int32 `json:"priority,omitempty"`
// status
// Enum: [Enabled Disabled]
Status string `json:"status,omitempty"`
}
// Validate validates this bucket replication rule
func (m *BucketReplicationRule) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateDeleteMarkerReplication(formats); err != nil {
res = append(res, err)
}
if err := m.validateDestination(formats); err != nil {
res = append(res, err)
}
if err := m.validateStatus(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *BucketReplicationRule) validateDeleteMarkerReplication(formats strfmt.Registry) error {
if swag.IsZero(m.DeleteMarkerReplication) { // not required
return nil
}
if m.DeleteMarkerReplication != nil {
if err := m.DeleteMarkerReplication.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("delete_marker_replication")
}
return err
}
}
return nil
}
func (m *BucketReplicationRule) validateDestination(formats strfmt.Registry) error {
if swag.IsZero(m.Destination) { // not required
return nil
}
if m.Destination != nil {
if err := m.Destination.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("destination")
}
return err
}
}
return nil
}
var bucketReplicationRuleTypeStatusPropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["Enabled","Disabled"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
bucketReplicationRuleTypeStatusPropEnum = append(bucketReplicationRuleTypeStatusPropEnum, v)
}
}
const (
// BucketReplicationRuleStatusEnabled captures enum value "Enabled"
BucketReplicationRuleStatusEnabled string = "Enabled"
// BucketReplicationRuleStatusDisabled captures enum value "Disabled"
BucketReplicationRuleStatusDisabled string = "Disabled"
)
// prop value enum
func (m *BucketReplicationRule) validateStatusEnum(path, location string, value string) error {
if err := validate.EnumCase(path, location, value, bucketReplicationRuleTypeStatusPropEnum, true); err != nil {
return err
}
return nil
}
func (m *BucketReplicationRule) validateStatus(formats strfmt.Registry) error {
if swag.IsZero(m.Status) { // not required
return nil
}
// value enum
if err := m.validateStatusEnum("status", "body", m.Status); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *BucketReplicationRule) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *BucketReplicationRule) UnmarshalBinary(b []byte) error {
var res BucketReplicationRule
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,117 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// BucketReplicationRuleMarker bucket replication rule marker
//
// swagger:model bucketReplicationRuleMarker
type BucketReplicationRuleMarker struct {
// status
// Enum: [Enabled Disabled]
Status string `json:"status,omitempty"`
}
// Validate validates this bucket replication rule marker
func (m *BucketReplicationRuleMarker) 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
}
var bucketReplicationRuleMarkerTypeStatusPropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["Enabled","Disabled"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
bucketReplicationRuleMarkerTypeStatusPropEnum = append(bucketReplicationRuleMarkerTypeStatusPropEnum, v)
}
}
const (
// BucketReplicationRuleMarkerStatusEnabled captures enum value "Enabled"
BucketReplicationRuleMarkerStatusEnabled string = "Enabled"
// BucketReplicationRuleMarkerStatusDisabled captures enum value "Disabled"
BucketReplicationRuleMarkerStatusDisabled string = "Disabled"
)
// prop value enum
func (m *BucketReplicationRuleMarker) validateStatusEnum(path, location string, value string) error {
if err := validate.EnumCase(path, location, value, bucketReplicationRuleMarkerTypeStatusPropEnum, true); err != nil {
return err
}
return nil
}
func (m *BucketReplicationRuleMarker) validateStatus(formats strfmt.Registry) error {
if swag.IsZero(m.Status) { // not required
return nil
}
// value enum
if err := m.validateStatusEnum("status", "body", m.Status); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *BucketReplicationRuleMarker) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *BucketReplicationRuleMarker) UnmarshalBinary(b []byte) error {
var res BucketReplicationRuleMarker
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"
)
// BucketVersioningResponse bucket versioning response
//
// swagger:model bucketVersioningResponse
type BucketVersioningResponse struct {
// is versioned
IsVersioned bool `json:"is_versioned,omitempty"`
}
// Validate validates this bucket versioning response
func (m *BucketVersioningResponse) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *BucketVersioningResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *BucketVersioningResponse) UnmarshalBinary(b []byte) error {
var res BucketVersioningResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,117 @@
// 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"
)
// ConsoleConfiguration console configuration
//
// swagger:model consoleConfiguration
type ConsoleConfiguration struct {
MetadataFields
// image
Image string `json:"image,omitempty"`
}
// UnmarshalJSON unmarshals this object from a JSON structure
func (m *ConsoleConfiguration) UnmarshalJSON(raw []byte) error {
// AO0
var aO0 MetadataFields
if err := swag.ReadJSON(raw, &aO0); err != nil {
return err
}
m.MetadataFields = aO0
// AO1
var dataAO1 struct {
Image string `json:"image,omitempty"`
}
if err := swag.ReadJSON(raw, &dataAO1); err != nil {
return err
}
m.Image = dataAO1.Image
return nil
}
// MarshalJSON marshals this object to a JSON structure
func (m ConsoleConfiguration) MarshalJSON() ([]byte, error) {
_parts := make([][]byte, 0, 2)
aO0, err := swag.WriteJSON(m.MetadataFields)
if err != nil {
return nil, err
}
_parts = append(_parts, aO0)
var dataAO1 struct {
Image string `json:"image,omitempty"`
}
dataAO1.Image = m.Image
jsonDataAO1, errAO1 := swag.WriteJSON(dataAO1)
if errAO1 != nil {
return nil, errAO1
}
_parts = append(_parts, jsonDataAO1)
return swag.ConcatJSON(_parts...), nil
}
// Validate validates this console configuration
func (m *ConsoleConfiguration) Validate(formats strfmt.Registry) error {
var res []error
// validation for a type composition with MetadataFields
if err := m.MetadataFields.Validate(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// MarshalBinary interface implementation
func (m *ConsoleConfiguration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ConsoleConfiguration) UnmarshalBinary(b []byte) error {
var res ConsoleConfiguration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,162 @@
// 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"
)
// CreateRemoteBucket create remote bucket
//
// swagger:model createRemoteBucket
type CreateRemoteBucket struct {
// access key
// Required: true
// Min Length: 3
AccessKey *string `json:"accessKey"`
// region
Region string `json:"region,omitempty"`
// secret key
// Required: true
// Min Length: 8
SecretKey *string `json:"secretKey"`
// source bucket
// Required: true
SourceBucket *string `json:"sourceBucket"`
// target bucket
// Required: true
TargetBucket *string `json:"targetBucket"`
// target URL
// Required: true
TargetURL *string `json:"targetURL"`
}
// Validate validates this create remote bucket
func (m *CreateRemoteBucket) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAccessKey(formats); err != nil {
res = append(res, err)
}
if err := m.validateSecretKey(formats); err != nil {
res = append(res, err)
}
if err := m.validateSourceBucket(formats); err != nil {
res = append(res, err)
}
if err := m.validateTargetBucket(formats); err != nil {
res = append(res, err)
}
if err := m.validateTargetURL(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *CreateRemoteBucket) validateAccessKey(formats strfmt.Registry) error {
if err := validate.Required("accessKey", "body", m.AccessKey); err != nil {
return err
}
if err := validate.MinLength("accessKey", "body", string(*m.AccessKey), 3); err != nil {
return err
}
return nil
}
func (m *CreateRemoteBucket) validateSecretKey(formats strfmt.Registry) error {
if err := validate.Required("secretKey", "body", m.SecretKey); err != nil {
return err
}
if err := validate.MinLength("secretKey", "body", string(*m.SecretKey), 8); err != nil {
return err
}
return nil
}
func (m *CreateRemoteBucket) validateSourceBucket(formats strfmt.Registry) error {
if err := validate.Required("sourceBucket", "body", m.SourceBucket); err != nil {
return err
}
return nil
}
func (m *CreateRemoteBucket) validateTargetBucket(formats strfmt.Registry) error {
if err := validate.Required("targetBucket", "body", m.TargetBucket); err != nil {
return err
}
return nil
}
func (m *CreateRemoteBucket) validateTargetURL(formats strfmt.Registry) error {
if err := validate.Required("targetURL", "body", m.TargetURL); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *CreateRemoteBucket) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *CreateRemoteBucket) UnmarshalBinary(b []byte) error {
var res CreateRemoteBucket
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -42,6 +42,9 @@ type CreateTenantRequest struct {
// annotations
Annotations map[string]string `json:"annotations,omitempty"`
// console
Console *ConsoleConfiguration `json:"console,omitempty"`
// console image
ConsoleImage string `json:"console_image,omitempty"`
@@ -105,6 +108,10 @@ type CreateTenantRequest struct {
func (m *CreateTenantRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConsole(formats); err != nil {
res = append(res, err)
}
if err := m.validateEncryption(formats); err != nil {
res = append(res, err)
}
@@ -139,6 +146,24 @@ func (m *CreateTenantRequest) Validate(formats strfmt.Registry) error {
return nil
}
func (m *CreateTenantRequest) validateConsole(formats strfmt.Registry) error {
if swag.IsZero(m.Console) { // not required
return nil
}
if m.Console != nil {
if err := m.Console.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("console")
}
return err
}
}
return nil
}
func (m *CreateTenantRequest) validateEncryption(formats strfmt.Registry) error {
if swag.IsZero(m.Encryption) { // not required

View File

@@ -32,6 +32,7 @@ import (
//
// swagger:model encryptionConfiguration
type EncryptionConfiguration struct {
MetadataFields
// aws
Aws *AwsConfiguration `json:"aws,omitempty"`
@@ -52,10 +53,100 @@ type EncryptionConfiguration struct {
Vault *VaultConfiguration `json:"vault,omitempty"`
}
// UnmarshalJSON unmarshals this object from a JSON structure
func (m *EncryptionConfiguration) UnmarshalJSON(raw []byte) error {
// AO0
var aO0 MetadataFields
if err := swag.ReadJSON(raw, &aO0); err != nil {
return err
}
m.MetadataFields = aO0
// AO1
var dataAO1 struct {
Aws *AwsConfiguration `json:"aws,omitempty"`
Client *KeyPairConfiguration `json:"client,omitempty"`
Gemalto *GemaltoConfiguration `json:"gemalto,omitempty"`
Image string `json:"image,omitempty"`
Server *KeyPairConfiguration `json:"server,omitempty"`
Vault *VaultConfiguration `json:"vault,omitempty"`
}
if err := swag.ReadJSON(raw, &dataAO1); err != nil {
return err
}
m.Aws = dataAO1.Aws
m.Client = dataAO1.Client
m.Gemalto = dataAO1.Gemalto
m.Image = dataAO1.Image
m.Server = dataAO1.Server
m.Vault = dataAO1.Vault
return nil
}
// MarshalJSON marshals this object to a JSON structure
func (m EncryptionConfiguration) MarshalJSON() ([]byte, error) {
_parts := make([][]byte, 0, 2)
aO0, err := swag.WriteJSON(m.MetadataFields)
if err != nil {
return nil, err
}
_parts = append(_parts, aO0)
var dataAO1 struct {
Aws *AwsConfiguration `json:"aws,omitempty"`
Client *KeyPairConfiguration `json:"client,omitempty"`
Gemalto *GemaltoConfiguration `json:"gemalto,omitempty"`
Image string `json:"image,omitempty"`
Server *KeyPairConfiguration `json:"server,omitempty"`
Vault *VaultConfiguration `json:"vault,omitempty"`
}
dataAO1.Aws = m.Aws
dataAO1.Client = m.Client
dataAO1.Gemalto = m.Gemalto
dataAO1.Image = m.Image
dataAO1.Server = m.Server
dataAO1.Vault = m.Vault
jsonDataAO1, errAO1 := swag.WriteJSON(dataAO1)
if errAO1 != nil {
return nil, errAO1
}
_parts = append(_parts, jsonDataAO1)
return swag.ConcatJSON(_parts...), nil
}
// Validate validates this encryption configuration
func (m *EncryptionConfiguration) Validate(formats strfmt.Registry) error {
var res []error
// validation for a type composition with MetadataFields
if err := m.MetadataFields.Validate(formats); err != nil {
res = append(res, err)
}
if err := m.validateAws(formats); err != nil {
res = append(res, err)
}

View File

@@ -0,0 +1,100 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// ListObjectsResponse list objects response
//
// swagger:model listObjectsResponse
type ListObjectsResponse struct {
// list of resulting objects
Objects []*BucketObject `json:"objects"`
// number of objects
Total int64 `json:"total,omitempty"`
}
// Validate validates this list objects response
func (m *ListObjectsResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateObjects(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ListObjectsResponse) validateObjects(formats strfmt.Registry) error {
if swag.IsZero(m.Objects) { // not required
return nil
}
for i := 0; i < len(m.Objects); i++ {
if swag.IsZero(m.Objects[i]) { // not required
continue
}
if m.Objects[i] != nil {
if err := m.Objects[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("objects" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ListObjectsResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ListObjectsResponse) UnmarshalBinary(b []byte) error {
var res ListObjectsResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,100 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// ListRemoteBucketsResponse list remote buckets response
//
// swagger:model listRemoteBucketsResponse
type ListRemoteBucketsResponse struct {
// list of remote buckets
Buckets []*RemoteBucket `json:"buckets"`
// number of remote buckets accessible to user
Total int64 `json:"total,omitempty"`
}
// Validate validates this list remote buckets response
func (m *ListRemoteBucketsResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateBuckets(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ListRemoteBucketsResponse) validateBuckets(formats strfmt.Registry) error {
if swag.IsZero(m.Buckets) { // not required
return nil
}
for i := 0; i < len(m.Buckets); i++ {
if swag.IsZero(m.Buckets[i]) { // not required
continue
}
if m.Buckets[i] != nil {
if err := m.Buckets[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("buckets" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ListRemoteBucketsResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ListRemoteBucketsResponse) UnmarshalBinary(b []byte) error {
var res ListRemoteBucketsResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -37,6 +37,12 @@ type MakeBucketRequest struct {
// name
// Required: true
Name *string `json:"name"`
// quota
Quota *SetBucketQuota `json:"quota,omitempty"`
// versioning
Versioning bool `json:"versioning,omitempty"`
}
// Validate validates this make bucket request
@@ -47,6 +53,10 @@ func (m *MakeBucketRequest) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateQuota(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
@@ -62,6 +72,24 @@ func (m *MakeBucketRequest) validateName(formats strfmt.Registry) error {
return nil
}
func (m *MakeBucketRequest) validateQuota(formats strfmt.Registry) error {
if swag.IsZero(m.Quota) { // not required
return nil
}
if m.Quota != nil {
if err := m.Quota.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("quota")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *MakeBucketRequest) MarshalBinary() ([]byte, error) {
if m == nil {

66
models/metadata_fields.go Normal file
View File

@@ -0,0 +1,66 @@
// 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"
)
// MetadataFields metadata fields
//
// swagger:model metadataFields
type MetadataFields struct {
// annotations
Annotations map[string]string `json:"annotations,omitempty"`
// labels
Labels map[string]string `json:"labels,omitempty"`
// node selector
NodeSelector map[string]string `json:"node_selector,omitempty"`
}
// Validate validates this metadata fields
func (m *MetadataFields) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *MetadataFields) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *MetadataFields) UnmarshalBinary(b []byte) error {
var res MetadataFields
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

37
models/parity_response.go Normal file
View File

@@ -0,0 +1,37 @@
// 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"
)
// ParityResponse parity response
//
// swagger:model parityResponse
type ParityResponse []string
// Validate validates this parity response
func (m ParityResponse) Validate(formats strfmt.Registry) error {
return nil
}

200
models/remote_bucket.go Normal file
View File

@@ -0,0 +1,200 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// RemoteBucket remote bucket
//
// swagger:model remoteBucket
type RemoteBucket struct {
// access key
// Required: true
// Min Length: 3
AccessKey *string `json:"accessKey"`
// remote a r n
// Required: true
RemoteARN *string `json:"remoteARN"`
// secret key
// Min Length: 8
SecretKey string `json:"secretKey,omitempty"`
// service
// Enum: [replication]
Service string `json:"service,omitempty"`
// source bucket
// Required: true
SourceBucket *string `json:"sourceBucket"`
// status
Status string `json:"status,omitempty"`
// target bucket
TargetBucket string `json:"targetBucket,omitempty"`
// target URL
TargetURL string `json:"targetURL,omitempty"`
}
// Validate validates this remote bucket
func (m *RemoteBucket) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAccessKey(formats); err != nil {
res = append(res, err)
}
if err := m.validateRemoteARN(formats); err != nil {
res = append(res, err)
}
if err := m.validateSecretKey(formats); err != nil {
res = append(res, err)
}
if err := m.validateService(formats); err != nil {
res = append(res, err)
}
if err := m.validateSourceBucket(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *RemoteBucket) validateAccessKey(formats strfmt.Registry) error {
if err := validate.Required("accessKey", "body", m.AccessKey); err != nil {
return err
}
if err := validate.MinLength("accessKey", "body", string(*m.AccessKey), 3); err != nil {
return err
}
return nil
}
func (m *RemoteBucket) validateRemoteARN(formats strfmt.Registry) error {
if err := validate.Required("remoteARN", "body", m.RemoteARN); err != nil {
return err
}
return nil
}
func (m *RemoteBucket) validateSecretKey(formats strfmt.Registry) error {
if swag.IsZero(m.SecretKey) { // not required
return nil
}
if err := validate.MinLength("secretKey", "body", string(m.SecretKey), 8); err != nil {
return err
}
return nil
}
var remoteBucketTypeServicePropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["replication"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
remoteBucketTypeServicePropEnum = append(remoteBucketTypeServicePropEnum, v)
}
}
const (
// RemoteBucketServiceReplication captures enum value "replication"
RemoteBucketServiceReplication string = "replication"
)
// prop value enum
func (m *RemoteBucket) validateServiceEnum(path, location string, value string) error {
if err := validate.EnumCase(path, location, value, remoteBucketTypeServicePropEnum, true); err != nil {
return err
}
return nil
}
func (m *RemoteBucket) validateService(formats strfmt.Registry) error {
if swag.IsZero(m.Service) { // not required
return nil
}
// value enum
if err := m.validateServiceEnum("service", "body", m.Service); err != nil {
return err
}
return nil
}
func (m *RemoteBucket) validateSourceBucket(formats strfmt.Registry) error {
if err := validate.Required("sourceBucket", "body", m.SourceBucket); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *RemoteBucket) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *RemoteBucket) UnmarshalBinary(b []byte) error {
var res RemoteBucket
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

137
models/set_bucket_quota.go Normal file
View File

@@ -0,0 +1,137 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// SetBucketQuota set bucket quota
//
// swagger:model setBucketQuota
type SetBucketQuota struct {
// amount
Amount int64 `json:"amount,omitempty"`
// enabled
// Required: true
Enabled *bool `json:"enabled"`
// quota type
// Enum: [fifo hard]
QuotaType string `json:"quota_type,omitempty"`
}
// Validate validates this set bucket quota
func (m *SetBucketQuota) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateEnabled(formats); err != nil {
res = append(res, err)
}
if err := m.validateQuotaType(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *SetBucketQuota) validateEnabled(formats strfmt.Registry) error {
if err := validate.Required("enabled", "body", m.Enabled); err != nil {
return err
}
return nil
}
var setBucketQuotaTypeQuotaTypePropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["fifo","hard"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
setBucketQuotaTypeQuotaTypePropEnum = append(setBucketQuotaTypeQuotaTypePropEnum, v)
}
}
const (
// SetBucketQuotaQuotaTypeFifo captures enum value "fifo"
SetBucketQuotaQuotaTypeFifo string = "fifo"
// SetBucketQuotaQuotaTypeHard captures enum value "hard"
SetBucketQuotaQuotaTypeHard string = "hard"
)
// prop value enum
func (m *SetBucketQuota) validateQuotaTypeEnum(path, location string, value string) error {
if err := validate.EnumCase(path, location, value, setBucketQuotaTypeQuotaTypePropEnum, true); err != nil {
return err
}
return nil
}
func (m *SetBucketQuota) validateQuotaType(formats strfmt.Registry) error {
if swag.IsZero(m.QuotaType) { // not required
return nil
}
// value enum
if err := m.validateQuotaTypeEnum("quota_type", "body", m.QuotaType); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *SetBucketQuota) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SetBucketQuota) UnmarshalBinary(b []byte) error {
var res SetBucketQuota
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"
)
// SetBucketVersioning set bucket versioning
//
// swagger:model setBucketVersioning
type SetBucketVersioning struct {
// versioning
Versioning bool `json:"versioning,omitempty"`
}
// Validate validates this set bucket versioning
func (m *SetBucketVersioning) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SetBucketVersioning) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SetBucketVersioning) UnmarshalBinary(b []byte) error {
var res SetBucketVersioning
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -23,6 +23,8 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
@@ -37,7 +39,7 @@ type TLSConfiguration struct {
Console *KeyPairConfiguration `json:"console,omitempty"`
// minio
Minio *KeyPairConfiguration `json:"minio,omitempty"`
Minio []*KeyPairConfiguration `json:"minio"`
}
// Validate validates this tls configuration
@@ -82,13 +84,20 @@ func (m *TLSConfiguration) validateMinio(formats strfmt.Registry) error {
return nil
}
if m.Minio != nil {
if err := m.Minio.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("minio")
}
return err
for i := 0; i < len(m.Minio); i++ {
if swag.IsZero(m.Minio[i]) { // not required
continue
}
if m.Minio[i] != nil {
if err := m.Minio[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("minio" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil

3
package-lock.json generated
View File

@@ -1,3 +0,0 @@
{
"lockfileVersion": 1
}

View File

@@ -22,22 +22,27 @@ 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"
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"
)
type ConfigurationActionSet struct {
@@ -208,22 +213,57 @@ var healActionSet = ConfigurationActionSet{
),
}
var remoteBucketsActionSet = ConfigurationActionSet{
actionTypes: iampolicy.NewActionSet(
iampolicy.AllAdminActions,
),
actions: iampolicy.NewActionSet(
iampolicy.ConfigUpdateAdminAction,
),
}
var replicationActionSet = ConfigurationActionSet{
actionTypes: iampolicy.NewActionSet(
iampolicy.AllAdminActions,
),
actions: iampolicy.NewActionSet(
iampolicy.ConfigUpdateAdminAction,
),
}
// objectBrowserActionSet no actions needed for this module to work
var objectBrowserActionSet = ConfigurationActionSet{
actionTypes: iampolicy.NewActionSet(),
actions: iampolicy.NewActionSet(),
}
// licenseActionSet no actions needed for this module to work
var licenseActionSet = ConfigurationActionSet{
actionTypes: iampolicy.NewActionSet(),
actions: iampolicy.NewActionSet(),
}
// endpointRules contains the mapping between endpoints and ActionSets, additional rules can be added here
var endpointRules = map[string]ConfigurationActionSet{
configuration: configurationActionSet,
users: usersActionSet,
groups: groupsActionSet,
iamPolicies: iamPoliciesActionSet,
dashboard: dashboardActionSet,
profiling: profilingActionSet,
trace: traceActionSet,
logs: logsActionSet,
watch: watchActionSet,
notifications: notificationsActionSet,
buckets: bucketsActionSet,
bucketsDetail: bucketsActionSet,
serviceAccounts: serviceAccountsActionSet,
heal: healActionSet,
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,
}
// 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: 2,
want: 5,
},
{
name: "policies endpoint",
@@ -63,7 +63,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"admin:ListUserPolicies",
},
},
want: 2,
want: 5,
},
{
name: "all admin endpoints",
@@ -72,7 +72,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"admin:*",
},
},
want: 11,
want: 16,
},
{
name: "all s3 endpoints",
@@ -81,7 +81,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"s3:*",
},
},
want: 4,
want: 7,
},
{
name: "all admin and s3 endpoints",
@@ -91,7 +91,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"s3:*",
},
},
want: 14,
want: 19,
},
{
name: "no endpoints",

View File

@@ -155,10 +155,10 @@ const (
// or data key provided as plaintext.
//
// The returned ciphertext data consists of:
// iv | AEAD ID | nonce | encrypted data
// 32 1 12 ~ len(data)
// AEAD ID | iv | nonce | encrypted data
// 1 16 12 ~ len(data)
func encrypt(plaintext, associatedData []byte) ([]byte, error) {
iv, err := sioutil.Random(32) // 32 bytes IV
iv, err := sioutil.Random(16) // 16 bytes IV
if err != nil {
return nil, err
}
@@ -186,7 +186,7 @@ func encrypt(plaintext, associatedData []byte) ([]byte, error) {
}
case c20p1305:
var sealingKey []byte
sealingKey, err = chacha20.HChaCha20(derivedKey, iv[:16]) // HChaCha20 expects nonce of 16 bytes
sealingKey, err = chacha20.HChaCha20(derivedKey, iv) // HChaCha20 expects nonce of 16 bytes
if err != nil {
return nil, err
}
@@ -202,11 +202,11 @@ func encrypt(plaintext, associatedData []byte) ([]byte, error) {
sealedBytes := aead.Seal(nil, nonce, plaintext, associatedData)
// ciphertext = iv | AEAD ID | nonce | sealed bytes
// ciphertext = AEAD ID | iv | nonce | sealed bytes
var buf bytes.Buffer
buf.Write(iv)
buf.WriteByte(algorithm)
buf.Write(iv)
buf.Write(nonce)
buf.Write(sealedBytes)
@@ -218,16 +218,16 @@ func encrypt(plaintext, associatedData []byte) ([]byte, error) {
// and a pbkdf2 derived key
func decrypt(ciphertext []byte, associatedData []byte) ([]byte, error) {
var (
iv [32]byte
algorithm [1]byte
iv [16]byte
nonce [12]byte // This depends on the AEAD but both used ciphers have the same nonce length.
)
r := bytes.NewReader(ciphertext)
if _, err := io.ReadFull(r, iv[:]); err != nil {
if _, err := io.ReadFull(r, algorithm[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(r, algorithm[:]); err != nil {
if _, err := io.ReadFull(r, iv[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(r, nonce[:]); err != nil {
@@ -249,7 +249,7 @@ func decrypt(ciphertext []byte, associatedData []byte) ([]byte, error) {
return nil, err
}
case c20p1305:
sealingKey, err := chacha20.HChaCha20(derivedKey, iv[:16]) // HChaCha20 expects nonce of 16 bytes
sealingKey, err := chacha20.HChaCha20(derivedKey, iv[:]) // HChaCha20 expects nonce of 16 bytes
if err != nil {
return nil, err
}

219
pkg/utils/parity.go Normal file
View File

@@ -0,0 +1,219 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package utils
import (
"errors"
"fmt"
"sort"
"github.com/minio/minio/pkg/ellipses"
)
// This file implements and supports ellipses pattern for
// `minio server` command line arguments.
// Supported set sizes this is used to find the optimal
// single set size.
var setSizes = []uint64{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
// getDivisibleSize - returns a greatest common divisor of
// all the ellipses sizes.
func getDivisibleSize(totalSizes []uint64) (result uint64) {
gcd := func(x, y uint64) uint64 {
for y != 0 {
x, y = y, x%y
}
return x
}
result = totalSizes[0]
for i := 1; i < len(totalSizes); i++ {
result = gcd(result, totalSizes[i])
}
return result
}
// isValidSetSize - checks whether given count is a valid set size for erasure coding.
var isValidSetSize = func(count uint64) bool {
return (count >= setSizes[0] && count <= setSizes[len(setSizes)-1])
}
// possibleSetCountsWithSymmetry returns symmetrical setCounts based on the
// input argument patterns, the symmetry calculation is to ensure that
// we also use uniform number of drives common across all ellipses patterns.
func possibleSetCountsWithSymmetry(setCounts []uint64, argPatterns []ellipses.ArgPattern) []uint64 {
var newSetCounts = make(map[uint64]struct{})
for _, ss := range setCounts {
var symmetry bool
for _, argPattern := range argPatterns {
for _, p := range argPattern {
if uint64(len(p.Seq)) > ss {
symmetry = uint64(len(p.Seq))%ss == 0
} else {
symmetry = ss%uint64(len(p.Seq)) == 0
}
}
}
// With no arg patterns, it is expected that user knows
// the right symmetry, so either ellipses patterns are
// provided (recommended) or no ellipses patterns.
if _, ok := newSetCounts[ss]; !ok && (symmetry || argPatterns == nil) {
newSetCounts[ss] = struct{}{}
}
}
setCounts = []uint64{}
for setCount := range newSetCounts {
setCounts = append(setCounts, setCount)
}
// Not necessarily needed but it ensures to the readers
// eyes that we prefer a sorted setCount slice for the
// subsequent function to figure out the right common
// divisor, it avoids loops.
sort.Slice(setCounts, func(i, j int) bool {
return setCounts[i] < setCounts[j]
})
return setCounts
}
func commonSetDriveCount(divisibleSize uint64, setCounts []uint64) (setSize uint64) {
// prefers setCounts to be sorted for optimal behavior.
if divisibleSize < setCounts[len(setCounts)-1] {
return divisibleSize
}
// Figure out largest value of total_drives_in_erasure_set which results
// in least number of total_drives/total_drives_erasure_set ratio.
prevD := divisibleSize / setCounts[0]
for _, cnt := range setCounts {
if divisibleSize%cnt == 0 {
d := divisibleSize / cnt
if d <= prevD {
prevD = d
setSize = cnt
}
}
}
return setSize
}
// getSetIndexes returns list of indexes which provides the set size
// on each index, this function also determines the final set size
// The final set size has the affinity towards choosing smaller
// indexes (total sets)
func getSetIndexes(args []string, totalSizes []uint64, argPatterns []ellipses.ArgPattern) (setIndexes [][]uint64, err error) {
if len(totalSizes) == 0 || len(args) == 0 {
return nil, errors.New("invalid argument")
}
setIndexes = make([][]uint64, len(totalSizes))
for _, totalSize := range totalSizes {
// Check if totalSize has minimum range upto setSize
if totalSize < setSizes[0] {
return nil, fmt.Errorf("incorrect number of endpoints provided %s", args)
}
}
commonSize := getDivisibleSize(totalSizes)
possibleSetCounts := func(setSize uint64) (ss []uint64) {
for _, s := range setSizes {
if setSize%s == 0 {
ss = append(ss, s)
}
}
return ss
}
setCounts := possibleSetCounts(commonSize)
if len(setCounts) == 0 {
err = fmt.Errorf("incorrect number of endpoints provided %s, number of disks %d is not divisible by any supported erasure set sizes %d", args, commonSize, setSizes)
return nil, err
}
// Returns possible set counts with symmetry.
setCounts = possibleSetCountsWithSymmetry(setCounts, argPatterns)
if len(setCounts) == 0 {
err = fmt.Errorf("no symmetric distribution detected with input endpoints provided %s, disks %d cannot be spread symmetrically by any supported erasure set sizes %d", args, commonSize, setSizes)
return nil, err
}
// Final set size with all the symmetry accounted for.
setSize := commonSetDriveCount(commonSize, setCounts)
// Check whether setSize is with the supported range.
if !isValidSetSize(setSize) {
err = fmt.Errorf("incorrect number of endpoints provided %s, number of disks %d is not divisible by any supported erasure set sizes %d", args, commonSize, setSizes)
return nil, err
}
for i := range totalSizes {
for j := uint64(0); j < totalSizes[i]/setSize; j++ {
setIndexes[i] = append(setIndexes[i], setSize)
}
}
return setIndexes, nil
}
// Return the total size for each argument patterns.
func getTotalSizes(argPatterns []ellipses.ArgPattern) []uint64 {
var totalSizes []uint64
for _, argPattern := range argPatterns {
var totalSize uint64 = 1
for _, p := range argPattern {
totalSize = totalSize * uint64(len(p.Seq))
}
totalSizes = append(totalSizes, totalSize)
}
return totalSizes
}
// PossibleParityValues returns possible parities for input args,
// parties are calculated in uniform manner for one zone or
// multiple zones, ensuring that parities returned are common
// and applicable across all zones.
func PossibleParityValues(args ...string) ([]string, error) {
setIndexes, err := parseEndpointSet(args...)
if err != nil {
return nil, err
}
maximumParity := setIndexes[0][0] / 2
var parities []string
for maximumParity >= 2 {
parities = append(parities, fmt.Sprintf("EC:%d", maximumParity))
maximumParity--
}
return parities, nil
}
// Parses all arguments and returns an endpointSet which is a collection
// of endpoints following the ellipses pattern, this is what is used
// by the object layer for initializing itself.
func parseEndpointSet(args ...string) (setIndexes [][]uint64, err error) {
var argPatterns = make([]ellipses.ArgPattern, len(args))
for i, arg := range args {
patterns, err := ellipses.FindEllipsesPatterns(arg)
if err != nil {
return nil, err
}
argPatterns[i] = patterns
}
return getSetIndexes(args, getTotalSizes(argPatterns), argPatterns)
}

281
pkg/utils/parity_test.go Normal file
View File

@@ -0,0 +1,281 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// All rights reserved
package utils
import (
"reflect"
"testing"
"github.com/minio/minio/pkg/ellipses"
)
func TestGetDivisibleSize(t *testing.T) {
testCases := []struct {
totalSizes []uint64
result uint64
}{{[]uint64{24, 32, 16}, 8},
{[]uint64{32, 8, 4}, 4},
{[]uint64{8, 8, 8}, 8},
{[]uint64{24}, 24},
}
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
gotGCD := getDivisibleSize(testCase.totalSizes)
if testCase.result != gotGCD {
t.Errorf("Expected %v, got %v", testCase.result, gotGCD)
}
})
}
}
// Test tests calculating set indexes.
func TestGetSetIndexes(t *testing.T) {
testCases := []struct {
args []string
totalSizes []uint64
indexes [][]uint64
success bool
}{
// Invalid inputs.
{
[]string{"data{1...3}"},
[]uint64{3},
nil,
false,
},
{
[]string{"data/controller1/export{1...2}, data/controller2/export{1...4}, data/controller3/export{1...8}"},
[]uint64{2, 4, 8},
nil,
false,
},
{
[]string{"data{1...17}/export{1...52}"},
[]uint64{14144},
nil,
false,
},
// Valid inputs.
{
[]string{"data{1...27}"},
[]uint64{27},
[][]uint64{{9, 9, 9}},
true,
},
{
[]string{"http://host{1...3}/data{1...180}"},
[]uint64{540},
[][]uint64{{15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15}},
true,
},
{
[]string{"http://host{1...2}.rack{1...4}/data{1...180}"},
[]uint64{1440},
[][]uint64{{16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}},
true,
},
{
[]string{"http://host{1...2}/data{1...180}"},
[]uint64{360},
[][]uint64{{12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12}},
true,
},
{
[]string{"data/controller1/export{1...4}, data/controller2/export{1...8}, data/controller3/export{1...12}"},
[]uint64{4, 8, 12},
[][]uint64{{4}, {4, 4}, {4, 4, 4}},
true,
},
{
[]string{"data{1...64}"},
[]uint64{64},
[][]uint64{{16, 16, 16, 16}},
true,
},
{
[]string{"data{1...24}"},
[]uint64{24},
[][]uint64{{12, 12}},
true,
},
{
[]string{"data/controller{1...11}/export{1...8}"},
[]uint64{88},
[][]uint64{{11, 11, 11, 11, 11, 11, 11, 11}},
true,
},
{
[]string{"data{1...4}"},
[]uint64{4},
[][]uint64{{4}},
true,
},
{
[]string{"data/controller1/export{1...10}, data/controller2/export{1...10}, data/controller3/export{1...10}"},
[]uint64{10, 10, 10},
[][]uint64{{10}, {10}, {10}},
true,
},
{
[]string{"data{1...16}/export{1...52}"},
[]uint64{832},
[][]uint64{{16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}},
true,
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
var argPatterns = make([]ellipses.ArgPattern, len(testCase.args))
for i, arg := range testCase.args {
patterns, err := ellipses.FindEllipsesPatterns(arg)
if err != nil {
t.Fatalf("Unexpected failure %s", err)
}
argPatterns[i] = patterns
}
gotIndexes, err := getSetIndexes(testCase.args, testCase.totalSizes, argPatterns)
if err != nil && testCase.success {
t.Errorf("Expected success but failed instead %s", err)
}
if err == nil && !testCase.success {
t.Errorf("Expected failure but passed instead")
}
if !reflect.DeepEqual(testCase.indexes, gotIndexes) {
t.Errorf("Expected %v, got %v", testCase.indexes, gotIndexes)
}
})
}
}
// Test tests possible parities returned for any input args
func TestPossibleParities(t *testing.T) {
testCases := []struct {
arg string
parities []string
success bool
}{
// Tests invalid inputs.
{
"...",
nil,
false,
},
// No range specified.
{
"{...}",
nil,
false,
},
// Invalid range.
{
"http://minio{2...3}/export/set{1...0}",
nil,
false,
},
// Range cannot be smaller than 4 minimum.
{
"/export{1..2}",
nil,
false,
},
// Unsupported characters.
{
"/export/test{1...2O}",
nil,
false,
},
// Tests valid inputs.
{
"{1...27}",
[]string{"EC:4", "EC:3", "EC:2"},
true,
},
{
"/export/set{1...64}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// Valid input for distributed setup.
{
"http://minio{2...3}/export/set{1...64}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// Supporting some advanced cases.
{
"http://minio{1...64}.mydomain.net/data",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
{
"http://rack{1...4}.mydomain.minio{1...16}/data",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// Supporting kubernetes cases.
{
"http://minio{0...15}.mydomain.net/data{0...1}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// No host regex, just disks.
{
"http://server1/data{1...32}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// No host regex, just disks with two position numerics.
{
"http://server1/data{01...32}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// More than 2 ellipses are supported as well.
{
"http://minio{2...3}/export/set{1...64}/test{1...2}",
[]string{"EC:8", "EC:7", "EC:6", "EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// More than 1 ellipses per argument for standalone setup.
{
"/export{1...10}/disk{1...10}",
[]string{"EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// IPv6 ellipses with hexadecimal expansion
{
"http://[2001:3984:3989::{1...a}]/disk{1...10}",
[]string{"EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
// IPv6 ellipses with hexadecimal expansion with 3 position numerics.
{
"http://[2001:3984:3989::{001...00a}]/disk{1...10}",
[]string{"EC:5", "EC:4", "EC:3", "EC:2"},
true,
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
gotPs, err := PossibleParityValues(testCase.arg)
if err != nil && testCase.success {
t.Errorf("Expected success but failed instead %s", err)
}
if err == nil && !testCase.success {
t.Errorf("Expected failure but passed instead")
}
if !reflect.DeepEqual(testCase.parities, gotPs) {
t.Errorf("Expected %v, got %v", testCase.parities, gotPs)
}
})
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "consoleTestUserAddOnly",
"Statement": [
{
"Action": [
"admin:CreateUser"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::*"
]
}
],
"version": "2012-10-17"
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
const rewireReactHotLoader = require('react-app-rewire-hot-loader');
/* config-overrides.js */
module.exports = function override(config, env) {
if (env === 'development') {
config.resolve.alias['react-dom'] = '@hot-loader/react-dom';
}
config = rewireReactHotLoader(config, env);
return config;
};

View File

@@ -5,6 +5,7 @@
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.7.4",
"@babel/plugin-transform-react-jsx-development": "^7.9.0",
"@hot-loader/react-dom": "^16.9.0",
"@material-ui/core": "^4.9.12",
"@material-ui/icons": "^4.9.1",
"@types/history": "^4.7.3",
@@ -29,9 +30,13 @@
"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",
"react-async-hook": "^3.6.1",
"react-chartjs-2": "^2.9.0",
"react-codemirror2": "^7.1.0",
"react-dom": "^16.12.0",
"react-hot-loader": "^4.13.0",
"react-moment": "^0.9.7",
"react-redux": "^7.1.3",
"react-router-dom": "^5.1.2",
@@ -42,10 +47,11 @@
"superagent": "^5.1.0",
"typeface-roboto": "^0.0.75",
"typescript": "3.6.4",
"use-debounce": "^5.0.1",
"websocket": "^1.0.31"
},
"scripts": {
"start": "PORT=5000 react-scripts start",
"start": "PORT=5000 react-app-rewired start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"

View File

@@ -0,0 +1,67 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 368.999 192.934">
<defs>
<style>
.cls-1{opacity:0.35;}.cls-12,.cls-15,.cls-16,.cls-17,.cls-2,.cls-5,.cls-6,.cls-7,.cls-8{opacity:0.5;}.cls-10,.cls-11,.cls-12,.cls-13,.cls-14,.cls-15,.cls-16,.cls-17,.cls-3,.cls-4,.cls-5,.cls-6,.cls-7,.cls-9{fill:none;stroke:#707070;stroke-miterlimit:10;}.cls-4{stroke-width:1px;}.cls-10,.cls-11,.cls-5,.cls-9{stroke-width:1.2px;}.cls-5{stroke-dasharray:2.619
2.182;}.cls-12,.cls-15,.cls-16,.cls-17,.cls-5,.cls-6,.cls-7,.cls-8{isolation:isolate;}.cls-6{stroke-width:1.6px;stroke-dasharray:2.144
1.786;}.cls-7{stroke-width:1.6px;stroke-dasharray:2.23 1.858;}.cls-10{stroke-dasharray:2.646
2.204;}.cls-11{stroke-dasharray:2.585 2.154;}.cls-12{stroke-width:1.8px;stroke-dasharray:2.484
2.07;}.cls-13{stroke-dasharray:2.984 2.487;}.cls-14{stroke-dasharray:2.773
2.311;}.cls-16{stroke-width:1.8px;}.cls-17{stroke-width:1.8px;}
</style>
</defs>
<title>BG_Illustration</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<g id="BG_Illustration" data-name="BG Illustration" class="cls-1">
<g id="Group_118" data-name="Group 118" class="cls-2">
<path id="Path_56" data-name="Path 56" class="cls-3"
d="M211.5,140.678l-52.726,29.078L79.687,126.139V29.652L132.411.571,211.5,44.188Z"/>
<path id="Path_58" data-name="Path 58" class="cls-3"
d="M158.776,169.756V73.271L211.5,44.193,158.776,73.271,79.688,29.654"/>
<path id="Path_59" data-name="Path 59" class="cls-4" d="M84.681,41l69.1,38.11v79.3l-69.1-38.11Z"/>
<line id="Line_37" data-name="Line 37" class="cls-4" x1="106.25" y1="52.782" x2="106.25"
y2="132.086"/>
<line id="Line_38" data-name="Line 38" class="cls-4" x1="153.783" y1="92.327" x2="106.25"
y2="65.999"/>
<line id="Line_39" data-name="Line 39" class="cls-4" x1="153.783" y1="105.545" x2="106.25"
y2="79.217"/>
<line id="Line_40" data-name="Line 40" class="cls-4" x1="153.783" y1="118.762" x2="106.25"
y2="92.434"/>
<line id="Line_41" data-name="Line 41" class="cls-4" x1="153.783" y1="131.979" x2="106.25"
y2="105.651"/>
<line id="Line_42" data-name="Line 42" class="cls-4" x1="153.783" y1="145.197" x2="106.25"
y2="118.869"/>
<path id="Path_60" data-name="Path 60" class="cls-4"
d="M166.723,151.031l38.8-22.487V62.916L166.723,85.4Z"/>
</g>
<path id="Path_62" data-name="Path 62" class="cls-5" d="M117.106,148.062l-76.18,43.33"/>
<path id="Path_63" data-name="Path 63" class="cls-6" d="M271.394,167.271l-44.483,25.3"/>
<path id="Path_64" data-name="Path 64" class="cls-7" d="M190.722,155.708l61.951,36.031"/>
<path id="Path_65" data-name="Path 65" class="cls-5" d="M237.7,36.385l28.182,17.229"/>
<g id="Path_66" data-name="Path 66" class="cls-8">
<line class="cls-9" x1="362.563" y1="69.327" x2="361.42" y2="68.688"/>
<line class="cls-10" x1="359.496" y1="67.613" x2="305.418" y2="37.39"/>
<polyline class="cls-9" points="304.456 36.852 303.313 36.213 302.158 36.83"/>
<line class="cls-11" x1="300.258" y1="37.844" x2="213.418" y2="84.213"/>
<line class="cls-9" x1="212.468" y1="84.72" x2="211.313" y2="85.337"/>
</g>
<path id="Path_67" data-name="Path 67" class="cls-12"
d="M79.648,192.571,31.786,166.344h-.868l-23.579,14.2"/>
<g id="Path_68" data-name="Path 68" class="cls-8">
<line class="cls-3" x1="22.871" y1="84.641" x2="24.156" y2="83.867"/>
<line class="cls-13" x1="26.286" y1="82.584" x2="48.654" y2="69.113"/>
<polyline class="cls-3" points="49.719 68.471 51.004 67.698 52.307 68.441"/>
<line class="cls-14" x1="54.315" y1="69.585" x2="75.395" y2="81.606"/>
<line class="cls-3" x1="76.399" y1="82.178" x2="77.702" y2="82.921"/>
</g>
<circle id="Ellipse_11" data-name="Ellipse 11" class="cls-15" cx="4.092" cy="183.59" r="3.592"/>
<circle id="Ellipse_12" data-name="Ellipse 12" class="cls-15" cx="274.986" cy="165.477" r="3.592"/>
<ellipse id="Ellipse_13" data-name="Ellipse 13" class="cls-16" cx="364.957" cy="71.922" rx="3.592"
ry="2.904"/>
<circle id="Ellipse_14" data-name="Ellipse 14" class="cls-15" cx="19.279" cy="87.681" r="3.592"/>
<ellipse id="Ellipse_15" data-name="Ellipse 15" class="cls-17" cx="234.106" cy="32.58" rx="3.592"
ry="2.649"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -25,6 +25,7 @@ import { connect } from "react-redux";
import { AppState } from "./store";
import { userLoggedIn } from "./actions";
import LoginCallback from "./screens/LoginPage/LoginCallback";
import { hot } from "react-hot-loader/root";
const isLoggedIn = () => {
return (
@@ -75,4 +76,4 @@ class Routes extends React.Component<RoutesProps> {
}
}
export default connector(Routes);
export default hot(connector(Routes));

View File

@@ -0,0 +1,75 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { SvgIcon } from "@material-ui/core";
class AllBucketsIcon extends React.Component {
render() {
return (
<SvgIcon viewBox="0 0 15.834 17.375">
<defs>
<linearGradient
id="a"
y1="0.5"
x2="1"
y2="0.5"
gradientUnits="objectBoundingBox"
>
<stop offset="0.044" stopColor="#362585" />
<stop offset="0.301" stopColor="#281b6f" />
<stop offset="1" stopColor="#1e1560" />
</linearGradient>
</defs>
<g transform="translate(0 0.375)">
<circle
style={{ opacity: 0.1, fill: "url(#a)" }}
cx="6.625"
cy="6.625"
r="6.625"
transform="translate(0 3.75)"
/>
<g transform="translate(3.092)">
<ellipse
style={{
fill: "none",
stroke: "#707070",
strokeMiterlimit: 10,
strokeWidth: "0.75px",
}}
cx="6.183"
cy="1.244"
rx="6.183"
ry="1.244"
transform="translate(0)"
/>
<path
style={{
fill: "none",
stroke: "#707070",
strokeMiterlimit: 10,
strokeWidth: "0.75px",
}}
d="M-3722.174,1225.225l-1.687,10.292a.858.858,0,0,1-.578.669,12.182,12.182,0,0,1-3.918.647,12.187,12.187,0,0,1-3.894-.639.878.878,0,0,1-.6-.678q-.843-5.145-1.687-10.291"
transform="translate(3734.541 -1223.981)"
/>
</g>
</g>
</SvgIcon>
);
}
}
export default AllBucketsIcon;

View File

@@ -0,0 +1,37 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { SvgIcon } from "@material-ui/core";
class ConsoleIcon extends React.Component {
render() {
return (
<SvgIcon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
<g transform="translate(-518 -361)">
<path
d="M-126,0V10h10V0Zm1.5,8.5V2.95h7V8.5Z"
transform="translate(644 361)"
/>
<rect width="2" height="1" transform="translate(520.272 364.772)" />
</g>
</svg>
</SvgIcon>
);
}
}
export default ConsoleIcon;

View File

@@ -0,0 +1,73 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { SvgIcon } from "@material-ui/core";
class EgressIcon extends React.Component {
render() {
return (
<SvgIcon viewBox="0 0 18.344 17.009">
<defs>
<linearGradient
id="a"
y1="0.5"
x2="1"
y2="0.5"
gradientUnits="objectBoundingBox"
>
<stop offset="0.044" stopColor="#362585" />
<stop offset="0.301" stopColor="#281b6f" />
<stop offset="1" stopColor="#1e1560" />
</linearGradient>
</defs>
<g transform="translate(0 0.25)">
<ellipse
style={{ opacity: 0.1, fill: "url(#a)" }}
cx="7.462"
cy="7.462"
rx="7.462"
ry="7.462"
transform="translate(0 1.835)"
/>
<rect
style={{
fill: "none",
stroke: "#707070",
strokeMiterlimit: 10,
strokeWidth: "0.5px",
}}
width="9.323"
height="9.323"
transform="translate(4.083)"
/>
<rect
style={{
fill: "none",
stroke: "#707070",
strokeMiterlimit: 10,
strokeWidth: "0.5px",
}}
width="8.223"
height="8.223"
transform="translate(9.871 5.307)"
/>
</g>
</SvgIcon>
);
}
}
export default EgressIcon;

View File

@@ -0,0 +1,53 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { SvgIcon } from "@material-ui/core";
class HealIcon extends React.Component {
render() {
return (
<SvgIcon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10.014 9.993">
<path
className="a"
d="M9.162,5.971h0L8.192,5,9.346,3.846a2.257,2.257,0,0,0,0-3.192,2.311,2.311,0,0,0-3.192,0L5,1.808,4.029.837,3.846.654a2.311,2.311,0,0,0-3.192,0,2.257,2.257,0,0,0,0,3.192l.184.183h0L1.808,5,.654,6.154A2.257,2.257,0,0,0,3.846,9.346L5,8.192l.971.971.183.183A2.257,2.257,0,0,0,9.346,6.154Zm-2.29-4.6a1.27,1.27,0,0,1,1.757,0,1.242,1.242,0,0,1,0,1.757L7.475,4.283,5.717,2.525Zm-5.5,1.757A1.243,1.243,0,0,1,3.129,1.371l.183.183L1.555,3.312Zm1.757,5.5a1.27,1.27,0,0,1-1.757,0,1.242,1.242,0,0,1,0-1.757L2.525,5.717,4.283,7.475Zm2.843-.9-.254-.253L2.525,4.283l-.253-.254L4.029,2.272l.254.253L7.475,5.717l.253.254Zm2.657.9a1.271,1.271,0,0,1-1.757,0l-.183-.183L8.446,6.688l.183.183h0a1.241,1.241,0,0,1,0,1.757Z"
transform="translate(0.007 -0.014)"
/>
<circle
cx="0.5"
cy="0.5"
r="0.5"
transform="translate(4.507 4.486)"
/>
<circle
cx="0.5"
cy="0.5"
r="0.5"
transform="translate(3.507 3.486)"
/>
<circle
cx="0.5"
cy="0.5"
r="0.5"
transform="translate(5.507 5.486)"
/>
</svg>
</SvgIcon>
);
}
}
export default HealIcon;

View File

@@ -0,0 +1,47 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { SvgIcon } from "@material-ui/core";
class LicenseIcon extends React.Component {
render() {
return (
<SvgIcon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 11">
<path fill="#fff" d="M11 11H0V2h11v9zM2 8v1h7V8zm0-3v1h5V5z"></path>
<g
fill="#07274a"
stroke="#fdfdfd"
strokeWidth="0.5"
transform="translate(7)"
>
<circle cx="3" cy="3" r="3" stroke="none"></circle>
<circle cx="3" cy="3" r="2.75" fill="none"></circle>
</g>
<path
fill="none"
stroke="#fff"
strokeWidth="0.5"
d="M8.73 2.794l.954.953 1.471-1.471"
></path>
</svg>
</SvgIcon>
);
}
}
export default LicenseIcon;

View File

@@ -0,0 +1,47 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { SvgIcon } from "@material-ui/core";
class LogoutIcon extends React.Component {
render() {
return (
<SvgIcon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12.122 10.571">
<g transform="translate(0 0.5)">
<path
style={{ fill: "none", stroke: "rgba(255,255,255,0.8)" }}
d="M4816.27,3755.205v-2.939h8.539v9.571h-8.539v-2.932"
transform="translate(-4813.187 -3752.266)"
/>
<path
style={{ fill: "none", stroke: "rgba(255,255,255,0.8)" }}
d="M4813.187,3757.052h8.081"
transform="translate(-4813.187 -3752.266)"
/>
<path
style={{ fill: "none", stroke: "rgba(255,255,255,0.8)" }}
d="M4806.5,3756.511l2.265,2.063-2.265,2.063"
transform="translate(-4800.808 -3753.863)"
/>
</g>
</svg>
</SvgIcon>
);
}
}
export default LogoutIcon;

View File

@@ -0,0 +1,64 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { SvgIcon } from "@material-ui/core";
class UsageIcon extends React.Component {
render() {
return (
<SvgIcon viewBox="0 0 16.172 17.187">
<defs>
<linearGradient
id="a"
y1="0.5"
x2="1"
y2="0.5"
gradientUnits="objectBoundingBox"
>
<stop offset="0.044" stopColor="#362585" />
<stop offset="0.301" stopColor="#281b6f" />
<stop offset="1" stopColor="#1e1560" />
</linearGradient>
</defs>
<path
style={{
fill: "none",
stroke: "#707070",
strokeMiterlimit: 10,
strokeWidth: "0.5px",
}}
d="M-4778.1,2239.582v6.425h6.425"
transform="translate(4787.594 -2239.582)"
/>
<path
fill={"#707070"}
d="M-4784.238,2247.532v-.581c0-.027.009-.054.012-.081.039-.313.055-.632.121-.939a6.744,6.744,0,0,1,3.064-4.441,6.514,6.514,0,0,1,3.293-1.032,6.923,6.923,0,0,1,2.667.423,6.793,6.793,0,0,1,4.119,4.333,6.053,6.053,0,0,1,.279,1.337c.006.083.014.164.021.247v.86c-.011.131-.018.261-.032.392a6.494,6.494,0,0,1-.626,2.147,6.807,6.807,0,0,1-4.044,3.528,6.052,6.052,0,0,1-1.663.3,6.576,6.576,0,0,1-2.565-.325,6.73,6.73,0,0,1-3.947-3.451,6.627,6.627,0,0,1-.658-2.288C-4784.212,2247.816-4784.225,2247.674-4784.238,2247.532Zm13.025-.306c-.024-.309-.021-.661-.082-1a6.206,6.206,0,0,0-1.658-3.293,6.153,6.153,0,0,0-4.1-1.9,5.984,5.984,0,0,0-2.476.355,6.188,6.188,0,0,0-4.134,5.708,6.453,6.453,0,0,0,.228,1.881,6.127,6.127,0,0,0,1.984,3.052,6.046,6.046,0,0,0,3.806,1.445,6.043,6.043,0,0,0,1.235-.065,6.249,6.249,0,0,0,3.783-2.2,6.2,6.2,0,0,0,1.352-3.048C-4771.228,2247.863-4771.233,2247.563-4771.212,2247.226Z"
transform="translate(4786.834 -2240.452)"
/>
<ellipse
style={{ opacity: 0.1, fill: "url(#a)" }}
cx="6.151"
cy="6.151"
rx="6.151"
ry="6.151"
transform="translate(0 4.886)"
/>
</SvgIcon>
);
}
}
export default UsageIcon;

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 81.879 23.119"><defs><style>.cls-1{fill:#1b1556;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M10.086,13.4,8.969,14.573a4.2,4.2,0,0,0-3.01-1.279,3.886,3.886,0,0,0-3.884,4.093,3.888,3.888,0,0,0,3.884,4.1A4.4,4.4,0,0,0,9,20.2l1.082,1.186A5.344,5.344,0,0,1,6,23.119a5.512,5.512,0,0,1-5.7-5.732A5.509,5.509,0,0,1,6,11.667,5.328,5.328,0,0,1,10.086,13.4Z"/><path class="cls-1" d="M23.608,17.387a5.637,5.637,0,0,1-5.8,5.732,5.628,5.628,0,0,1-5.791-5.732,5.626,5.626,0,0,1,5.791-5.72A5.635,5.635,0,0,1,23.608,17.387Zm-9.825,0a4.024,4.024,0,1,0,8.046,0,4.024,4.024,0,1,0-8.046,0Z"/><path class="cls-1" d="M36.862,16.073V22.9H35.129V16.643c0-2.093-1.163-3.325-3.174-3.325a3.24,3.24,0,0,0-3.371,3.372V22.9H26.839V11.888H28.56v1.569a4.354,4.354,0,0,1,3.709-1.79A4.261,4.261,0,0,1,36.862,16.073Z"/><path class="cls-1" d="M48.2,14.225a6.872,6.872,0,0,0-3.605-1.035c-1.569,0-2.6.663-2.6,1.732,0,.919.8,1.372,2.244,1.547l1.3.163c2.338.3,3.7,1.267,3.7,3.069,0,2.093-1.884,3.407-4.849,3.407A7.725,7.725,0,0,1,39.791,21.7l.8-1.3a5.8,5.8,0,0,0,3.815,1.2c1.86,0,3.034-.616,3.034-1.778,0-.884-.744-1.419-2.3-1.605l-1.314-.151c-2.477-.3-3.639-1.408-3.639-3.046,0-2.082,1.755-3.338,4.4-3.338a8.067,8.067,0,0,1,4.372,1.2Z"/><path class="cls-1" d="M63.033,17.387a5.8,5.8,0,0,1-11.593,0,5.8,5.8,0,0,1,11.593,0Zm-9.825,0a4.023,4.023,0,1,0,8.045,0,4.023,4.023,0,1,0-8.045,0Z"/><path class="cls-1" d="M68.008,22.9H66.264V6.155h1.744Z"/><path class="cls-1" d="M81.879,17.353a5.606,5.606,0,0,1-.035.65H73.019a3.743,3.743,0,0,0,3.9,3.593A5.1,5.1,0,0,0,80.4,20.213l.931,1.186a6.179,6.179,0,0,1-4.524,1.72A5.394,5.394,0,0,1,71.24,17.4a5.406,5.406,0,0,1,5.465-5.732C79.693,11.667,81.856,14,81.879,17.353ZM73.043,16.6h7.069a3.446,3.446,0,0,0-3.442-3.384A3.59,3.59,0,0,0,73.043,16.6Z"/><rect class="cls-1" x="13.484" y="0.12" width="2.328" height="6.875"/><path class="cls-1" d="M10.662.215,5.936,3.1a.21.21,0,0,1-.219,0L.992.215A.651.651,0,0,0,.654.12H.648A.648.648,0,0,0,0,.768v6.22H2.327V4.028a.233.233,0,0,1,.354-.2l2.648,1.62a.829.829,0,0,0,.853.008l2.8-1.639a.232.232,0,0,1,.35.2V6.988h2.327V.768A.648.648,0,0,0,11.006.12H11A.651.651,0,0,0,10.662.215Z"/><path class="cls-1" d="M27.422.12H25.061V3.25a.233.233,0,0,1-.342.205L18.6.2A.662.662,0,0,0,18.3.12h0a.648.648,0,0,0-.648.648v6.22h2.342V3.863a.233.233,0,0,1,.342-.206L26.47,6.915a.646.646,0,0,0,.3.076h0a.648.648,0,0,0,.648-.648Z"/><path class="cls-1" d="M29.252,7V.12h1.072V7Z"/><path class="cls-1" d="M36.629,7.119c-2.882,0-4.927-1.368-4.927-3.56S33.759,0,36.629,0s4.938,1.367,4.938,3.559S39.547,7.119,36.629,7.119Zm0-6.208c-2.143,0-3.794.936-3.794,2.648s1.651,2.648,3.794,2.648,3.805-.923,3.805-2.648S38.772.911,36.629.911Z"/></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="121.755" height="29.822" viewBox="0 0 121.755 29.822"><defs><style>.a{fill:#fff;}</style></defs><g transform="translate(3016.56 -1037.757)"><path class="a" d="M-3000.913,1053.692l-1.772,1.194a6.088,6.088,0,0,0-5.135-2.652,6.348,6.348,0,0,0-6.522,6.654,6.348,6.348,0,0,0,6.522,6.654,6.031,6.031,0,0,0,5.124-2.64l1.735,1.266a8.126,8.126,0,0,1-6.859,3.411,8.422,8.422,0,0,1-8.74-8.691,8.422,8.422,0,0,1,8.74-8.691A7.963,7.963,0,0,1-3000.913,1053.692Z"/><path class="a" d="M-2980.919,1058.888a8.422,8.422,0,0,1-8.74,8.691,8.422,8.422,0,0,1-8.739-8.691,8.421,8.421,0,0,1,8.739-8.691A8.422,8.422,0,0,1-2980.919,1058.888Zm-15.261,0a6.347,6.347,0,0,0,6.521,6.654,6.347,6.347,0,0,0,6.521-6.654,6.347,6.347,0,0,0-6.521-6.654A6.347,6.347,0,0,0-2996.18,1058.888Z"/><path class="a" d="M-2962.831,1067.338h-1.917l-10.2-13.26-.012,13.248h-2.122v-16.888h1.917l10.21,13.26V1050.45h2.122Z"/><path class="a" d="M-2947.009,1053.777a8.835,8.835,0,0,0-5-1.555c-2.471,0-4.231,1.109-4.231,2.929,0,1.531,1.29,2.315,3.821,2.628l1.484.181c2.856.35,5.3,1.507,5.3,4.484,0,3.364-3.05,5.123-6.7,5.123a10.935,10.935,0,0,1-6.654-2.194l1.157-1.687a9.018,9.018,0,0,0,5.5,1.868c2.519,0,4.5-1.025,4.5-2.929,0-1.567-1.41-2.314-4.038-2.64l-1.567-.193c-2.784-.337-5-1.627-5-4.508,0-3.255,2.893-5.075,6.449-5.075a10.336,10.336,0,0,1,6.076,1.844Z"/><path class="a" d="M-2925.292,1058.888a8.422,8.422,0,0,1-8.74,8.691,8.422,8.422,0,0,1-8.739-8.691,8.421,8.421,0,0,1,8.739-8.691A8.422,8.422,0,0,1-2925.292,1058.888Zm-15.261,0a6.348,6.348,0,0,0,6.521,6.654,6.347,6.347,0,0,0,6.521-6.654,6.347,6.347,0,0,0-6.521-6.654A6.348,6.348,0,0,0-2940.553,1058.888Z"/><path class="a" d="M-2909.663,1067.326h-11.79V1050.45h2.122v14.863h9.668Z"/><path class="a" d="M-2894.8,1067.326h-11.982V1050.45h11.862v1.988h-9.74v5.389h9.427v2h-9.427v5.509h9.86Z"/><rect class="a" width="2.576" height="7.547" transform="translate(-3001.66 1037.924)"/><path class="a" d="M-3004.759,1037.995l-5.23,3.194a.229.229,0,0,1-.242,0l-5.23-3.194a.726.726,0,0,0-.374-.1h-.006a.717.717,0,0,0-.717.717v6.864h2.574v-3.257a.258.258,0,0,1,.392-.22l2.931,1.793a.919.919,0,0,0,.944.009l3.092-1.814a.258.258,0,0,1,.388.222v3.267h2.575v-6.864a.717.717,0,0,0-.717-.717h-.006A.723.723,0,0,0-3004.759,1037.995Z"/><path class="a" d="M-2986.212,1037.922h-2.613v3.463a.258.258,0,0,1-.379.228l-6.771-3.607a.723.723,0,0,0-.337-.084h0a.717.717,0,0,0-.717.717v6.832h2.592v-3.408a.258.258,0,0,1,.379-.227l6.8,3.606a.714.714,0,0,0,.336.083h0a.716.716,0,0,0,.717-.717v-6.886Z"/><path class="a" d="M-2984.121,1045.469v-7.547h1.2v7.547Z"/><path class="a" d="M-2976.024,1045.635c-3.189,0-5.451-1.513-5.451-3.939s2.276-3.939,5.451-3.939,5.466,1.513,5.466,3.939S-2972.794,1045.635-2976.024,1045.635Zm0-6.87c-2.371,0-4.2,1.036-4.2,2.931s1.826,2.93,4.2,2.93,4.212-1.022,4.212-2.93S-2973.652,1038.765-2976.024,1038.765Z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Buckets Server
// 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
@@ -19,7 +19,7 @@ import {
createStyles,
StyledProps,
Theme,
withStyles
withStyles,
} from "@material-ui/core/styles";
import history from "../../../history";
@@ -28,7 +28,7 @@ import {
RouteComponentProps,
Router,
Switch,
withRouter
withRouter,
} from "react-router-dom";
import { connect } from "react-redux";
import { AppState } from "../../../store";
@@ -41,62 +41,62 @@ import ViewBucket from "./ViewBucket/ViewBucket";
const styles = (theme: Theme) =>
createStyles({
root: {
display: "flex"
display: "flex",
},
toolbar: {
background: theme.palette.background.default,
color: "black",
paddingRight: 24 // keep right padding when drawer closed
paddingRight: 24, // keep right padding when drawer closed
},
toolbarIcon: {
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
padding: "0 8px",
...theme.mixins.toolbar
...theme.mixins.toolbar,
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
duration: theme.transitions.duration.leavingScreen,
}),
},
menuButton: {
marginRight: 36
marginRight: 36,
},
menuButtonHidden: {
display: "none"
display: "none",
},
title: {
flexGrow: 1
flexGrow: 1,
},
appBarSpacer: {
height: "5px"
height: "5px",
},
content: {
flexGrow: 1,
height: "100vh",
overflow: "auto"
overflow: "auto",
},
container: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4)
paddingBottom: theme.spacing(4),
},
paper: {
padding: theme.spacing(2),
display: "flex",
overflow: "auto",
flexDirection: "column"
flexDirection: "column",
},
fixedHeight: {
minHeight: 240
}
minHeight: 240,
},
});
const mapState = (state: AppState) => ({
open: state.system.sidebarOpen
open: state.system.sidebarOpen,
});
const connector = connect(mapState, { setMenuOpen });

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 from "react";
import React, { useEffect, useState } from "react";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { Button, LinearProgress } from "@material-ui/core";
@@ -23,6 +23,22 @@ 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";
import { connect } from "react-redux";
import {
addBucketName,
addBucketQuota,
addBucketQuotaSize,
addBucketQuotaType,
addBucketQuotaUnit,
addBucketVersioned,
} from "../actions";
import { useDebounce } from "use-debounce";
import { MakeBucketRequest } from "../types";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
const styles = (theme: Theme) =>
createStyles({
@@ -32,6 +48,20 @@ const styles = (theme: Theme) =>
buttonContainer: {
textAlign: "right",
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const,
},
quotaSizeContainer: {
flexGrow: 1,
},
sizeFactorContainer: {
flexGrow: 0,
maxWidth: 80,
marginLeft: 8,
alignSelf: "flex-start" as const,
},
...modalBasic,
});
@@ -39,119 +69,238 @@ interface IAddBucketProps {
classes: any;
open: boolean;
closeModalAndRefresh: () => void;
}
interface IAddBucketState {
addLoading: boolean;
addError: string;
addBucketName: typeof addBucketName;
addBucketVersioned: typeof addBucketVersioned;
addBucketQuota: typeof addBucketQuota;
addBucketQuotaType: typeof addBucketQuotaType;
addBucketQuotaSize: typeof addBucketQuotaSize;
addBucketQuotaUnit: typeof addBucketQuotaUnit;
bucketName: string;
versioned: boolean;
enableQuota: boolean;
quotaType: string;
quotaSize: string;
quotaUnit: string;
}
class AddBucket extends React.Component<IAddBucketProps, IAddBucketState> {
state: IAddBucketState = {
addLoading: false,
addError: "",
bucketName: "",
};
const AddBucket = ({
classes,
open,
closeModalAndRefresh,
addBucketName,
addBucketVersioned,
addBucketQuota,
addBucketQuotaType,
addBucketQuotaSize,
addBucketQuotaUnit,
bucketName,
versioned,
enableQuota,
quotaType,
quotaSize,
quotaUnit,
}: IAddBucketProps) => {
const [bName, setBName] = useState<string>(bucketName);
const [addLoading, setAddLoading] = useState<boolean>(false);
const [addError, setAddError] = useState<string>("");
addRecord(event: React.FormEvent) {
const addRecord = (event: React.FormEvent) => {
event.preventDefault();
const { bucketName, addLoading } = this.state;
if (addLoading) {
return;
}
this.setState({ addLoading: true }, () => {
api
.invoke("POST", "/api/v1/buckets", {
name: bucketName,
})
.then((res) => {
this.setState(
{
addLoading: false,
addError: "",
},
() => {
this.props.closeModalAndRefresh();
}
);
})
.catch((err) => {
this.setState({
addLoading: false,
addError: err,
});
});
});
}
setAddLoading(true);
render() {
const { classes, open } = this.props;
const { addLoading, addError, bucketName } = this.state;
return (
<ModalWrapper
title="Create Bucket"
modalOpen={open}
onClose={() => {
this.setState({ addError: "" }, () => {
this.props.closeModalAndRefresh();
});
let request: MakeBucketRequest = {
name: bucketName,
versioning: versioned,
};
if (enableQuota) {
const amount = getBytes(quotaSize, quotaUnit, false);
request.quota = {
enabled: true,
quota_type: quotaType,
amount: parseInt(amount),
};
}
api
.invoke("POST", "/api/v1/buckets", request)
.then((res) => {
setAddLoading(false);
setAddError("");
closeModalAndRefresh();
})
.catch((err) => {
setAddLoading(false);
setAddError(err);
});
};
const [value] = useDebounce(bName, 1000);
useEffect(() => {
console.log("called");
addBucketName(value);
}, [value]);
return (
<ModalWrapper
title="Create Bucket"
modalOpen={open}
onClose={() => {
setAddError("");
closeModalAndRefresh();
}}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
addRecord(e);
}}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
this.addRecord(e);
}}
>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
{addError !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{addError}
</Typography>
</Grid>
)}
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
{addError !== "" && (
<Grid item xs={12}>
<InputBoxWrapper
id="bucket-name"
name="bucket-name"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ bucketName: e.target.value });
}}
label="Bucket Name"
value={bucketName}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
>
Save
</Button>
</Grid>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{addError}
</Typography>
</Grid>
)}
<Grid item xs={12}>
<InputBoxWrapper
id="bucket-name"
name="bucket-name"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setBName(event.target.value);
}}
label="Bucket Name"
value={bName}
/>
</Grid>
<Grid item xs={12}>
<FormSwitchWrapper
value="versioned"
id="versioned"
name="versioned"
checked={versioned}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
addBucketVersioned(event.target.checked);
}}
label={"Turn On Versioning"}
/>
</Grid>
<Grid item xs={12}>
<FormSwitchWrapper
value="bucket_quota"
id="bucket_quota"
name="bucket_quota"
checked={enableQuota}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
addBucketQuota(event.target.checked);
}}
label={"Enable Bucket Quota"}
/>
</Grid>
{enableQuota && (
<React.Fragment>
<Grid item xs={12}>
<SelectWrapper
value={quotaType}
label="Quota Type"
id="quota_type"
name="quota_type"
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
addBucketQuotaType(e.target.value as string);
}}
options={[
{ value: "hard", label: "Hard" },
{ value: "fifo", label: "FIFO" },
]}
/>
</Grid>
<Grid item xs={12}>
<div className={classes.multiContainer}>
<div className={classes.quotaSizeContainer}>
<InputBoxWrapper
type="number"
id="quota_size"
name="quota_size"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
addBucketQuotaSize(e.target.value);
}}
label="Quota"
value={quotaSize}
required
min="1"
/>
</div>
<div className={classes.sizeFactorContainer}>
<SelectWrapper
label="&nbsp;"
id="quota_unit"
name="quota_unit"
value={quotaUnit}
onChange={(
e: React.ChangeEvent<{ value: unknown }>
) => {
addBucketQuotaUnit(e.target.value as string);
}}
options={factorForDropdown()}
/>
</div>
</div>
</Grid>
</React.Fragment>
)}
</Grid>
</form>
</ModalWrapper>
);
}
}
<Grid item xs={12} className={classes.buttonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
>
Save
</Button>
</Grid>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</Grid>
</form>
</ModalWrapper>
);
};
export default withStyles(styles)(AddBucket);
const mapState = (state: AppState) => ({
addBucketModalOpen: state.buckets.open,
bucketName: state.buckets.addBucketName,
versioned: state.buckets.addBucketVersioning,
enableQuota: state.buckets.addBucketQuotaEnabled,
quotaType: state.buckets.addBucketQuotaType,
quotaSize: state.buckets.addBucketQuotaSize,
quotaUnit: state.buckets.addBucketQuotaUnit,
});
const connector = connect(mapState, {
addBucketName: addBucketName,
addBucketVersioned: addBucketVersioned,
addBucketQuota: addBucketQuota,
addBucketQuotaType: addBucketQuotaType,
addBucketQuotaSize: addBucketQuotaSize,
addBucketQuotaUnit: addBucketQuotaUnit,
});
export default connector(withStyles(styles)(AddBucket));

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 from "react";
import React, { useEffect, useState } from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import { Button } from "@material-ui/core";
@@ -31,6 +31,15 @@ import DeleteBucket from "./DeleteBucket";
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
import { CreateIcon } from "../../../../icons";
import { niceBytes } from "../../../../common/utils";
import { AppState } from "../../../../store";
import { connect } from "react-redux";
import { addBucketOpen, addBucketReset } from "../actions";
import {
actionsTray,
containerForHeader,
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import PageHeader from "../../Common/PageHeader/PageHeader";
const styles = (theme: Theme) =>
createStyles({
@@ -62,188 +71,155 @@ 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)),
});
interface IListBucketsProps {
classes: any;
addBucketOpen: typeof addBucketOpen;
addBucketModalOpen: boolean;
addBucketReset: typeof addBucketReset;
}
interface IListBucketsState {
records: Bucket[];
totalRecords: number;
loading: boolean;
error: string;
deleteError: string;
addScreenOpen: boolean;
page: number;
rowsPerPage: number;
deleteOpen: boolean;
selectedBucket: string;
filterBuckets: string;
}
const ListBuckets = ({
classes,
addBucketOpen,
addBucketModalOpen,
addBucketReset,
}: IListBucketsProps) => {
const [records, setRecords] = useState<Bucket[]>([]);
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);
const [selectedBucket, setSelectedBucket] = useState<string>("");
const [filterBuckets, setFilterBuckets] = useState<string>("");
class ListBuckets extends React.Component<
IListBucketsProps,
IListBucketsState
> {
state: IListBucketsState = {
records: [],
totalRecords: 0,
loading: false,
error: "",
deleteError: "",
addScreenOpen: false,
page: 0,
rowsPerPage: 10,
deleteOpen: false,
selectedBucket: "",
filterBuckets: "",
useEffect(() => {
if (loading) {
const fetchRecords = () => {
setLoading(true);
const offset = page * rowsPerPage;
api
.invoke(
"GET",
`/api/v1/buckets?offset=${offset}&limit=${rowsPerPage}`
)
.then((res: BucketList) => {
setLoading(false);
setRecords(res.buckets || []);
setTotalRecords(!res.buckets ? 0 : res.total);
setError("");
// if we get 0 results, and page > 0 , go down 1 page
if (
(res.buckets === undefined ||
res.buckets == null ||
res.buckets.length === 0) &&
page > 0
) {
const newPage = page - 1;
setPage(newPage);
setLoading(true);
}
})
.catch((err: any) => {
setLoading(false);
setError(err);
});
};
fetchRecords();
}
}, [loading, page, rowsPerPage]);
const closeAddModalAndRefresh = () => {
addBucketOpen(false);
addBucketReset();
setLoading(true);
};
fetchRecords() {
this.setState({ loading: true }, () => {
const { page, rowsPerPage } = this.state;
const offset = page * rowsPerPage;
api
.invoke("GET", `/api/v1/buckets?offset=${offset}&limit=${rowsPerPage}`)
.then((res: BucketList) => {
this.setState({
loading: false,
records: res.buckets || [],
totalRecords: !res.buckets ? 0 : res.total,
error: "",
});
// if we get 0 results, and page > 0 , go down 1 page
if (
(res.buckets === undefined ||
res.buckets == null ||
res.buckets.length === 0) &&
page > 0
) {
const newPage = page - 1;
this.setState({ page: newPage }, () => {
this.fetchRecords();
});
}
})
.catch((err: any) => {
this.setState({ loading: false, error: err });
});
});
}
const closeDeleteModalAndRefresh = (refresh: boolean) => {
setDeleteOpen(false);
if (refresh) {
setLoading(true);
}
};
closeAddModalAndRefresh() {
this.setState({ addScreenOpen: false }, () => {
this.fetchRecords();
});
}
useEffect(() => {
setLoading(true);
}, []);
closeDeleteModalAndRefresh(refresh: boolean) {
this.setState({ deleteOpen: false }, () => {
if (refresh) {
this.fetchRecords();
}
});
}
useEffect(() => {
setLoading(true);
}, [page, rowsPerPage]);
componentDidMount(): void {
this.fetchRecords();
}
const confirmDeleteBucket = (bucket: string) => {
setDeleteOpen(true);
setSelectedBucket(bucket);
};
bucketFilter(): void {}
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
};
render() {
const { classes } = this.props;
const {
records,
totalRecords,
addScreenOpen,
loading,
page,
rowsPerPage,
deleteOpen,
selectedBucket,
filterBuckets,
} = this.state;
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
const rPP = parseInt(event.target.value, 10);
setPage(0);
setRowsPerPage(rPP);
};
const tableActions = [
{ type: "view", to: `/buckets`, sendOnlyId: true },
{ type: "delete", onClick: confirmDeleteBucket, sendOnlyId: true },
];
const offset = page * rowsPerPage;
const offset = page * rowsPerPage;
const handleChangePage = (event: unknown, newPage: number) => {
this.setState({ page: newPage });
};
const displayParsedDate = (date: string) => {
return <Moment>{date}</Moment>;
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
const rPP = parseInt(event.target.value, 10);
this.setState({ page: 0, rowsPerPage: rPP });
};
const confirmDeleteBucket = (bucket: string) => {
this.setState({ deleteOpen: true, selectedBucket: bucket });
};
const tableActions = [
{ type: "view", to: `/buckets`, sendOnlyId: true },
{ type: "delete", onClick: confirmDeleteBucket, sendOnlyId: true },
];
const displayParsedDate = (date: string) => {
return <Moment>{date}</Moment>;
};
const filteredRecords = records
.slice(offset, offset + rowsPerPage)
.filter((b: Bucket) => {
if (filterBuckets === "") {
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);
return (
<React.Fragment>
{addScreenOpen && (
<AddBucket
open={addScreenOpen}
closeModalAndRefresh={() => {
this.closeAddModalAndRefresh();
}}
/>
)}
{deleteOpen && (
<DeleteBucket
deleteOpen={deleteOpen}
selectedBucket={selectedBucket}
closeDeleteModalAndRefresh={(refresh: boolean) => {
this.closeDeleteModalAndRefresh(refresh);
}}
/>
)}
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Buckets</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
return (
<React.Fragment>
{addBucketModalOpen && (
<AddBucket
open={addBucketModalOpen}
closeModalAndRefresh={() => {
closeAddModalAndRefresh();
}}
/>
)}
{deleteOpen && (
<DeleteBucket
deleteOpen={deleteOpen}
selectedBucket={selectedBucket}
closeDeleteModalAndRefresh={(refresh: boolean) => {
closeDeleteModalAndRefresh(refresh);
}}
/>
)}
<PageHeader label={"Buckets"} />
<Grid container>
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Buckets"
@@ -251,9 +227,7 @@ class ListBuckets extends React.Component<
id="search-resource"
label=""
onChange={(val) => {
this.setState({
filterBuckets: val.target.value,
});
setFilterBuckets(val.target.value);
}}
InputProps={{
disableUnderline: true,
@@ -269,9 +243,7 @@ class ListBuckets extends React.Component<
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
this.setState({
addScreenOpen: true,
});
addBucketOpen(true);
}}
>
Create Bucket
@@ -317,9 +289,18 @@ class ListBuckets extends React.Component<
/>
</Grid>
</Grid>
</React.Fragment>
);
}
}
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(ListBuckets);
const mapState = (state: AppState) => ({
addBucketModalOpen: state.buckets.open,
});
const connector = connect(mapState, {
addBucketOpen: addBucketOpen,
addBucketReset: addBucketReset,
});
export default connector(withStyles(styles)(ListBuckets));

View File

@@ -0,0 +1,160 @@
// 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import React from "react";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
LinearProgress,
} from "@material-ui/core";
import api from "../../../../../../common/api";
import Typography from "@material-ui/core/Typography";
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red",
},
});
interface IDeleteObjectProps {
classes: any;
closeDeleteModalAndRefresh: (refresh: boolean) => void;
deleteOpen: boolean;
selectedObject: string;
selectedBucket: string;
}
interface IDeleteObjectState {
deleteLoading: boolean;
deleteError: string;
}
class DeleteObject extends React.Component<
IDeleteObjectProps,
IDeleteObjectState
> {
state: IDeleteObjectState = {
deleteLoading: false,
deleteError: "",
};
removeRecord() {
const { deleteLoading } = this.state;
const { selectedObject, selectedBucket } = this.props;
if (deleteLoading) {
return;
}
var recursive = false;
if (selectedObject.endsWith("/")) {
recursive = true;
}
this.setState({ deleteLoading: true }, () => {
api
.invoke(
"DELETE",
`/api/v1/buckets/${selectedBucket}/objects?path=${selectedObject}&recursive=${recursive}`
)
.then((res: any) => {
this.setState(
{
deleteLoading: false,
deleteError: "",
},
() => {
this.props.closeDeleteModalAndRefresh(true);
}
);
})
.catch((err) => {
this.setState({
deleteLoading: false,
deleteError: err,
});
});
});
}
render() {
const { classes, deleteOpen, selectedObject } = this.props;
const { deleteLoading, deleteError } = this.state;
return (
<Dialog
open={deleteOpen}
onClose={() => {
this.setState({ deleteError: "" }, () => {
this.props.closeDeleteModalAndRefresh(false);
});
}}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Delete</DialogTitle>
<DialogContent>
{deleteLoading && <LinearProgress />}
<DialogContentText id="alert-dialog-description">
Are you sure you want to delete: <b>{selectedObject}</b>?{" "}
{deleteError !== "" && (
<React.Fragment>
<br />
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{deleteError}
</Typography>
</React.Fragment>
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
onClick={() => {
this.setState({ deleteError: "" }, () => {
this.props.closeDeleteModalAndRefresh(false);
});
}}
color="primary"
disabled={deleteLoading}
>
Cancel
</Button>
<Button
onClick={() => {
this.setState({ deleteError: "" }, () => {
this.removeRecord();
});
}}
color="secondary"
disabled={deleteLoading}
>
Delete
</Button>
</DialogActions>
</Dialog>
);
}
}
export default withStyles(styles)(DeleteObject);

View File

@@ -0,0 +1,285 @@
// 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import { Button } from "@material-ui/core";
import Typography from "@material-ui/core/Typography";
import TextField from "@material-ui/core/TextField";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import { BucketObject, BucketObjectsList } from "./types";
import api from "../../../../../../common/api";
import React, { useEffect, useState } from "react";
import TableWrapper from "../../../../Common/TableWrapper/TableWrapper";
import { MinTablePaginationActions } from "../../../../../../common/MinTablePaginationActions";
import { CreateIcon } from "../../.././../../../icons";
import { niceBytes } from "../../../../../../common/utils";
import Moment from "react-moment";
import DeleteObject from "./DeleteObject";
import {
actionsTray,
containerForHeader,
searchField,
} from "../../../../Common/FormComponents/common/styleLibrary";
import PageHeader from "../../../../Common/PageHeader/PageHeader";
import storage from "local-storage-fallback";
const styles = (theme: Theme) =>
createStyles({
seeMore: {
marginTop: theme.spacing(3),
},
paper: {
display: "flex",
overflow: "auto",
flexDirection: "column",
},
addSideBar: {
width: "320px",
padding: "20px",
},
errorBlock: {
color: "red",
},
tableToolbar: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(0),
},
minTableHeader: {
color: "#393939",
"& tr": {
"& th": {
fontWeight: "bold",
},
},
},
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
});
interface IListObjectsProps {
classes: any;
match: any;
}
interface IListObjectsState {
records: BucketObject[];
totalRecords: number;
loading: boolean;
error: string;
deleteOpen: boolean;
deleteError: string;
selectedObject: string;
selectedBucket: string;
filterObjects: string;
}
class ListObjects extends React.Component<
IListObjectsProps,
IListObjectsState
> {
state: IListObjectsState = {
records: [],
totalRecords: 0,
loading: false,
error: "",
deleteOpen: false,
deleteError: "",
selectedObject: "",
selectedBucket: "",
filterObjects: "",
};
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 });
});
});
};
componentDidMount(): void {
this.fetchRecords();
}
closeDeleteModalAndRefresh(refresh: boolean) {
this.setState({ deleteOpen: false }, () => {
if (refresh) {
this.fetchRecords();
}
});
}
download(bucketName: string, objectName: string) {
var anchor = document.createElement("a");
document.body.appendChild(anchor);
const token: string = storage.getItem("token")!;
var xhr = new XMLHttpRequest();
xhr.open(
"GET",
`/api/v1/buckets/${bucketName}/objects/download?prefix=${objectName}`,
true
);
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
xhr.responseType = "blob";
xhr.onload = function(e) {
if (this.status == 200) {
var blob = new Blob([this.response], {
type: "octet/stream",
});
var blobUrl = window.URL.createObjectURL(blob);
anchor.href = blobUrl;
anchor.download = objectName;
anchor.click();
window.URL.revokeObjectURL(blobUrl);
anchor.remove();
}
};
xhr.send();
}
bucketFilter(): void {}
render() {
const { classes } = this.props;
const {
records,
loading,
selectedObject,
selectedBucket,
deleteOpen,
filterObjects,
} = this.state;
const displayParsedDate = (date: string) => {
return <Moment>{date}</Moment>;
};
const confirmDeleteObject = (object: string) => {
this.setState({ deleteOpen: true, selectedObject: object });
};
const downloadObject = (object: string) => {
this.download(selectedBucket, object);
};
const tableActions = [
{ type: "download", onClick: downloadObject, sendOnlyId: true },
{ type: "delete", onClick: confirmDeleteObject, sendOnlyId: true },
];
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={(refresh: boolean) => {
this.closeDeleteModalAndRefresh(refresh);
}}
/>
)}
<PageHeader label="Objects" />
<Grid container>
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Objects"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
this.setState({
filterObjects: val.target.value,
});
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "Name", elementKey: "name" },
{
label: "Last Modified",
elementKey: "last_modified",
renderFunction: displayParsedDate,
},
{
label: "Size",
elementKey: "size",
renderFunction: niceBytes,
},
]}
isLoading={loading}
entityName="Objects"
idField="name"
records={filteredRecords}
/>
</Grid>
</Grid>
</Grid>
</React.Fragment>
);
}
}
export default withStyles(styles)(ListObjects);

View File

@@ -0,0 +1,27 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
export interface BucketObject {
name: string;
size: number;
last_modified: Date;
content_type: string;
}
export interface BucketObjectsList {
objects: BucketObject[];
total: number;
}

View File

@@ -0,0 +1,235 @@
// 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, useEffect } from "react";
import get from "lodash/get";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
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";
interface IReplicationModal {
open: boolean;
closeModalAndRefresh: () => any;
classes: any;
bucketName: string;
}
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red",
},
minTableHeader: {
color: "#393939",
"& tr": {
"& th": {
fontWeight: "bold",
},
},
},
buttonContainer: {
textAlign: "right",
},
...modalBasic,
});
const AddReplicationModal = ({
open,
closeModalAndRefresh,
classes,
bucketName,
}: IReplicationModal) => {
const [addError, setAddError] = useState("");
const [addLoading, setAddLoading] = useState(false);
const [accessKey, setAccessKey] = useState("");
const [secretKey, setSecretKey] = useState("");
const [targetURL, setTargetURL] = useState("");
const [targetBucket, setTargetBucket] = useState("");
const [region, setRegion] = useState("");
const addRecord = () => {
const remoteBucketInfo = {
accessKey: accessKey,
secretKey: secretKey,
sourceBucket: bucketName,
targetURL: targetURL,
targetBucket: targetBucket,
region: region,
};
api
.invoke("POST", "/api/v1/remote-buckets", remoteBucketInfo)
.then(() => {
api
.invoke("GET", "/api/v1/remote-buckets")
.then((res: any) => {
const remoteBuckets = get(res, "buckets", []);
const remoteBucket = remoteBuckets.find(
(itemRemote: IRemoteBucket) => {
return itemRemote.sourceBucket === bucketName;
}
);
if (remoteBucket && remoteBucket.remoteARN) {
const remoteARN = remoteBucket.remoteARN;
const replicationInfo = {
destination_bucket: targetBucket,
arn: remoteARN,
};
api
.invoke(
"POST",
`/api/v1/buckets/${bucketName}/replication`,
replicationInfo
)
.then(() => {
setAddLoading(false);
setAddError("");
closeModalAndRefresh();
})
.catch((err) => {
setAddLoading(false);
setAddError(err);
});
}
})
.catch((err) => {
setAddError(err);
});
})
.catch((err) => {
setAddLoading(false);
setAddError(err);
});
};
return (
<ModalWrapper
modalOpen={open}
onClose={() => {
setAddError("");
closeModalAndRefresh();
}}
title="Set Bucket Replication"
>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setAddLoading(true);
addRecord();
}}
>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
{addError !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{addError}
</Typography>
</Grid>
)}
<Grid item xs={12}>
<InputBoxWrapper
id="target"
name="target"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAccessKey(e.target.value);
}}
label="Access Key"
value={accessKey}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="target"
name="target"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setSecretKey(e.target.value);
}}
label="Secret Key"
value={secretKey}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="target"
name="target"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setTargetURL(e.target.value);
}}
placeholder="https://play.min.io:9000"
label="Target URL"
value={targetURL}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="target"
name="target"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setTargetBucket(e.target.value);
}}
label="Target Bucket"
value={targetBucket}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="target"
name="target"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setRegion(e.target.value);
}}
label="Region"
value={region}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
>
Save
</Button>
</Grid>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</Grid>
</form>
</ModalWrapper>
);
};
export default withStyles(styles)(AddReplicationModal);

View File

@@ -23,8 +23,18 @@ import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import CircularProgress from "@material-ui/core/CircularProgress";
import api from "../../../../common/api";
import { BucketEvent, BucketEventList, BucketInfo, BucketList } from "../types";
import { Button } from "@material-ui/core";
import {
BucketEvent,
BucketEventList,
BucketInfo,
BucketList,
BucketReplication,
BucketReplicationDestination,
BucketReplicationRule,
BucketReplicationRuleDeleteMarker,
BucketVersioning,
} from "../types";
import { Box, Button } from "@material-ui/core";
import Typography from "@material-ui/core/Typography";
import SetAccessPolicy from "./SetAccessPolicy";
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
@@ -33,6 +43,9 @@ import AddEvent from "./AddEvent";
import DeleteEvent from "./DeleteEvent";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import { niceBytes } from "../../../../common/utils";
import AddReplicationModal from "./AddReplicationModal";
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
import PageHeader from "../../Common/PageHeader/PageHeader";
const styles = (theme: Theme) =>
createStyles({
@@ -117,6 +130,14 @@ const styles = (theme: Theme) =>
capitalizeFirst: {
textTransform: "capitalize",
},
doubleElement: {
display: "flex",
justifyContent: "space-between",
},
tabPan: {
marginTop: "5px",
},
...containerForHeader(theme.spacing(4)),
});
interface IViewBucketProps {
@@ -124,9 +145,40 @@ interface IViewBucketProps {
match: any;
}
interface TabPanelProps {
children?: React.ReactNode;
index: any;
value: any;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
style={{ marginTop: "5px" }}
{...other}
>
{value === index && <React.Fragment>{children}</React.Fragment>}
</div>
);
}
function a11yProps(index: any) {
return {
id: `simple-tab-${index}`,
"aria-controls": `simple-tabpanel-${index}`,
};
}
interface IViewBucketState {
info: BucketInfo | null;
records: BucketEvent[];
replicationRules: BucketReplicationRule[];
totalRecords: number;
loadingBucket: boolean;
loadingEvents: boolean;
@@ -137,18 +189,23 @@ interface IViewBucketState {
setAccessPolicyScreenOpen: boolean;
page: number;
rowsPerPage: number;
curTab: number;
addScreenOpen: boolean;
deleteOpen: boolean;
selectedBucket: string;
selectedEvent: BucketEvent | null;
bucketSize: string;
errorSize: string;
replicationSet: boolean;
openSetReplication: boolean;
isVersioned: boolean;
}
class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
state: IViewBucketState = {
info: null,
records: [],
replicationRules: [],
totalRecords: 0,
loadingBucket: true,
loadingEvents: true,
@@ -158,6 +215,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
errBucket: "",
setAccessPolicyScreenOpen: false,
page: 0,
curTab: 0,
rowsPerPage: 10,
addScreenOpen: false,
deleteOpen: false,
@@ -165,6 +223,9 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
selectedEvent: null,
bucketSize: "0",
errorSize: "",
replicationSet: false,
openSetReplication: false,
isVersioned: false,
};
fetchEvents() {
@@ -195,6 +256,29 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
.catch((err: any) => {
this.setState({ loadingEvents: false, error: err });
});
api
.invoke("GET", `/api/v1/buckets/${bucketName}/versioning`)
.then((res: BucketVersioning) => {
this.setState({
isVersioned: res.is_versioned,
});
})
.catch((err: any) => {
this.setState({ error: err });
});
api
.invoke("GET", `/api/v1/buckets/${bucketName}/replication`)
.then((res: BucketReplication) => {
const r = res.rules ? res.rules : [];
this.setState({
replicationRules: r,
});
})
.catch((err: any) => {
this.setState({ error: err });
});
});
}
@@ -279,6 +363,11 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
selectedEvent,
bucketSize,
loadingSize,
replicationSet,
openSetReplication,
isVersioned,
replicationRules,
curTab,
} = this.state;
const offset = page * rowsPerPage;
@@ -301,6 +390,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
};
let accessPolicy = "n/a";
if (info !== null) {
accessPolicy = info.access;
}
@@ -309,9 +399,26 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
return <React.Fragment>{events.join(", ")}</React.Fragment>;
};
const ruleDestDisplay = (events: BucketReplicationDestination) => {
return (
<React.Fragment>
{events.bucket.replace("arn:aws:s3:::", "")}
</React.Fragment>
);
};
const ruleDelDisplay = (events: BucketReplicationRuleDeleteMarker) => {
return <React.Fragment>{events.status}</React.Fragment>;
};
const setOpenReplicationOpen = (open = false) => {
this.setState({ openSetReplication: open });
};
const tableActions = [{ type: "delete", onClick: confirmDeleteEvent }];
const filteredRecords = records.slice(offset, offset + rowsPerPage);
const filteredRules = replicationRules.slice(offset, offset + rowsPerPage);
return (
<React.Fragment>
@@ -335,130 +442,203 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
}}
/>
)}
{openSetReplication && (
<AddReplicationModal
closeModalAndRefresh={() => {
setOpenReplicationOpen(false);
this.fetchEvents();
}}
open={openSetReplication}
bucketName={bucketName}
/>
)}
<PageHeader label={`Bucket > ${match.params["bucketName"]}`} />
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">
{`Bucket > ${match.params["bucketName"]}`}
</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<div className={classes.headerContainer}>
<div>
<Paper className={classes.paperContainer}>
<div className={classes.gridContainer}>
<div>Access Policy:</div>
<div className={classes.capitalizeFirst}>
{loadingBucket ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
accessPolicy.toLowerCase()
)}
</div>
<div>Reported Usage:</div>
<div>
{loadingSize ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
niceBytes(bucketSize)
)}
</div>
</div>
</Paper>
</div>
<div className={classes.masterActions}>
<Grid item xs={12} className={classes.container}>
<Grid item xs={12}>
<div className={classes.headerContainer}>
<div>
<Paper className={classes.paperContainer}>
<div className={classes.gridContainer}>
<div>Access Policy:</div>
<div className={classes.capitalizeFirst}>
{loadingBucket ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
accessPolicy.toLowerCase()
)}
</div>
<div>Reported Usage:</div>
<div>
{loadingSize ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
niceBytes(bucketSize)
)}
</div>
<div>Replication:</div>
<div className={classes.doubleElement}>
<span>{replicationRules.length ? "Yes" : "No"}</span>
</div>
<div>Versioning:</div>
<div>{isVersioned ? "Yes" : "No"}&nbsp;</div>
</div>
</Paper>
</div>
<div className={classes.masterActions}>
<div>
<Button
variant="contained"
color="primary"
fullWidth
size="medium"
onClick={() => {
this.setState({
setAccessPolicyScreenOpen: true,
});
}}
>
Change Access Policy
</Button>
</div>
</div>
</div>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid container item xs={12}>
<Grid item xs={6}>
<Tabs
value={curTab}
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
this.setState({ curTab: newValue });
}}
indicatorColor="primary"
textColor="primary"
aria-label="cluster-tabs"
>
<Tab label="Events" {...a11yProps(0)} />
<Tab label="Replication" {...a11yProps(1)} />
</Tabs>
</Grid>
<Grid item xs={6} className={classes.actionsTray}>
{curTab === 0 && (
<Button
variant="contained"
color="primary"
fullWidth
startIcon={<CreateIcon />}
size="medium"
onClick={() => {
this.setState({
setAccessPolicyScreenOpen: true,
addScreenOpen: true,
});
}}
>
Change Access Policy
Subscribe to Event
</Button>
</div>
</div>
</div>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={6}>
<Tabs
value={0}
indicatorColor="primary"
textColor="primary"
aria-label="cluster-tabs"
>
<Tab label="Events" />
</Tabs>
</Grid>
<Grid item xs={6} className={classes.actionsTray}>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
size="medium"
onClick={() => {
this.setState({
addScreenOpen: true,
});
}}
>
Subcribe to Event
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "SQS", elementKey: "arn" },
{
label: "Events",
elementKey: "events",
renderFunction: eventsDisplay,
},
{ label: "Prefix", elementKey: "prefix" },
{ label: "Suffix", elementKey: "suffix" },
]}
isLoading={loadingEvents}
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,
}}
/>
)}
{curTab === 1 && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
size="medium"
onClick={() => {
this.setState({
openSetReplication: true,
});
}}
>
Add Replication Rule
</Button>
)}
</Grid>
</Grid>
<Grid item xs={12}>
<TabPanel index={0} value={curTab}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "SQS", elementKey: "arn" },
{
label: "Events",
elementKey: "events",
renderFunction: eventsDisplay,
},
{ label: "Prefix", elementKey: "prefix" },
{ label: "Suffix", elementKey: "suffix" },
]}
isLoading={loadingEvents}
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}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "ID", elementKey: "id" },
{
label: "Priority",
elementKey: "priority",
},
{
label: "Destination",
elementKey: "destination",
renderFunction: ruleDestDisplay,
},
{
label: "Delete Replication",
elementKey: "delete_marker_replication",
renderFunction: ruleDelDisplay,
},
{ label: "Status", elementKey: "status" },
]}
isLoading={loadingEvents}
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>
</Grid>
</Grid>

View File

@@ -0,0 +1,126 @@
// 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 ADD_BUCKET_OPEN = "ADD_BUCKET_OPEN";
export const ADD_BUCKET_NAME = "ADD_BUCKET_NAME";
export const ADD_BUCKET_VERSIONED = "ADD_BUCKET_VERSIONED";
export const ADD_BUCKET_QUOTA = "ADD_BUCKET_QUOTA";
export const ADD_BUCKET_QUOTA_TYPE = "ADD_BUCKET_QUOTA_TYPE";
export const ADD_BUCKET_QUOTA_SIZE = "ADD_BUCKET_QUOTA_SIZE";
export const ADD_BUCKET_QUOTA_UNIT = "ADD_BUCKET_QUOTA_UNIT";
export const ADD_BUCKET_RESET = "ADD_BUCKET_RESET";
interface AddBucketOpenAction {
type: typeof ADD_BUCKET_OPEN;
open: boolean;
}
interface AddBucketNameAction {
type: typeof ADD_BUCKET_NAME;
name: string;
}
interface AddBucketVersionedAction {
type: typeof ADD_BUCKET_VERSIONED;
versioned: boolean;
}
interface AddBucketQuotaAction {
type: typeof ADD_BUCKET_QUOTA;
quota: boolean;
}
interface AddBucketQuotaTypeAction {
type: typeof ADD_BUCKET_QUOTA_TYPE;
quotaType: string;
}
interface AddBucketQuotaSizeAction {
type: typeof ADD_BUCKET_QUOTA_SIZE;
quotaSize: string;
}
interface AddBucketQuotaUnitAction {
type: typeof ADD_BUCKET_QUOTA_UNIT;
quotaUnit: string;
}
interface AddBucketResetAction {
type: typeof ADD_BUCKET_RESET;
}
export type BucketActionTypes =
| AddBucketOpenAction
| AddBucketNameAction
| AddBucketVersionedAction
| AddBucketQuotaAction
| AddBucketQuotaTypeAction
| AddBucketQuotaSizeAction
| AddBucketQuotaUnitAction
| AddBucketResetAction;
export function addBucketOpen(open: boolean) {
return {
type: ADD_BUCKET_OPEN,
open: open,
};
}
export function addBucketName(name: string) {
return {
type: ADD_BUCKET_NAME,
name: name,
};
}
export function addBucketVersioned(versioned: boolean) {
return {
type: ADD_BUCKET_VERSIONED,
versioned: versioned,
};
}
export function addBucketQuota(quota: boolean) {
return {
type: ADD_BUCKET_QUOTA,
quota: quota,
};
}
export function addBucketQuotaType(quotaType: string) {
return {
type: ADD_BUCKET_QUOTA_TYPE,
quotaType: quotaType,
};
}
export function addBucketQuotaSize(quotaSize: string) {
return {
type: ADD_BUCKET_QUOTA_SIZE,
quotaSize: quotaSize,
};
}
export function addBucketQuotaUnit(quotaUnit: string) {
return {
type: ADD_BUCKET_QUOTA_UNIT,
quotaUnit: quotaUnit,
};
}
export function addBucketReset() {
return {
type: ADD_BUCKET_RESET,
};
}

View File

@@ -0,0 +1,102 @@
// 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 {
ADD_BUCKET_NAME,
ADD_BUCKET_OPEN,
ADD_BUCKET_QUOTA,
ADD_BUCKET_QUOTA_SIZE,
ADD_BUCKET_QUOTA_TYPE,
ADD_BUCKET_QUOTA_UNIT,
ADD_BUCKET_RESET,
ADD_BUCKET_VERSIONED,
BucketActionTypes,
} from "./actions";
export interface BucketsState {
open: boolean;
addBucketName: string;
addBucketVersioning: boolean;
addBucketQuotaEnabled: boolean;
addBucketQuotaType: string;
addBucketQuotaSize: string;
addBucketQuotaUnit: string;
}
const initialState: BucketsState = {
open: false,
addBucketName: "",
addBucketVersioning: false,
addBucketQuotaEnabled: false,
addBucketQuotaType: "hard",
addBucketQuotaSize: "1",
addBucketQuotaUnit: "TiB",
};
export function bucketsReducer(
state = initialState,
action: BucketActionTypes
): BucketsState {
switch (action.type) {
case ADD_BUCKET_OPEN:
return {
...state,
open: action.open,
};
case ADD_BUCKET_NAME:
return {
...state,
addBucketName: action.name,
};
case ADD_BUCKET_VERSIONED:
return {
...state,
addBucketVersioning: action.versioned,
};
case ADD_BUCKET_QUOTA:
return {
...state,
addBucketQuotaEnabled: action.quota,
};
case ADD_BUCKET_QUOTA_TYPE:
return {
...state,
addBucketQuotaType: action.quotaType,
};
case ADD_BUCKET_QUOTA_SIZE:
return {
...state,
addBucketQuotaSize: action.quotaSize,
};
case ADD_BUCKET_QUOTA_UNIT:
return {
...state,
addBucketQuotaUnit: action.quotaUnit,
};
case ADD_BUCKET_RESET:
return {
...state,
addBucketName: "",
addBucketVersioning: false,
addBucketQuotaEnabled: false,
addBucketQuotaType: "hard",
addBucketQuotaSize: "1",
addBucketQuotaUnit: "TiB",
};
default:
return state;
}
}

View File

@@ -45,3 +45,49 @@ export interface BucketEventList {
export interface ArnList {
arns: string[];
}
export interface BucketVersioning {
is_versioned: boolean;
}
export interface BucketReplicationRuleDeleteMarker {
status: string;
}
export interface BucketReplicationDestination {
bucket: string;
}
export interface BucketReplicationRule {
id: string;
status: string;
priority: number;
delete_marker_replication: BucketReplicationRuleDeleteMarker;
Destination: BucketReplicationDestination;
}
export interface BucketReplication {
rules: BucketReplicationRule[];
}
export interface QuotaRequest {
enabled: boolean;
quota_type: string;
amount: number;
}
export interface MakeBucketRequest {
name: string;
versioning: boolean;
quota?: QuotaRequest;
}
export interface IRemoteBucket {
name: string;
accessKey: string;
secretKey: string;
sourceBucket: string;
targetURL: string;
targetBucket: string;
remoteARN: string;
status: string;
service: string;
}

View File

@@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import get from "lodash/get";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { NewServiceAccount } from "./types";
import { Button } from "@material-ui/core";
@@ -67,6 +68,8 @@ const CredentialsPrompt = ({
return null;
}
const consoleCreds = get(newServiceAccount, "console", null);
return (
<ModalWrapper
modalOpen={open}
@@ -87,6 +90,21 @@ const CredentialsPrompt = ({
<b>Secret Key:</b> {newServiceAccount.secretKey}
</li>
</ul>
{consoleCreds && (
<React.Fragment>
<Grid item xs={12}>
<strong>Console Credentials</strong>
<ul>
<li>
<b>Access Key:</b> {consoleCreds.accessKey}
</li>
<li>
<b>Secret Key:</b> {consoleCreds.secretKey}
</li>
</ul>
</Grid>
</React.Fragment>
)}
<Typography
component="p"
variant="body1"
@@ -99,11 +117,23 @@ const CredentialsPrompt = ({
<Grid item xs={12} className={classes.buttonContainer}>
<Button
onClick={() => {
let consoleExtras = {};
if (consoleCreds) {
consoleExtras = {
console: {
access_key: consoleCreds.accessKey,
secret_key: consoleCreds.secretKey,
},
};
}
download(
"credentials.json",
JSON.stringify({
access_key: newServiceAccount.accessKey,
secret_key: newServiceAccount.secretKey,
...consoleExtras,
})
);
}}

View File

@@ -17,4 +17,10 @@
export interface NewServiceAccount {
accessKey: string;
secretKey: string;
console?: ConsoleSA;
}
export interface ConsoleSA {
accessKey: string;
secretKey: string;
}

View File

@@ -14,8 +14,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import React, { useState } from "react";
import get from "lodash/get";
import { Grid, InputLabel, Tooltip } from "@material-ui/core";
import IconButton from "@material-ui/core/IconButton";
import AttachFileIcon from "@material-ui/icons/AttachFile";
import CancelIcon from "@material-ui/icons/Cancel";
import HelpIcon from "@material-ui/icons/Help";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
@@ -24,7 +28,7 @@ import { fileProcess } from "./utils";
interface InputBoxProps {
label: string;
classes: any;
onChange: (e: string) => void;
onChange: (e: string, i: string) => void;
id: string;
name: string;
disabled?: boolean;
@@ -32,6 +36,7 @@ interface InputBoxProps {
required?: boolean;
error?: string;
accept?: string;
value?: string;
}
const styles = (theme: Theme) =>
@@ -41,6 +46,7 @@ const styles = (theme: Theme) =>
textBoxContainer: {
flexGrow: 1,
position: "relative",
flexDirection: "column",
},
errorState: {
color: "#b53b4b",
@@ -49,6 +55,27 @@ const styles = (theme: Theme) =>
top: 7,
right: 7,
},
errorText: {
margin: "0",
fontSize: "0.75rem",
marginTop: 3,
textAlign: "left",
fontFamily: "Lato,sans-serif",
fontWeight: 400,
lineHeight: "1.66",
color: "#dc1f2e",
},
valueString: {
maxWidth: 350,
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
marginTop: 2,
},
fileReselect: {
display: "flex",
alignItems: "center",
},
});
const FileSelector = ({
@@ -62,7 +89,10 @@ const FileSelector = ({
required,
error = "",
accept = "",
value = "",
}: InputBoxProps) => {
const [showFileSelector, setShowSelector] = useState(false);
return (
<React.Fragment>
<Grid
@@ -92,19 +122,62 @@ const FileSelector = ({
)}
</InputLabel>
)}
<div className={classes.textBoxContainer}>
<input
type="file"
name={name}
onChange={(e) => {
fileProcess(e, (data: any) => {
onChange(data);
});
}}
accept={accept}
required
/>
</div>
{showFileSelector || value === "" ? (
<div className={classes.textBoxContainer}>
<input
type="file"
name={name}
onChange={(e) => {
const fileName = get(e, "target.files[0].name", "");
fileProcess(e, (data: any) => {
onChange(data, fileName);
});
}}
accept={accept}
required={required}
disabled={disabled}
/>
{value !== "" && (
<IconButton
color="primary"
aria-label="upload picture"
component="span"
onClick={() => {
setShowSelector(false);
}}
disableRipple={false}
disableFocusRipple={false}
>
<CancelIcon />
</IconButton>
)}
{error !== "" && (
<React.Fragment>
<br />
<span className={classes.errorText}>{error}</span>
</React.Fragment>
)}
</div>
) : (
<div className={classes.fileReselect}>
<div className={classes.valueString}>{value}</div>
<IconButton
color="primary"
aria-label="upload picture"
component="span"
onClick={() => {
setShowSelector(true);
}}
disableRipple={false}
disableFocusRipple={false}
>
<AttachFileIcon />
</IconButton>
</div>
)}
</Grid>
</React.Fragment>
);

View File

@@ -0,0 +1,200 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { InputLabel, Switch, Tooltip } from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import { actionsTray, fieldBasic } from "../common/styleLibrary";
import HelpIcon from "@material-ui/icons/Help";
interface IFormSwitch {
label: string;
classes: any;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
value: string | boolean;
id: string;
name: string;
disabled?: boolean;
tooltip?: string;
index?: number;
checked: boolean;
}
const styles = (theme: Theme) =>
createStyles({
seeMore: {
marginTop: theme.spacing(3),
},
paper: {
display: "flex",
overflow: "auto",
flexDirection: "column",
paddingTop: 15,
boxShadow: "none",
},
addSideBar: {
width: "320px",
padding: "20px",
},
errorBlock: {
color: "red",
},
tableToolbar: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(0),
},
wrapCell: {
maxWidth: "200px",
whiteSpace: "normal",
wordWrap: "break-word",
},
minTableHeader: {
color: "#393939",
"& tr": {
"& th": {
fontWeight: "bold",
},
},
},
noFound: {
textAlign: "center",
padding: "10px 0",
},
tableContainer: {
maxHeight: 200,
},
stickyHeader: {
backgroundColor: "#fff",
},
actionsTitle: {
fontWeight: 600,
color: "#000",
fontSize: 16,
alignSelf: "center",
},
tableBlock: {
marginTop: 15,
},
filterField: {
width: 375,
fontWeight: 600,
"& .input": {
"&::placeholder": {
fontWeight: 600,
color: "#000",
},
},
},
wrapperContainer: {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
borderBottom: "#9c9c9c 1px solid",
paddingBottom: 14,
marginBottom: 20,
},
...actionsTray,
...fieldBasic,
});
const StyledSwitch = withStyles({
root: {
alignItems: "flex-start",
height: 18,
padding: "0 12px",
display: "flex",
position: "relative",
},
switchBase: {
color: "#fff",
padding: 0,
top: "initial",
"&$checked": {
color: "#fff",
},
"&$checked + $track": {
backgroundColor: "#000",
opacity: 1,
height: 15,
},
},
checked: {},
track: {
height: 15,
backgroundColor: "#000",
opacity: 1,
padding: 0,
marginTop: 1.5,
},
thumb: {
backgroundColor: "#fff",
border: "#000 1px solid",
boxShadow: "none",
width: 18,
height: 18,
padding: 0,
marginLeft: 10,
},
})(Switch);
const FormSwitchWrapper = ({
label,
onChange,
value,
id,
name,
checked = false,
disabled = false,
tooltip = "",
classes,
}: IFormSwitch) => {
return (
<React.Fragment>
<Grid item xs={12} className={`${classes.wrapperContainer}`}>
{label !== "" && (
<InputLabel htmlFor={id} className={classes.inputLabel}>
<span>{label}</span>
{tooltip !== "" && (
<div className={classes.tooltipContainer}>
<Tooltip title={tooltip} placement="top-start">
<HelpIcon className={classes.tooltip} />
</Tooltip>
</div>
)}
</InputLabel>
)}
<div className={classes.textBoxContainer}>
<StyledSwitch
checked={checked}
onChange={onChange}
color="primary"
name={name}
inputProps={{ "aria-label": "primary checkbox" }}
disabled={disabled}
disableRipple
disableFocusRipple
disableTouchRipple
value={value}
/>
</div>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(FormSwitchWrapper);

View File

@@ -71,13 +71,16 @@ const styles = (theme: Theme) =>
const inputStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
borderColor: "#393939",
borderRadius: 0,
"&::before": {
borderColor: "#9c9c9c",
},
},
input: {
padding: "11px 20px",
padding: "15px 5px 10px",
color: "#393939",
fontSize: 14,
fontSize: 13,
fontWeight: 600,
},
error: {
color: "#b53b4b",
@@ -160,7 +163,6 @@ const InputBoxWrapper = ({
<InputField
id={id}
name={name}
variant="outlined"
fullWidth
value={value}
disabled={disabled}
@@ -172,6 +174,10 @@ const InputBoxWrapper = ({
error={error !== ""}
helperText={error}
placeholder={placeholder}
InputLabelProps={{
shrink: true,
}}
className={classes.inputRebase}
/>
</div>
</Grid>

View File

@@ -50,26 +50,25 @@ const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
...tooltipHelper,
inputLabel: {
...fieldBasic.inputLabel,
width: 215,
},
});
const SelectStyled = withStyles((theme: Theme) =>
createStyles({
root: {
lineHeight: 1,
"label + &": {
marginTop: theme.spacing(3),
},
},
input: {
borderRadius: 0,
position: "relative",
color: "#393939",
fontSize: 14,
padding: "11px 20px",
border: "1px solid #c4c4c4",
fontSize: 13,
fontWeight: 600,
padding: "15px 20px 10px 10px",
borderBottom: "1px solid #9c9c9c",
display: "flex",
alignItems: "center",
"&:hover": {
borderColor: "#393939",
},
@@ -106,7 +105,7 @@ const SelectWrapper = ({
)}
</InputLabel>
)}
<FormControl variant="outlined" fullWidth>
<FormControl fullWidth>
<Select
id={id}
name={name}

View File

@@ -18,28 +18,24 @@
export const fieldBasic = {
inputLabel: {
fontWeight: 500,
fontWeight: 600,
marginRight: 10,
width: 160,
fontSize: 14,
color: "#393939",
textAlign: "right" as const,
display: "flex",
textOverflow: "ellipsis",
fontSize: 15,
color: "#000",
textAlign: "left" as const,
overflow: "hidden",
justifyContent: "flex-end",
"& span": {
display: "flex",
alignItems: "center",
},
display: "flex",
},
fieldLabelError: {
paddingBottom: 22,
},
fieldContainer: {
display: "flex",
alignItems: "center",
marginBottom: 10,
marginBottom: 20,
},
tooltipContainer: {
marginLeft: 5,
@@ -66,16 +62,80 @@ export const tooltipHelper = {
};
const checkBoxBasic = {
width: 16,
height: 16,
borderRadius: 3,
width: 14,
height: 14,
borderRadius: 2,
};
export const checkboxIcons = {
unCheckedIcon: { ...checkBoxBasic, border: "1px solid #d0d0d0" },
unCheckedIcon: { ...checkBoxBasic, border: "1px solid #c3c3c3" },
checkedIcon: {
...checkBoxBasic,
border: "1px solid #201763",
backgroundColor: "#201763",
border: "1px solid #081C42",
backgroundColor: "#081C42",
},
};
export const containerForHeader = (bottomSpacing: any) => ({
container: {
padding: "110px 33px 30px",
paddingBottom: bottomSpacing,
"& h6": {
color: "#777777",
fontSize: 14,
},
"& p": {
"& span:not(*[class*='smallUnit'])": {
fontSize: 16,
},
},
},
});
export const actionsTray = {
actionsTray: {
display: "flex",
justifyContent: "space-between",
"& button": {
flexGrow: 0,
marginLeft: 15,
},
},
};
export const searchField = {
searchField: {
flexGrow: 1,
marginRight: 30,
background: "#FFFFFF",
borderRadius: 5,
border: "#EAEDEE 1px solid",
display: "flex",
justifyContent: "center",
padding: "0 16px",
"& input": {
fontSize: 14,
color: "#000",
"&::placeholder": {
color: "#393939",
opacity: 1,
},
},
},
};
export const predefinedList = {
predefinedTitle: {
fontSize: 16,
fontWeight: 600,
color: "#000",
margin: "10px 0",
},
predefinedList: {
backgroundColor: "#eaeaea",
padding: "12px 10px",
color: "#393939",
fontSize: 12,
fontWeight: 600,
},
};

View File

@@ -16,7 +16,12 @@
import React from "react";
import { Dialog, DialogContent, DialogTitle } from "@material-ui/core";
import IconButton from "@material-ui/core/IconButton";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import {
createStyles,
makeStyles,
Theme,
withStyles,
} from "@material-ui/core/styles";
interface IModalProps {
classes: any;
@@ -31,16 +36,16 @@ const baseCloseLine = {
borderLeft: "2px solid #707070",
height: 33,
width: 1,
position: "absolute"
position: "absolute",
};
const styles = (theme: Theme) =>
createStyles({
dialogContainer: {
padding: "12px 26px 22px"
padding: "8px 15px 22px",
},
closeContainer: {
textAlign: "right"
textAlign: "right",
},
closeButton: {
width: 45,
@@ -48,40 +53,48 @@ const styles = (theme: Theme) =>
padding: 0,
backgroundColor: "initial",
"&:hover": {
backgroundColor: "initial"
backgroundColor: "initial",
},
"&:active": {
backgroundColor: "initial"
}
backgroundColor: "initial",
},
},
modalCloseIcon: {
fontSize: 35,
color: "#707070",
fontWeight: 300,
"&:hover": {
color: "#000"
}
color: "#000",
},
},
closeIcon: {
"&::before": {
...baseCloseLine,
transform: "rotate(45deg)"
transform: "rotate(45deg)",
},
"&::after": {
...baseCloseLine,
transform: "rotate(-45deg)"
transform: "rotate(-45deg)",
},
"&:hover::before, &:hover::after": {
borderColor: "#000"
borderColor: "#000",
},
width: 24,
height: 24,
display: "block",
position: "relative"
position: "relative",
},
titleClass: {
padding: "0px 24px 12px"
}
padding: "0px 50px 12px",
"& h2": {
fontWeight: 600,
color: "#000",
fontSize: 22,
},
},
modalContent: {
padding: "0 50px",
},
});
const ModalWrapper = ({
@@ -89,7 +102,7 @@ const ModalWrapper = ({
modalOpen,
title,
children,
classes
classes,
}: IModalProps) => {
return (
<Dialog
@@ -114,7 +127,9 @@ const ModalWrapper = ({
<DialogTitle id="alert-dialog-title" className={classes.titleClass}>
{title}
</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogContent className={classes.modalContent}>
{children}
</DialogContent>
</div>
</Dialog>
);

View File

@@ -0,0 +1,48 @@
import React from "react";
import Grid from "@material-ui/core/Grid";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
interface IPageHeader {
classes: any;
label: any;
}
const styles = (theme: Theme) =>
createStyles({
headerContainer: {
position: "absolute",
width: "100%",
height: 77,
display: "flex",
backgroundColor: "#fff",
borderBottom: "#E3E3E3 1px solid",
left: 0,
},
label: {
display: "flex",
justifyContent: "flex-start",
alignItems: "center",
},
labelStyle: {
color: "#000",
fontSize: 18,
fontWeight: 700,
marginLeft: 55,
marginTop: 8,
},
});
const PageHeader = ({ classes, label }: IPageHeader) => {
return (
<Grid container className={classes.headerContainer}>
<Grid item xs={12} className={classes.label}>
<Typography variant="h4" className={classes.labelStyle}>
{label}
</Typography>
</Grid>
</Grid>
);
};
export default withStyles(styles)(PageHeader);

View File

@@ -22,6 +22,8 @@ import DeleteIcon from "./TableActionIcons/DeleteIcon";
import DescriptionIcon from "./TableActionIcons/DescriptionIcon";
import CloudIcon from "./TableActionIcons/CloudIcon";
import ConsoleIcon from "./TableActionIcons/ConsoleIcon";
import GetAppIcon from "@material-ui/icons/GetApp";
import SvgIcon from "@material-ui/core/SvgIcon";
import { Link } from "react-router-dom";
interface IActionButton {
@@ -48,6 +50,10 @@ const defineIcon = (type: string, selected: boolean) => {
return <CloudIcon active={selected} />;
case "console":
return <ConsoleIcon active={selected} />;
case "download":
return (
<SvgIcon component={GetAppIcon} fontSize="small" color="primary" />
);
}
return null;

View File

@@ -7,12 +7,13 @@ const PencilIcon = ({ active = false }: IIcon) => {
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
viewBox="0 0 10 11.429"
>
<path
fill={active ? selected : unSelected}
d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"
></path>
d="M-43.375,11.429-48.35,8.664l-5.025,2.764V0h10Z"
transform="translate(53.375)"
/>
</svg>
);
};

View File

@@ -2,5 +2,5 @@ export interface IIcon {
active: boolean;
}
export const unSelected = "#adadad";
export const selected = "#201763";
export const unSelected = "#081C42";
export const selected = "#081C42";

View File

@@ -49,6 +49,7 @@ interface IColumns {
elementKey: string;
sortable?: boolean;
renderFunction?: (input: any) => any;
globalClass?: any;
}
interface IPaginatorConfig {
@@ -101,6 +102,8 @@ const styles = (theme: Theme) =>
flexDirection: "column",
padding: "19px 38px",
minHeight: "200px",
boxShadow: "none",
border: "#e7e7e7 1px solid",
},
minTableHeader: {
color: "#393939",
@@ -118,7 +121,8 @@ const styles = (theme: Theme) =>
},
rowSelected: {
...rowText,
color: "#201763",
color: "#081C42",
fontWeight: 600,
},
paginatorContainer: {
display: "flex",
@@ -161,7 +165,10 @@ const styles = (theme: Theme) =>
const titleColumnsMap = (columns: IColumns[]) => {
return columns.map((column: IColumns, index: number) => {
return (
<TableCell key={`tbCT-${column.elementKey}-${index}`}>
<TableCell
key={`tbCT-${column.elementKey}-${index}`}
className={column.globalClass}
>
{column.label}
</TableCell>
);
@@ -265,7 +272,7 @@ const TableWrapper = ({
</Grid>
</Grid>
)}
{records && records.length > 0 ? (
{records && !isLoading && records.length > 0 ? (
<Table size="small" stickyHeader={stickyHeader}>
<TableHead className={classes.minTableHeader}>
<TableRow>
@@ -279,7 +286,10 @@ const TableWrapper = ({
</TableCell>
)}
{titleColumnsMap(columns)}
{itemActions && itemActions.length > 0 && (
{((itemActions && itemActions.length > 1) ||
(itemActions &&
itemActions.length === 1 &&
itemActions[0].type !== "view")) && (
<TableCell
align="center"
className={classes.actionsContainer}
@@ -329,7 +339,10 @@ const TableWrapper = ({
</TableCell>
)}
{rowColumnsMap(columns, record, classes, isSelected)}
{itemActions && itemActions.length > 0 && (
{((itemActions && itemActions.length > 1) ||
(itemActions &&
itemActions.length === 1 &&
itemActions[0].type !== "view")) && (
<TableCell
align="center"
className={classes.actionsContainer}

View File

@@ -27,6 +27,12 @@ import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import { configurationElements } from "../utils";
import { IConfigurationElement } from "../types";
import EditConfiguration from "../CustomForms/EditConfiguration";
import {
actionsTray,
containerForHeader,
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import PageHeader from "../../Common/PageHeader/PageHeader";
interface IListConfiguration {
classes: any;
@@ -43,21 +49,12 @@ const styles = (theme: Theme) =>
keyName: {
marginLeft: 5,
},
actionsTray: {
textAlign: "right",
"& button": {
marginLeft: 10,
},
},
searchField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
},
iconText: {
lineHeight: "24px",
},
...searchField,
...actionsTray,
...containerForHeader(theme.spacing(4)),
});
const ConfigurationsList = ({ classes }: IListConfiguration) => {
@@ -103,47 +100,44 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
selectedConfiguration={selectedConfiguration}
/>
)}
<PageHeader label="Configurations List" />
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Configurations List</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
{error !== "" && <Grid container>{error}</Grid>}
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Filter"
className={classes.searchField}
id="search-resource"
label=""
onChange={(event) => {
setFilter(event.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "Configuration", elementKey: "configuration_id" },
]}
isLoading={false}
records={filteredRecords}
entityName="Configurations"
idField="configuration_id"
/>
<Grid item xs={12} className={classes.container}>
{error !== "" && <Grid container>{error}</Grid>}
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Filter"
className={classes.searchField}
id="search-resource"
label=""
onChange={(event) => {
setFilter(event.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "Configuration", elementKey: "configuration_id" },
]}
isLoading={false}
records={filteredRecords}
entityName="Configurations"
idField="configuration_id"
/>
</Grid>
</Grid>
</Grid>
</React.Fragment>

View File

@@ -51,8 +51,6 @@ import Permissions from "./Permissions/Permissions";
import Dashboard from "./Dashboard/Dashboard";
import Menu from "./Menu/Menu";
import api from "../../common/api";
import storage from "local-storage-fallback";
import NotFoundPage from "../NotFoundPage";
import ServiceAccounts from "./ServiceAccounts/ServiceAccounts";
import Users from "./Users/Users";
import Groups from "./Groups/Groups";
@@ -69,6 +67,9 @@ import { ISessionResponse } from "./types";
import { saveSessionResponse } from "./actions";
import TenantDetails from "./Tenants/TenantDetails/TenantDetails";
import { clearSession } from "../../common/utils";
import ObjectBrowser from "./ObjectBrowser/ObjectBrowser";
import ListObjects from "./Buckets/ListBuckets/Objects/ListObjects/ListObjects";
import License from "./License/License";
function Copyright() {
return (
@@ -83,7 +84,7 @@ function Copyright() {
);
}
const drawerWidth = 254;
const drawerWidth = 245;
const styles = (theme: Theme) =>
createStyles({
@@ -135,6 +136,9 @@ const styles = (theme: Theme) =>
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: "hidden",
background:
"transparent linear-gradient(90deg, #073052 0%, #081C42 100%) 0% 0% no-repeat padding-box",
boxShadow: "0px 3px 7px #00000014",
},
drawerPaperClose: {
overflowX: "hidden",
@@ -147,17 +151,15 @@ const styles = (theme: Theme) =>
width: theme.spacing(9),
},
},
appBarSpacer: {
height: "5px",
},
content: {
flexGrow: 1,
height: "100vh",
overflow: "auto",
position: "relative",
},
container: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4),
margin: 0,
},
paper: {
padding: theme.spacing(2),
@@ -253,6 +255,14 @@ const Console = ({
component: Buckets,
path: "/buckets/:bucketName",
},
{
component: ObjectBrowser,
path: "/object-browser",
},
{
component: ListObjects,
path: "/object-browser/:bucket?",
},
{
component: Watch,
path: "/watch",
@@ -313,6 +323,10 @@ const Console = ({
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName",
},
{
component: License,
path: "/license",
},
];
const allowedRoutes = routes.filter((route: any) => allowedPages[route.path]);
@@ -359,8 +373,7 @@ const Console = ({
)}
</div>
)}
<div className={classes.appBarSpacer} />
<Container maxWidth="lg" className={classes.container}>
<Container className={classes.container}>
<Router history={history}>
<Switch>
{allowedRoutes.map((route: any) => (

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
@@ -27,6 +27,11 @@ import { Usage } from "./types";
import api from "../../../common/api";
import { niceBytes } from "../../../common/utils";
import { LinearProgress } from "@material-ui/core";
import PageHeader from "../Common/PageHeader/PageHeader";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import AllBucketsIcon from "../../../icons/AllBucketsIcon";
import UsageIcon from "../../../icons/UsageIcon";
import EgressIcon from "../../../icons/EgressIcon";
const styles = (theme: Theme) =>
createStyles({
@@ -77,26 +82,24 @@ const styles = (theme: Theme) =>
height: "100vh",
overflow: "auto",
},
container: {
paddingBottom: theme.spacing(4),
"& h6": {
color: "#777777",
fontSize: 14,
},
"& p": {
"& span": {
fontSize: 16,
},
},
},
...containerForHeader(theme.spacing(4)),
paper: {
padding: theme.spacing(2),
display: "flex",
overflow: "auto",
flexDirection: "column",
border: "#eaedee 1px solid",
borderRadius: 5,
boxShadow: "none",
},
fixedHeight: {
minHeight: 240,
height: 165,
minWidth: 247,
marginRight: 20,
padding: "25px 28px",
"& svg": {
maxHeight: 18,
},
},
consumptionValue: {
color: "#000000",
@@ -107,6 +110,35 @@ const styles = (theme: Theme) =>
marginRight: 10,
color: "#777777",
},
notationContainer: {
display: "flex",
},
dashboardBG: {
width: 390,
height: 255,
zIndex: 500,
position: "absolute",
backgroundSize: "fill",
backgroundImage: "url(/images/BG_IllustrationDarker.svg)",
backgroundPosition: "right bottom",
right: 0,
bottom: 0,
backgroundRepeat: "no-repeat",
},
dashboardContainer: {
zIndex: 600,
position: "absolute",
},
elementTitle: {
fontWeight: 500,
color: "#777777",
fontSize: 14,
marginTop: -9,
},
smallUnit: {
fontSize: 20,
},
});
interface IDashboardProps {
@@ -142,7 +174,19 @@ const Dashboard = ({ classes }: IDashboardProps) => {
if (usage === undefined) {
return "0";
}
return niceBytes(usage);
const niceBytesUsage = niceBytes(usage).split(" ");
if (niceBytesUsage.length !== 2) {
return niceBytesUsage.join(" ");
}
return (
<React.Fragment>
{niceBytesUsage[0]}
<span className={classes.smallUnit}>{niceBytesUsage[1]}</span>
</React.Fragment>
);
};
const prettyNumber = (usage: number | undefined) => {
@@ -155,11 +199,10 @@ const Dashboard = ({ classes }: IDashboardProps) => {
return (
<React.Fragment>
<Grid container>
<PageHeader label="Dashboard" />
<div className={classes.dashboardBG} />
<Grid container className={classes.dashboardContainer}>
<Grid container spacing={3} className={classes.container}>
<Grid container>
<Typography variant="h2">MinIO Console</Typography>
</Grid>
{error !== "" && <Grid container>{error}</Grid>}
{loading ? (
<Grid item xs={12} md={12} lg={12}>
@@ -167,50 +210,53 @@ const Dashboard = ({ classes }: IDashboardProps) => {
</Grid>
) : (
<React.Fragment>
<Grid item xs={12} md={4} lg={4}>
<Grid item className={classes.notationContainer}>
<Paper className={fixedHeightPaper}>
<Grid container direction="row" alignItems="center">
<Grid item className={classes.icon}>
<ViewHeadlineIcon />
<AllBucketsIcon />
</Grid>
<Grid item>
<Typography variant="h6">Total Buckets</Typography>
<Typography className={classes.elementTitle}>
All buckets
</Typography>
</Grid>
</Grid>
<Typography className={classes.consumptionValue}>
{usage ? prettyNumber(usage.buckets) : 0}
</Typography>
</Paper>
</Grid>
<Grid item xs={12} md={4} lg={4}>
<Paper className={fixedHeightPaper}>
<Grid container direction="row" alignItems="center">
<Grid item className={classes.icon}>
<NetworkCheckIcon />
<UsageIcon />
</Grid>
<Grid item>
<Typography variant="h6"> Total Objects</Typography>
</Grid>
</Grid>
<Typography className={classes.consumptionValue}>
{usage ? prettyNumber(usage.objects) : 0}
</Typography>
</Paper>
</Grid>
<Grid item xs={12} md={4} lg={4}>
<Paper className={fixedHeightPaper}>
<Grid container direction="row" alignItems="center">
<Grid item className={classes.icon}>
<PieChartIcon />
</Grid>
<Grid item>
<Typography variant="h6">Usage</Typography>
<Typography className={classes.elementTitle}>
Usage
</Typography>
</Grid>
</Grid>
<Typography className={classes.consumptionValue}>
{usage ? prettyUsage(usage.usage + "") : 0}
</Typography>
</Paper>
<Paper className={fixedHeightPaper}>
<Grid container direction="row" alignItems="center">
<Grid item className={classes.icon}>
<EgressIcon />
</Grid>
<Grid item>
<Typography className={classes.elementTitle}>
{" "}
Total Objects
</Typography>
</Grid>
</Grid>
<Typography className={classes.consumptionValue}>
{usage ? prettyNumber(usage.objects) : 0}
</Typography>
</Paper>
</Grid>
</React.Fragment>
)}

View File

@@ -31,6 +31,12 @@ import AddGroup from "../Groups/AddGroup";
import DeleteGroup from "./DeleteGroup";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import SetPolicy from "../Policies/SetPolicy";
import {
actionsTray,
containerForHeader,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
interface IGroupsProps {
classes: any;
@@ -72,18 +78,9 @@ 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)),
});
const Groups = ({ classes }: IGroupsProps) => {
@@ -215,71 +212,68 @@ const Groups = ({ classes }: IGroupsProps) => {
}}
/>
)}
<PageHeader label={"Groups"} />
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Groups</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
{error !== "" ? <Grid container>{error}</Grid> : <React.Fragment />}
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Groups"
className={classes.searchField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
setFilter(e.target.value);
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setSelectedGroup(null);
setGroupOpen(true);
}}
>
Create Group
</Button>
</Grid>
<Grid item xs={12} className={classes.container}>
{error !== "" ? <Grid container>{error}</Grid> : <React.Fragment />}
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Groups"
className={classes.searchField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
setFilter(e.target.value);
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setSelectedGroup(null);
setGroupOpen(true);
}}
>
Create Group
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Name", elementKey: "" }]}
isLoading={loading}
records={filteredRecords}
entityName="Groups"
idField=""
paginatorConfig={{
rowsPerPageOptions: [5, 10, 25],
colSpan: 3,
count: totalRecords,
rowsPerPage: rowsPerPage,
page: page,
SelectProps: {
inputProps: { "aria-label": "rows per page" },
native: true,
},
onChangePage: handleChangePage,
onChangeRowsPerPage: handleChangeRowsPerPage,
ActionsComponent: MinTablePaginationActions,
}}
/>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Name", elementKey: "" }]}
isLoading={loading}
records={filteredRecords}
entityName="Groups"
idField=""
paginatorConfig={{
rowsPerPageOptions: [5, 10, 25],
colSpan: 3,
count: totalRecords,
rowsPerPage: rowsPerPage,
page: page,
SelectProps: {
inputProps: { "aria-label": "rows per page" },
native: true,
},
onChangePage: handleChangePage,
onChangeRowsPerPage: handleChangeRowsPerPage,
ActionsComponent: MinTablePaginationActions,
}}
/>
</Grid>
</Grid>
</Grid>
</React.Fragment>

View File

@@ -166,7 +166,7 @@ const UsersSelectors = ({
return (
<React.Fragment>
<Title>Members</Title>
<Title>Assign Users</Title>
{error !== "" ? <div>{error}</div> : <React.Fragment />}
<Grid item xs={12}>
<Paper className={classes.paper}>

View File

@@ -15,6 +15,8 @@ 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 PageHeader from "../Common/PageHeader/PageHeader";
const styles = (theme: Theme) =>
createStyles({
@@ -58,6 +60,7 @@ const styles = (theme: Theme) =>
lastElementWPadding: {
paddingRight: "78",
},
...containerForHeader(theme.spacing(4)),
});
interface IHeal {
@@ -195,130 +198,127 @@ const Heal = ({ classes }: IHeal) => {
}));
return (
<React.Fragment>
<PageHeader label="Heal" />
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Heal</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<FormControl variant="outlined">
<Select
id="bucket-name"
name="bucket-name"
value={bucketName}
onChange={(e) => {
setBucketName(e.target.value as string);
}}
className={classes.fieldContainer}
disabled={false}
>
<MenuItem value="" key={`select-bucket-name-default`}>
Select Bucket
</MenuItem>
{bucketNames.map((option) => (
<MenuItem
value={option.value}
key={`select-bucket-name-${option.label}`}
>
{option.label}
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<FormControl variant="outlined">
<Select
id="bucket-name"
name="bucket-name"
value={bucketName}
onChange={(e) => {
setBucketName(e.target.value as string);
}}
className={classes.fieldContainer}
disabled={false}
>
<MenuItem value="" key={`select-bucket-name-default`}>
Select Bucket
</MenuItem>
))}
</Select>
</FormControl>
<TextField
placeholder="Prefix"
className={classes.inputField}
id="prefix-resource"
label=""
disabled={false}
InputProps={{
disableUnderline: true,
}}
onChange={(e) => {
setPrefix(e.target.value);
{bucketNames.map((option) => (
<MenuItem
value={option.value}
key={`select-bucket-name-${option.label}`}
>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
placeholder="Prefix"
className={classes.inputField}
id="prefix-resource"
label=""
disabled={false}
InputProps={{
disableUnderline: true,
}}
onChange={(e) => {
setPrefix(e.target.value);
}}
/>
<Button
type="submit"
variant="contained"
color="primary"
disabled={start}
onClick={() => setStart(true)}
>
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>
<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",
},
}}
/>
<Button
type="submit"
variant="contained"
color="primary"
disabled={start}
onClick={() => setStart(true)}
>
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>
<br />
Size scanned: {hStatus.sizeScanned}
<br />
Objects healed: {hStatus.objectsHealed} / {hStatus.objectsScanned}
<br />
Healing time: {hStatus.healDuration}s
</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>
</React.Fragment>
);

View File

@@ -0,0 +1,335 @@
// 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 clsx from "clsx";
import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper";
import Button from "@material-ui/core/Button";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import CheckCircleIcon from "@material-ui/icons/CheckCircle";
import PageHeader from "../Common/PageHeader/PageHeader";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import { planDetails, planItems, planButtons } from "./utils";
const styles = (theme: Theme) =>
createStyles({
pageTitle: {
fontSize: 18,
marginBottom: 20,
},
paper: {
padding: "20px 52px 20px 28px",
},
tableContainer: {
marginLeft: 28,
},
detailsContainer: {
textAlign: "center",
paddingTop: 18,
paddingBottom: 12,
borderRadius: "3px 3px 0 0",
marginLeft: 8,
maxWidth: "calc(25% - 8px)",
},
detailsContainerBorder: {
border: "1px solid #e2e2e2",
borderBottom: 0,
},
detailsContainerBorderHighlighted: {
border: "1px solid #9a93ad",
borderBottom: 0,
},
detailsTitle: {
fontSize: 17,
fontWeight: 700,
marginBottom: 26,
},
detailsPrice: {
fontSize: 12,
fontWeight: 700,
marginBottom: 8,
},
detailsCapacityMax: {
minHeight: 28,
fontSize: 10,
fontWeight: 700,
marginBottom: 12,
padding: "0% 15%",
color: "#474747",
},
detailsCapacityMin: {
fontSize: 10,
},
itemContainer: {
height: 36,
borderTop: "1px solid #e5e5e5",
},
itemContainerDetail: {
height: 48,
borderTop: "1px solid #e5e5e5",
},
item: {
height: "100%",
borderLeft: "1px solid #e2e2e2",
borderRight: "1px solid #e2e2e2",
textAlign: "center",
fontSize: 10,
fontWeight: 700,
display: "flex",
alignItems: "center",
alignContent: "center",
marginLeft: 8,
maxWidth: "calc(25% - 8px)",
},
itemFirst: {
borderLeft: 0,
borderRight: 0,
},
itemHighlighted: {
borderLeft: "1px solid #9a93ad",
borderRight: "1px solid #9a93ad",
},
field: {
textAlign: "left",
fontWeight: 400,
},
checkIcon: {
height: 12,
color:
"transparent linear-gradient(90deg, #073052 0%, #081c42 100%) 0% 0% no-repeat padding-box",
},
buttonContainer: {
paddingTop: 8,
paddingBottom: 24,
height: "100%",
display: "flex",
justifyContent: "center",
borderRadius: "0 0 3px 3px",
border: "1px solid #e2e2e2",
borderTop: 0,
marginLeft: 8,
maxWidth: "calc(25% - 8px)",
},
buttonContainerBlank: {
border: 0,
},
buttonContainerHighlighted: {
border: "1px solid #9a93ad",
borderTop: 0,
},
button: {
textTransform: "none",
fontSize: 15,
fontWeight: 700,
},
...containerForHeader(theme.spacing(4)),
});
interface ILicense {
classes: any;
}
const License = ({ classes }: ILicense) => {
return (
<React.Fragment>
<PageHeader label="License" />
<Grid container>
<Grid item xs={12} className={classes.container}>
<Paper className={classes.paper}>
<Grid container>
<Grid item xs={12}>
<Typography
component="h2"
variant="h6"
className={classes.pageTitle}
>
Upgrade to commercial license
</Typography>
</Grid>
<Grid container item xs={12} className={classes.tableContainer}>
<Grid container item xs={12}>
<Grid item xs={3} className={classes.detailsContainer} />
{planDetails.map((details: any) => {
return (
<Grid
container
item
xs={3}
className={clsx(
classes.detailsContainer,
classes.detailsContainerBorder,
{
[classes.detailsContainerBorderHighlighted]:
details.title !== "Community",
}
)}
>
<Grid item xs={12} className={classes.detailsTitle}>
{details.title}
</Grid>
<Grid item xs={12} className={classes.detailsPrice}>
{details.price}
</Grid>
<Grid
item
xs={12}
className={classes.detailsCapacityMax}
>
{details.capacityMax || ""}
</Grid>
<Grid
item
xs={12}
className={classes.detailsCapacityMin}
>
{details.capacityMin}
</Grid>
</Grid>
);
})}
</Grid>
{planItems.map((item: any) => {
return (
<Grid
container
item
xs={12}
className={clsx(
classes.itemContainer,
item.communityDetail && classes.itemContainerDetail
)}
>
<Grid
item
xs={3}
className={clsx(
classes.item,
classes.field,
classes.itemFirst
)}
>
{item.field}
</Grid>
<Grid container item xs={3} className={classes.item}>
<Grid item xs={12}>
{item.community === "N/A" ? (
""
) : item.community === "Yes" ? (
<CheckCircleIcon className={classes.checkIcon} />
) : (
item.community
)}
</Grid>
{item.communityDetail !== undefined && (
<Grid item xs={12}>
{item.communityDetail}
</Grid>
)}
</Grid>
<Grid
container
item
xs={3}
className={clsx(classes.item, classes.itemHighlighted)}
>
<Grid item xs={12}>
{item.standard === "N/A" ? (
""
) : item.standard === "Yes" ? (
<CheckCircleIcon className={classes.checkIcon} />
) : (
item.standard
)}
</Grid>
{item.standardDetail !== undefined && (
<Grid item xs={12}>
{item.standardDetail}
</Grid>
)}
</Grid>
<Grid
container
item
xs={3}
className={clsx(classes.item, classes.itemHighlighted)}
>
<Grid item xs={12}>
{item.enterprise === "N/A" ? (
""
) : item.enterprise === "Yes" ? (
<CheckCircleIcon className={classes.checkIcon} />
) : (
item.enterprise
)}
</Grid>
{item.enterpriseDetail !== undefined && (
<Grid item xs={12}>
{item.enterpriseDetail}
</Grid>
)}
</Grid>
</Grid>
);
})}
<Grid container item xs={12}>
<Grid
item
xs={3}
className={clsx(
classes.buttonContainer,
classes.buttonContainerBlank
)}
/>
{planButtons.map((button: any) => {
return (
<Grid
container
item
xs={3}
className={clsx(classes.buttonContainer, {
[classes.buttonContainerHighlighted]:
button.text === "Subscribe",
})}
>
<Button
variant={
button.text === "Subscribe"
? "contained"
: "outlined"
}
color="primary"
className={classes.button}
target="_blank"
href={button.link}
>
{button.text}
</Button>
</Grid>
);
})}
</Grid>
</Grid>
</Grid>
</Paper>
</Grid>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(License);

View File

@@ -0,0 +1,119 @@
// 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 planDetails = [
{
title: "Community",
price: "Free",
capacityMin: "(No minimum)",
},
{
title: "Standard",
price: "$10/TB/month",
capacityMax: "Up to 10PB. No additional charges for capacity over 10PB",
capacityMin: "(25TB minimum)",
},
{
title: "Enterprise",
price: "$20/TB/month",
capacityMax: "Up to 5PB. No additional charges for capacity over 5PB",
capacityMin: "(100TB minimum)",
},
];
export const planItems = [
{
field: "License",
community: "100% Open Source",
communityDetail: "Apache License v2, GNU AGPL v3",
standard: "Dual License",
standardDetail: "Commercial + Open Source",
enterprise: "Dual License",
enterpriseDetail: "Commercial + Open Source",
},
{
field: "Software Release",
community: "Update to latest",
standard: "1 Year Long Term Support",
enterprise: "5 Years Long Term Support",
},
{
field: "SLA",
community: "No SLA",
standard: "<24 hours",
enterprise: "<1 hour",
},
{
field: "Support",
community: "Community:",
communityDetail: "Public Slack Channel + Github Issues",
standard: "24x7 L4 direct engineering",
standardDetail: "Support via SUBNET",
enterprise: "24x7 L4 direct engineering",
enterpriseDetail: "Support via SUBNET",
},
{
field: "Security Updates & Critical Bugs",
community: "Self Update",
standard: "Guided Update",
enterprise: "Guided Update",
},
{
field: "Panic Button",
community: "N/A",
standard: "1 per year",
enterprise: "Unlimited",
},
{
field: "Annual Architecture Review",
community: "N/A",
standard: "Yes",
enterprise: "Yes",
},
{
field: "Annual Performance Review",
community: "N/A",
standard: "Yes",
enterprise: "Yes",
},
{
field: "Indemnification",
community: "N/A",
standard: "N/A",
enterprise: "Yes",
},
{
field: "Security + Policy Review",
community: "N/A",
standard: "N/A",
enterprise: "Yes",
},
];
export const planButtons = [
{
text: "Slack Community",
link: "https://slack.min.io",
},
{
text: "Subscribe",
link: "https://min.io/pricing",
},
{
text: "Subscribe",
link: "https://min.io/pricing",
},
];

View File

@@ -23,6 +23,9 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { timeFromDate } from "../../../common/utils";
import { isNullOrUndefined } from "util";
import { wsProtocol } from "../../../utils/wsUtils";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import { Grid } from "@material-ui/core";
import PageHeader from "../Common/PageHeader/PageHeader";
const styles = (theme: Theme) =>
createStyles({
@@ -54,6 +57,7 @@ const styles = (theme: Theme) =>
ansidefault: {
color: "black",
},
...containerForHeader(theme.spacing(4)),
});
interface ILogs {
@@ -243,16 +247,20 @@ const Logs = ({
};
return (
<div>
<h1>Logs</h1>
<div className={classes.logList}>
<ul>
{messages.map((m) => {
return renderLog(m);
})}
</ul>
</div>
</div>
<React.Fragment>
<PageHeader label="Logs" />
<Grid container>
<Grid className={classes.container} item xs={12}>
<div className={classes.logList}>
<ul>
{messages.map((m) => {
return renderLog(m);
})}
</ul>
</div>
</Grid>
</Grid>
</React.Fragment>
);
};

View File

@@ -21,6 +21,8 @@ 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";
@@ -51,48 +53,61 @@ import {
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";
const styles = (theme: Theme) =>
createStyles({
logo: {
paddingTop: "42px",
marginBottom: "20px",
textAlign: "center",
paddingTop: 25,
marginBottom: 30,
paddingLeft: 45,
"& img": {
width: "120px",
width: 120,
},
},
menuList: {
"& .active": {
borderTopLeftRadius: "3px",
borderBottomLeftRadius: "3px",
borderTopLeftRadius: 2,
borderBottomLeftRadius: 2,
color: "#fff",
background:
"transparent linear-gradient(90deg, #362585 0%, #362585 7%, #281B6F 39%, #1F1661 100%) 0% 0% no-repeat padding-box;",
boxShadow: "4px 4px 4px #A5A5A512",
fontWeight: 700,
backgroundColor: "rgba(255, 255, 255, .18)",
"& .MuiSvgIcon-root": {
color: "white",
},
"& .MuiTypography-root": {
color: "#fff",
fontWeight: 700,
},
},
paddingLeft: "30px",
"& .MuiSvgIcon-root": {
fontSize: 16,
color: "#362585",
color: "rgba(255, 255, 255, 0.8)",
maxWidth: 14,
},
"& .MuiListItemIcon-root": {
minWidth: "25px",
minWidth: 25,
},
"& .MuiTypography-root": {
fontSize: "12px",
color: "#2e2e2e",
fontSize: 12,
color: "rgba(255, 255, 255, 0.8)",
},
"& .MuiListItem-gutters": {
paddingRight: 0,
fontWeight: 300,
},
"& .MuiListItem-root": {
padding: "2px 0 2px 16px",
marginBottom: 8,
marginLeft: 30,
width: "calc(100% - 30px)",
},
"& .MuiCollapse-container .MuiCollapse-wrapper .MuiCollapse-wrapperInner .MuiDivider-root": {
backgroundColor: "rgba(112,112,112,0.5)",
marginBottom: 12,
height: 1,
},
},
extraMargin: {
@@ -101,31 +116,34 @@ const styles = (theme: Theme) =>
},
},
groupTitle: {
color: "#220c7c",
color: "#fff",
fontSize: 10,
textTransform: "uppercase",
fontWeight: 700,
marginBottom: 3,
cursor: "pointer",
userSelect: "none",
display: "flex",
justifyContent: "space-between",
},
subTitleMenu: {
fontWeight: 700,
marginLeft: 10,
"&.MuiTypography-root": {
fontSize: 13,
color: "#220c7c",
color: "#fff",
},
},
selectorArrow: {
marginLeft: 3,
marginRight: 20,
marginTop: 1,
display: "inline-block",
width: 0,
height: 0,
borderStyle: "solid",
borderWidth: "3px 2.5px 0 2.5px",
borderColor: "#220C7C transparent transparent transparent",
borderWidth: "4px 4px 0 4px",
borderColor:
"rgba(255, 255, 255, .29) transparent transparent transparent",
transform: "rotateZ(0deg)",
transitionDuration: "0.2s",
},
@@ -185,9 +203,9 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
group: "User",
type: "item",
component: NavLink,
to: "/buckets",
name: "Buckets",
icon: <BucketsIcon />,
to: "/object-browser",
name: "Object Browser",
icon: <DescriptionIcon />,
},
{
group: "User",
@@ -197,6 +215,14 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
name: "Service Accounts",
icon: <ServiceAccountsIcon />,
},
{
group: "Admin",
type: "item",
component: NavLink,
to: "/buckets",
name: "Buckets",
icon: <BucketsIcon />,
},
{
group: "Admin",
type: "item",
@@ -226,8 +252,8 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
type: "item",
component: NavLink,
to: "/logs",
name: "Console Logs",
icon: <WebAssetIcon />,
name: "Logs",
icon: <ConsoleIcon />,
},
{
group: "Tools",
@@ -251,13 +277,7 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
component: NavLink,
to: "/heal",
name: "Heal",
icon: <HealingIcon />,
},
{
group: "Admin",
type: "title",
name: "Configurations",
component: Typography,
icon: <HealIcon />,
},
{
group: "Admin",
@@ -266,7 +286,6 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
to: "/notification-endpoints",
name: "Lambda Notifications",
icon: <LambdaNotificationsIcon />,
extraMargin: true,
},
{
group: "Admin",
@@ -275,7 +294,6 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
to: "/configurations-list",
name: "Configurations List",
icon: <ConfigurationsListIcon />,
extraMargin: true,
},
{
group: "Operator",
@@ -301,6 +319,14 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
name: "Warp",
icon: <WarpIcon />,
},
{
group: "License",
type: "item",
component: NavLink,
to: "/license",
name: "License",
icon: <LicenseIcon />,
},
];
const allowedPages = pages.reduce((result: any, item: any, index: any) => {
@@ -351,7 +377,7 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
}
}}
>
{groupMember.label}
<span>{groupMember.label}</span>
{groupMember.collapsible && (
<span
className={`${classes.selectorArrow} ${
@@ -413,7 +439,7 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
<ListItem button onClick={logout}>
<ListItemIcon>
<ExitToApp />
<LogoutIcon />
</ListItemIcon>
<ListItemText primary="Logout" />
</ListItem>

View File

@@ -20,4 +20,5 @@ export const menuGroups = [
{ label: "Admin", group: "Admin", collapsible: true },
{ label: "Tools", group: "Tools", collapsible: true },
{ label: "Operator", group: "Operator", collapsible: true },
{ label: "", group: "License", collapsible: false },
];

View File

@@ -27,7 +27,7 @@ import { MinTablePaginationActions } from "../../../common/MinTablePaginationAct
import {
NotificationEndpointItem,
NotificationEndpointsList,
TransformedEndpointItem
TransformedEndpointItem,
} from "./types";
import { notificationTransform } from "./utils";
import { CreateIcon } from "../../../icons";
@@ -35,6 +35,12 @@ import api from "../../../common/api";
import FiberManualRecordIcon from "@material-ui/icons/FiberManualRecord";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import AddNotificationEndpoint from "./AddNotificationEndpoint";
import {
actionsTray,
containerForHeader,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
interface IListNotificationEndpoints {
classes: any;
@@ -43,29 +49,20 @@ interface IListNotificationEndpoints {
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
color: "red",
},
strongText: {
fontWeight: 700
fontWeight: 700,
},
keyName: {
marginLeft: 5
},
actionsTray: {
textAlign: "right",
"& button": {
marginLeft: 10
}
},
searchField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012"
marginLeft: 5,
},
iconText: {
lineHeight: "24px"
}
lineHeight: "24px",
},
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
});
const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
@@ -96,7 +93,7 @@ const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
setError("");
setIsLoading(false);
})
.catch(err => {
.catch((err) => {
setError(err);
setIsLoading(false);
});
@@ -115,8 +112,8 @@ const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
type: "delete",
onClick: (row: any) => {
//confirmDeleteBucket(row.name);
}
}
},
},
];
const filteredRecords = records.filter((b: TransformedEndpointItem) => {
@@ -136,7 +133,7 @@ const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
<div
style={{
display: "flex",
alignItems: "center"
alignItems: "center",
}}
>
<FiberManualRecordIcon
@@ -158,83 +155,80 @@ const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
}}
/>
)}
<PageHeader label="Lambda Notification Targets" />
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Lambda Notification Targets</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
{error !== "" && <Grid container>{error}</Grid>}
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Filter"
className={classes.searchField}
id="search-resource"
label=""
onChange={event => {
setFilter(event.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
)
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddScreenOpen(true);
}}
>
Add Notification Target
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "Service", elementKey: "service_name" },
{
label: "Status",
elementKey: "status",
renderFunction: statusDisplay
}
]}
isLoading={isLoading}
records={filteredRecords}
entityName="Notification Endpoints"
idField="service_name"
paginatorConfig={{
rowsPerPageOptions: [5, 10, 25],
colSpan: 3,
count: totalRecords,
rowsPerPage: rowsPerPage,
page: page,
SelectProps: {
inputProps: { "aria-label": "rows per page" },
native: true
},
onChangePage: (event: unknown, newPage: number) => {
setPage(newPage);
},
onChangeRowsPerPage: (
event: React.ChangeEvent<HTMLInputElement>
) => {
const rPP = parseInt(event.target.value, 10);
setRowsPerPage(rPP);
},
ActionsComponent: MinTablePaginationActions
}}
/>
<Grid item xs={12} className={classes.container}>
{error !== "" && <Grid container>{error}</Grid>}
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Filter"
className={classes.searchField}
id="search-resource"
label=""
onChange={(event) => {
setFilter(event.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddScreenOpen(true);
}}
>
Add Notification Target
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "Service", elementKey: "service_name" },
{
label: "Status",
elementKey: "status",
renderFunction: statusDisplay,
},
]}
isLoading={isLoading}
records={filteredRecords}
entityName="Notification Endpoints"
idField="service_name"
paginatorConfig={{
rowsPerPageOptions: [5, 10, 25],
colSpan: 3,
count: totalRecords,
rowsPerPage: rowsPerPage,
page: page,
SelectProps: {
inputProps: { "aria-label": "rows per page" },
native: true,
},
onChangePage: (event: unknown, newPage: number) => {
setPage(newPage);
},
onChangeRowsPerPage: (
event: React.ChangeEvent<HTMLInputElement>
) => {
const rPP = parseInt(event.target.value, 10);
setRowsPerPage(rPP);
},
ActionsComponent: MinTablePaginationActions,
}}
/>
</Grid>
</Grid>
</Grid>
</React.Fragment>

View File

@@ -0,0 +1,234 @@
// 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, useEffect } from "react";
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";
import { Button } from "@material-ui/core";
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,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
const styles = (theme: Theme) =>
createStyles({
seeMore: {
marginTop: theme.spacing(3),
},
paper: {
display: "flex",
overflow: "auto",
flexDirection: "column",
},
addSideBar: {
width: "320px",
padding: "20px",
},
errorBlock: {
color: "red",
},
tableToolbar: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(0),
},
minTableHeader: {
color: "#393939",
"& tr": {
"& th": {
fontWeight: "bold",
},
},
},
usedSpaceCol: {
width: 150,
},
subTitleLabel: {
alignItems: "center",
display: "flex",
},
...actionsTray,
...searchField,
});
interface IBrowseBucketsProps {
classes: any;
}
const BrowseBuckets = ({ classes }: IBrowseBucketsProps) => {
const [loading, setLoading] = useState<boolean>(true);
const [page, setPage] = useState<number>(0);
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
const [error, setError] = useState<string>("");
const [records, setRecords] = useState<Bucket[]>([]);
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
const [filterBuckets, setFilterBuckets] = useState<string>("");
const offset = page * rowsPerPage;
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);
setError("");
// if we get 0 results, and page > 0 , go down 1 page
if (
(res.buckets === undefined ||
res.buckets == null ||
res.buckets.length === 0) &&
page > 0
) {
const newPage = page - 1;
setPage(newPage);
setLoading(true);
}
})
.catch((err: any) => {
setLoading(false);
setError(err);
});
}
}, [loading]);
const closeAddModalAndRefresh = () => {
setAddScreenOpen(false);
setLoading(false);
};
const filteredRecords = records
.filter((b: Bucket) => {
if (filterBuckets === "") {
return true;
}
return b.name.indexOf(filterBuckets) >= 0;
})
.slice(offset, offset + rowsPerPage);
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
const rPP = parseInt(event.target.value, 10);
setPage(0);
setRowsPerPage(rPP);
};
return (
<React.Fragment>
{addScreenOpen && (
<AddBucket
open={addScreenOpen}
closeModalAndRefresh={closeAddModalAndRefresh}
/>
)}
<Grid container>
<Grid item xs={2} className={classes.subTitleLabel}>
<Typography variant="h6">Buckets</Typography>
</Grid>
<Grid item xs={10} className={classes.actionsTray}>
<TextField
placeholder="Search Buckets"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setFilterBuckets(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddScreenOpen(true);
}}
>
Add Bucket
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
{error !== "" && <span className={classes.errorBlock}>{error}</span>}
<TableWrapper
itemActions={[
{ type: "view", to: `/object-browser`, sendOnlyId: true },
]}
columns={[
{ label: "Name", elementKey: "name" },
{
label: "Used Space",
elementKey: "size",
renderFunction: niceBytes,
globalClass: classes.usedSpaceCol,
},
]}
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,
}}
/>
</Grid>
</Grid>
</React.Fragment>
);
};
export default 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Grid, Typography } from "@material-ui/core";
import BrowseBuckets from "./BrowseBuckets";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
interface IObjectBrowserProps {
match: any;
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
watchList: {
background: "white",
maxHeight: "400",
overflow: "auto",
"& ul": {
margin: "4",
padding: "0",
},
"& ul li": {
listStyle: "none",
margin: "0",
padding: "0",
borderBottom: "1px solid #dedede",
},
},
actionsTray: {
textAlign: "right",
"& button": {
marginLeft: 10,
},
},
inputField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
marginLeft: 10,
boxShadow: "0px 3px 6px #00000012",
},
fieldContainer: {
background: "#FFFFFF",
padding: 0,
borderRadius: 5,
marginLeft: 10,
textAlign: "left",
minWidth: "206",
boxShadow: "0px 3px 6px #00000012",
},
lastElementWPadding: {
paddingRight: "78",
},
...containerForHeader(theme.spacing(4)),
});
const ObjectBrowser = ({ match, classes }: IObjectBrowserProps) => {
const pathIn = get(match, "path", "");
return (
<React.Fragment>
<PageHeader label={"Object Browser"} />
<Grid container>
<Grid item xs={12} className={classes.container}>
{pathIn === "/object-browser" && <BrowseBuckets />}
</Grid>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(ObjectBrowser);

View File

@@ -41,6 +41,19 @@ const styles = (theme: Theme) =>
},
codeMirror: {
fontSize: 14,
"& .CodeMirror": {
color: "#fff",
backgroundColor: "#081C42",
},
"& .CodeMirror-gutter": {
backgroundColor: "#081C4280",
},
"& .CodeMirror-linenumber": {
color: "#000",
fontSize: 10,
height: 20,
lineHeight: "20px",
},
},
buttonContainer: {
textAlign: "right",

View File

@@ -30,6 +30,12 @@ import AddPolicy from "./AddPolicy";
import DeletePolicy from "./DeletePolicy";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import api from "../../../common/api";
import {
actionsTray,
containerForHeader,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
const styles = (theme: Theme) =>
createStyles({
@@ -61,18 +67,9 @@ 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)),
});
interface IPoliciesProps {
@@ -222,70 +219,67 @@ const Policies = ({ classes }: IPoliciesProps) => {
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
/>
)}
<PageHeader label="IAM Policies" />
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">IAM Policies</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Policies"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setPage(0);
setFilterPolicies(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddScreenOpen(true);
setPolicyEdit(null);
}}
>
Create Policy
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loading}
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 item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Policies"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setPage(0);
setFilterPolicies(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddScreenOpen(true);
setPolicyEdit(null);
}}
>
Create Policy
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loading}
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>
</Grid>
</React.Fragment>

View File

@@ -31,6 +31,12 @@ import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import { stringSort } from "../../../utils/sortFunctions";
import PageHeader from "../Common/PageHeader/PageHeader";
import {
actionsTray,
containerForHeader,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
const styles = (theme: Theme) =>
createStyles({
@@ -73,18 +79,9 @@ const styles = (theme: Theme) =>
iconRoot: {
textAlign: "center",
},
actionsTray: {
textAlign: "right",
"& button": {
marginLeft: 10,
},
},
searchField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
},
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
});
interface IServiceAccountsProps {
@@ -225,81 +222,78 @@ const ServiceAccounts = ({ classes }: IServiceAccountsProps) => {
entity="Service Account"
/>
)}
<PageHeader label="Service Accounts" />
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Service Accounts</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Service Accounts"
className={classes.searchField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
setFilter(e.target.value);
setPage(0);
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddScreenOpen(true);
setSelectedServiceAccount(null);
}}
>
Create service account
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
{error !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Service Accounts"
className={classes.searchField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
setFilter(e.target.value);
setPage(0);
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddScreenOpen(true);
setSelectedServiceAccount(null);
}}
>
{error}
</Typography>
Create service account
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
{error !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{error}
</Typography>
</Grid>
)}
<Grid item xs={12}>
<TableWrapper
isLoading={loading}
records={paginatedRecords}
entityName={"Service Accounts"}
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 item xs={12}>
<TableWrapper
isLoading={loading}
records={paginatedRecords}
entityName={"Service Accounts"}
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>
</React.Fragment>

View File

@@ -29,7 +29,7 @@ import TableRow from "@material-ui/core/TableRow";
import api from "../../../../common/api";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
import CheckboxWrapper from "../../Common/FormComponents/CheckboxWrapper/CheckboxWrapper";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import {
calculateDistribution,
@@ -40,7 +40,7 @@ import {
} from "../../../../common/utils";
import {
commonFormValidation,
IValidation
IValidation,
} from "../../../../utils/validationFunctions";
import GenericWizard from "../../Common/GenericWizard/GenericWizard";
import { IWizardElement } from "../../Common/GenericWizard/types";
@@ -65,19 +65,19 @@ interface IAddTenantProps {
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
color: "red",
},
buttonContainer: {
textAlign: "right"
textAlign: "right",
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const
justifyContent: "flex-start" as const,
},
sizeFactorContainer: {
marginLeft: 8,
alignSelf: "flex-start" as const
alignSelf: "flex-start" as const,
},
headerElement: {
position: "sticky",
@@ -85,16 +85,16 @@ const styles = (theme: Theme) =>
paddingTop: 5,
marginBottom: 10,
backgroundColor: "#fff",
zIndex: 500
zIndex: 500,
},
tableTitle: {
fontWeight: 700,
width: "30%"
width: "30%",
},
zoneError: {
color: "#dc1f2e",
fontSize: "0.75rem",
paddingLeft: 120
paddingLeft: 120,
},
error: {
color: "#dc1f2e",
@@ -111,7 +111,7 @@ interface Opts {
const AddTenant = ({
open,
closeModalAndRefresh,
classes
classes,
}: IAddTenantProps) => {
// Fields
const [addSending, setAddSending] = useState<boolean>(false);
@@ -177,6 +177,9 @@ const AddTenant = ({
const [nameTenantValid, setNameTenantValid] = useState<boolean>(false);
const [configValid, setConfigValid] = useState<boolean>(false);
const [configureValid, setConfigureValid] = useState<boolean>(false);
const [idpValid, setIdpValid] = useState<boolean>(false);
const [securityValid, setSecurityValid] = useState<boolean>(false);
const [encryptionValid, setEncryptionValid] = useState<boolean>(false);
// Custom Elements
const [customDockerhub, setCustomDockerhub] = useState<boolean>(false);
@@ -197,6 +200,20 @@ const AddTenant = ({
gemaltoCA: "",
});
// Files States
const [tlsKeyVal, setTlsKeyVal] = useState<string>("");
const [tlsCertVal, setTlsCertVal] = useState<string>("");
const [consoleKeyVal, setConsoleKeyVal] = useState<string>("");
const [consoleCertVal, setConsoleCertVal] = useState<string>("");
const [serverKeyVal, setServerKeyVal] = useState<string>("");
const [serverCertVal, setServerCertVal] = useState<string>("");
const [clientKeyVal, setClientKeyVal] = useState<string>("");
const [clientCertVal, setClientCertVal] = useState<string>("");
const [vaultKeyVal, setVaultKeyVal] = useState<string>("");
const [vaultCertVal, setVaultCertVal] = useState<string>("");
const [vaultCAVal, setVaultCAVal] = useState<string>("");
const [gemaltoCAVal, setGemaltoCAVal] = useState<string>("");
/*Debounce functions*/
// Storage Quotas
@@ -212,7 +229,9 @@ const AddTenant = ({
const elements = get(res, "elements", []);
const newStorage = elements.map((storageClass: any) => {
const name = get(storageClass, "name", "").split(".")[0];
const name = get(storageClass, "name", "").split(
".storageclass.storage.k8s.io/requests.storage"
)[0];
return { label: name, value: name };
});
@@ -265,16 +284,6 @@ const AddTenant = ({
);
setDistribution(distrCalculate);
/*const errorDistribution = get(distrCalculate, "error", "");
if (errorDistribution === "") {
const disksPerServer = get(distrCalculate, "disks", 0);
const totalNodes = get(distrCalculate, "nodes", 0);
const sizePerVolume = get(distrCalculate, "pvSize", 0);
getParity(totalNodes, disksPerServer, sizePerVolume);
}*/
};
/*Calculate Allocation End*/
@@ -301,8 +310,8 @@ const AddTenant = ({
setNameTenantValid(
!("tenant-name" in commonValidation) &&
!("namespace" in commonValidation) &&
storageClasses.length > 0
!("namespace" in commonValidation) &&
storageClasses.length > 0
);
setValidationErrors(commonValidation);
@@ -337,9 +346,9 @@ const AddTenant = ({
setConfigValid(
!("nodes" in commonValidation) &&
!("volume_size" in commonValidation) &&
!("memory_per_node" in commonValidation) &&
distribution.error === ""
!("volume_size" in commonValidation) &&
!("memory_per_node" in commonValidation) &&
distribution.error === ""
);
setValidationErrors(commonValidation);
@@ -356,8 +365,16 @@ const AddTenant = ({
required: true,
value: imageName,
pattern: /^((.*?)\/(.*?):(.+))$/,
customPatternMessage: "Format must be of form: 'minio/minio:VERSION'"
}
customPatternMessage: "Format must be of form: 'minio/minio:VERSION'",
},
{
fieldKey: "consoleImage",
required: true,
value: consoleImage,
pattern: /^((.*?)\/(.*?):(.+))$/,
customPatternMessage:
"Format must be of form: 'minio/console:VERSION'",
},
];
}
@@ -366,7 +383,302 @@ const AddTenant = ({
setConfigureValid(Object.keys(commonVal).length === 0);
setValidationErrors(commonVal);
}, [customDockerhub, imageName]);
}, [customDockerhub, imageName, consoleImage]);
useEffect(() => {
let customIDPValidation: IValidation[] = [];
if (idpSelection === "none") {
setIdpValid(true);
setValidationErrors({});
return;
}
if (idpSelection === "OpenID") {
customIDPValidation = [
...customIDPValidation,
{
fieldKey: "openID_URL",
required: true,
value: openIDURL,
},
{
fieldKey: "openID_clientID",
required: true,
value: openIDClientID,
},
{
fieldKey: "openID_secretID",
required: true,
value: openIDSecretID,
},
];
}
if (idpSelection === "AD") {
customIDPValidation = [
...customIDPValidation,
{
fieldKey: "AD_URL",
required: true,
value: ADURL,
},
{
fieldKey: "ad_userNameFilter",
required: true,
value: ADUserNameFilter,
},
{
fieldKey: "ad_groupBaseDN",
required: true,
value: ADGroupBaseDN,
},
{
fieldKey: "ad_groupSearchFilter",
required: true,
value: ADGroupSearchFilter,
},
{
fieldKey: "ad_nameAttribute",
required: true,
value: ADNameAttribute,
},
];
}
const commonVal = commonFormValidation(customIDPValidation);
setIdpValid(Object.keys(commonVal).length === 0);
setValidationErrors(commonVal);
}, [
idpSelection,
openIDURL,
openIDClientID,
openIDSecretID,
ADURL,
ADUserNameFilter,
ADGroupBaseDN,
ADGroupSearchFilter,
ADNameAttribute,
]);
useEffect(() => {
let securityValidation: IValidation[] = [];
if (!enableTLS) {
setSecurityValid(true);
setValidationErrors({});
return;
}
if (tlsType === "autocert") {
setSecurityValid(true);
setValidationErrors({});
return;
}
securityValidation = [
...securityValidation,
{
fieldKey: "tlsKey",
required: true,
value: filesBase64.tlsKey,
},
{
fieldKey: "tlsCert",
required: true,
value: filesBase64.tlsCert,
},
{
fieldKey: "consoleKey",
required: true,
value: filesBase64.consoleKey,
},
{
fieldKey: "consoleCert",
required: true,
value: filesBase64.consoleCert,
},
];
const commonVal = commonFormValidation(securityValidation);
setSecurityValid(Object.keys(commonVal).length === 0);
setValidationErrors(commonVal);
}, [enableTLS, filesBase64, tlsType]);
useEffect(() => {
let encryptionValidation: IValidation[] = [];
if (enableEncryption) {
if (enableTLS && tlsType !== "autocert") {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "serverKey",
required: true,
value: filesBase64.serverKey,
},
{
fieldKey: "serverCert",
required: true,
value: filesBase64.serverCert,
},
{
fieldKey: "clientKey",
required: true,
value: filesBase64.clientKey,
},
{
fieldKey: "clientCert",
required: true,
value: filesBase64.clientCert,
},
];
}
if (encryptionType === "vault") {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "vault_endpoint",
required: true,
value: vaultEndpoint,
},
{
fieldKey: "vault_engine",
required: true,
value: vaultEngine,
},
{
fieldKey: "vault_id",
required: true,
value: vaultId,
},
{
fieldKey: "vault_secret",
required: true,
value: vaultSecret,
},
{
fieldKey: "vault_key",
required: true,
value: filesBase64.vaultKey,
},
{
fieldKey: "vault_cert",
required: true,
value: filesBase64.vaultCert,
},
{
fieldKey: "vault_ca",
required: true,
value: filesBase64.vaultCA,
},
{
fieldKey: "vault_ping",
required: true,
value: vaultPing,
customValidation: parseInt(vaultPing) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
{
fieldKey: "vault_retry",
required: true,
value: vaultRetry,
customValidation: parseInt(vaultRetry) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
];
}
if (encryptionType === "aws") {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "aws_endpoint",
required: true,
value: awsEndpoint,
},
{
fieldKey: "aws_region",
required: true,
value: awsRegion,
},
{
fieldKey: "aws_accessKey",
required: true,
value: awsAccessKey,
},
{
fieldKey: "aws_secretKey",
required: true,
value: awsSecretKey,
},
];
}
if (encryptionType === "gemalto") {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "gemalto_endpoint",
required: true,
value: gemaltoEndpoint,
},
{
fieldKey: "gemalto_token",
required: true,
value: gemaltoToken,
},
{
fieldKey: "gemalto_domain",
required: true,
value: gemaltoDomain,
},
{
fieldKey: "gemalto_retry",
required: true,
value: gemaltoRetry,
customValidation: parseInt(gemaltoRetry) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
{
fieldKey: "gemalto_ca",
required: true,
value: filesBase64.gemaltoCA,
},
];
}
}
const commonVal = commonFormValidation(encryptionValidation);
setEncryptionValid(Object.keys(commonVal).length === 0);
setValidationErrors(commonVal);
}, [
enableEncryption,
encryptionType,
filesBase64,
vaultEndpoint,
vaultEngine,
vaultId,
vaultSecret,
vaultPing,
vaultRetry,
awsEndpoint,
awsRegion,
awsSecretKey,
awsAccessKey,
gemaltoEndpoint,
gemaltoToken,
gemaltoDomain,
gemaltoRetry,
]);
const clearValidationError = (fieldKey: string) => {
const newValidationElement = { ...validationErrors };
@@ -593,6 +905,10 @@ const AddTenant = ({
const newSrvAcc: NewServiceAccount = {
accessKey: res.access_key,
secretKey: res.secret_key,
console: {
accessKey: res.console.access_key,
secretKey: res.console.secret_key,
},
};
setAddSending(false);
@@ -620,7 +936,7 @@ const AddTenant = ({
enabled: true,
action: () => {
closeModalAndRefresh(false, null);
}
},
};
const wizardSteps: IWizardElement[] = [
@@ -685,12 +1001,12 @@ const AddTenant = ({
</span>
<br />
<br />
<CheckboxWrapper
<FormSwitchWrapper
value="adv_mode"
id="adv_mode"
name="adv_mode"
checked={advancedMode}
onChange={e => {
onChange={(e) => {
const targetD = e.target;
const checked = targetD.checked;
@@ -703,8 +1019,8 @@ const AddTenant = ({
),
buttons: [
cancelButton,
{ label: "Next", type: "next", enabled: nameTenantValid }
]
{ label: "Next", type: "next", enabled: nameTenantValid },
],
},
{
label: "Configure",
@@ -717,7 +1033,7 @@ const AddTenant = ({
</div>
<Grid item xs={12}>
<CheckboxWrapper
<FormSwitchWrapper
value="custom_dockerhub"
id="custom_dockerhub"
name="custom_dockerhub"
@@ -767,7 +1083,7 @@ const AddTenant = ({
</React.Fragment>
)}
<Grid item xs={12}>
<CheckboxWrapper
<FormSwitchWrapper
value="enable_prometheus"
id="enable_prometheus"
name="enable_prometheus"
@@ -882,7 +1198,7 @@ const AddTenant = ({
/>
</Grid>
<Grid item xs={12}>
<CheckboxWrapper
<FormSwitchWrapper
value="ad_skipTLS"
id="ad_skipTLS"
name="ad_skipTLS"
@@ -897,7 +1213,7 @@ const AddTenant = ({
/>
</Grid>
<Grid item xs={12}>
<CheckboxWrapper
<FormSwitchWrapper
value="ad_serverInsecure"
id="ad_serverInsecure"
name="ad_serverInsecure"
@@ -974,7 +1290,7 @@ const AddTenant = ({
buttons: [
cancelButton,
{ label: "Back", type: "back", enabled: true },
{ label: "Next", type: "next", enabled: true },
{ label: "Next", type: "next", enabled: idpValid },
],
},
{
@@ -986,7 +1302,7 @@ const AddTenant = ({
<h3>Security</h3>
</div>
<Grid item xs={12}>
<CheckboxWrapper
<FormSwitchWrapper
value="enableTLS"
id="enableTLS"
name="enableTLS"
@@ -1024,50 +1340,66 @@ const AddTenant = ({
<h5>MinIO TLS Certs</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("tlsKey", encodedValue);
setTlsKeyVal(fileName);
clearValidationError("tlsKey");
}}
accept=".key,.pem"
id="tlsKey"
name="tlsKey"
label="Key"
error={validationErrors["tlsKey"] || ""}
value={tlsKeyVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("tlsCert", encodedValue);
setTlsCertVal(fileName);
clearValidationError("tlsCert");
}}
accept=".cer,.crt,.cert,.pem"
id="tlsCert"
name="tlsCert"
label="Cert"
error={validationErrors["tlsCert"] || ""}
value={tlsCertVal}
required
/>
</Grid>
<h5>Console TLS Certs</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("consoleKey", encodedValue);
setConsoleKeyVal(fileName);
clearValidationError("consoleKey");
}}
accept=".key,.pem"
id="consoleKey"
name="consoleKey"
label="Key"
error={validationErrors["consoleKey"] || ""}
value={consoleKeyVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("consoleCert", encodedValue);
setConsoleCertVal(fileName);
clearValidationError("consoleCert");
}}
accept=".cer,.crt,.cert,.pem"
id="consoleCert"
name="consoleCert"
label="Cert"
error={validationErrors["consoleCert"] || ""}
value={consoleCertVal}
required
/>
</Grid>
@@ -1080,8 +1412,8 @@ const AddTenant = ({
buttons: [
cancelButton,
{ label: "Back", type: "back", enabled: true },
{ label: "Next", type: "next", enabled: true }
]
{ label: "Next", type: "next", enabled: securityValid },
],
},
{
label: "Encryption",
@@ -1093,7 +1425,7 @@ const AddTenant = ({
<span>How would you like to encrypt the information at rest.</span>
</div>
<Grid item xs={12}>
<CheckboxWrapper
<FormSwitchWrapper
value="enableEncryption"
id="enableEncryption"
name="enableEncryption"
@@ -1132,50 +1464,66 @@ const AddTenant = ({
<h5>Server</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("serverKey", encodedValue);
setServerKeyVal(fileName);
clearValidationError("serverKey");
}}
accept=".key,.pem"
id="serverKey"
name="serverKey"
label="Key"
error={validationErrors["serverKey"] || ""}
value={serverKeyVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("serverCert", encodedValue);
setServerCertVal(fileName);
clearValidationError("serverCert");
}}
accept=".cer,.crt,.cert,.pem"
id="serverCert"
name="serverCert"
label="Cert"
error={validationErrors["serverCert"] || ""}
value={serverCertVal}
required
/>
</Grid>
<h5>Client</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("clientKey", encodedValue);
setClientKeyVal(fileName);
clearValidationError("clientKey");
}}
accept=".key,.pem"
id="clientKey"
name="clientKey"
label="Key"
error={validationErrors["clientKey"] || ""}
value={clientKeyVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("clientCert", encodedValue);
setClientCertVal(fileName);
clearValidationError("clientCert");
}}
accept=".cer,.crt,.cert,.pem"
id="clientCert"
name="clientCert"
label="Cert"
error={validationErrors["clientCert"] || ""}
value={clientCertVal}
required
/>
</Grid>
@@ -1204,9 +1552,11 @@ const AddTenant = ({
name="vault_engine"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setVaultEngine(e.target.value);
clearValidationError("vault_engine");
}}
label="Engine"
value={vaultEngine}
error={validationErrors["vault_engine"] || ""}
required
/>
</Grid>
@@ -1266,7 +1616,7 @@ const AddTenant = ({
setVaultSecret(e.target.value);
clearValidationError("vault_secret");
}}
label="Id"
label="Secret"
value={vaultSecret}
error={validationErrors["vault_secret"] || ""}
required
@@ -1280,46 +1630,60 @@ const AddTenant = ({
name="vault_retry"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setVaultRetry(e.target.value);
clearValidationError("vault_retry");
}}
label="Retry"
value={vaultRetry}
error={validationErrors["vault_retry"] || ""}
required
/>
</Grid>
<h5>TLS</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("vaultKey", encodedValue);
setVaultKeyVal(fileName);
clearValidationError("vault_key");
}}
accept=".key,.pem"
id="vault_key"
name="vault_key"
label="Key"
error={validationErrors["vault_key"] || ""}
value={vaultKeyVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("vaultCert", encodedValue);
setVaultCertVal(fileName);
clearValidationError("vault_cert");
}}
accept=".cer,.crt,.cert,.pem"
id="vault_cert"
name="vault_cert"
label="Cert"
error={validationErrors["vault_cert"] || ""}
value={vaultCertVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("vaultCA", encodedValue);
setVaultCAVal(fileName);
clearValidationError("vault_ca");
}}
accept=".cer,.crt,.cert,.pem"
id="vault_ca"
name="vault_ca"
label="CA"
error={validationErrors["vault_ca"] || ""}
value={vaultCAVal}
required
/>
</Grid>
@@ -1332,9 +1696,11 @@ const AddTenant = ({
name="vault_ping"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setVaultPing(e.target.value);
clearValidationError("vault_ping");
}}
label="Ping"
value={vaultPing}
error={validationErrors["vault_ping"] || ""}
required
/>
</Grid>
@@ -1446,11 +1812,11 @@ const AddTenant = ({
name="gemalto_token"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setGemaltoToken(e.target.value);
clearValidationError("gemalto_endpoint");
clearValidationError("gemalto_token");
}}
label="Token"
value={gemaltoToken}
error={validationErrors["gemalto_endpoint"] || ""}
error={validationErrors["gemalto_token"] || ""}
required
/>
</Grid>
@@ -1476,6 +1842,7 @@ const AddTenant = ({
name="gemalto_retry"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setGemaltoRetry(e.target.value);
clearValidationError("gemalto_retry");
}}
label="Domain"
value={gemaltoRetry}
@@ -1485,13 +1852,17 @@ const AddTenant = ({
<h5>TLS</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("gemaltoCA", encodedValue);
setGemaltoCAVal(fileName);
clearValidationError("gemalto_ca");
}}
accept=".cer,.crt,.cert,.pem"
id="gemalto_ca"
name="gemalto_ca"
label="CA"
error={validationErrors["gemalto_ca"] || ""}
value={gemaltoCAVal}
required
/>
</Grid>
@@ -1504,8 +1875,8 @@ const AddTenant = ({
buttons: [
cancelButton,
{ label: "Back", type: "back", enabled: true },
{ label: "Next", type: "next", enabled: true }
]
{ label: "Next", type: "next", enabled: encryptionValid },
],
},
{
label: "Tenant Size",
@@ -1720,16 +2091,16 @@ const AddTenant = ({
enabled: !addSending,
action: () => {
setAddSending(true);
}
}
]
}
},
},
],
},
];
let filteredWizardSteps = wizardSteps;
if (!advancedMode) {
filteredWizardSteps = wizardSteps.filter(step => !step.advancedOnly);
filteredWizardSteps = wizardSteps.filter((step) => !step.advancedOnly);
}
return (

View File

@@ -20,7 +20,7 @@ import Typography from "@material-ui/core/Typography";
import TextField from "@material-ui/core/TextField";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import { Button } from "@material-ui/core";
import { Button, IconButton } from "@material-ui/core";
import { CreateIcon } from "../../../../icons";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
@@ -33,6 +33,9 @@ import AddTenant from "./AddTenant";
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 PageHeader from "../../Common/PageHeader/PageHeader";
interface ITenantsList {
classes: any;
@@ -80,6 +83,7 @@ const styles = (theme: Theme) =>
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
},
...containerForHeader(theme.spacing(4)),
});
const ListTenants = ({ classes }: ITenantsList) => {
@@ -244,73 +248,81 @@ const ListTenants = ({ classes }: ITenantsList) => {
entity="Tenant"
/>
)}
<PageHeader label={"Users"} />
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Tenants</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Tenants"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setFilterTenants(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setCreateTenantOpen(true);
}}
>
Create Tenant
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "Name", elementKey: "name" },
{ label: "Capacity", elementKey: "capacity" },
{ label: "# of Zones", elementKey: "zone_count" },
{ label: "State", elementKey: "currentState" },
]}
isLoading={isLoading}
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 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}
id="search-resource"
label=""
onChange={(val) => {
setFilterTenants(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setCreateTenantOpen(true);
}}
>
Create Tenant
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "Name", elementKey: "name" },
{ label: "Capacity", elementKey: "capacity" },
{ label: "# of Zones", elementKey: "zone_count" },
{ label: "State", elementKey: "currentState" },
]}
isLoading={isLoading}
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>
</Grid>
</React.Fragment>

View File

@@ -8,7 +8,7 @@ import Grid from "@material-ui/core/Grid";
import {
factorForDropdown,
getTotalSize,
niceBytes
niceBytes,
} from "../../../../common/utils";
import { Button, LinearProgress } from "@material-ui/core";
import api from "../../../../common/api";
@@ -24,18 +24,18 @@ interface IAddZoneProps {
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
color: "red",
},
buttonContainer: {
textAlign: "right"
textAlign: "right",
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const
justifyContent: "flex-start" as const,
},
sizeFactorContainer: {
marginLeft: 8
marginLeft: 8,
},
bottomContainer: {
display: "flex",
@@ -43,31 +43,31 @@ const styles = (theme: Theme) =>
alignItems: "center",
"& div": {
flexGrow: 1,
width: "100%"
}
width: "100%",
},
},
factorElements: {
display: "flex",
justifyContent: "flex-start"
justifyContent: "flex-start",
},
sizeNumber: {
fontSize: 35,
fontWeight: 700,
textAlign: "center"
textAlign: "center",
},
sizeDescription: {
fontSize: 14,
color: "#777",
textAlign: "center"
textAlign: "center",
},
...modalBasic
...modalBasic,
});
const AddZoneModal = ({
tenant,
classes,
open,
onCloseZoneAndReload
onCloseZoneAndReload,
}: IAddZoneProps) => {
const [addSending, setAddSending] = useState<boolean>(false);
const [numberOfNodes, setNumberOfNodes] = useState<number>(0);
@@ -96,8 +96,8 @@ const AddZoneModal = ({
volume_configuration: {
size: volumeSize * 1073741824,
storage_class: "",
labels: null
}
labels: null,
},
};
api
.invoke(
@@ -109,7 +109,7 @@ const AddZoneModal = ({
setAddSending(false);
onCloseZoneAndReload(true);
})
.catch(err => {
.catch((err) => {
setAddSending(false);
// setDeleteError(err);
});

View File

@@ -42,25 +42,25 @@ interface ITenantDetailsProps {
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
color: "red",
},
buttonContainer: {
textAlign: "right"
textAlign: "right",
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const
justifyContent: "flex-start" as const,
},
sizeFactorContainer: {
marginLeft: 8
marginLeft: 8,
},
containerHeader: {
display: "flex",
justifyContent: "space-between"
justifyContent: "space-between",
},
paperContainer: {
padding: "15px 15px 15px 50px"
padding: "15px 15px 15px 50px",
},
infoGrid: {
display: "grid",
@@ -68,42 +68,29 @@ const styles = (theme: Theme) =>
gridGap: 8,
"& div": {
display: "flex",
alignItems: "center"
alignItems: "center",
},
"& div:nth-child(odd)": {
justifyContent: "flex-end",
fontWeight: 700
fontWeight: 700,
},
"& div:nth-child(2n)": {
paddingRight: 35
}
paddingRight: 35,
},
},
masterActions: {
width: "25%",
minWidth: "120px",
"& div": {
margin: "5px 0px"
}
margin: "5px 0px",
},
},
actionsTray: {
textAlign: "right"
textAlign: "right",
},
...modalBasic
...modalBasic,
});
const mainPagination = {
rowsPerPageOptions: [5, 10, 25],
colSpan: 3,
count: 0,
rowsPerPage: 0,
page: 0,
SelectProps: {
inputProps: { "aria-label": "rows per page" },
native: true
},
ActionsComponent: MinTablePaginationActions
};
const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
const [selectedTab, setSelectedTab] = useState<number>(0);
const [capacity, setCapacity] = useState<number>(0);
@@ -180,7 +167,7 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
setError("");
setLoading(false);
})
.catch(err => {
.catch((err) => {
setError(err);
setLoading(false);
});
@@ -227,10 +214,10 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
<div>{niceBytes(capacity.toString(10))}</div>
<div>Minio:</div>
<div>{tenant ? tenant.image : ""}</div>
<div>Console:</div>
<div>{tenant ? tenant.console_image : ""}</div>
<div>Zones:</div>
<div>{zoneCount}</div>
<div>Console:</div>
<div>{tenant ? tenant.console_image : ""}</div>
<div>Instances:</div>
<div>{instances}</div>
<div>Volumes:</div>
@@ -274,26 +261,35 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
itemActions={[
{
type: "delete",
onClick: element => {
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" }
{ label: "# of Drives", elementKey: "volumes" },
]}
isLoading={false}
records={zones}
entityName="Zones"
idField="name"
paginatorConfig={{
...mainPagination,
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: () => {}
onChangeRowsPerPage: () => {},
}}
/>
</Grid>

View File

@@ -13,6 +13,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 } from "react";
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
import { AppState } from "../../../store";
@@ -22,6 +23,9 @@ 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 { Grid } from "@material-ui/core";
const styles = (theme: Theme) =>
createStyles({
@@ -40,6 +44,7 @@ const styles = (theme: Theme) =>
borderBottom: "1px solid #dedede",
},
},
...containerForHeader(theme.spacing(4)),
});
interface ITrace {
@@ -92,23 +97,27 @@ const Trace = ({
}, [traceMessageReceived]);
return (
<div>
<h1>Trace</h1>
<div className={classes.logList}>
<ul>
{messages.map((m) => {
return (
<li key={m.key}>
{timeFromDate(m.time)} - {m.api}[{m.statusCode} {m.statusMsg}]{" "}
{m.api} {m.host} {m.client} {m.callStats.duration} {" "}
{niceBytes(m.callStats.rx + "")} {" "}
{niceBytes(m.callStats.tx + "")}
</li>
);
})}
</ul>
</div>
</div>
<React.Fragment>
<PageHeader label={"Trace"} />
<Grid container>
<Grid item xs={12} className={classes.container}>
<div className={classes.logList}>
<ul>
{messages.map((m) => {
return (
<li key={m.key}>
{timeFromDate(m.time)} - {m.api}[{m.statusCode}{" "}
{m.statusMsg}] {m.api} {m.host} {m.client}{" "}
{m.callStats.duration} {niceBytes(m.callStats.rx + "")} {" "}
{niceBytes(m.callStats.tx + "")}
</li>
);
})}
</ul>
</div>
</Grid>
</Grid>
</React.Fragment>
);
};

View File

@@ -18,7 +18,10 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button, LinearProgress } from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import {
modalBasic,
predefinedList,
} from "../Common/FormComponents/common/styleLibrary";
import api from "../../../common/api";
import GroupsSelectors from "./GroupsSelectors";
import Title from "../../../common/Title";
@@ -46,6 +49,7 @@ const styles = (theme: Theme) =>
textAlign: "right",
},
...modalBasic,
...predefinedList,
});
const AddToGroup = ({
@@ -121,10 +125,10 @@ const AddToGroup = ({
</Grid>
)}
<Grid item xs={12}>
<Title>Users to be altered</Title>
<Grid item xs={12} className={classes.predefinedTitle}>
Selected Users
</Grid>
<Grid item xs={12}>
<Grid item xs={12} className={classes.predefinedList}>
{checkedUsers.join(", ")}
</Grid>
<Grid item xs={12}>

View File

@@ -195,7 +195,7 @@ class AddUserContent extends React.Component<
this.props.closeModalAndRefresh();
}}
modalOpen={this.props.open}
title={selectedUser !== null ? "Edit User" : "Add User"}
title={selectedUser !== null ? "Edit User" : "Create User"}
>
<React.Fragment>
<form

View File

@@ -28,6 +28,7 @@ import { stringSort } from "../../../utils/sortFunctions";
import { GroupsList } from "../Groups/types";
import get from "lodash/get";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import { actionsTray } from "../Common/FormComponents/common/styleLibrary";
interface IGroupsProps {
classes: any;
@@ -41,10 +42,11 @@ const styles = (theme: Theme) =>
marginTop: theme.spacing(3),
},
paper: {
// padding: theme.spacing(2),
display: "flex",
overflow: "auto",
flexDirection: "column",
paddingTop: 15,
boxShadow: "none",
},
addSideBar: {
width: "320px",
@@ -70,20 +72,6 @@ const styles = (theme: Theme) =>
},
},
},
actionsTray: {
textAlign: "left",
"& button": {
marginLeft: 10,
},
},
filterField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
width: "100%",
zIndex: 500,
},
noFound: {
textAlign: "center",
padding: "10px 0",
@@ -94,6 +82,26 @@ const styles = (theme: Theme) =>
stickyHeader: {
backgroundColor: "#fff",
},
actionsTitle: {
fontWeight: 600,
color: "#000",
fontSize: 16,
alignSelf: "center",
},
tableBlock: {
marginTop: 15,
},
filterField: {
width: 375,
fontWeight: 600,
"& .input": {
"&::placeholder": {
fontWeight: 600,
color: "#000",
},
},
},
...actionsTray,
});
const GroupsSelectors = ({
@@ -164,7 +172,6 @@ const GroupsSelectors = ({
return (
<React.Fragment>
<Title>Groups</Title>
<Grid item xs={12}>
<Paper className={classes.paper}>
{loading && <LinearProgress />}
@@ -172,13 +179,13 @@ const GroupsSelectors = ({
{records != null && records.length > 0 ? (
<React.Fragment>
<Grid item xs={12} className={classes.actionsTray}>
<span className={classes.actionsTitle}>Assign Groups</span>
<TextField
placeholder="Filter Groups"
placeholder="Filter by Group"
className={classes.filterField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
@@ -190,7 +197,7 @@ const GroupsSelectors = ({
}}
/>
</Grid>
<Grid item xs={12}>
<Grid item xs={12} className={classes.tableBlock}>
<TableWrapper
columns={[{ label: "Group", elementKey: "" }]}
onSelect={selectionChanged}

View File

@@ -36,6 +36,12 @@ import AddToGroup from "./AddToGroup";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import DescriptionIcon from "@material-ui/icons/Description";
import SetPolicy from "../Policies/SetPolicy";
import {
actionsTray,
containerForHeader,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
const styles = (theme: Theme) =>
createStyles({
@@ -72,18 +78,9 @@ 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)),
});
interface IUsersProps {
@@ -307,90 +304,86 @@ class Users extends React.Component<IUsersProps, IUsersState> {
}}
/>
)}
<PageHeader label={"Users"} />
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Users</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Users"
className={classes.searchField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
this.setState({ filter: e.target.value, page: 0 });
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<GroupIcon />}
disabled={checkedUsers.length <= 0}
onClick={() => {
if (checkedUsers.length > 0) {
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Users"
className={classes.searchField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
this.setState({ filter: e.target.value, page: 0 });
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<GroupIcon />}
disabled={checkedUsers.length <= 0}
onClick={() => {
if (checkedUsers.length > 0) {
this.setState({
addGroupOpen: true,
});
}
}}
>
Add to Group
</Button>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
this.setState({
addGroupOpen: true,
addScreenOpen: true,
selectedUser: null,
});
}
}}
>
Add to Group
</Button>
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
this.setState({
addScreenOpen: true,
selectedUser: null,
});
}}
>
Create User
</Button>
</Grid>
}}
>
Create User
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Access Key", elementKey: "accessKey" }]}
onSelect={selectionChanged}
selectedItems={checkedUsers}
isLoading={loading}
records={paginatedRecords}
entityName="Users"
idField="accessKey"
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 item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Access Key", elementKey: "accessKey" }]}
onSelect={selectionChanged}
selectedItems={checkedUsers}
isLoading={loading}
records={paginatedRecords}
entityName="Users"
idField="accessKey"
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>
</Grid>
</React.Fragment>

View File

@@ -25,6 +25,8 @@ import { niceBytes, timeFromDate } from "../../../common/utils";
import { wsProtocol } from "../../../utils/wsUtils";
import api from "../../../common/api";
import { FormControl, MenuItem, Select } from "@material-ui/core";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
const styles = (theme: Theme) =>
createStyles({
@@ -65,6 +67,7 @@ const styles = (theme: Theme) =>
minWidth: "206px",
boxShadow: "0px 3px 6px #00000012",
},
...containerForHeader(theme.spacing(4)),
});
interface IWatch {
@@ -155,94 +158,91 @@ const Watch = ({
}));
return (
<React.Fragment>
<PageHeader label="Watch" />
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Watch</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<FormControl variant="outlined">
<Select
id="bucket-name"
name="bucket-name"
value={bucketName}
onChange={(e) => {
setBucketName(e.target.value as string);
}}
className={classes.fieldContainer}
disabled={start}
>
<MenuItem
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<FormControl variant="outlined">
<Select
id="bucket-name"
name="bucket-name"
value={bucketName}
key={`select-bucket-name-default`}
disabled={true}
onChange={(e) => {
setBucketName(e.target.value as string);
}}
className={classes.fieldContainer}
disabled={start}
>
Select Bucket
</MenuItem>
{bucketNames.map((option) => (
<MenuItem
value={option.value}
key={`select-bucket-name-${option.label}`}
value={bucketName}
key={`select-bucket-name-default`}
disabled={true}
>
{option.label}
Select Bucket
</MenuItem>
))}
</Select>
</FormControl>
<TextField
placeholder="Prefix"
className={classes.inputField}
id="prefix-resource"
label=""
disabled={start}
InputProps={{
disableUnderline: true,
}}
onChange={(e) => {
setPrefix(e.target.value);
}}
/>
<TextField
placeholder="Suffix"
className={classes.inputField}
id="suffix-resource"
label=""
disabled={start}
InputProps={{
disableUnderline: true,
}}
onChange={(e) => {
setSuffix(e.target.value);
}}
/>
<Button
type="submit"
variant="contained"
color="primary"
disabled={start}
onClick={() => setStart(true)}
>
Start
</Button>
</Grid>
<Grid item xs={12}>
<br />
{bucketNames.map((option) => (
<MenuItem
value={option.value}
key={`select-bucket-name-${option.label}`}
>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
placeholder="Prefix"
className={classes.inputField}
id="prefix-resource"
label=""
disabled={start}
InputProps={{
disableUnderline: true,
}}
onChange={(e) => {
setPrefix(e.target.value);
}}
/>
<TextField
placeholder="Suffix"
className={classes.inputField}
id="suffix-resource"
label=""
disabled={start}
InputProps={{
disableUnderline: true,
}}
onChange={(e) => {
setSuffix(e.target.value);
}}
/>
<Button
type="submit"
variant="contained"
color="primary"
disabled={start}
onClick={() => setStart(true)}
>
Start
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<div className={classes.watchList}>
<ul>
{messages.map((m) => {
return (
<li key={m.key}>
{timeFromDate(m.Time)} - {niceBytes(m.Size + "")} - {m.Type}{" "}
- {m.Path}
</li>
);
})}
</ul>
</div>
</Grid>
</Grid>
<div className={classes.watchList}>
<ul>
{messages.map((m) => {
return (
<li key={m.key}>
{timeFromDate(m.Time)} - {niceBytes(m.Size + "")} - {m.Type} -{" "}
{m.Path}
</li>
);
})}
</ul>
</div>
</React.Fragment>
);
};

View File

@@ -18,6 +18,7 @@ import React, { useEffect, useState } from "react";
import request from "superagent";
import storage from "local-storage-fallback";
import { connect, ConnectedProps } from "react-redux";
import ErrorIcon from "@material-ui/icons/Error";
import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import Grid from "@material-ui/core/Grid";
@@ -30,22 +31,31 @@ import api from "../../common/api";
import { ILoginDetails, loginStrategyType } from "./types";
import { setSession } from "../../common/utils";
import history from "../../history";
import { Error } from "@material-ui/icons";
const styles = (theme: Theme) =>
createStyles({
"@global": {
body: {
backgroundColor: "#F4F4F4",
backgroundColor: "#FAFAFA",
},
},
paper: {
marginTop: theme.spacing(16),
borderRadius: "3px",
borderRadius: 8,
display: "flex",
flexDirection: "column",
alignItems: "center",
width: "800px",
width: 800,
height: 424,
margin: "auto",
position: "absolute",
top: "50%",
left: "50%",
marginLeft: -400,
marginTop: -212,
"&.MuiPaper-root": {
borderRadius: 8,
},
},
avatar: {
margin: theme.spacing(1),
@@ -53,36 +63,66 @@ const styles = (theme: Theme) =>
},
form: {
width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(3),
},
submit: {
margin: theme.spacing(3, 0, 2),
margin: "30px 0px 16px",
height: 40,
boxShadow: "none",
padding: "16px 30px",
},
errorBlock: {
color: "red",
backgroundColor: "#C72C48",
width: 800,
height: 64,
display: "flex",
justifyContent: "center",
alignItems: "center",
position: "absolute",
left: "50%",
top: "50%",
marginLeft: -400,
marginTop: -290,
color: "#fff",
fontWeight: 700,
fontSize: 14,
borderRadius: 8,
},
mainContainer: {
borderRadius: "3px",
position: "relative",
height: 424,
},
theOcean: {
borderTopLeftRadius: "3px",
borderBottomLeftRadius: "3px",
borderTopLeftRadius: 8,
borderBottomLeftRadius: 8,
background:
"transparent linear-gradient(333deg, #281B6F 1%, #271260 13%, #120D53 83%) 0% 0% no-repeat padding-box;",
"transparent linear-gradient(to bottom, #073052 0%,#05122b 100%); 0% 0% no-repeat padding-box;",
},
oceanBg: {
backgroundImage: "url(/images/BG_Illustration.svg)",
backgroundRepeat: "no-repeat",
backgroundPosition: "bottom left",
height: "100%",
width: "100%",
width: 324,
},
theLogin: {
padding: "76px 62px 20px 62px",
padding: "40px 45px 20px 45px",
},
loadingLoginStrategy: {
textAlign: "center",
},
headerTitle: {
marginBottom: 10,
},
submitContainer: {
textAlign: "right",
},
disclaimer: {
fontSize: 12,
marginTop: 30,
},
jwtInput: {
marginTop: 45,
},
});
const mapState = (state: SystemState) => ({
@@ -182,61 +222,60 @@ const Login = ({ classes, userLoggedIn }: ILoginProps) => {
case loginStrategyType.form: {
loginComponent = (
<React.Fragment>
<Typography component="h1" variant="h6">
Login
<Typography
component="h1"
variant="h6"
className={classes.headerTitle}
>
Console Login
</Typography>
<form className={classes.form} noValidate onSubmit={formSubmit}>
<Grid container spacing={2}>
{error !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{error}
</Typography>
</Grid>
)}
<Grid item xs={12}>
<TextField
required
fullWidth
id="accessKey"
value={accessKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setAccessKey(e.target.value)
}
label="Access Key"
label="Enter Access Key"
name="accessKey"
autoComplete="username"
/>
</Grid>
<Grid item xs={12}>
<TextField
required
fullWidth
value={secretKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setSecretKey(e.target.value)
}
name="secretKey"
label="Secret Key"
label="Enter Secret Key"
type="password"
id="secretKey"
autoComplete="current-password"
/>
</Grid>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Login
</Button>
<Grid item xs={12} className={classes.submitContainer}>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.submit}
disabled={secretKey === "" || accessKey === ""}
>
Login
</Button>
</Grid>
<Grid item xs={12} className={classes.disclaimer}>
<strong>Don't have an access key?</strong>
<br />
<br />
Contact your administrator to have one made
</Grid>
</form>
</React.Fragment>
);
@@ -245,7 +284,11 @@ const Login = ({ classes, userLoggedIn }: ILoginProps) => {
case loginStrategyType.redirect: {
loginComponent = (
<React.Fragment>
<Typography component="h1" variant="h6">
<Typography
component="h1"
variant="h6"
className={classes.headerTitle}
>
Login
</Typography>
<Button
@@ -255,7 +298,6 @@ const Login = ({ classes, userLoggedIn }: ILoginProps) => {
window.location.hostname
)}
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
@@ -269,23 +311,16 @@ const Login = ({ classes, userLoggedIn }: ILoginProps) => {
case loginStrategyType.serviceAccount: {
loginComponent = (
<React.Fragment>
<Typography component="h1" variant="h6">
Login
<Typography
component="h1"
variant="h6"
className={classes.headerTitle}
>
Operator Login
</Typography>
<form className={classes.form} noValidate onSubmit={formSubmit}>
<Grid container spacing={2}>
{error !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{error}
</Typography>
</Grid>
)}
<Grid item xs={12}>
<Grid item xs={12} className={classes.jwtInput}>
<TextField
required
fullWidth
@@ -300,15 +335,22 @@ const Login = ({ classes, userLoggedIn }: ILoginProps) => {
/>
</Grid>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Login
</Button>
<Grid item xs={12} className={classes.submitContainer}>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.submit}
disabled={jwt === ""}
>
Login
</Button>
</Grid>
<Grid item xs={12} className={classes.disclaimer}>
<strong>Don't have an access key?</strong>
<br />
Contact your administrator to have one made
</Grid>
</form>
</React.Fragment>
);
@@ -321,16 +363,24 @@ const Login = ({ classes, userLoggedIn }: ILoginProps) => {
}
return (
<Paper className={classes.paper}>
<Grid container className={classes.mainContainer}>
<Grid item xs={7} className={classes.theOcean}>
<div className={classes.oceanBg} />
<React.Fragment>
{error !== "" && (
<div className={classes.errorBlock}>
<ErrorIcon fontSize="small" />
&nbsp;{error}
</div>
)}
<Paper className={classes.paper}>
<Grid container className={classes.mainContainer}>
<Grid item xs={7} className={classes.theOcean}>
<div className={classes.oceanBg} />
</Grid>
<Grid item xs={5} className={classes.theLogin}>
{loginComponent}
</Grid>
</Grid>
<Grid item xs={5} className={classes.theLogin}>
{loginComponent}
</Grid>
</Grid>
</Paper>
</Paper>
</React.Fragment>
);
};

Some files were not shown because too many files have changed in this diff Show More