Compare commits

...

17 Commits

Author SHA1 Message Date
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
155 changed files with 16217 additions and 1591 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-20200921211523-69f9eef5b7b5
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-20200921211523-69f9eef5b7b5 h1:yQ7WHA2wbTTGHz0Z4xXSRxfUTm5fafM0ajvlZlQ2Zl4=
github.com/minio/operator v0.0.0-20200921211523-69f9eef5b7b5/go.mod h1:6lavbNo2YuJWeQR5bZYsEWdbpRCO2KrTyfQ0PtC/AN4=
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.25
image: minio/console:v0.4.0
imagePullPolicy: "IfNotPresent"
args:
- server

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:v0.3.25
image: minio/console:v0.4.0
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
}

69
models/bucket_object.go Normal file
View File

@@ -0,0 +1,69 @@
// 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"`
// last modified
LastModified string `json:"last_modified,omitempty"`
// name
Name string `json:"name,omitempty"`
// size
Size int64 `json:"size,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,26 @@ import (
// endpoints definition
var (
configuration = "/configurations-list"
users = "/users"
groups = "/groups"
iamPolicies = "/policies"
dashboard = "/dashboard"
profiling = "/profiling"
trace = "/trace"
logs = "/logs"
watch = "/watch"
notifications = "/notification-endpoints"
buckets = "/buckets"
bucketsDetail = "/buckets/:bucketName"
serviceAccounts = "/service-accounts"
tenants = "/tenants"
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
heal = "/heal"
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"
)
type ConfigurationActionSet struct {
@@ -208,22 +212,50 @@ 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(),
}
// 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,
}
// 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: 4,
},
{
name: "policies endpoint",
@@ -63,7 +63,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"admin:ListUserPolicies",
},
},
want: 2,
want: 4,
},
{
name: "all admin endpoints",
@@ -72,7 +72,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"admin:*",
},
},
want: 11,
want: 15,
},
{
name: "all s3 endpoints",
@@ -81,7 +81,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"s3:*",
},
},
want: 4,
want: 6,
},
{
name: "all admin and s3 endpoints",
@@ -91,7 +91,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"s3:*",
},
},
want: 14,
want: 18,
},
{
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

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

@@ -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,21 @@ 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";
const styles = (theme: Theme) =>
createStyles({
@@ -32,6 +47,15 @@ const styles = (theme: Theme) =>
buttonContainer: {
textAlign: "right",
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const,
},
sizeFactorContainer: {
marginLeft: 8,
alignSelf: "flex-start" as const,
},
...modalBasic,
});
@@ -39,119 +63,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}>
<CheckboxWrapper
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}>
<CheckboxWrapper
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>
<InputBoxWrapper
type="number"
id="quota_size"
name="quota_size"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
addBucketQuotaSize(e.target.value);
}}
label="Size"
value={quotaSize}
required
min="1"
/>
</div>
<div className={classes.sizeFactorContainer}>
<SelectWrapper
label=""
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,12 @@ 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 { logMessageReceived, logResetMessages } from "../../Logs/actions";
import { addBucketOpen, addBucketReset } from "../actions";
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
import PageHeader from "../../Common/PageHeader/PageHeader";
const styles = (theme: Theme) =>
createStyles({
@@ -74,176 +80,153 @@ const styles = (theme: Theme) =>
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
},
...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 +234,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 +250,7 @@ class ListBuckets extends React.Component<
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
this.setState({
addScreenOpen: true,
});
addBucketOpen(true);
}}
>
Create Bucket
@@ -317,9 +296,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,158 @@
// 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 { BucketObjectsList } from "../ListObjects/types";
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: BucketObjectsList) => {
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.removeRecord();
}}
color="secondary"
autoFocus
>
Delete
</Button>
</DialogActions>
</Dialog>
);
}
}
export default withStyles(styles)(DeleteObject);

View File

@@ -0,0 +1,253 @@
// 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";
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: {
textAlign: "right",
"& button": {
marginLeft: 10,
},
},
searchField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
},
});
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();
}
});
}
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 tableActions = [
{ 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);
}}
/>
)}
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Objects</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<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>
</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,212 @@
// 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,
IRemoteBucketsResponse,
} from "../../RemoteBuckets/types";
import RemoteBucketsList from "../../RemoteBuckets/RemoteBuckets";
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 [loadingForm, setLoadingForm] = useState(true);
const [addLoading, setAddLoading] = useState(false);
const [remoteURL, setRemoteURL] = useState("");
const [source, setSource] = useState("");
const [target, setTarget] = useState("");
const [ARN, setARN] = useState("");
const [arnValues, setARNValues] = useState([]);
useEffect(() => {
if (addLoading) {
addRecord();
}
}, [addLoading]);
useEffect(() => {
if (loadingForm) {
getARNValues();
}
});
const addRecord = () => {
const replicationInfo = {
destination_bucket: target,
arn: ARN,
};
api
.invoke(
"POST",
`/api/v1/buckets/${bucketName}/replication`,
replicationInfo
)
.then((res) => {
setAddLoading(false);
setAddError("");
closeModalAndRefresh();
})
.catch((err) => {
setAddLoading(false);
setAddError(err);
});
};
const getARNValues = () => {
api
.invoke("GET", "/api/v1/remote-buckets")
.then((res: any) => {
const remoteBuckets = get(res, "buckets", []);
const remoteARNS = remoteBuckets.map((itemRemote: IRemoteBucket) => {
return { label: itemRemote.remoteARN, value: itemRemote.remoteARN };
});
setLoadingForm(false);
setARNValues(remoteARNS);
})
.catch((err) => {
setLoadingForm(false);
});
};
return (
<ModalWrapper
modalOpen={open}
onClose={() => {
setAddError("");
closeModalAndRefresh();
}}
title="Set Bucket Replication"
>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setAddLoading(true);
}}
>
{loadingForm && (
<Grid container>
<Grid item xs={12}>
<LinearProgress />
</Grid>
</Grid>
)}
{!loadingForm && (
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
{addError !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{addError}
</Typography>
</Grid>
)}
<Grid item xs={12}>
<InputBoxWrapper
id="target"
name="target"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setTarget(e.target.value);
}}
label="Destination Bucket"
value={target}
/>
</Grid>
<Grid item xs={12}>
<SelectWrapper
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
setARN(e.target.value as string);
}}
id="arn"
name="arn"
label={"ARN"}
value={ARN}
options={arnValues}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
>
Save
</Button>
</Grid>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</Grid>
)}
</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,201 @@ 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>
<Button
variant="contained"
color="primary"
fullWidth
size="medium"
onClick={() => {
this.setState({
setAccessPolicyScreenOpen: true,
});
}}
>
Change Access Policy
</Button>
<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>
</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,
}}
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={6}>
<Tabs
value={curTab}
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
this.setState({ curTab: newValue });
}}
indicatorColor="primary"
textColor="primary"
aria-label="cluster-tabs"
>
<Tab label="Events" {...a11yProps(0)} />
<Tab label="Replication" {...a11yProps(1)} />
</Tabs>
</Grid>
<Grid item xs={6} className={classes.actionsTray}>
{curTab === 0 && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
size="medium"
onClick={() => {
this.setState({
addScreenOpen: true,
});
}}
>
Subscribe to Event
</Button>
)}
{curTab === 1 && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
size="medium"
onClick={() => {
this.setState({
openSetReplication: true,
});
}}
>
Add Replication Rule
</Button>
)}
</Grid>
<Grid 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,37 @@ 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;
}

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

@@ -79,3 +79,19 @@ export const checkboxIcons = {
backgroundColor: "#201763",
},
};
export const containerForHeader = (bottomSpacing: any) => ({
container: {
padding: "110px 33px 30px",
paddingBottom: bottomSpacing,
"& h6": {
color: "#777777",
fontSize: 14,
},
"& p": {
"& span": {
fontSize: 16,
},
},
},
});

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

@@ -49,6 +49,7 @@ interface IColumns {
elementKey: string;
sortable?: boolean;
renderFunction?: (input: any) => any;
globalClass?: any;
}
interface IPaginatorConfig {
@@ -161,7 +162,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 +269,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 +283,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 +336,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,8 @@ import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import { configurationElements } from "../utils";
import { IConfigurationElement } from "../types";
import EditConfiguration from "../CustomForms/EditConfiguration";
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
import PageHeader from "../../Common/PageHeader/PageHeader";
interface IListConfiguration {
classes: any;
@@ -58,6 +60,7 @@ const styles = (theme: Theme) =>
iconText: {
lineHeight: "24px",
},
...containerForHeader(theme.spacing(4)),
});
const ConfigurationsList = ({ classes }: IListConfiguration) => {
@@ -103,47 +106,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 RemoteBuckets from "./RemoteBuckets/RemoteBuckets";
import ObjectBrowser from "./ObjectBrowser/ObjectBrowser";
import ListObjects from "./Buckets/ListBuckets/Objects/ListObjects/ListObjects";
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",
@@ -269,6 +279,10 @@ const Console = ({
component: Policies,
path: "/policies",
},
{
component: RemoteBuckets,
path: "/remote-buckets",
},
{
component: Trace,
path: "/trace",
@@ -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,8 @@ 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";
const styles = (theme: Theme) =>
createStyles({
@@ -77,26 +79,20 @@ 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,
minHeight: 165,
minWidth: 247,
marginRight: 20,
},
consumptionValue: {
color: "#000000",
@@ -107,6 +103,9 @@ const styles = (theme: Theme) =>
marginRight: 10,
color: "#777777",
},
notationContainer: {
display: "flex",
},
});
interface IDashboardProps {
@@ -155,11 +154,9 @@ const Dashboard = ({ classes }: IDashboardProps) => {
return (
<React.Fragment>
<PageHeader label="Dashboard" />
<Grid container>
<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,22 +164,33 @@ 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 />
</Grid>
<Grid item>
<Typography variant="h6">Total Buckets</Typography>
<Typography variant="h6">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}>
<PieChartIcon />
</Grid>
<Grid item>
<Typography variant="h6">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}>
@@ -197,21 +205,6 @@ const Dashboard = ({ classes }: IDashboardProps) => {
</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>
</Grid>
</Grid>
<Typography className={classes.consumptionValue}>
{usage ? prettyUsage(usage.usage + "") : 0}
</Typography>
</Paper>
</Grid>
</React.Fragment>
)}
</Grid>

View File

@@ -31,6 +31,8 @@ import AddGroup from "../Groups/AddGroup";
import DeleteGroup from "./DeleteGroup";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import SetPolicy from "../Policies/SetPolicy";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
interface IGroupsProps {
classes: any;
@@ -84,6 +86,7 @@ const styles = (theme: Theme) =>
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
},
...containerForHeader(theme.spacing(4)),
});
const Groups = ({ classes }: IGroupsProps) => {
@@ -215,71 +218,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

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

@@ -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,9 @@ 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 CloudUploadIcon from "@material-ui/icons/CloudUpload";
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";
@@ -55,44 +58,53 @@ import { clearSession } from "../../../common/utils";
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 +113,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,17 +200,17 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
group: "User",
type: "item",
component: NavLink,
to: "/buckets",
name: "Buckets",
icon: <BucketsIcon />,
to: "/service-accounts",
name: "Service Accounts",
icon: <ServiceAccountsIcon />,
},
{
group: "User",
type: "item",
component: NavLink,
to: "/service-accounts",
name: "Service Accounts",
icon: <ServiceAccountsIcon />,
to: "/object-browser",
name: "Object Browser",
icon: <DescriptionIcon />,
},
{
group: "Admin",
@@ -213,6 +228,14 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
name: "Groups",
icon: <GroupsIcon />,
},
{
group: "Admin",
type: "item",
component: NavLink,
to: "/buckets",
name: "Buckets",
icon: <BucketsIcon />,
},
{
group: "Admin",
type: "item",
@@ -221,6 +244,14 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
name: "IAM Policies",
icon: <IAMPoliciesIcon />,
},
{
group: "Admin",
type: "item",
component: NavLink,
to: "/remote-buckets",
name: "Remote Buckets",
icon: <CloudUploadIcon />,
},
{
group: "Tools",
type: "item",
@@ -253,12 +284,6 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
name: "Heal",
icon: <HealingIcon />,
},
{
group: "Admin",
type: "title",
name: "Configurations",
component: Typography,
},
{
group: "Admin",
type: "item",
@@ -266,7 +291,6 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
to: "/notification-endpoints",
name: "Lambda Notifications",
icon: <LambdaNotificationsIcon />,
extraMargin: true,
},
{
group: "Admin",
@@ -275,7 +299,6 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
to: "/configurations-list",
name: "Configurations List",
icon: <ConfigurationsListIcon />,
extraMargin: true,
},
{
group: "Operator",
@@ -351,7 +374,7 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
}
}}
>
{groupMember.label}
<span>{groupMember.label}</span>
{groupMember.collapsible && (
<span
className={`${classes.selectorArrow} ${

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,8 @@ import api from "../../../common/api";
import FiberManualRecordIcon from "@material-ui/icons/FiberManualRecord";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import AddNotificationEndpoint from "./AddNotificationEndpoint";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
interface IListNotificationEndpoints {
classes: any;
@@ -43,29 +45,30 @@ interface IListNotificationEndpoints {
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
color: "red",
},
strongText: {
fontWeight: 700
fontWeight: 700,
},
keyName: {
marginLeft: 5
marginLeft: 5,
},
actionsTray: {
textAlign: "right",
"& button": {
marginLeft: 10
}
marginLeft: 10,
},
},
searchField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012"
boxShadow: "0px 3px 6px #00000012",
},
iconText: {
lineHeight: "24px"
}
lineHeight: "24px",
},
...containerForHeader(theme.spacing(4)),
});
const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
@@ -96,7 +99,7 @@ const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
setError("");
setIsLoading(false);
})
.catch(err => {
.catch((err) => {
setError(err);
setIsLoading(false);
});
@@ -115,8 +118,8 @@ const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
type: "delete",
onClick: (row: any) => {
//confirmDeleteBucket(row.name);
}
}
},
},
];
const filteredRecords = records.filter((b: TransformedEndpointItem) => {
@@ -136,7 +139,7 @@ const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
<div
style={{
display: "flex",
alignItems: "center"
alignItems: "center",
}}
>
<FiberManualRecordIcon
@@ -158,83 +161,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,240 @@
// 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";
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: {
textAlign: "right",
"& button": {
marginLeft: 10,
},
},
searchField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
},
usedSpaceCol: {
width: 150,
},
subTitleLabel: {
alignItems: "center",
display: "flex",
},
});
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={6} className={classes.subTitleLabel}>
<Typography variant="h6">Buckets</Typography>
</Grid>
<Grid item xs={6} 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

@@ -30,6 +30,8 @@ import AddPolicy from "./AddPolicy";
import DeletePolicy from "./DeletePolicy";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import api from "../../../common/api";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
const styles = (theme: Theme) =>
createStyles({
@@ -73,6 +75,7 @@ const styles = (theme: Theme) =>
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
},
...containerForHeader(theme.spacing(4)),
});
interface IPoliciesProps {
@@ -222,70 +225,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

@@ -0,0 +1,209 @@
// 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 Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { Button, LinearProgress } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { 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 SelectWrapper from "../Common/FormComponents/SelectWrapper/SelectWrapper";
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red",
},
buttonContainer: {
textAlign: "right",
},
...modalBasic,
});
interface IAddBucketProps {
classes: any;
open: boolean;
closeModalAndRefresh: () => void;
}
const AddRemoteBucket = ({
classes,
open,
closeModalAndRefresh,
}: IAddBucketProps) => {
const [addLoading, setAddLoading] = useState(false);
const [addError, setAddError] = useState("");
const [accessKey, setAccessKey] = useState("");
const [secretKey, setSecretKey] = useState("");
const [sourceBucket, setSourceBucket] = useState("");
const [targetURL, setTargetURL] = useState("");
const [targetBucket, setTargetBucket] = useState("");
const [region, setRegion] = useState("");
useEffect(() => {
if (addLoading) {
addRecord();
}
}, [addLoading]);
const addRecord = () => {
const remoteBucketInfo = {
accessKey: accessKey,
secretKey: secretKey,
sourceBucket: sourceBucket,
targetURL: targetURL,
targetBucket: targetBucket,
region: region,
};
api
.invoke("POST", "/api/v1/remote-buckets", remoteBucketInfo)
.then((res) => {
setAddLoading(false);
setAddError("");
closeModalAndRefresh();
})
.catch((err) => {
setAddLoading(false);
setAddError(err);
});
};
return (
<ModalWrapper
title="Create Remote 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>) => {
e.preventDefault();
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="accessKey"
name="accessKey"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAccessKey(e.target.value);
}}
label="Access Key"
value={accessKey}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="secretKey"
name="secretKey"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setSecretKey(e.target.value);
}}
label="Secret Key"
value={secretKey}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="sourceBucket"
name="sourceBucket"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setSourceBucket(e.target.value);
}}
label="Source Bucket"
value={sourceBucket}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="targetURL"
name="targetURL"
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="targetBucket"
name="targetBucket"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setTargetBucket(e.target.value);
}}
label="Target Bucket"
value={targetBucket}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="region"
name="region"
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)(AddRemoteBucket);

View File

@@ -0,0 +1,137 @@
// 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import get from "lodash/get";
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 IDeleteEventProps {
classes: any;
closeDeleteModalAndRefresh: (refresh: boolean) => void;
deleteOpen: boolean;
bucketName: any;
sourceBucket: string;
}
interface IDeleteEventState {
deleteLoading: boolean;
deleteError: string;
}
const DeleteRemoteBucket = ({
deleteOpen,
closeDeleteModalAndRefresh,
classes,
bucketName,
sourceBucket,
}: IDeleteEventProps) => {
const [deleteError, setDeleteError] = useState("");
const [deleteLoading, setDeleteLoading] = useState(false);
useEffect(() => {
if (deleteLoading) {
removeRecord();
}
}, [deleteLoading]);
const removeRecord = () => {
api
.invoke("DELETE", `/api/v1/remote-buckets/${sourceBucket}/${bucketName}`)
.then(() => {
setDeleteLoading(false);
setDeleteError("");
closeDeleteModalAndRefresh(true);
})
.catch((err) => {
setDeleteLoading(false);
setDeleteError(err);
});
};
return (
<Dialog
open={deleteOpen}
onClose={() => {
setDeleteError("");
closeDeleteModalAndRefresh(false);
}}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Delete Remote Bucket</DialogTitle>
<DialogContent>
{deleteLoading && <LinearProgress />}
<DialogContentText id="alert-dialog-description">
Are you sure you want to delete <strong>'{bucketName}'</strong> Remote
Bucket?
{deleteError !== "" && (
<React.Fragment>
<br />
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{deleteError}
</Typography>
</React.Fragment>
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
onClick={() => {
setDeleteError("");
closeDeleteModalAndRefresh(false);
}}
color="primary"
disabled={deleteLoading}
>
Cancel
</Button>
<Button
onClick={() => {
setDeleteLoading(true);
}}
color="secondary"
autoFocus
>
Delete
</Button>
</DialogActions>
</Dialog>
);
};
export default withStyles(styles)(DeleteRemoteBucket);

View File

@@ -0,0 +1,279 @@
// 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 { 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 Moment from "react-moment";
import api from "../../../common/api";
import { Bucket } from "../Buckets/types";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import AddRemoteBucket from "./AddRemoteBucket";
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
import { CreateIcon } from "../../../icons";
import { IRemoteBucket, IRemoteBucketsResponse } from "./types";
import DeleteRemoteBucket from "./DeleteRemoteBucket";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
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: {
textAlign: "right",
"& button": {
marginLeft: 10,
},
},
searchField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
},
...containerForHeader(theme.spacing(4)),
});
interface IRemoteListBucketsProps {
classes: any;
}
const RemoteBucketsList = ({ classes }: IRemoteListBucketsProps) => {
const [records, setRecords] = useState<IRemoteBucket[]>([]);
const [totalRecords, setTotalRecords] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string>("");
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
const [deleteScreenOpen, setDeleteOpen] = useState<boolean>(false);
const [page, setPage] = useState<number>(0);
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
const [selectedBucket, setSelectedBucket] = useState<IRemoteBucket>({
remoteARN: "",
accessKey: "",
name: "",
secretKey: "",
service: "",
sourceBucket: "",
status: "",
targetBucket: "",
targetURL: "",
});
const [filterBuckets, setFilterBuckets] = useState<string>("");
useEffect(() => {
if (loading) {
fetchRecords();
}
}, [loading]);
const closeAddModalAndRefresh = () => {
setAddScreenOpen(false);
setLoading(true);
};
const closeDeleteModalAndRefresh = (reload: boolean) => {
setDeleteOpen(false);
if (reload) {
setLoading(true);
}
};
const fetchRecords = () => {
const offset = page * rowsPerPage;
api
.invoke("GET", `/api/v1/remote-buckets`)
.then((res: IRemoteBucketsResponse) => {
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);
});
};
const offset = page * 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);
};
const confirmDeleteRemoteBucket = (arnRemoteBucket: IRemoteBucket) => {
setSelectedBucket(arnRemoteBucket);
setDeleteOpen(true);
};
const tableActions = [{ type: "delete", onClick: confirmDeleteRemoteBucket }];
const filteredRecords = records
.slice(offset, offset + rowsPerPage)
.filter((b: IRemoteBucket) => {
if (filterBuckets === "") {
return true;
} else {
if (b.name.indexOf(filterBuckets) >= 0) {
return true;
} else {
return false;
}
}
});
return (
<React.Fragment>
{addScreenOpen && (
<AddRemoteBucket
open={addScreenOpen}
closeModalAndRefresh={() => {
closeAddModalAndRefresh();
}}
/>
)}
{deleteScreenOpen && (
<DeleteRemoteBucket
bucketName={selectedBucket.remoteARN}
sourceBucket={selectedBucket.sourceBucket}
closeDeleteModalAndRefresh={(reload) => {
closeDeleteModalAndRefresh(reload);
}}
deleteOpen={deleteScreenOpen}
/>
)}
<PageHeader label="Remote Buckets" />
<Grid container>
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Remote 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);
}}
>
Create Remote Bucket
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "Remote ARN", elementKey: "remoteARN" },
{ label: "Source Bucket", elementKey: "sourceBucket" },
{ label: "Target Bucket", elementKey: "targetBucket" },
{ label: "Status", elementKey: "status" },
]}
isLoading={loading}
records={filteredRecords}
entityName="Remote Buckets"
idField="remoteARN"
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>
);
};
export default withStyles(styles)(RemoteBucketsList);

View File

@@ -0,0 +1,32 @@
// 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 IRemoteBucketsResponse {
buckets: IRemoteBucket[];
total: number;
}
export interface IRemoteBucket {
name: string;
accessKey: string;
secretKey: string;
sourceBucket: string;
targetURL: string;
targetBucket: string;
remoteARN: string;
status: string;
service: string;
}

View File

@@ -31,6 +31,8 @@ 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 { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
const styles = (theme: Theme) =>
createStyles({
@@ -85,6 +87,7 @@ const styles = (theme: Theme) =>
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
},
...containerForHeader(theme.spacing(4)),
});
interface IServiceAccountsProps {
@@ -225,81 +228,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

@@ -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[] = [
@@ -690,7 +1006,7 @@ const AddTenant = ({
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",
@@ -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 },
],
},
{
@@ -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",
@@ -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,7 @@ 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";
interface ITenantsList {
classes: any;
@@ -252,6 +253,17 @@ const ListTenants = ({ classes }: ITenantsList) => {
<br />
</Grid>
<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}

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

@@ -36,6 +36,8 @@ import AddToGroup from "./AddToGroup";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import DescriptionIcon from "@material-ui/icons/Description";
import SetPolicy from "../Policies/SetPolicy";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
const styles = (theme: Theme) =>
createStyles({
@@ -84,6 +86,7 @@ const styles = (theme: Theme) =>
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
},
...containerForHeader(theme.spacing(4)),
});
interface IUsersProps {
@@ -307,90 +310,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

@@ -21,6 +21,7 @@ import { traceReducer } from "./screens/Console/Trace/reducers";
import { logReducer } from "./screens/Console/Logs/reducers";
import { watchReducer } from "./screens/Console/Watch/reducers";
import { consoleReducer } from "./screens/Console/reducer";
import { bucketsReducer } from "./screens/Console/Buckets/reducers";
const globalReducer = combineReducers({
system: systemReducer,
@@ -28,6 +29,7 @@ const globalReducer = combineReducers({
logs: logReducer,
watch: watchReducer,
console: consoleReducer,
buckets: bucketsReducer,
});
declare global {

View File

@@ -19,7 +19,7 @@ export interface IValidation {
required: boolean;
pattern?: RegExp;
customPatternMessage?: string;
customValidation?: boolean;
customValidation?: boolean; // The validation to trigger the error
customValidationMessage?: string;
value: string;
}

View File

@@ -1205,6 +1205,16 @@
dependencies:
"@hapi/hoek" "^8.3.0"
"@hot-loader/react-dom@^16.9.0":
version "16.9.0"
resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.9.0.tgz#7782cec9d78172f3e4c86a317ba7a73bd0271acd"
integrity sha512-MsOdCBB7c5YNyB4iDDct+tS7AihvYyfwZVV+z/QnbTjPgxH98kqIDXO92nU7tLXp0OtYFErHZfcWjtszP/572w==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.15.0"
"@iarna/cli@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@iarna/cli/-/cli-1.2.0.tgz#0f7af5e851afe895104583c4ca07377a8094d641"
@@ -4371,6 +4381,11 @@ dom-serializer@0:
domelementtype "^2.0.1"
entities "^2.0.0"
dom-walk@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
domain-browser@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
@@ -5119,7 +5134,7 @@ fast-json-stable-stringify@^2.0.0:
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fast-levenshtein@~2.0.6:
fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
@@ -5631,6 +5646,14 @@ global-prefix@^3.0.0:
kind-of "^6.0.2"
which "^1.3.1"
global@^4.3.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
dependencies:
min-document "^2.19.0"
process "^0.11.10"
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@@ -8060,6 +8083,13 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
min-document@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=
dependencies:
dom-walk "^0.1.0"
mini-create-react-context@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040"
@@ -10099,7 +10129,7 @@ promzard@^0.3.0:
dependencies:
read "1"
prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -10316,6 +10346,23 @@ react-app-polyfill@^1.0.6:
regenerator-runtime "^0.13.3"
whatwg-fetch "^3.0.0"
react-app-rewire-hot-loader@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/react-app-rewire-hot-loader/-/react-app-rewire-hot-loader-2.0.1.tgz#9b47d299dac4d861dafdf18b49c3698d0ff04a52"
integrity sha512-XTUPOv5q0zJeEtXECfilDPfqCiktSVbIyupiqigIHOO8EMZec2YfNpwZzqDWMu3yfaEHRs54qRCcCgRR/1w3EQ==
react-app-rewired@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/react-app-rewired/-/react-app-rewired-2.1.6.tgz#33ee3076a7f34d6a7c94e649cac67e7c8c580de8"
integrity sha512-06flj0kK5tf/RN4naRv/sn6j3sQd7rsURoRLKLpffXDzJeNiAaTNic+0I8Basojy5WDwREkTqrMLewSAjcb13w==
dependencies:
semver "^5.6.0"
react-async-hook@^3.6.1:
version "3.6.1"
resolved "https://registry.yarnpkg.com/react-async-hook/-/react-async-hook-3.6.1.tgz#aed3e492d87319392865c83ed7cef3609e2a7fef"
integrity sha512-YWBB2feVQF79t5u2raMPHlZ8975Jds+guCvkWVC4kRLDlSCouLsYpQm4DGSqPeHvoHYVVcDfqNayLZAXQmnxnw==
react-chartjs-2@^2.9.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-2.10.0.tgz#857e7f4788cae27e872624ba7826d53cf82f1ee6"
@@ -10374,6 +10421,20 @@ react-error-overlay@^6.0.7:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
react-hot-loader@^4.13.0:
version "4.13.0"
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.13.0.tgz#c27e9408581c2a678f5316e69c061b226dc6a202"
integrity sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA==
dependencies:
fast-levenshtein "^2.0.6"
global "^4.3.0"
hoist-non-react-statics "^3.3.0"
loader-utils "^1.1.0"
prop-types "^15.6.1"
react-lifecycles-compat "^3.0.4"
shallowequal "^1.1.0"
source-map "^0.7.3"
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -11162,6 +11223,14 @@ saxes@^3.1.9:
dependencies:
xmlchars "^2.1.1"
scheduler@^0.15.0:
version "0.15.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.15.0.tgz#6bfcf80ff850b280fed4aeecc6513bc0b4f17f8e"
integrity sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler@^0.19.1:
version "0.19.1"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
@@ -11343,6 +11412,11 @@ shallow-clone@^3.0.0:
dependencies:
kind-of "^6.0.2"
shallowequal@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -11561,6 +11635,11 @@ source-map@^0.5.0, source-map@^0.5.6:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
source-map@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
spdx-correct@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
@@ -12478,6 +12557,11 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
use-debounce@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-5.0.1.tgz#b11861bc63970bfc6fd8393a0a3cc3d7cb5944ce"
integrity sha512-YHzlVa7qKFRlrMpVRiStGWGE0lumwJiQPvsTMlatsIrck39ycyJkdf0npUYG5MJEVxaJQWwvpmePRYZb534upg==
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"

63
restapi/admin_parity.go Normal file
View File

@@ -0,0 +1,63 @@
// 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 restapi
import (
"fmt"
"log"
"github.com/minio/console/pkg/utils"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/admin_api"
)
func registerParityHandlers(api *operations.ConsoleAPI) {
api.AdminAPIGetParityHandler = admin_api.GetParityHandlerFunc(func(params admin_api.GetParityParams, principal *models.Principal) middleware.Responder {
resp, err := getParityResponse(params)
if err != nil {
return admin_api.NewGetParityDefault(int(err.Code)).WithPayload(err)
}
return admin_api.NewGetParityOK().WithPayload(resp)
})
}
func GetParityInfo(nodes int64, disksPerNode int64) (models.ParityResponse, error) {
parityVals, err := utils.PossibleParityValues(fmt.Sprintf(`http://minio{1...%d}/export/set{1...%d}`, nodes, disksPerNode))
if err != nil {
return nil, err
}
return parityVals, nil
}
func getParityResponse(params admin_api.GetParityParams) (models.ParityResponse, *models.Error) {
nodes := params.Nodes
disksPerNode := params.DisksPerNode
parityValues, err := GetParityInfo(nodes, disksPerNode)
if err != nil {
log.Println("error getting parity info:", err)
return nil, prepareError(err)
}
return parityValues, nil
}

View File

@@ -0,0 +1,79 @@
// 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 restapi
import (
"encoding/json"
"reflect"
"testing"
"github.com/minio/console/models"
)
func Test_getParityInfo(t *testing.T) {
tests := []struct {
description string
wantErr bool
nodes int64
disksPerNode int64
expectedResp models.ParityResponse
}{
{
description: "Incorrect Number of endpoints provided",
wantErr: true,
nodes: 1,
disksPerNode: 1,
expectedResp: nil,
},
{
description: "Number of endpoints is valid",
wantErr: false,
nodes: 4,
disksPerNode: 10,
expectedResp: models.ParityResponse{"EC:4", "EC:3", "EC:2"},
},
{
description: "More nodes than disks",
wantErr: false,
nodes: 4,
disksPerNode: 1,
expectedResp: models.ParityResponse{"EC:2"},
},
{
description: "More disks than nodes",
wantErr: false,
nodes: 2,
disksPerNode: 50,
expectedResp: models.ParityResponse{"EC:5", "EC:4", "EC:3", "EC:2"},
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
parity, err := GetParityInfo(tt.nodes, tt.disksPerNode)
if (err != nil) != tt.wantErr {
t.Errorf("GetParityInfo() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(parity, tt.expectedResp) {
ji, _ := json.Marshal(parity)
vi, _ := json.Marshal(tt.expectedResp)
t.Errorf("\ngot: %s \nwant: %s", ji, vi)
}
})
}
}

View File

@@ -0,0 +1,217 @@
// 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 restapi
import (
"context"
"errors"
"log"
"net/url"
"strconv"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
"github.com/minio/console/restapi/operations/user_api"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/madmin"
)
func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
// return list of remote buckets
api.UserAPIListRemoteBucketsHandler = user_api.ListRemoteBucketsHandlerFunc(func(params user_api.ListRemoteBucketsParams, session *models.Principal) middleware.Responder {
listResp, err := getListRemoteBucketsResponse(session)
if err != nil {
return user_api.NewListRemoteBucketsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return user_api.NewListRemoteBucketsOK().WithPayload(listResp)
})
// return information about a specific bucket
api.UserAPIRemoteBucketDetailsHandler = user_api.RemoteBucketDetailsHandlerFunc(func(params user_api.RemoteBucketDetailsParams, session *models.Principal) middleware.Responder {
response, err := getRemoteBucketDetailsResponse(session, params)
if err != nil {
return user_api.NewRemoteBucketDetailsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return user_api.NewRemoteBucketDetailsOK().WithPayload(response)
})
// delete remote bucket
api.UserAPIDeleteRemoteBucketHandler = user_api.DeleteRemoteBucketHandlerFunc(func(params user_api.DeleteRemoteBucketParams, session *models.Principal) middleware.Responder {
err := getDeleteRemoteBucketResponse(session, params)
if err != nil {
return user_api.NewDeleteRemoteBucketDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return user_api.NewDeleteRemoteBucketNoContent()
})
// set remote bucket
api.UserAPIAddRemoteBucketHandler = user_api.AddRemoteBucketHandlerFunc(func(params user_api.AddRemoteBucketParams, session *models.Principal) middleware.Responder {
err := getAddRemoteBucketResponse(session, params)
if err != nil {
return user_api.NewAddRemoteBucketDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return user_api.NewAddRemoteBucketCreated()
})
}
func getListRemoteBucketsResponse(session *models.Principal) (*models.ListRemoteBucketsResponse, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
}
adminClient := adminClient{client: mAdmin}
buckets, err := listRemoteBuckets(ctx, adminClient)
if err != nil {
log.Println("error listing remote buckets:", err)
return nil, err
}
return &models.ListRemoteBucketsResponse{
Buckets: buckets,
Total: int64(len(buckets)),
}, nil
}
func getRemoteBucketDetailsResponse(session *models.Principal, params user_api.RemoteBucketDetailsParams) (*models.RemoteBucket, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
}
adminClient := adminClient{client: mAdmin}
bucket, err := getRemoteBucket(ctx, adminClient, params.Name)
if err != nil {
log.Println("error getting remote bucket details:", err)
return nil, err
}
return bucket, nil
}
func getDeleteRemoteBucketResponse(session *models.Principal, params user_api.DeleteRemoteBucketParams) error {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
}
adminClient := adminClient{client: mAdmin}
err = deleteRemoteBucket(ctx, adminClient, params.SourceBucketName, params.Arn)
if err != nil {
log.Println("error deleting remote bucket: ", err)
return err
}
return err
}
func getAddRemoteBucketResponse(session *models.Principal, params user_api.AddRemoteBucketParams) error {
ctx := context.Background()
mAdmin, err := newMAdminClient(session)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
}
adminClient := adminClient{client: mAdmin}
err = addRemoteBucket(ctx, adminClient, *params.Body)
if err != nil {
log.Println("error adding remote bucket: ", err)
return err
}
return err
}
func listRemoteBuckets(ctx context.Context, client MinioAdmin) ([]*models.RemoteBucket, error) {
var remoteBuckets []*models.RemoteBucket
buckets, err := client.listRemoteBuckets(ctx, "", "")
if err != nil {
return nil, err
}
for _, bucket := range buckets {
remoteBucket := &models.RemoteBucket{
AccessKey: swag.String(bucket.Credentials.AccessKey),
RemoteARN: swag.String(bucket.Arn),
SecretKey: bucket.Credentials.SecretKey,
Service: "replication",
SourceBucket: swag.String(bucket.SourceBucket),
Status: "",
TargetBucket: bucket.TargetBucket,
TargetURL: bucket.Endpoint,
}
remoteBuckets = append(remoteBuckets, remoteBucket)
}
return remoteBuckets, nil
}
func getRemoteBucket(ctx context.Context, client MinioAdmin, name string) (*models.RemoteBucket, error) {
remoteBucket, err := client.getRemoteBucket(ctx, name, "")
if err != nil {
return nil, err
}
if remoteBucket == nil {
return nil, errors.New("bucket not found")
}
return &models.RemoteBucket{
AccessKey: &remoteBucket.Credentials.AccessKey,
RemoteARN: &remoteBucket.Arn,
SecretKey: remoteBucket.Credentials.SecretKey,
Service: "replication",
SourceBucket: &remoteBucket.SourceBucket,
Status: "",
TargetBucket: remoteBucket.TargetBucket,
TargetURL: remoteBucket.Endpoint,
}, nil
}
func deleteRemoteBucket(ctx context.Context, client MinioAdmin, sourceBucketName, arn string) error {
return client.removeRemoteBucket(ctx, sourceBucketName, arn)
}
func addRemoteBucket(ctx context.Context, client MinioAdmin, params models.CreateRemoteBucket) error {
TargetURL := *params.TargetURL
accessKey := *params.AccessKey
secretKey := *params.SecretKey
u, err := url.Parse(TargetURL)
if err != nil {
return errors.New("malformed Remote target URL")
}
secure := u.Scheme == "https"
host := u.Host
if u.Port() == "" {
port := 80
if secure {
port = 443
}
host = host + ":" + strconv.Itoa(port)
}
creds := &auth.Credentials{AccessKey: accessKey, SecretKey: secretKey}
remoteBucket := &madmin.BucketTarget{
TargetBucket: *params.TargetBucket,
Secure: secure,
Credentials: creds,
Endpoint: host,
Path: "",
API: "s3v4",
Type: "replication",
Region: params.Region,
}
_, err = client.addRemoteBucket(ctx, *params.SourceBucket, remoteBucket)
return err
}

View File

@@ -569,28 +569,28 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
}
}
isEncryptionAvailable := false
isEncryptionEnabled := false
if tenantReq.EnableTLS != nil && *tenantReq.EnableTLS {
// If user request autoCert, Operator will generate certificate keypair for MinIO (server), Console (server) and KES (server and app mTLS)
isEncryptionAvailable = true
isEncryptionEnabled = true
minInst.Spec.RequestAutoCert = *tenantReq.EnableTLS
}
if !minInst.Spec.RequestAutoCert && tenantReq.TLS != nil && tenantReq.TLS.Minio != nil {
if !minInst.Spec.RequestAutoCert && tenantReq.TLS != nil && len(tenantReq.TLS.Minio) > 0 {
// User provided TLS certificates for MinIO
isEncryptionAvailable = true
isEncryptionEnabled = true
// disable autoCert
minInst.Spec.RequestAutoCert = false
// Certificates used by the MinIO instance
externalCertSecretName := fmt.Sprintf("%s-instance-external-certificates", secretName)
externalCertSecret, err := createOrReplaceExternalCertSecret(ctx, &k8sClient, ns, tenantReq.TLS.Minio, externalCertSecretName, tenantName)
externalCertSecret, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, tenantReq.TLS.Minio, externalCertSecretName, tenantName)
if err != nil {
return nil, prepareError(err)
}
minInst.Spec.ExternalCertSecret = externalCertSecret
}
if tenantReq.Encryption != nil && isEncryptionAvailable {
if tenantReq.Encryption != nil && isEncryptionEnabled {
// Enable auto encryption
minInst.Spec.Env = append(minInst.Spec.Env, corev1.EnvVar{
Name: "MINIO_KMS_AUTO_ENCRYPTION",
@@ -599,16 +599,24 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
// KES client mTLSCertificates used by MinIO instance, only if autoCert is not enabled
if !minInst.Spec.RequestAutoCert {
tenantExternalClientCertSecretName := fmt.Sprintf("%s-tenant-external-client-cert", secretName)
minInst.Spec.ExternalClientCertSecret, err = createOrReplaceExternalCertSecret(ctx, &k8sClient, ns, tenantReq.Encryption.Client, tenantExternalClientCertSecretName, tenantName)
certificates := []*models.KeyPairConfiguration{tenantReq.Encryption.Client}
certificateSecrets, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, certificates, tenantExternalClientCertSecretName, tenantName)
if err != nil {
return nil, prepareError(errorGeneric)
}
if len(certificateSecrets) > 0 {
minInst.Spec.ExternalClientCertSecret = certificateSecrets[0]
}
}
// KES configuration for Tenant instance
minInst.Spec.KES, err = getKESConfiguration(ctx, &k8sClient, ns, tenantReq.Encryption, secretName, tenantName, minInst.Spec.RequestAutoCert)
if err != nil {
return nil, prepareError(errorGeneric)
}
// Set Labels, Annotations and Node Selector for KES
minInst.Spec.KES.Labels = tenantReq.Encryption.Labels
minInst.Spec.KES.Annotations = tenantReq.Encryption.Annotations
minInst.Spec.KES.NodeSelector = tenantReq.Encryption.NodeSelector
}
// optionals are set below
@@ -669,7 +677,7 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
return nil, prepareError(errorGeneric)
}
const consoleVersion = "minio/console:v0.3.25"
const consoleVersion = "minio/console:v0.4.0"
minInst.Spec.Console = &operator.ConsoleConfiguration{
Replicas: 1,
Image: consoleVersion,
@@ -683,11 +691,21 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
if !minInst.Spec.RequestAutoCert && tenantReq.TLS != nil && tenantReq.TLS.Console != nil {
// Certificates used by the console instance
externalCertSecretName := fmt.Sprintf("%s-console-external-certificates", secretName)
externalCertSecret, err := createOrReplaceExternalCertSecret(ctx, &k8sClient, ns, tenantReq.TLS.Console, externalCertSecretName, tenantName)
certificates := []*models.KeyPairConfiguration{tenantReq.TLS.Console}
externalCertSecret, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, certificates, externalCertSecretName, tenantName)
if err != nil {
return nil, prepareError(errorGeneric)
}
minInst.Spec.Console.ExternalCertSecret = externalCertSecret
if len(externalCertSecret) > 0 {
minInst.Spec.Console.ExternalCertSecret = externalCertSecret[0]
}
}
// Set Labels, Annotations and Node Selector for Console
if tenantReq.Console != nil {
minInst.Spec.Console.Annotations = tenantReq.Console.Annotations
minInst.Spec.Console.Labels = tenantReq.Console.Labels
minInst.Spec.Console.NodeSelector = tenantReq.Console.NodeSelector
}
}

View File

@@ -51,7 +51,7 @@ func tenantUpdateCertificates(ctx context.Context, operatorClient OperatorClient
if tenant.ExternalCert() && body.Minio != nil {
minioCertSecretName := fmt.Sprintf("%s-instance-external-certificates", secretName)
// update certificates
if _, err := createOrReplaceExternalCertSecret(ctx, clientSet, namespace, body.Minio, minioCertSecretName, tenantName); err != nil {
if _, err := createOrReplaceExternalCertSecrets(ctx, clientSet, namespace, body.Minio, minioCertSecretName, tenantName); err != nil {
return err
}
// restart MinIO pods
@@ -66,7 +66,8 @@ func tenantUpdateCertificates(ctx context.Context, operatorClient OperatorClient
if tenant.ConsoleExternalCert() && tenant.HasConsoleEnabled() && body.Console != nil {
consoleCertSecretName := fmt.Sprintf("%s-console-external-certificates", secretName)
// update certificates
if _, err := createOrReplaceExternalCertSecret(ctx, clientSet, namespace, body.Console, consoleCertSecretName, tenantName); err != nil {
certificates := []*models.KeyPairConfiguration{body.Console}
if _, err := createOrReplaceExternalCertSecrets(ctx, clientSet, namespace, certificates, consoleCertSecretName, tenantName); err != nil {
return err
}
// restart Console pods
@@ -119,7 +120,8 @@ func tenantUpdateEncryption(ctx context.Context, operatorClient OperatorClientI,
if tenant.KESExternalCert() && body.Server != nil {
kesExternalCertSecretName := fmt.Sprintf("%s-kes-external-cert", secretName)
// update certificates
if _, err := createOrReplaceExternalCertSecret(ctx, clientSet, namespace, body.Server, kesExternalCertSecretName, tenantName); err != nil {
certificates := []*models.KeyPairConfiguration{body.Server}
if _, err := createOrReplaceExternalCertSecrets(ctx, clientSet, namespace, certificates, kesExternalCertSecretName, tenantName); err != nil {
return err
}
}
@@ -127,7 +129,8 @@ func tenantUpdateEncryption(ctx context.Context, operatorClient OperatorClientI,
if tenant.ExternalClientCert() && body.Client != nil {
tenantExternalClientCertSecretName := fmt.Sprintf("%s-tenant-external-client-cert", secretName)
// Update certificates
if _, err := createOrReplaceExternalCertSecret(ctx, clientSet, namespace, body.Client, tenantExternalClientCertSecretName, tenantName); err != nil {
certificates := []*models.KeyPairConfiguration{body.Client}
if _, err := createOrReplaceExternalCertSecrets(ctx, clientSet, namespace, certificates, tenantExternalClientCertSecretName, tenantName); err != nil {
return err
}
// Restart MinIO pods to mount the new client secrets
@@ -195,7 +198,6 @@ func getKESConfiguration(ctx context.Context, clientSet K8sClientI, ns string, e
kesConfiguration = &operator.KESConfig{
Image: "minio/kes:v0.11.0",
Replicas: 1,
Metadata: nil,
}
// Using custom image for KES
if encryptionCfg.Image != "" {
@@ -203,12 +205,15 @@ func getKESConfiguration(ctx context.Context, clientSet K8sClientI, ns string, e
}
// Generate server certificates for KES only if autoCert is disabled
if !autoCert {
kesExternalCertSecret, err := createOrReplaceExternalCertSecret(ctx, clientSet, ns, encryptionCfg.Server, kesExternalCertSecretName, tenantName)
certificates := []*models.KeyPairConfiguration{encryptionCfg.Server}
certificateSecrets, err := createOrReplaceExternalCertSecrets(ctx, clientSet, ns, certificates, kesExternalCertSecretName, tenantName)
if err != nil {
return nil, err
}
// External TLS certificates used by KES
kesConfiguration.ExternalCertSecret = kesExternalCertSecret
if len(certificateSecrets) > 0 {
// External TLS certificates used by KES
kesConfiguration.ExternalCertSecret = certificateSecrets[0]
}
}
// Prepare kesConfiguration for KES
serverConfigSecret, clientCertSecret, err := createOrReplaceKesConfigurationSecrets(ctx, clientSet, ns, encryptionCfg, kesConfigurationSecretName, kesClientCertSecretName, tenantName)
@@ -222,50 +227,54 @@ func getKESConfiguration(ctx context.Context, clientSet K8sClientI, ns string, e
return kesConfiguration, nil
}
// createOrReplaceExternalCertSecret receives a keypair, public and private key, encoded in base64, decode it and generate a new kubernetes secret
// to be used by the operator for TLS encryption
func createOrReplaceExternalCertSecret(ctx context.Context, clientSet K8sClientI, ns string, keyPair *models.KeyPairConfiguration, secretName, tenantName string) (*operator.LocalCertificateReference, error) {
if keyPair == nil || keyPair.Crt == nil || keyPair.Key == nil || *keyPair.Crt == "" || *keyPair.Key == "" {
return nil, errors.New("certificate files must not be empty")
}
// delete secret with same name if exists
err := clientSet.deleteSecret(ctx, ns, secretName, metav1.DeleteOptions{})
if err != nil {
// log the error if any and continue
log.Println(err)
}
imm := true
tlsCrt, err := base64.StdEncoding.DecodeString(*keyPair.Crt)
if err != nil {
return nil, err
}
tlsKey, err := base64.StdEncoding.DecodeString(*keyPair.Key)
if err != nil {
return nil, err
}
externalTLSCertificateSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
// createOrReplaceExternalCertSecrets receives an array of KeyPairs (public and private key), encoded in base64, decode it and generate an equivalent number of kubernetes
// secrets to be used by the operator for TLS encryption
func createOrReplaceExternalCertSecrets(ctx context.Context, clientSet K8sClientI, ns string, keyPairs []*models.KeyPairConfiguration, secretName, tenantName string) ([]*operator.LocalCertificateReference, error) {
var keyPairSecrets []*operator.LocalCertificateReference
for _, keyPair := range keyPairs {
if keyPair == nil || keyPair.Crt == nil || keyPair.Key == nil || *keyPair.Crt == "" || *keyPair.Key == "" {
return nil, errors.New("certificate files must not be empty")
}
// delete secret with same name if exists
err := clientSet.deleteSecret(ctx, ns, secretName, metav1.DeleteOptions{})
if err != nil {
// log the error if any and continue
log.Println(err)
}
imm := true
tlsCrt, err := base64.StdEncoding.DecodeString(*keyPair.Crt)
if err != nil {
return nil, err
}
tlsKey, err := base64.StdEncoding.DecodeString(*keyPair.Key)
if err != nil {
return nil, err
}
externalTLSCertificateSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Labels: map[string]string{
operator.TenantLabel: tenantName,
},
},
},
Type: corev1.SecretTypeTLS,
Immutable: &imm,
Data: map[string][]byte{
"tls.crt": tlsCrt,
"tls.key": tlsKey,
},
Type: corev1.SecretTypeTLS,
Immutable: &imm,
Data: map[string][]byte{
"tls.crt": tlsCrt,
"tls.key": tlsKey,
},
}
_, err = clientSet.createSecret(ctx, ns, externalTLSCertificateSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
// Certificates used by the minio instance
keyPairSecrets = append(keyPairSecrets, &operator.LocalCertificateReference{
Name: secretName,
Type: "kubernetes.io/tls",
})
}
_, err = clientSet.createSecret(ctx, ns, externalTLSCertificateSecret, metav1.CreateOptions{})
if err != nil {
return nil, err
}
// Certificates used by the minio instance
return &operator.LocalCertificateReference{
Name: secretName,
Type: "kubernetes.io/tls",
}, nil
return keyPairSecrets, nil
}
func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sClientI, ns string, encryptionCfg *models.EncryptionConfiguration, kesConfigurationSecretName, kesClientCertSecretName, tenantName string) (*corev1.LocalObjectReference, *operator.LocalCertificateReference, error) {

View File

@@ -83,7 +83,7 @@ func Test_tenantUpdateCertificates(t *testing.T) {
wantErr: true,
},
{
name: "error replacing external certs for tenant because of missing keypair",
name: "error replacing external certs for tenant because of empty keypair values",
args: args{
ctx: context.Background(),
opClient: opClient,
@@ -91,14 +91,24 @@ func Test_tenantUpdateCertificates(t *testing.T) {
namespace: "",
params: admin_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Minio: &models.KeyPairConfiguration{},
Minio: []*models.KeyPairConfiguration{
{
Crt: nil,
Key: nil,
},
},
},
},
mockDeletePodCollection: func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
return nil
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return &operator.Tenant{
Spec: operator.TenantSpec{
ExternalCertSecret: &operator.LocalCertificateReference{
Name: "secret",
ExternalCertSecret: []*operator.LocalCertificateReference{
{
Name: "secret",
},
},
},
}, nil
@@ -115,17 +125,21 @@ func Test_tenantUpdateCertificates(t *testing.T) {
namespace: "",
params: admin_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Minio: &models.KeyPairConfiguration{
Crt: &badCrt,
Key: &badKey,
Minio: []*models.KeyPairConfiguration{
{
Crt: &badCrt,
Key: &badKey,
},
},
},
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return &operator.Tenant{
Spec: operator.TenantSpec{
ExternalCertSecret: &operator.LocalCertificateReference{
Name: "secret",
ExternalCertSecret: []*operator.LocalCertificateReference{
{
Name: "secret",
},
},
},
}, nil
@@ -145,17 +159,21 @@ func Test_tenantUpdateCertificates(t *testing.T) {
namespace: "",
params: admin_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Minio: &models.KeyPairConfiguration{
Crt: &crt,
Key: &key,
Minio: []*models.KeyPairConfiguration{
{
Crt: &crt,
Key: &key,
},
},
},
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return &operator.Tenant{
Spec: operator.TenantSpec{
ExternalCertSecret: &operator.LocalCertificateReference{
Name: "secret",
ExternalCertSecret: []*operator.LocalCertificateReference{
{
Name: "secret",
},
},
},
}, nil
@@ -178,17 +196,21 @@ func Test_tenantUpdateCertificates(t *testing.T) {
namespace: "",
params: admin_api.TenantUpdateCertificateParams{
Body: &models.TLSConfiguration{
Minio: &models.KeyPairConfiguration{
Crt: &crt,
Key: &key,
Minio: []*models.KeyPairConfiguration{
{
Crt: &crt,
Key: &key,
},
},
},
},
mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*operator.Tenant, error) {
return &operator.Tenant{
Spec: operator.TenantSpec{
ExternalCertSecret: &operator.LocalCertificateReference{
Name: "secret",
ExternalCertSecret: []*operator.LocalCertificateReference{
{
Name: "secret",
},
},
},
}, nil

View File

@@ -996,7 +996,7 @@ func Test_UpdateTenantAction(t *testing.T) {
},
params: admin_api.UpdateTenantParams{
Body: &models.UpdateTenantRequest{
ConsoleImage: "minio/console:v0.3.25",
ConsoleImage: "minio/console:v0.4.0",
},
},
},

View File

@@ -34,3 +34,16 @@ var minioServerInfoMock func(ctx context.Context) (madmin.InfoMessage, error)
func (ac adminClientMock) serverInfo(ctx context.Context) (madmin.InfoMessage, error) {
return minioServerInfoMock(ctx)
}
func (ac adminClientMock) listRemoteBuckets(ctx context.Context, bucket, arnType string) (targets []madmin.BucketTarget, err error) {
return nil, nil
}
func (ac adminClientMock) getRemoteBucket(ctx context.Context, bucket, arnType string) (targets *madmin.BucketTarget, err error) {
return nil, nil
}
func (ac adminClientMock) removeRemoteBucket(ctx context.Context, bucket, arn string) error {
return nil
}
func (ac adminClientMock) addRemoteBucket(ctx context.Context, bucket string, target *madmin.BucketTarget) (string, error) {
return "", nil
}

View File

@@ -97,6 +97,11 @@ type MinioAdmin interface {
addServiceAccount(ctx context.Context, policy *iampolicy.Policy) (mauth.Credentials, error)
listServiceAccounts(ctx context.Context) (madmin.ListServiceAccountsResp, error)
deleteServiceAccount(ctx context.Context, serviceAccount string) error
// Remote Buckets
listRemoteBuckets(ctx context.Context, bucket, arnType string) (targets []madmin.BucketTarget, err error)
getRemoteBucket(ctx context.Context, bucket, arnType string) (targets *madmin.BucketTarget, err error)
removeRemoteBucket(ctx context.Context, bucket, arn string) error
addRemoteBucket(ctx context.Context, bucket string, target *madmin.BucketTarget) (string, error)
}
// Interface implementation
@@ -247,6 +252,38 @@ func (ac adminClient) heal(ctx context.Context, bucket, prefix string, healOpts
return ac.client.Heal(ctx, bucket, prefix, healOpts, clientToken, forceStart, forceStop)
}
// listRemoteBuckets - return a list of remote buckets
func (ac adminClient) listRemoteBuckets(ctx context.Context, bucket, arnType string) (targets []madmin.BucketTarget, err error) {
return ac.client.ListRemoteTargets(ctx, bucket, arnType)
}
// getRemoteBucket - gets remote bucked based on a given bucket name
func (ac adminClient) getRemoteBucket(ctx context.Context, bucket, arnType string) (*madmin.BucketTarget, error) {
targets, err := ac.client.ListRemoteTargets(ctx, bucket, arnType)
if err != nil {
return nil, err
}
if len(targets) > 0 {
return &targets[0], nil
}
return nil, err
}
// removeRemoteBucket removes a remote target associated with particular ARN for this bucket
func (ac adminClient) removeRemoteBucket(ctx context.Context, bucket, arn string) error {
return ac.client.RemoveRemoteTarget(ctx, bucket, arn)
}
// addRemoteBucket sets up a remote target for this bucket
func (ac adminClient) addRemoteBucket(ctx context.Context, bucket string, target *madmin.BucketTarget) (string, error) {
return ac.client.SetRemoteTarget(ctx, bucket, target)
}
// addRemoteBucket sets up a remote target for this bucket
func (ac adminClient) setBucketQuota(ctx context.Context, bucket string, quota *madmin.BucketQuota) error {
return ac.client.SetBucketQuota(ctx, bucket, quota)
}
func newMAdminClient(sessionClaims *models.Principal) (*madmin.AdminClient, error) {
adminClient, err := newAdminFromClaims(sessionClaims)
if err != nil {

View File

@@ -21,6 +21,8 @@ import (
"fmt"
"strings"
"github.com/minio/minio-go/v7/pkg/replication"
"errors"
"github.com/minio/console/models"
@@ -51,6 +53,7 @@ type MinioClient interface {
removeBucket(ctx context.Context, bucketName string) error
getBucketNotification(ctx context.Context, bucketName string) (config notification.Configuration, err error)
getBucketPolicy(ctx context.Context, bucketName string) (string, error)
listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo
}
// Interface implementation
@@ -93,6 +96,26 @@ func (c minioClient) getBucketPolicy(ctx context.Context, bucketName string) (st
return c.client.GetBucketPolicy(ctx, bucketName)
}
// implements minio.enableVersioning(ctx, bucketName)
func (c minioClient) enableVersioning(ctx context.Context, bucketName string) error {
return c.client.EnableVersioning(ctx, bucketName)
}
// implements minio.getBucketVersioning(ctx, bucketName)
func (c minioClient) getBucketVersioning(ctx context.Context, bucketName string) (minio.BucketVersioningConfiguration, error) {
return c.client.GetBucketVersioning(ctx, bucketName)
}
// implements minio.getBucketVersioning(ctx, bucketName)
func (c minioClient) getBucketReplication(ctx context.Context, bucketName string) (replication.Config, error) {
return c.client.GetBucketReplication(ctx, bucketName)
}
// implements minio.listObjects(ctx)
func (c minioClient) listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
return c.client.ListObjects(ctx, bucket, opts)
}
// MCClient interface with all functions to be implemented
// by mock when testing, it should include all mc/S3Client respective api calls
// that are used within this project.
@@ -100,6 +123,8 @@ type MCClient interface {
addNotificationConfig(ctx context.Context, arn string, events []string, prefix, suffix string, ignoreExisting bool) *probe.Error
removeNotificationConfig(ctx context.Context, arn string, event string, prefix string, suffix string) *probe.Error
watch(ctx context.Context, options mc.WatchOptions) (*mc.WatchObject, *probe.Error)
remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass bool, contentCh <-chan *mc.ClientContent) <-chan *probe.Error
list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent
}
// Interface implementation
@@ -124,6 +149,18 @@ func (c mcClient) watch(ctx context.Context, options mc.WatchOptions) (*mc.Watch
return c.client.Watch(ctx, options)
}
func (c mcClient) setReplication(ctx context.Context, cfg *replication.Config, opts replication.Options) *probe.Error {
return c.client.SetReplication(ctx, cfg, opts)
}
func (c mcClient) remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass bool, contentCh <-chan *mc.ClientContent) <-chan *probe.Error {
return c.client.Remove(ctx, isIncomplete, isRemoveBucket, isBypass, contentCh)
}
func (c mcClient) list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent {
return c.client.List(ctx, opts)
}
// ConsoleCredentials interface with all functions to be implemented
// by mock when testing, it should include all needed consoleCredentials.Login api calls
// that are used within this project.
@@ -247,7 +284,7 @@ func newMinioClient(claims *models.Principal) (*minio.Client, error) {
}
// newS3BucketClient creates a new mc S3Client to talk to the server based on a bucket
func newS3BucketClient(claims *models.Principal, bucketName string) (*mc.S3Client, error) {
func newS3BucketClient(claims *models.Principal, bucketName string, prefix string) (*mc.S3Client, error) {
endpoint := getMinIOServer()
useTLS := getMinIOEndpointIsSecure()
@@ -255,6 +292,10 @@ func newS3BucketClient(claims *models.Principal, bucketName string) (*mc.S3Clien
endpoint += fmt.Sprintf("/%s", bucketName)
}
if strings.TrimSpace(prefix) != "" {
endpoint += fmt.Sprintf("/%s", prefix)
}
if claims == nil {
return nil, fmt.Errorf("the provided credentials are invalid")
}

View File

@@ -106,6 +106,8 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
registerAdminNotificationEndpointsHandlers(api)
// Register admin Service Account Handlers
registerServiceAccountsHandlers(api)
// Register admin remote buckets
registerAdminBucketRemoteHandlers(api)
// Operator Console
// Register tenant handlers
@@ -114,6 +116,12 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
registerResourceQuotaHandlers(api)
// Register Nodes' handlers
registerNodesHandlers(api)
// Register Parity' handlers
registerParityHandlers(api)
// Register Object's Handlers
registerObjectsHandlers(api)
// Register Bucket Quota's Handlers
registerBucketQuotaHandlers(api)
api.PreServerShutdown = func() {}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,90 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// GetParityHandlerFunc turns a function with the right signature into a get parity handler
type GetParityHandlerFunc func(GetParityParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn GetParityHandlerFunc) Handle(params GetParityParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// GetParityHandler interface for that can handle valid get parity params
type GetParityHandler interface {
Handle(GetParityParams, *models.Principal) middleware.Responder
}
// NewGetParity creates a new http.Handler for the get parity operation
func NewGetParity(ctx *middleware.Context, handler GetParityHandler) *GetParity {
return &GetParity{Context: ctx, Handler: handler}
}
/*GetParity swagger:route GET /get-parity/{nodes}/{disksPerNode} AdminAPI getParity
Gets parity by sending number of nodes & number of disks
*/
type GetParity struct {
Context *middleware.Context
Handler GetParityHandler
}
func (o *GetParity) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewGetParityParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
r = aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,154 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// NewGetParityParams creates a new GetParityParams object
// no default values defined in spec.
func NewGetParityParams() GetParityParams {
return GetParityParams{}
}
// GetParityParams contains all the bound params for the get parity operation
// typically these are obtained from a http.Request
//
// swagger:parameters GetParity
type GetParityParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
Minimum: 1
In: path
*/
DisksPerNode int64
/*
Required: true
Minimum: 2
In: path
*/
Nodes int64
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewGetParityParams() beforehand.
func (o *GetParityParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
rDisksPerNode, rhkDisksPerNode, _ := route.Params.GetOK("disksPerNode")
if err := o.bindDisksPerNode(rDisksPerNode, rhkDisksPerNode, route.Formats); err != nil {
res = append(res, err)
}
rNodes, rhkNodes, _ := route.Params.GetOK("nodes")
if err := o.bindNodes(rNodes, rhkNodes, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindDisksPerNode binds and validates parameter DisksPerNode from path.
func (o *GetParityParams) bindDisksPerNode(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
value, err := swag.ConvertInt64(raw)
if err != nil {
return errors.InvalidType("disksPerNode", "path", "int64", raw)
}
o.DisksPerNode = value
if err := o.validateDisksPerNode(formats); err != nil {
return err
}
return nil
}
// validateDisksPerNode carries on validations for parameter DisksPerNode
func (o *GetParityParams) validateDisksPerNode(formats strfmt.Registry) error {
if err := validate.MinimumInt("disksPerNode", "path", int64(o.DisksPerNode), 1, false); err != nil {
return err
}
return nil
}
// bindNodes binds and validates parameter Nodes from path.
func (o *GetParityParams) bindNodes(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
value, err := swag.ConvertInt64(raw)
if err != nil {
return errors.InvalidType("nodes", "path", "int64", raw)
}
o.Nodes = value
if err := o.validateNodes(formats); err != nil {
return err
}
return nil
}
// validateNodes carries on validations for parameter Nodes
func (o *GetParityParams) validateNodes(formats strfmt.Registry) error {
if err := validate.MinimumInt("nodes", "path", int64(o.Nodes), 2, false); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,136 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// GetParityOKCode is the HTTP code returned for type GetParityOK
const GetParityOKCode int = 200
/*GetParityOK A successful response.
swagger:response getParityOK
*/
type GetParityOK struct {
/*
In: Body
*/
Payload models.ParityResponse `json:"body,omitempty"`
}
// NewGetParityOK creates GetParityOK with default headers values
func NewGetParityOK() *GetParityOK {
return &GetParityOK{}
}
// WithPayload adds the payload to the get parity o k response
func (o *GetParityOK) WithPayload(payload models.ParityResponse) *GetParityOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get parity o k response
func (o *GetParityOK) SetPayload(payload models.ParityResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetParityOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
payload := o.Payload
if payload == nil {
// return empty array
payload = models.ParityResponse{}
}
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
/*GetParityDefault Generic error response.
swagger:response getParityDefault
*/
type GetParityDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewGetParityDefault creates GetParityDefault with default headers values
func NewGetParityDefault(code int) *GetParityDefault {
if code <= 0 {
code = 500
}
return &GetParityDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the get parity default response
func (o *GetParityDefault) WithStatusCode(code int) *GetParityDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the get parity default response
func (o *GetParityDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the get parity default response
func (o *GetParityDefault) WithPayload(payload *models.Error) *GetParityDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get parity default response
func (o *GetParityDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetParityDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,126 @@
// 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 admin_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
"github.com/go-openapi/swag"
)
// GetParityURL generates an URL for the get parity operation
type GetParityURL struct {
DisksPerNode int64
Nodes int64
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetParityURL) WithBasePath(bp string) *GetParityURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetParityURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *GetParityURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/get-parity/{nodes}/{disksPerNode}"
disksPerNode := swag.FormatInt64(o.DisksPerNode)
if disksPerNode != "" {
_path = strings.Replace(_path, "{disksPerNode}", disksPerNode, -1)
} else {
return nil, errors.New("disksPerNode is required on GetParityURL")
}
nodes := swag.FormatInt64(o.Nodes)
if nodes != "" {
_path = strings.Replace(_path, "{nodes}", nodes, -1)
} else {
return nil, errors.New("nodes is required on GetParityURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *GetParityURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *GetParityURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *GetParityURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on GetParityURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on GetParityURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *GetParityURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -63,6 +63,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
BinProducer: runtime.ByteStreamProducer(),
JSONProducer: runtime.JSONProducer(),
UserAPIAddBucketReplicationHandler: user_api.AddBucketReplicationHandlerFunc(func(params user_api.AddBucketReplicationParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.AddBucketReplication has not yet been implemented")
}),
AdminAPIAddGroupHandler: admin_api.AddGroupHandlerFunc(func(params admin_api.AddGroupParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.AddGroup has not yet been implemented")
}),
@@ -72,6 +75,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
AdminAPIAddPolicyHandler: admin_api.AddPolicyHandlerFunc(func(params admin_api.AddPolicyParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.AddPolicy has not yet been implemented")
}),
UserAPIAddRemoteBucketHandler: user_api.AddRemoteBucketHandlerFunc(func(params user_api.AddRemoteBucketParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.AddRemoteBucket has not yet been implemented")
}),
AdminAPIAddUserHandler: admin_api.AddUserHandlerFunc(func(params admin_api.AddUserParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.AddUser has not yet been implemented")
}),
@@ -108,15 +114,33 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
UserAPIDeleteBucketEventHandler: user_api.DeleteBucketEventHandlerFunc(func(params user_api.DeleteBucketEventParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.DeleteBucketEvent has not yet been implemented")
}),
UserAPIDeleteObjectHandler: user_api.DeleteObjectHandlerFunc(func(params user_api.DeleteObjectParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.DeleteObject has not yet been implemented")
}),
UserAPIDeleteRemoteBucketHandler: user_api.DeleteRemoteBucketHandlerFunc(func(params user_api.DeleteRemoteBucketParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.DeleteRemoteBucket has not yet been implemented")
}),
UserAPIDeleteServiceAccountHandler: user_api.DeleteServiceAccountHandlerFunc(func(params user_api.DeleteServiceAccountParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.DeleteServiceAccount has not yet been implemented")
}),
AdminAPIDeleteTenantHandler: admin_api.DeleteTenantHandlerFunc(func(params admin_api.DeleteTenantParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.DeleteTenant has not yet been implemented")
}),
UserAPIGetBucketQuotaHandler: user_api.GetBucketQuotaHandlerFunc(func(params user_api.GetBucketQuotaParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.GetBucketQuota has not yet been implemented")
}),
UserAPIGetBucketReplicationHandler: user_api.GetBucketReplicationHandlerFunc(func(params user_api.GetBucketReplicationParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.GetBucketReplication has not yet been implemented")
}),
UserAPIGetBucketVersioningHandler: user_api.GetBucketVersioningHandlerFunc(func(params user_api.GetBucketVersioningParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.GetBucketVersioning has not yet been implemented")
}),
AdminAPIGetMaxAllocatableMemHandler: admin_api.GetMaxAllocatableMemHandlerFunc(func(params admin_api.GetMaxAllocatableMemParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.GetMaxAllocatableMem has not yet been implemented")
}),
AdminAPIGetParityHandler: admin_api.GetParityHandlerFunc(func(params admin_api.GetParityParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.GetParity has not yet been implemented")
}),
AdminAPIGetResourceQuotaHandler: admin_api.GetResourceQuotaHandlerFunc(func(params admin_api.GetResourceQuotaParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.GetResourceQuota has not yet been implemented")
}),
@@ -144,9 +168,15 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
AdminAPIListGroupsHandler: admin_api.ListGroupsHandlerFunc(func(params admin_api.ListGroupsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.ListGroups has not yet been implemented")
}),
UserAPIListObjectsHandler: user_api.ListObjectsHandlerFunc(func(params user_api.ListObjectsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.ListObjects has not yet been implemented")
}),
AdminAPIListPoliciesHandler: admin_api.ListPoliciesHandlerFunc(func(params admin_api.ListPoliciesParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.ListPolicies has not yet been implemented")
}),
UserAPIListRemoteBucketsHandler: user_api.ListRemoteBucketsHandlerFunc(func(params user_api.ListRemoteBucketsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.ListRemoteBuckets has not yet been implemented")
}),
AdminAPIListTenantsHandler: admin_api.ListTenantsHandlerFunc(func(params admin_api.ListTenantsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.ListTenants has not yet been implemented")
}),
@@ -186,6 +216,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
AdminAPIProfilingStopHandler: admin_api.ProfilingStopHandlerFunc(func(params admin_api.ProfilingStopParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.ProfilingStop has not yet been implemented")
}),
UserAPIRemoteBucketDetailsHandler: user_api.RemoteBucketDetailsHandlerFunc(func(params user_api.RemoteBucketDetailsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.RemoteBucketDetails has not yet been implemented")
}),
AdminAPIRemoveGroupHandler: admin_api.RemoveGroupHandlerFunc(func(params admin_api.RemoveGroupParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.RemoveGroup has not yet been implemented")
}),
@@ -201,6 +234,12 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
UserAPISessionCheckHandler: user_api.SessionCheckHandlerFunc(func(params user_api.SessionCheckParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.SessionCheck has not yet been implemented")
}),
UserAPISetBucketQuotaHandler: user_api.SetBucketQuotaHandlerFunc(func(params user_api.SetBucketQuotaParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.SetBucketQuota has not yet been implemented")
}),
UserAPISetBucketVersioningHandler: user_api.SetBucketVersioningHandlerFunc(func(params user_api.SetBucketVersioningParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.SetBucketVersioning has not yet been implemented")
}),
AdminAPISetConfigHandler: admin_api.SetConfigHandlerFunc(func(params admin_api.SetConfigParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.SetConfig has not yet been implemented")
}),
@@ -283,12 +322,16 @@ type ConsoleAPI struct {
// APIAuthorizer provides access control (ACL/RBAC/ABAC) by providing access to the request and authenticated principal
APIAuthorizer runtime.Authorizer
// UserAPIAddBucketReplicationHandler sets the operation handler for the add bucket replication operation
UserAPIAddBucketReplicationHandler user_api.AddBucketReplicationHandler
// AdminAPIAddGroupHandler sets the operation handler for the add group operation
AdminAPIAddGroupHandler admin_api.AddGroupHandler
// AdminAPIAddNotificationEndpointHandler sets the operation handler for the add notification endpoint operation
AdminAPIAddNotificationEndpointHandler admin_api.AddNotificationEndpointHandler
// AdminAPIAddPolicyHandler sets the operation handler for the add policy operation
AdminAPIAddPolicyHandler admin_api.AddPolicyHandler
// UserAPIAddRemoteBucketHandler sets the operation handler for the add remote bucket operation
UserAPIAddRemoteBucketHandler user_api.AddRemoteBucketHandler
// AdminAPIAddUserHandler sets the operation handler for the add user operation
AdminAPIAddUserHandler admin_api.AddUserHandler
// AdminAPIAdminInfoHandler sets the operation handler for the admin info operation
@@ -313,12 +356,24 @@ type ConsoleAPI struct {
UserAPIDeleteBucketHandler user_api.DeleteBucketHandler
// UserAPIDeleteBucketEventHandler sets the operation handler for the delete bucket event operation
UserAPIDeleteBucketEventHandler user_api.DeleteBucketEventHandler
// UserAPIDeleteObjectHandler sets the operation handler for the delete object operation
UserAPIDeleteObjectHandler user_api.DeleteObjectHandler
// UserAPIDeleteRemoteBucketHandler sets the operation handler for the delete remote bucket operation
UserAPIDeleteRemoteBucketHandler user_api.DeleteRemoteBucketHandler
// UserAPIDeleteServiceAccountHandler sets the operation handler for the delete service account operation
UserAPIDeleteServiceAccountHandler user_api.DeleteServiceAccountHandler
// AdminAPIDeleteTenantHandler sets the operation handler for the delete tenant operation
AdminAPIDeleteTenantHandler admin_api.DeleteTenantHandler
// UserAPIGetBucketQuotaHandler sets the operation handler for the get bucket quota operation
UserAPIGetBucketQuotaHandler user_api.GetBucketQuotaHandler
// UserAPIGetBucketReplicationHandler sets the operation handler for the get bucket replication operation
UserAPIGetBucketReplicationHandler user_api.GetBucketReplicationHandler
// UserAPIGetBucketVersioningHandler sets the operation handler for the get bucket versioning operation
UserAPIGetBucketVersioningHandler user_api.GetBucketVersioningHandler
// AdminAPIGetMaxAllocatableMemHandler sets the operation handler for the get max allocatable mem operation
AdminAPIGetMaxAllocatableMemHandler admin_api.GetMaxAllocatableMemHandler
// AdminAPIGetParityHandler sets the operation handler for the get parity operation
AdminAPIGetParityHandler admin_api.GetParityHandler
// AdminAPIGetResourceQuotaHandler sets the operation handler for the get resource quota operation
AdminAPIGetResourceQuotaHandler admin_api.GetResourceQuotaHandler
// AdminAPIGetTenantUsageHandler sets the operation handler for the get tenant usage operation
@@ -337,8 +392,12 @@ type ConsoleAPI struct {
AdminAPIListConfigHandler admin_api.ListConfigHandler
// AdminAPIListGroupsHandler sets the operation handler for the list groups operation
AdminAPIListGroupsHandler admin_api.ListGroupsHandler
// UserAPIListObjectsHandler sets the operation handler for the list objects operation
UserAPIListObjectsHandler user_api.ListObjectsHandler
// AdminAPIListPoliciesHandler sets the operation handler for the list policies operation
AdminAPIListPoliciesHandler admin_api.ListPoliciesHandler
// UserAPIListRemoteBucketsHandler sets the operation handler for the list remote buckets operation
UserAPIListRemoteBucketsHandler user_api.ListRemoteBucketsHandler
// AdminAPIListTenantsHandler sets the operation handler for the list tenants operation
AdminAPIListTenantsHandler admin_api.ListTenantsHandler
// UserAPIListUserServiceAccountsHandler sets the operation handler for the list user service accounts operation
@@ -365,6 +424,8 @@ type ConsoleAPI struct {
AdminAPIProfilingStartHandler admin_api.ProfilingStartHandler
// AdminAPIProfilingStopHandler sets the operation handler for the profiling stop operation
AdminAPIProfilingStopHandler admin_api.ProfilingStopHandler
// UserAPIRemoteBucketDetailsHandler sets the operation handler for the remote bucket details operation
UserAPIRemoteBucketDetailsHandler user_api.RemoteBucketDetailsHandler
// AdminAPIRemoveGroupHandler sets the operation handler for the remove group operation
AdminAPIRemoveGroupHandler admin_api.RemoveGroupHandler
// AdminAPIRemovePolicyHandler sets the operation handler for the remove policy operation
@@ -375,6 +436,10 @@ type ConsoleAPI struct {
AdminAPIRestartServiceHandler admin_api.RestartServiceHandler
// UserAPISessionCheckHandler sets the operation handler for the session check operation
UserAPISessionCheckHandler user_api.SessionCheckHandler
// UserAPISetBucketQuotaHandler sets the operation handler for the set bucket quota operation
UserAPISetBucketQuotaHandler user_api.SetBucketQuotaHandler
// UserAPISetBucketVersioningHandler sets the operation handler for the set bucket versioning operation
UserAPISetBucketVersioningHandler user_api.SetBucketVersioningHandler
// AdminAPISetConfigHandler sets the operation handler for the set config operation
AdminAPISetConfigHandler admin_api.SetConfigHandler
// AdminAPISetPolicyHandler sets the operation handler for the set policy operation
@@ -470,6 +535,9 @@ func (o *ConsoleAPI) Validate() error {
unregistered = append(unregistered, "KeyAuth")
}
if o.UserAPIAddBucketReplicationHandler == nil {
unregistered = append(unregistered, "user_api.AddBucketReplicationHandler")
}
if o.AdminAPIAddGroupHandler == nil {
unregistered = append(unregistered, "admin_api.AddGroupHandler")
}
@@ -479,6 +547,9 @@ func (o *ConsoleAPI) Validate() error {
if o.AdminAPIAddPolicyHandler == nil {
unregistered = append(unregistered, "admin_api.AddPolicyHandler")
}
if o.UserAPIAddRemoteBucketHandler == nil {
unregistered = append(unregistered, "user_api.AddRemoteBucketHandler")
}
if o.AdminAPIAddUserHandler == nil {
unregistered = append(unregistered, "admin_api.AddUserHandler")
}
@@ -515,15 +586,33 @@ func (o *ConsoleAPI) Validate() error {
if o.UserAPIDeleteBucketEventHandler == nil {
unregistered = append(unregistered, "user_api.DeleteBucketEventHandler")
}
if o.UserAPIDeleteObjectHandler == nil {
unregistered = append(unregistered, "user_api.DeleteObjectHandler")
}
if o.UserAPIDeleteRemoteBucketHandler == nil {
unregistered = append(unregistered, "user_api.DeleteRemoteBucketHandler")
}
if o.UserAPIDeleteServiceAccountHandler == nil {
unregistered = append(unregistered, "user_api.DeleteServiceAccountHandler")
}
if o.AdminAPIDeleteTenantHandler == nil {
unregistered = append(unregistered, "admin_api.DeleteTenantHandler")
}
if o.UserAPIGetBucketQuotaHandler == nil {
unregistered = append(unregistered, "user_api.GetBucketQuotaHandler")
}
if o.UserAPIGetBucketReplicationHandler == nil {
unregistered = append(unregistered, "user_api.GetBucketReplicationHandler")
}
if o.UserAPIGetBucketVersioningHandler == nil {
unregistered = append(unregistered, "user_api.GetBucketVersioningHandler")
}
if o.AdminAPIGetMaxAllocatableMemHandler == nil {
unregistered = append(unregistered, "admin_api.GetMaxAllocatableMemHandler")
}
if o.AdminAPIGetParityHandler == nil {
unregistered = append(unregistered, "admin_api.GetParityHandler")
}
if o.AdminAPIGetResourceQuotaHandler == nil {
unregistered = append(unregistered, "admin_api.GetResourceQuotaHandler")
}
@@ -551,9 +640,15 @@ func (o *ConsoleAPI) Validate() error {
if o.AdminAPIListGroupsHandler == nil {
unregistered = append(unregistered, "admin_api.ListGroupsHandler")
}
if o.UserAPIListObjectsHandler == nil {
unregistered = append(unregistered, "user_api.ListObjectsHandler")
}
if o.AdminAPIListPoliciesHandler == nil {
unregistered = append(unregistered, "admin_api.ListPoliciesHandler")
}
if o.UserAPIListRemoteBucketsHandler == nil {
unregistered = append(unregistered, "user_api.ListRemoteBucketsHandler")
}
if o.AdminAPIListTenantsHandler == nil {
unregistered = append(unregistered, "admin_api.ListTenantsHandler")
}
@@ -593,6 +688,9 @@ func (o *ConsoleAPI) Validate() error {
if o.AdminAPIProfilingStopHandler == nil {
unregistered = append(unregistered, "admin_api.ProfilingStopHandler")
}
if o.UserAPIRemoteBucketDetailsHandler == nil {
unregistered = append(unregistered, "user_api.RemoteBucketDetailsHandler")
}
if o.AdminAPIRemoveGroupHandler == nil {
unregistered = append(unregistered, "admin_api.RemoveGroupHandler")
}
@@ -608,6 +706,12 @@ func (o *ConsoleAPI) Validate() error {
if o.UserAPISessionCheckHandler == nil {
unregistered = append(unregistered, "user_api.SessionCheckHandler")
}
if o.UserAPISetBucketQuotaHandler == nil {
unregistered = append(unregistered, "user_api.SetBucketQuotaHandler")
}
if o.UserAPISetBucketVersioningHandler == nil {
unregistered = append(unregistered, "user_api.SetBucketVersioningHandler")
}
if o.AdminAPISetConfigHandler == nil {
unregistered = append(unregistered, "admin_api.SetConfigHandler")
}
@@ -741,6 +845,10 @@ func (o *ConsoleAPI) initHandlerCache() {
o.handlers = make(map[string]map[string]http.Handler)
}
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/buckets/{bucket_name}/replication"] = user_api.NewAddBucketReplication(o.context, o.UserAPIAddBucketReplicationHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
@@ -756,6 +864,10 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/remote-buckets"] = user_api.NewAddRemoteBucket(o.context, o.UserAPIAddRemoteBucketHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/users"] = admin_api.NewAddUser(o.context, o.AdminAPIAddUserHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
@@ -804,6 +916,14 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["DELETE"] == nil {
o.handlers["DELETE"] = make(map[string]http.Handler)
}
o.handlers["DELETE"]["/buckets/{bucket_name}/objects"] = user_api.NewDeleteObject(o.context, o.UserAPIDeleteObjectHandler)
if o.handlers["DELETE"] == nil {
o.handlers["DELETE"] = make(map[string]http.Handler)
}
o.handlers["DELETE"]["/remote-buckets/{source-bucket-name}/{arn}"] = user_api.NewDeleteRemoteBucket(o.context, o.UserAPIDeleteRemoteBucketHandler)
if o.handlers["DELETE"] == nil {
o.handlers["DELETE"] = make(map[string]http.Handler)
}
o.handlers["DELETE"]["/service-accounts/{access_key}"] = user_api.NewDeleteServiceAccount(o.context, o.UserAPIDeleteServiceAccountHandler)
if o.handlers["DELETE"] == nil {
o.handlers["DELETE"] = make(map[string]http.Handler)
@@ -812,10 +932,26 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{name}/quota"] = user_api.NewGetBucketQuota(o.context, o.UserAPIGetBucketQuotaHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{bucket_name}/replication"] = user_api.NewGetBucketReplication(o.context, o.UserAPIGetBucketReplicationHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{bucket_name}/versioning"] = user_api.NewGetBucketVersioning(o.context, o.UserAPIGetBucketVersioningHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/cluster/max-allocatable-memory"] = admin_api.NewGetMaxAllocatableMem(o.context, o.AdminAPIGetMaxAllocatableMemHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/get-parity/{nodes}/{disksPerNode}"] = admin_api.NewGetParity(o.context, o.AdminAPIGetParityHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/namespaces/{namespace}/resourcequotas/{resource-quota-name}"] = admin_api.NewGetResourceQuota(o.context, o.AdminAPIGetResourceQuotaHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
@@ -852,10 +988,18 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/buckets/{bucket_name}/objects"] = user_api.NewListObjects(o.context, o.UserAPIListObjectsHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/policies"] = admin_api.NewListPolicies(o.context, o.AdminAPIListPoliciesHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/remote-buckets"] = user_api.NewListRemoteBuckets(o.context, o.UserAPIListRemoteBucketsHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/namespaces/{namespace}/tenants"] = admin_api.NewListTenants(o.context, o.AdminAPIListTenantsHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
@@ -905,6 +1049,10 @@ func (o *ConsoleAPI) initHandlerCache() {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/profiling/stop"] = admin_api.NewProfilingStop(o.context, o.AdminAPIProfilingStopHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/remote-buckets/{name}"] = user_api.NewRemoteBucketDetails(o.context, o.UserAPIRemoteBucketDetailsHandler)
if o.handlers["DELETE"] == nil {
o.handlers["DELETE"] = make(map[string]http.Handler)
}
@@ -928,6 +1076,14 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/buckets/{name}/quota"] = user_api.NewSetBucketQuota(o.context, o.UserAPISetBucketQuotaHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/buckets/{bucket_name}/versioning"] = user_api.NewSetBucketVersioning(o.context, o.UserAPISetBucketVersioningHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/configs/{name}"] = admin_api.NewSetConfig(o.context, o.AdminAPISetConfigHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)

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