Compare commits

..

4 Commits

Author SHA1 Message Date
Minio Trusted
16647b88e9 update to v0.7.4 2021-05-27 16:32:21 -07:00
Daniel Valdivia
59c4ac6336 Add Sync, Bandwidth and Health Check Period to replication set (#771)
* Add Sync, Bandwidth and Health Check Period to replication set

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

* Update Columns

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

* Add Prefix and Tags

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

* Last fields

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2021-05-27 16:25:00 -07:00
Minio Trusted
49f0b83613 instead of go get use 'go install' 2021-05-26 10:54:27 -07:00
Alex
622f9ed3cd Added URl routing for tabs in buckets (#769)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2021-05-25 16:16:15 -07:00
44 changed files with 2299 additions and 1213 deletions

View File

@@ -55,7 +55,7 @@ docker pull minio/console
### Build from source
```
GO111MODULE=on go get github.com/minio/console/cmd/console
GO111MODULE=on go install github.com/minio/console/cmd/console@latest
```
> You will need a working Go environment. Therefore, please follow [How to install Go](https://golang.org/doc/install).
> Minimum version required is go1.16

View File

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

View File

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

View File

@@ -36,31 +36,49 @@ import (
// swagger:model bucketReplicationRule
type BucketReplicationRule struct {
// bandwidth
Bandwidth string `json:"bandwidth,omitempty"`
// delete marker replication
DeleteMarkerReplication *BucketReplicationRuleMarker `json:"delete_marker_replication,omitempty"`
DeleteMarkerReplication bool `json:"delete_marker_replication,omitempty"`
// deletes replication
DeletesReplication bool `json:"deletes_replication,omitempty"`
// destination
Destination *BucketReplicationDestination `json:"destination,omitempty"`
// health check period
HealthCheckPeriod int64 `json:"healthCheckPeriod,omitempty"`
// id
ID string `json:"id,omitempty"`
// metadata replication
MetadataReplication bool `json:"metadata_replication,omitempty"`
// prefix
Prefix string `json:"prefix,omitempty"`
// priority
Priority int32 `json:"priority,omitempty"`
// status
// Enum: [Enabled Disabled]
Status string `json:"status,omitempty"`
// sync mode
// Enum: [async sync]
SyncMode *string `json:"syncMode,omitempty"`
// tags
Tags string `json:"tags,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)
}
@@ -69,30 +87,16 @@ func (m *BucketReplicationRule) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateSyncMode(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
@@ -154,6 +158,49 @@ func (m *BucketReplicationRule) validateStatus(formats strfmt.Registry) error {
return nil
}
var bucketReplicationRuleTypeSyncModePropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["async","sync"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
bucketReplicationRuleTypeSyncModePropEnum = append(bucketReplicationRuleTypeSyncModePropEnum, v)
}
}
const (
// BucketReplicationRuleSyncModeAsync captures enum value "async"
BucketReplicationRuleSyncModeAsync string = "async"
// BucketReplicationRuleSyncModeSync captures enum value "sync"
BucketReplicationRuleSyncModeSync string = "sync"
)
// prop value enum
func (m *BucketReplicationRule) validateSyncModeEnum(path, location string, value string) error {
if err := validate.EnumCase(path, location, value, bucketReplicationRuleTypeSyncModePropEnum, true); err != nil {
return err
}
return nil
}
func (m *BucketReplicationRule) validateSyncMode(formats strfmt.Registry) error {
if swag.IsZero(m.SyncMode) { // not required
return nil
}
// value enum
if err := m.validateSyncModeEnum("syncMode", "body", *m.SyncMode); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *BucketReplicationRule) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -1,117 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 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

@@ -23,6 +23,8 @@ package models
// 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"
@@ -39,6 +41,12 @@ type CreateRemoteBucket struct {
// Min Length: 3
AccessKey *string `json:"accessKey"`
// bandwidth
Bandwidth int64 `json:"bandwidth,omitempty"`
// health check period
HealthCheckPeriod int32 `json:"healthCheckPeriod,omitempty"`
// region
Region string `json:"region,omitempty"`
@@ -51,6 +59,10 @@ type CreateRemoteBucket struct {
// Required: true
SourceBucket *string `json:"sourceBucket"`
// sync mode
// Enum: [async sync]
SyncMode *string `json:"syncMode,omitempty"`
// target bucket
// Required: true
TargetBucket *string `json:"targetBucket"`
@@ -76,6 +88,10 @@ func (m *CreateRemoteBucket) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateSyncMode(formats); err != nil {
res = append(res, err)
}
if err := m.validateTargetBucket(formats); err != nil {
res = append(res, err)
}
@@ -125,6 +141,49 @@ func (m *CreateRemoteBucket) validateSourceBucket(formats strfmt.Registry) error
return nil
}
var createRemoteBucketTypeSyncModePropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["async","sync"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
createRemoteBucketTypeSyncModePropEnum = append(createRemoteBucketTypeSyncModePropEnum, v)
}
}
const (
// CreateRemoteBucketSyncModeAsync captures enum value "async"
CreateRemoteBucketSyncModeAsync string = "async"
// CreateRemoteBucketSyncModeSync captures enum value "sync"
CreateRemoteBucketSyncModeSync string = "sync"
)
// prop value enum
func (m *CreateRemoteBucket) validateSyncModeEnum(path, location string, value string) error {
if err := validate.EnumCase(path, location, value, createRemoteBucketTypeSyncModePropEnum, true); err != nil {
return err
}
return nil
}
func (m *CreateRemoteBucket) validateSyncMode(formats strfmt.Registry) error {
if swag.IsZero(m.SyncMode) { // not required
return nil
}
// value enum
if err := m.validateSyncModeEnum("syncMode", "body", *m.SyncMode); err != nil {
return err
}
return nil
}
func (m *CreateRemoteBucket) validateTargetBucket(formats strfmt.Registry) error {
if err := validate.Required("targetBucket", "body", m.TargetBucket); err != nil {

View File

@@ -23,6 +23,7 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"strconv"
"github.com/go-openapi/errors"
@@ -41,19 +42,44 @@ type MultiBucketReplication struct {
// Min Length: 3
AccessKey *string `json:"accessKey"`
// bandwidth
Bandwidth int64 `json:"bandwidth,omitempty"`
// buckets relation
// Required: true
// Min Length: 1
BucketsRelation []*MultiBucketsRelation `json:"bucketsRelation"`
// health check period
HealthCheckPeriod int32 `json:"healthCheckPeriod,omitempty"`
// prefix
Prefix string `json:"prefix,omitempty"`
// region
Region string `json:"region,omitempty"`
// replicate delete markers
ReplicateDeleteMarkers bool `json:"replicateDeleteMarkers,omitempty"`
// replicate deletes
ReplicateDeletes bool `json:"replicateDeletes,omitempty"`
// replicate metadata
ReplicateMetadata bool `json:"replicateMetadata,omitempty"`
// secret key
// Required: true
// Min Length: 8
SecretKey *string `json:"secretKey"`
// sync mode
// Enum: [async sync]
SyncMode *string `json:"syncMode,omitempty"`
// tags
Tags string `json:"tags,omitempty"`
// target URL
// Required: true
TargetURL *string `json:"targetURL"`
@@ -75,6 +101,10 @@ func (m *MultiBucketReplication) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateSyncMode(formats); err != nil {
res = append(res, err)
}
if err := m.validateTargetURL(formats); err != nil {
res = append(res, err)
}
@@ -136,6 +166,49 @@ func (m *MultiBucketReplication) validateSecretKey(formats strfmt.Registry) erro
return nil
}
var multiBucketReplicationTypeSyncModePropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["async","sync"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
multiBucketReplicationTypeSyncModePropEnum = append(multiBucketReplicationTypeSyncModePropEnum, v)
}
}
const (
// MultiBucketReplicationSyncModeAsync captures enum value "async"
MultiBucketReplicationSyncModeAsync string = "async"
// MultiBucketReplicationSyncModeSync captures enum value "sync"
MultiBucketReplicationSyncModeSync string = "sync"
)
// prop value enum
func (m *MultiBucketReplication) validateSyncModeEnum(path, location string, value string) error {
if err := validate.EnumCase(path, location, value, multiBucketReplicationTypeSyncModePropEnum, true); err != nil {
return err
}
return nil
}
func (m *MultiBucketReplication) validateSyncMode(formats strfmt.Registry) error {
if swag.IsZero(m.SyncMode) { // not required
return nil
}
// value enum
if err := m.validateSyncModeEnum("syncMode", "body", *m.SyncMode); err != nil {
return err
}
return nil
}
func (m *MultiBucketReplication) validateTargetURL(formats strfmt.Registry) error {
if err := validate.Required("targetURL", "body", m.TargetURL); err != nil {

View File

@@ -41,6 +41,12 @@ type RemoteBucket struct {
// Min Length: 3
AccessKey *string `json:"accessKey"`
// bandwidth
Bandwidth int64 `json:"bandwidth,omitempty"`
// health check period
HealthCheckPeriod int64 `json:"healthCheckPeriod,omitempty"`
// remote a r n
// Required: true
RemoteARN *string `json:"remoteARN"`
@@ -60,6 +66,9 @@ type RemoteBucket struct {
// status
Status string `json:"status,omitempty"`
// sync mode
SyncMode string `json:"syncMode,omitempty"`
// target bucket
TargetBucket string `json:"targetBucket,omitempty"`

View File

@@ -22,34 +22,41 @@ import (
// endpoints definition
var (
configuration = "/settings"
users = "/users"
usersDetail = "/users/:userName"
groups = "/groups"
iamPolicies = "/policies"
policiesDetail = "/policies/:policyName"
dashboard = "/dashboard"
profiling = "/profiling"
buckets = "/buckets"
bucketsDetail = "/buckets/:bucketName"
serviceAccounts = "/account"
changePassword = "/account/change-password"
tenants = "/tenants"
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
storage = "/storage"
storageVolumes = "/storage/volumes"
storageDrives = "/storage/drives"
remoteBuckets = "/remote-buckets"
replication = "/replication"
objectBrowser = "/object-browser/:bucket/*"
objectBrowserBucket = "/object-browser/:bucket"
mainObjectBrowser = "/object-browser"
license = "/license"
watch = "/watch"
heal = "/heal"
trace = "/trace"
logs = "/logs"
healthInfo = "/health-info"
configuration = "/settings"
users = "/users"
usersDetail = "/users/:userName"
groups = "/groups"
iamPolicies = "/policies"
policiesDetail = "/policies/:policyName"
dashboard = "/dashboard"
profiling = "/profiling"
buckets = "/buckets"
bucketsDetail = "/buckets/:bucketName"
bucketsDetailSummary = "/buckets/:bucketName/summary"
bucketsDetailEvents = "/buckets/:bucketName/events"
bucketsDetailReplication = "/buckets/:bucketName/replication"
bucketsDetailLifecycle = "/buckets/:bucketName/lifecycle"
bucketsDetailAccess = "/buckets/:bucketName/access"
bucketsDetailAccessPolicies = "/buckets/:bucketName/access/policies"
bucketsDetailAccessUsers = "/buckets/:bucketName/access/users"
serviceAccounts = "/account"
changePassword = "/account/change-password"
tenants = "/tenants"
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
storage = "/storage"
storageVolumes = "/storage/volumes"
storageDrives = "/storage/drives"
remoteBuckets = "/remote-buckets"
replication = "/replication"
objectBrowser = "/object-browser/:bucket/*"
objectBrowserBucket = "/object-browser/:bucket"
mainObjectBrowser = "/object-browser"
license = "/license"
watch = "/watch"
heal = "/heal"
trace = "/trace"
logs = "/logs"
healthInfo = "/health-info"
)
type ConfigurationActionSet struct {
@@ -276,29 +283,36 @@ var displayRules = map[string]func() bool{
// endpointRules contains the mapping between endpoints and ActionSets, additional rules can be added here
var endpointRules = map[string]ConfigurationActionSet{
configuration: configurationActionSet,
users: usersActionSet,
usersDetail: usersActionSet,
groups: groupsActionSet,
iamPolicies: iamPoliciesActionSet,
policiesDetail: iamPoliciesActionSet,
dashboard: dashboardActionSet,
profiling: profilingActionSet,
buckets: bucketsActionSet,
bucketsDetail: bucketsActionSet,
serviceAccounts: serviceAccountsActionSet,
changePassword: changePasswordActionSet,
remoteBuckets: remoteBucketsActionSet,
replication: replicationActionSet,
objectBrowser: objectBrowserActionSet,
mainObjectBrowser: objectBrowserActionSet,
objectBrowserBucket: objectBrowserActionSet,
license: licenseActionSet,
watch: watchActionSet,
heal: healActionSet,
trace: traceActionSet,
logs: logsActionSet,
healthInfo: healthInfoActionSet,
configuration: configurationActionSet,
users: usersActionSet,
usersDetail: usersActionSet,
groups: groupsActionSet,
iamPolicies: iamPoliciesActionSet,
policiesDetail: iamPoliciesActionSet,
dashboard: dashboardActionSet,
profiling: profilingActionSet,
buckets: bucketsActionSet,
bucketsDetail: bucketsActionSet,
bucketsDetailSummary: bucketsActionSet,
bucketsDetailEvents: bucketsActionSet,
bucketsDetailReplication: bucketsActionSet,
bucketsDetailLifecycle: bucketsActionSet,
bucketsDetailAccess: bucketsActionSet,
bucketsDetailAccessPolicies: bucketsActionSet,
bucketsDetailAccessUsers: bucketsActionSet,
serviceAccounts: serviceAccountsActionSet,
changePassword: changePasswordActionSet,
remoteBuckets: remoteBucketsActionSet,
replication: replicationActionSet,
objectBrowser: objectBrowserActionSet,
mainObjectBrowser: objectBrowserActionSet,
objectBrowserBucket: objectBrowserActionSet,
license: licenseActionSet,
watch: watchActionSet,
heal: healActionSet,
trace: traceActionSet,
logs: logsActionSet,
healthInfo: healthInfoActionSet,
}
// operatorRules contains the mapping between endpoints and ActionSets for operator only mode

View File

@@ -81,7 +81,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"s3:*",
},
},
want: 7,
want: 14,
},
{
name: "all admin and s3 endpoints",
@@ -91,7 +91,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"s3:*",
},
},
want: 23,
want: 30,
},
{
name: "Console User - default endpoints",

View File

@@ -1,25 +1,25 @@
{
"files": {
"main.css": "/static/css/main.a19f3d53.chunk.css",
"main.js": "/static/js/main.0047f81c.chunk.js",
"main.js.map": "/static/js/main.0047f81c.chunk.js.map",
"main.js": "/static/js/main.f0e70269.chunk.js",
"main.js.map": "/static/js/main.f0e70269.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.f48e99e5.js",
"runtime-main.js.map": "/static/js/runtime-main.f48e99e5.js.map",
"static/css/2.13d7fda8.chunk.css": "/static/css/2.13d7fda8.chunk.css",
"static/js/2.791bc763.chunk.js": "/static/js/2.791bc763.chunk.js",
"static/js/2.791bc763.chunk.js.map": "/static/js/2.791bc763.chunk.js.map",
"static/css/2.76b14b73.chunk.css": "/static/css/2.76b14b73.chunk.css",
"static/js/2.59d83018.chunk.js": "/static/js/2.59d83018.chunk.js",
"static/js/2.59d83018.chunk.js.map": "/static/js/2.59d83018.chunk.js.map",
"index.html": "/index.html",
"static/css/2.13d7fda8.chunk.css.map": "/static/css/2.13d7fda8.chunk.css.map",
"static/css/2.76b14b73.chunk.css.map": "/static/css/2.76b14b73.chunk.css.map",
"static/css/main.a19f3d53.chunk.css.map": "/static/css/main.a19f3d53.chunk.css.map",
"static/js/2.791bc763.chunk.js.LICENSE.txt": "/static/js/2.791bc763.chunk.js.LICENSE.txt",
"static/js/2.59d83018.chunk.js.LICENSE.txt": "/static/js/2.59d83018.chunk.js.LICENSE.txt",
"static/media/minio_console_logo.0837460e.svg": "/static/media/minio_console_logo.0837460e.svg",
"static/media/minio_operator_logo.1312b7c9.svg": "/static/media/minio_operator_logo.1312b7c9.svg"
},
"entrypoints": [
"static/js/runtime-main.f48e99e5.js",
"static/css/2.13d7fda8.chunk.css",
"static/js/2.791bc763.chunk.js",
"static/css/2.76b14b73.chunk.css",
"static/js/2.59d83018.chunk.js",
"static/css/main.a19f3d53.chunk.css",
"static/js/main.0047f81c.chunk.js"
"static/js/main.f0e70269.chunk.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.13d7fda8.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.791bc763.chunk.js"></script><script src="/static/js/main.0047f81c.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.76b14b73.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.59d83018.chunk.js"></script><script src="/static/js/main.f0e70269.chunk.js"></script></body></html>

View File

@@ -1,2 +1,2 @@
.ReactVirtualized__Table__headerRow{font-weight:700;text-transform:uppercase}.ReactVirtualized__Table__headerRow,.ReactVirtualized__Table__row{display:flex;flex-direction:row;align-items:center}.ReactVirtualized__Table__headerTruncatedText{display:inline-block;max-width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.ReactVirtualized__Table__headerColumn,.ReactVirtualized__Table__rowColumn{margin-right:10px;min-width:0}.ReactVirtualized__Table__rowColumn{text-overflow:ellipsis;white-space:nowrap}.ReactVirtualized__Table__headerColumn:first-of-type,.ReactVirtualized__Table__rowColumn:first-of-type{margin-left:10px}.ReactVirtualized__Table__sortableHeaderColumn{cursor:pointer}.ReactVirtualized__Table__sortableHeaderIconContainer{display:flex;align-items:center}.ReactVirtualized__Table__sortableHeaderIcon{flex:0 0 24px;height:1em;width:1em;fill:currentColor}.react-grid-layout{position:relative;transition:height .2s ease}.react-grid-item{transition:all .2s ease;transition-property:left,top}.react-grid-item img{pointer-events:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.react-grid-item.cssTransforms{transition-property:transform}.react-grid-item.resizing{z-index:1;will-change:width,height}.react-grid-item.react-draggable-dragging{transition:none;z-index:3;will-change:transform}.react-grid-item.dropping{visibility:hidden}.react-grid-item.react-grid-placeholder{background:red;opacity:.2;transition-duration:.1s;z-index:2;-webkit-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.react-grid-item>.react-resizable-handle{position:absolute;width:20px;height:20px}.react-grid-item>.react-resizable-handle:after{content:"";position:absolute;right:3px;bottom:3px;width:5px;height:5px;border-right:2px solid rgba(0,0,0,.4);border-bottom:2px solid rgba(0,0,0,.4)}.react-resizable-hide>.react-resizable-handle{display:none}.react-grid-item>.react-resizable-handle.react-resizable-handle-sw{bottom:0;left:0;cursor:sw-resize;transform:rotate(90deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-se{bottom:0;right:0;cursor:se-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-nw{top:0;left:0;cursor:nw-resize;transform:rotate(180deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-ne{top:0;right:0;cursor:ne-resize;transform:rotate(270deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-e,.react-grid-item>.react-resizable-handle.react-resizable-handle-w{top:50%;margin-top:-10px;cursor:ew-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-w{left:0;transform:rotate(135deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-e{right:0;transform:rotate(315deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-n,.react-grid-item>.react-resizable-handle.react-resizable-handle-s{left:50%;margin-left:-10px;cursor:ns-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-n{top:0;transform:rotate(225deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-s{bottom:0;transform:rotate(45deg)}.react-resizable{position:relative}.react-resizable-handle{position:absolute;width:20px;height:20px;background-repeat:no-repeat;background-origin:content-box;box-sizing:border-box;background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgd2lkdGg9IjYiIGhlaWdodD0iNiI+PHBhdGggZD0iTTYgNkgwVjQuMmg0LjJWMEg2djZ6IiBvcGFjaXR5PSIuMzAyIi8+PC9zdmc+");background-position:100% 100%;padding:0 3px 3px 0}.react-resizable-handle-sw{bottom:0;left:0;cursor:sw-resize;transform:rotate(90deg)}.react-resizable-handle-se{bottom:0;right:0;cursor:se-resize}.react-resizable-handle-nw{top:0;left:0;cursor:nw-resize;transform:rotate(180deg)}.react-resizable-handle-ne{top:0;right:0;cursor:ne-resize;transform:rotate(270deg)}.react-resizable-handle-e,.react-resizable-handle-w{top:50%;margin-top:-10px;cursor:ew-resize}.react-resizable-handle-w{left:0;transform:rotate(135deg)}.react-resizable-handle-e{right:0;transform:rotate(315deg)}.react-resizable-handle-n,.react-resizable-handle-s{left:50%;margin-left:-10px;cursor:ns-resize}.react-resizable-handle-n{top:0;transform:rotate(225deg)}.react-resizable-handle-s{bottom:0;transform:rotate(45deg)}
/*# sourceMappingURL=2.13d7fda8.chunk.css.map */
/*# sourceMappingURL=2.76b14b73.chunk.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -78,7 +78,7 @@
},
"proxy": "http://localhost:9090/",
"devDependencies": {
"prettier": "2.2.1",
"prettier": "2.3.0",
"typescript": "^4.1.3"
}
}

View File

@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, Fragment } from "react";
import { connect } from "react-redux";
import { AppState } from "../../../../store";
import { setErrorSnackMessage } from "../../../../actions";
@@ -46,15 +46,15 @@ function a11yProps(index: any) {
}
interface IAccessDetailsProps {
bucketName: string;
session: ISessionResponse;
setErrorSnackMessage: typeof setErrorSnackMessage;
classes: any;
match: any;
}
const AccessDetails = ({
bucketName,
classes,
match,
setErrorSnackMessage,
session,
}: IAccessDetailsProps) => {
@@ -64,6 +64,8 @@ const AccessDetails = ({
const [loadingUsers, setLoadingUsers] = useState<boolean>(true);
const [bucketUsers, setBucketUsers] = useState<User[]>([]);
const bucketName = match.params["bucketName"];
const usersEnabled = session.pages?.indexOf("/users") > -1;
const PolicyActions = [
@@ -115,46 +117,49 @@ const AccessDetails = ({
}, [loadingPolicies, setErrorSnackMessage, bucketName]);
return (
<Paper>
<Tabs
value={curTab}
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
setCurTab(newValue);
}}
indicatorColor="primary"
textColor="primary"
aria-label="cluster-tabs"
variant="scrollable"
scrollButtons="auto"
>
<Tab label="Policies" {...a11yProps(0)} />
{usersEnabled && <Tab label="Users" {...a11yProps(1)} />}
</Tabs>
<TabPanel index={0} value={curTab}>
<TableWrapper
noBackground={true}
itemActions={PolicyActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loadingPolicies}
records={bucketPolicy}
entityName="Policies"
idField="name"
/>
</TabPanel>
{usersEnabled && (
<TabPanel index={1} value={curTab}>
<Fragment>
<br />
<Paper>
<Tabs
value={curTab}
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
setCurTab(newValue);
}}
indicatorColor="primary"
textColor="primary"
aria-label="cluster-tabs"
variant="scrollable"
scrollButtons="auto"
>
<Tab label="Policies" {...a11yProps(0)} />
{usersEnabled && <Tab label="Users" {...a11yProps(1)} />}
</Tabs>
<TabPanel index={0} value={curTab}>
<TableWrapper
noBackground={true}
itemActions={userTableActions}
columns={[{ label: "User", elementKey: "accessKey" }]}
isLoading={loadingUsers}
records={bucketUsers}
entityName="Users"
idField="accessKey"
itemActions={PolicyActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loadingPolicies}
records={bucketPolicy}
entityName="Policies"
idField="name"
/>
</TabPanel>
)}
</Paper>
{usersEnabled && (
<TabPanel index={1} value={curTab}>
<TableWrapper
noBackground={true}
itemActions={userTableActions}
columns={[{ label: "User", elementKey: "accessKey" }]}
isLoading={loadingUsers}
records={bucketUsers}
entityName="Users"
idField="accessKey"
/>
</TabPanel>
)}
</Paper>
</Fragment>
);
};

View File

@@ -26,6 +26,10 @@ import { setModalErrorSnackMessage } from "../../../../actions";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import api from "../../../../common/api";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import { getBytes, k8sfactorForDropdown } from "../../../../common/utils";
import QueryMultiSelector from "../../Common/FormComponents/QueryMultiSelector/QueryMultiSelector";
interface IReplicationModal {
open: boolean;
@@ -48,6 +52,11 @@ const styles = (theme: Theme) =>
buttonContainer: {
textAlign: "right",
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const,
},
...modalBasic,
});
@@ -58,12 +67,23 @@ const AddReplicationModal = ({
bucketName,
setModalErrorSnackMessage,
}: IReplicationModal) => {
const [addLoading, setAddLoading] = useState(false);
const [accessKey, setAccessKey] = useState("");
const [secretKey, setSecretKey] = useState("");
const [targetURL, setTargetURL] = useState("");
const [targetBucket, setTargetBucket] = useState("");
const [region, setRegion] = useState("");
const [addLoading, setAddLoading] = useState<boolean>(false);
const [accessKey, setAccessKey] = useState<string>("");
const [secretKey, setSecretKey] = useState<string>("");
const [targetURL, setTargetURL] = useState<string>("");
const [targetStorageClass, setTargetStorageClass] = useState<string>("");
const [prefix, setPrefix] = useState<string>("");
const [targetBucket, setTargetBucket] = useState<string>("");
const [region, setRegion] = useState<string>("");
const [useTLS, setUseTLS] = useState<boolean>(true);
const [repDeleteMarker, setRepDeleteMarker] = useState<boolean>(true);
const [repDelete, setRepDelete] = useState<boolean>(true);
const [repMetadata, setRepMetadata] = useState<boolean>(true);
const [tags, setTags] = useState<string>("");
const [replicationMode, setReplicationMode] = useState<string>("async");
const [bandwidthScalar, setBandwidthScalar] = useState<string>("100");
const [bandwidthUnit, setBandwidthUnit] = useState<string>("Gi");
const [healthCheck, setHealthCheck] = useState<string>("60");
const addRecord = () => {
const replicate = [
@@ -73,12 +93,27 @@ const AddReplicationModal = ({
},
];
const hc = parseInt(healthCheck);
const endURL = `${useTLS ? "https://" : "http://"}${targetURL}`;
const remoteBucketsInfo = {
accessKey: accessKey,
secretKey: secretKey,
targetURL: targetURL,
targetURL: endURL,
region: region,
bucketsRelation: replicate,
syncMode: replicationMode,
bandwidth:
replicationMode === "async"
? parseInt(getBytes(bandwidthScalar, bandwidthUnit, true))
: 0,
healthCheckPeriod: hc,
prefix: prefix,
tags: tags,
replicateDeleteMarkers: repDeleteMarker,
replicateDeletes: repDelete,
replicateMetadata: repMetadata,
};
api
@@ -129,6 +164,30 @@ const AddReplicationModal = ({
>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
<Grid item xs={12}>
<InputBoxWrapper
id="targetURL"
name="targetURL"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setTargetURL(e.target.value);
}}
placeholder="play.min.io"
label="Target URL"
value={targetURL}
/>
</Grid>
<Grid item xs={12}>
<FormSwitchWrapper
checked={useTLS}
id="useTLS"
name="useTLS"
label="Use TLS"
onChange={(e) => {
setUseTLS(e.target.checked);
}}
value="yes"
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="accessKey"
@@ -151,18 +210,6 @@ const AddReplicationModal = ({
value={secretKey}
/>
</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"
@@ -185,6 +232,146 @@ const AddReplicationModal = ({
value={region}
/>
</Grid>
<Grid item xs={12}>
<SelectWrapper
id="replication_mode"
name="replication_mode"
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
setReplicationMode(e.target.value as string);
}}
label="Replication Mode"
value={replicationMode}
options={[
{ label: "Asynchronous", value: "async" },
{ label: "Synchronous", value: "sync" },
]}
/>
</Grid>
{replicationMode === "async" && (
<Grid item xs={12}>
<div className={classes.multiContainer}>
<div>
<InputBoxWrapper
type="number"
id="bandwidth_scalar"
name="bandwidth_scalar"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setBandwidthScalar(e.target.value as string);
}}
label="Bandwidth"
value={bandwidthScalar}
min="0"
/>
</div>
<div className={classes.sizeFactorContainer}>
<SelectWrapper
label={"Unit"}
id="bandwidth_unit"
name="bandwidth_unit"
value={bandwidthUnit}
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
setBandwidthUnit(e.target.value as string);
}}
options={k8sfactorForDropdown()}
/>
</div>
</div>
</Grid>
)}
<Grid item xs={12}>
<InputBoxWrapper
id="healthCheck"
name="healthCheck"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setHealthCheck(e.target.value as string);
}}
label="Health Check Duration"
value={healthCheck}
/>
</Grid>
<h3>Object Filters</h3>
<Grid item xs={12}>
<InputBoxWrapper
id="prefix"
name="prefix"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPrefix(e.target.value);
}}
placeholder="prefix"
label="Prefix"
value={prefix}
/>
</Grid>
<Grid item xs={12}>
<QueryMultiSelector
name="tags"
label="Tags"
elements={""}
onChange={(vl: string) => {
setTags(vl);
}}
keyPlaceholder="Tag Key"
valuePlaceholder="Tag Value"
withBorder
/>
</Grid>
<h3>Storage Configuration</h3>
<Grid item xs={12}>
<InputBoxWrapper
id="storageClass"
name="storageClass"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setTargetStorageClass(e.target.value);
}}
placeholder="STANDARD_IA,REDUCED_REDUNDANCY etc"
label="Storage Class"
value={targetStorageClass}
/>
</Grid>
<h3>Replication Options</h3>
<Grid item xs={12}>
<FormSwitchWrapper
checked={repDeleteMarker}
id="deleteMarker"
name="deleteMarker"
label="Delete Marker"
onChange={(e) => {
console.log(e);
console.log(e.target.checked);
setRepDeleteMarker(e.target.checked);
}}
value={repDeleteMarker}
description={"Replicate soft deletes"}
/>
</Grid>
<Grid item xs={12}>
<FormSwitchWrapper
checked={repDelete}
id="repDelete"
name="repDelete"
label="Deletes"
onChange={(e) => {
setRepDelete(e.target.checked);
}}
value={repDelete}
description={"Replicate versioned deletes"}
/>
</Grid>
{/*TODO: This will be enabled later on when we update the dependency on mc*/}
{/*<Grid item xs={12}>*/}
{/* <FormSwitchWrapper*/}
{/* checked={repMetadata}*/}
{/* id="repMetadata"*/}
{/* name="repMeta"*/}
{/* label="Replicate Metadata"*/}
{/* onChange={(e) => {*/}
{/* setRepMetadata(e.target.checked);*/}
{/* }}*/}
{/* value={repMetadata}*/}
{/* description={"Replicate object metadata"}*/}
{/* />*/}
{/*</Grid>*/}
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<Button

View File

@@ -0,0 +1,191 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 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, Fragment } from "react";
import { connect } from "react-redux";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button, TextField } from "@material-ui/core";
import get from "lodash/get";
import Grid from "@material-ui/core/Grid";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import { CreateIcon } from "../../../../icons";
import { BucketEvent, BucketEventList } from "../types";
import { setErrorSnackMessage } from "../../../../actions";
import { AppState } from "../../../../store";
import {
actionsTray,
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import api from "../../../../common/api";
import DeleteEvent from "./DeleteEvent";
import AddEvent from "./AddEvent";
const styles = (theme: Theme) =>
createStyles({
...searchField,
...actionsTray,
actionsTray: {
...actionsTray.actionsTray,
padding: "15px 0 0",
},
});
interface IBucketEventsProps {
classes: any;
match: any;
setErrorSnackMessage: typeof setErrorSnackMessage;
}
const BucketEventsPanel = ({
classes,
match,
setErrorSnackMessage,
}: IBucketEventsProps) => {
const [addEventScreenOpen, setAddEventScreenOpen] = useState<boolean>(false);
const [loadingEvents, setLoadingEvents] = useState<boolean>(true);
const [records, setRecords] = useState<BucketEvent[]>([]);
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [selectedEvent, setSelectedEvent] = useState<BucketEvent | null>(null);
const [filter, setFilter] = useState<string>("");
const bucketName = match.params["bucketName"];
useEffect(() => {
if (loadingEvents) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/events`)
.then((res: BucketEventList) => {
const events = get(res, "events", []);
setLoadingEvents(false);
setRecords(events || []);
})
.catch((err: any) => {
setLoadingEvents(false);
setErrorSnackMessage(err);
});
}
}, [loadingEvents, setErrorSnackMessage, bucketName]);
const eventsDisplay = (events: string[]) => {
return <Fragment>{events.join(", ")}</Fragment>;
};
const confirmDeleteEvent = (evnt: BucketEvent) => {
setDeleteOpen(true);
setSelectedEvent(evnt);
};
const closeAddEventAndRefresh = () => {
setAddEventScreenOpen(false);
setLoadingEvents(true);
};
const closeDeleteModalAndRefresh = (refresh: boolean) => {
setDeleteOpen(false);
if (refresh) {
setLoadingEvents(true);
}
};
const tableActions = [{ type: "delete", onClick: confirmDeleteEvent }];
return (
<Fragment>
{deleteOpen && (
<DeleteEvent
deleteOpen={deleteOpen}
selectedBucket={bucketName}
bucketEvent={selectedEvent}
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
/>
)}
{addEventScreenOpen && (
<AddEvent
open={addEventScreenOpen}
selectedBucket={bucketName}
closeModalAndRefresh={closeAddEventAndRefresh}
/>
)}
<Grid container>
<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 />}
size="medium"
onClick={() => {
setAddEventScreenOpen(true);
}}
>
Subscribe 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={records}
entityName="Events"
idField="id"
/>
</Grid>
</Grid>
</Fragment>
);
};
const mapState = (state: AppState) => ({
session: state.console.session,
});
const connector = connect(mapState, {
setErrorSnackMessage,
});
export default withStyles(styles)(connector(BucketEventsPanel));

View File

@@ -0,0 +1,233 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 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, { Fragment, useState, useEffect } from "react";
import { connect } from "react-redux";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button, TextField } from "@material-ui/core";
import get from "lodash/get";
import * as reactMoment from "react-moment";
import Grid from "@material-ui/core/Grid";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import { LifeCycleItem } from "../types";
import { CreateIcon } from "../../../../icons";
import {
actionsTray,
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import { setErrorSnackMessage } from "../../../../actions";
import api from "../../../../common/api";
import EditLifecycleConfiguration from "./EditLifecycleConfiguration";
import AddLifecycleModal from "./AddLifecycleModal";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import { AppState } from "../../../../store";
const styles = (theme: Theme) =>
createStyles({
...searchField,
...actionsTray,
actionsTray: {
...actionsTray.actionsTray,
padding: "15px 0 0",
},
});
interface IBucketLifecyclePanelProps {
classes: any;
match: any;
setErrorSnackMessage: typeof setErrorSnackMessage;
}
const BucketLifecyclePanel = ({
classes,
match,
setErrorSnackMessage,
}: IBucketLifecyclePanelProps) => {
const [loadingLifecycle, setLoadingLifecycle] = useState<boolean>(true);
const [lifecycleRecords, setLifecycleRecords] = useState<LifeCycleItem[]>([]);
const [addLifecycleOpen, setAddLifecycleOpen] = useState<boolean>(false);
const [editLifecycleOpen, setEditLifecycleOpen] = useState<boolean>(false);
const bucketName = match.params["bucketName"];
useEffect(() => {
if (loadingLifecycle) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/lifecycle`)
.then((res: any) => {
const records = get(res, "lifecycle", []);
setLifecycleRecords(records || []);
setLoadingLifecycle(false);
})
.catch((err) => {
console.error(err);
setLoadingLifecycle(false);
});
}
}, [loadingLifecycle, setLoadingLifecycle, bucketName]);
const closeEditLCAndRefresh = (refresh: boolean) => {
setEditLifecycleOpen(false);
if (refresh) {
setLoadingLifecycle(true);
}
};
const closeAddLCAndRefresh = (refresh: boolean) => {
setAddLifecycleOpen(false);
if (refresh) {
setLoadingLifecycle(true);
}
};
const expirationRender = (expiration: any) => {
if (expiration.days) {
return `${expiration.days} day${expiration.days > 1 ? "s" : ""}`;
}
if (expiration.date === "0001-01-01T00:00:00Z") {
return "";
}
return <reactMoment.default>{expiration.date}</reactMoment.default>;
};
const transitionRender = (transition: any) => {
if (transition.days) {
return `${transition.days} day${transition.days > 1 ? "s" : ""}`;
}
if (transition.date === "0001-01-01T00:00:00Z") {
return "";
}
return <reactMoment.default>{transition.date}</reactMoment.default>;
};
const renderStorageClass = (objectST: any) => {
const stClass = get(objectST, "transition.storage_class", "");
return stClass;
};
const lifecycleColumns = [
{ label: "ID", elementKey: "id" },
{
label: "Prefix",
elementKey: "prefix",
},
{
label: "Status",
elementKey: "status",
},
{
label: "Expiration",
elementKey: "expiration",
renderFunction: expirationRender,
},
{
label: "Transition",
elementKey: "transition",
renderFunction: transitionRender,
},
{
label: "Storage Class",
elementKey: "storage_class",
renderFunction: renderStorageClass,
renderFullObject: true,
},
];
return (
<Fragment>
{editLifecycleOpen && (
<EditLifecycleConfiguration
open={editLifecycleOpen}
closeModalAndRefresh={closeEditLCAndRefresh}
selectedBucket={bucketName}
lifecycle={{
id: "",
}}
/>
)}
{addLifecycleOpen && (
<AddLifecycleModal
open={addLifecycleOpen}
bucketName={bucketName}
closeModalAndRefresh={closeAddLCAndRefresh}
/>
)}
<Grid container>
<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 />}
size="medium"
onClick={() => {
setAddLifecycleOpen(true);
}}
>
Add Lifecycle Rule
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={[]}
columns={lifecycleColumns}
isLoading={loadingLifecycle}
records={lifecycleRecords}
entityName="Lifecycle"
customEmptyMessage="There are no Lifecycle rules yet"
idField="id"
/>
</Grid>
</Grid>
</Fragment>
);
};
const mapState = (state: AppState) => ({
session: state.console.session,
});
const connector = connect(mapState, {
setErrorSnackMessage,
});
export default withStyles(styles)(connector(BucketLifecyclePanel));

View File

@@ -0,0 +1,295 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState, Fragment } from "react";
import { connect } from "react-redux";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button, IconButton, TextField } from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import { CreateIcon } from "../../../../icons";
import { setErrorSnackMessage } from "../../../../actions";
import {
actionsTray,
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import {
BucketReplication,
BucketReplicationDestination,
BucketReplicationRule,
HasPermissionResponse,
} from "../types";
import api from "../../../../common/api";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import AddReplicationModal from "./AddReplicationModal";
import DeleteReplicationRule from "./DeleteReplicationRule";
import { AppState } from "../../../../store";
import RefreshIcon from "@material-ui/icons/Refresh";
interface IBucketReplicationProps {
classes: any;
match: any;
setErrorSnackMessage: typeof setErrorSnackMessage;
}
const styles = (theme: Theme) =>
createStyles({
...searchField,
...actionsTray,
actionsTray: {
...actionsTray.actionsTray,
padding: "15px 0 0",
},
});
const BucketReplicationPanel = ({
classes,
match,
setErrorSnackMessage,
}: IBucketReplicationProps) => {
const [canPutReplication, setCanPutReplication] = useState<boolean>(false);
const [loadingReplication, setLoadingReplication] = useState<boolean>(true);
const [replicationRules, setReplicationRules] = useState<
BucketReplicationRule[]
>([]);
const [loadingPerms, setLoadingPerms] = useState<boolean>(true);
const [canGetReplication, setCanGetReplication] = useState<boolean>(false);
const [deleteReplicationModal, setDeleteReplicationModal] =
useState<boolean>(false);
const [openSetReplication, setOpenSetReplication] = useState<boolean>(false);
const [selectedRRule, setSelectedRRule] = useState<string>("");
const bucketName = match.params["bucketName"];
useEffect(() => {
if (loadingPerms) {
api
.invoke("POST", `/api/v1/has-permission`, {
actions: [
{
id: "PutReplicationConfiguration",
action: "s3:PutReplicationConfiguration",
bucket_name: bucketName,
},
{
id: "GetReplicationConfiguration",
action: "s3:GetReplicationConfiguration",
bucket_name: bucketName,
},
],
})
.then((res: HasPermissionResponse) => {
setLoadingPerms(false);
if (!res.permissions) {
return;
}
const actions = res.permissions ? res.permissions : [];
let userCanPutReplication = actions.find(
(s) => s.id === "PutReplicationConfiguration"
);
if (userCanPutReplication && userCanPutReplication.can) {
setCanPutReplication(true);
} else {
setCanPutReplication(false);
}
let canGetReplication = actions.find(
(s) => s.id === "GetReplicationConfiguration"
);
if (canGetReplication && canGetReplication.can) {
setCanGetReplication(true);
} else {
setCanGetReplication(false);
}
setLoadingPerms(false);
})
.catch((err: any) => {
setLoadingPerms(false);
setErrorSnackMessage(err);
});
}
}, [bucketName, loadingPerms, setErrorSnackMessage]);
useEffect(() => {
if (loadingReplication) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/replication`)
.then((res: BucketReplication) => {
const r = res.rules ? res.rules : [];
setReplicationRules(r);
setLoadingReplication(false);
})
.catch((err: any) => {
setErrorSnackMessage(err);
setLoadingReplication(false);
});
}
}, [loadingReplication, setErrorSnackMessage, bucketName]);
if (!canGetReplication) {
return null;
}
const closeAddReplication = () => {
setOpenReplicationOpen(false);
setLoadingReplication(true);
};
const setOpenReplicationOpen = (open = false) => {
setOpenSetReplication(open);
};
const closeReplicationModalDelete = (refresh: boolean) => {
setDeleteReplicationModal(false);
if (refresh) {
setLoadingReplication(true);
}
};
const confirmDeleteReplication = (replication: BucketReplicationRule) => {
setSelectedRRule(replication.id);
setDeleteReplicationModal(true);
};
const ruleDestDisplay = (events: BucketReplicationDestination) => {
return <Fragment>{events.bucket.replace("arn:aws:s3:::", "")}</Fragment>;
};
const tagDisplay = (events: BucketReplicationRule) => {
return <Fragment>{events && events.tags !== "" ? "Yes" : "No"}</Fragment>;
};
const replicationTableActions: any = [
{
type: "delete",
onClick: confirmDeleteReplication,
disableButtonFunction: () => replicationRules.length > 1,
},
];
return (
<Fragment>
{openSetReplication && (
<AddReplicationModal
closeModalAndRefresh={closeAddReplication}
open={openSetReplication}
bucketName={bucketName}
/>
)}
{deleteReplicationModal && (
<DeleteReplicationRule
deleteOpen={deleteReplicationModal}
selectedBucket={bucketName}
closeDeleteModalAndRefresh={closeReplicationModalDelete}
ruleToDelete={selectedRRule}
/>
)}
<Grid container>
<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>
),
}}
/>
<IconButton
color="primary"
aria-label="Refresh Replication Rules"
component="span"
onClick={() => {
setLoadingReplication(true);
}}
>
<RefreshIcon />
</IconButton>
{canPutReplication && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
size="medium"
onClick={() => {
setOpenReplicationOpen(true);
}}
>
Add Replication Rule
</Button>
)}
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={replicationTableActions}
columns={[
{
label: "Priority",
elementKey: "priority",
},
{
label: "Destination",
elementKey: "destination",
renderFunction: ruleDestDisplay,
},
{
label: "Prefix",
elementKey: "prefix",
},
{
label: "Tags",
elementKey: "tags",
renderFunction: tagDisplay,
},
{ label: "Status", elementKey: "status" },
]}
isLoading={loadingReplication}
records={replicationRules}
entityName="Replication Rules"
idField="id"
/>
</Grid>
</Grid>
</Fragment>
);
};
const mapState = (state: AppState) => ({
session: state.console.session,
});
const connector = connect(mapState, {
setErrorSnackMessage,
});
export default withStyles(styles)(connector(BucketReplicationPanel));

View File

@@ -0,0 +1,468 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 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, { Fragment, useState, useEffect } from "react";
import { connect } from "react-redux";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button, CircularProgress } from "@material-ui/core";
import get from "lodash/get";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { AppState } from "../../../../store";
import { setErrorSnackMessage } from "../../../../actions";
import {
BucketEncryptionInfo,
BucketInfo,
BucketObjectLocking,
BucketReplication,
BucketVersioning,
} from "../types";
import { niceBytes } from "../../../../common/utils";
import { BucketList } from "../../Watch/types";
import {
buttonsStyles,
hrClass,
} from "../../Common/FormComponents/common/styleLibrary";
import api from "../../../../common/api";
import SetAccessPolicy from "./SetAccessPolicy";
import SetRetentionConfig from "./SetRetentionConfig";
import EnableBucketEncryption from "./EnableBucketEncryption";
import EnableVersioningModal from "./EnableVersioningModal";
import UsageIcon from "../../../../icons/UsageIcon";
interface IBucketSummaryProps {
classes: any;
match: any;
setErrorSnackMessage: typeof setErrorSnackMessage;
}
const styles = (theme: Theme) =>
createStyles({
paperContainer: {
padding: 15,
paddingLeft: 50,
display: "flex",
},
elementTitle: {
fontWeight: 500,
color: "#777777",
fontSize: 14,
marginTop: -9,
},
consumptionValue: {
color: "#000000",
fontSize: "48px",
fontWeight: "bold",
},
reportedUsage: {
padding: "15px",
},
...hrClass,
...buttonsStyles,
});
const BucketSummary = ({
classes,
match,
setErrorSnackMessage,
}: IBucketSummaryProps) => {
const [info, setInfo] = useState<BucketInfo | null>(null);
const [encryptionCfg, setEncryptionCfg] =
useState<BucketEncryptionInfo | null>(null);
const [bucketSize, setBucketSize] = useState<string>("0");
const [hasObjectLocking, setHasObjectLocking] = useState<boolean>(false);
const [accessPolicyScreenOpen, setAccessPolicyScreenOpen] =
useState<boolean>(false);
const [replicationRules, setReplicationRules] = useState<boolean>(false);
const [loadingObjectLocking, setLoadingLocking] = useState<boolean>(true);
const [loadingSize, setLoadingSize] = useState<boolean>(true);
const [loadingBucket, setLoadingBucket] = useState<boolean>(true);
const [loadingEncryption, setLoadingEncryption] = useState<boolean>(true);
const [loadingVersioning, setLoadingVersioning] = useState<boolean>(true);
const [loadingReplication, setLoadingReplication] = useState<boolean>(true);
const [isVersioned, setIsVersioned] = useState<boolean>(false);
const [encryptionEnabled, setEncryptionEnabled] = useState<boolean>(false);
const [retentionConfigOpen, setRetentionConfigOpen] =
useState<boolean>(false);
const [enableEncryptionScreenOpen, setEnableEncryptionScreenOpen] =
useState<boolean>(false);
const [enableVersioningOpen, setEnableVersioningOpen] =
useState<boolean>(false);
const bucketName = match.params["bucketName"];
let accessPolicy = "n/a";
if (info !== null) {
accessPolicy = info.access;
}
// Effects
useEffect(() => {
if (loadingBucket) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}`)
.then((res: BucketInfo) => {
setLoadingBucket(false);
setInfo(res);
})
.catch((err) => {
setLoadingBucket(false);
setErrorSnackMessage(err);
});
}
}, [loadingBucket, setErrorSnackMessage, bucketName]);
useEffect(() => {
if (loadingEncryption) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/encryption/info`)
.then((res: BucketEncryptionInfo) => {
if (res.algorithm) {
setEncryptionEnabled(true);
setEncryptionCfg(res);
}
setLoadingEncryption(false);
})
.catch((err) => {
if (
err === "The server side encryption configuration was not found"
) {
setEncryptionEnabled(false);
setEncryptionCfg(null);
}
setLoadingEncryption(false);
});
}
}, [loadingEncryption, bucketName]);
useEffect(() => {
if (loadingVersioning) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/versioning`)
.then((res: BucketVersioning) => {
setIsVersioned(res.is_versioned);
setLoadingVersioning(false);
})
.catch((err: any) => {
setErrorSnackMessage(err);
setLoadingVersioning(false);
});
}
}, [loadingVersioning, setErrorSnackMessage, bucketName]);
useEffect(() => {
if (loadingVersioning) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/object-locking`)
.then((res: BucketObjectLocking) => {
setHasObjectLocking(res.object_locking_enabled);
setLoadingLocking(false);
})
.catch((err: any) => {
setErrorSnackMessage(err);
setLoadingLocking(false);
});
}
}, [
loadingObjectLocking,
setErrorSnackMessage,
bucketName,
loadingVersioning,
]);
useEffect(() => {
if (loadingSize) {
api
.invoke("GET", `/api/v1/buckets`)
.then((res: BucketList) => {
const resBuckets = get(res, "buckets", []);
const bucketInfo = resBuckets.find(
(bucket) => bucket.name === bucketName
);
const size = get(bucketInfo, "size", "0");
setLoadingSize(false);
setBucketSize(size);
})
.catch((err: any) => {
setLoadingSize(false);
setErrorSnackMessage(err);
});
}
}, [loadingSize, setErrorSnackMessage, bucketName]);
useEffect(() => {
if (loadingReplication) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/replication`)
.then((res: BucketReplication) => {
const r = res.rules ? res.rules : [];
setReplicationRules(r.length > 0);
setLoadingReplication(false);
})
.catch((err: any) => {
setErrorSnackMessage(err);
setLoadingReplication(false);
});
}
}, [loadingReplication, setErrorSnackMessage, bucketName]);
const loadAllBucketData = () => {
setLoadingBucket(true);
setLoadingSize(true);
setLoadingVersioning(true);
setLoadingEncryption(true);
};
const setBucketVersioning = () => {
setEnableVersioningOpen(true);
};
const closeEnableBucketEncryption = () => {
setEnableEncryptionScreenOpen(false);
loadAllBucketData();
};
const closeSetAccessPolicy = () => {
setAccessPolicyScreenOpen(false);
loadAllBucketData();
};
const closeRetentionConfig = () => {
setRetentionConfigOpen(false);
loadAllBucketData();
};
const closeEnableVersioning = (refresh: boolean) => {
setEnableVersioningOpen(false);
if (refresh) {
loadAllBucketData();
}
};
return (
<Fragment>
{enableEncryptionScreenOpen && (
<EnableBucketEncryption
open={enableEncryptionScreenOpen}
selectedBucket={bucketName}
encryptionEnabled={encryptionEnabled}
encryptionCfg={encryptionCfg}
closeModalAndRefresh={closeEnableBucketEncryption}
/>
)}
{accessPolicyScreenOpen && (
<SetAccessPolicy
bucketName={bucketName}
open={accessPolicyScreenOpen}
actualPolicy={accessPolicy}
closeModalAndRefresh={closeSetAccessPolicy}
/>
)}
{retentionConfigOpen && (
<SetRetentionConfig
bucketName={bucketName}
open={retentionConfigOpen}
closeModalAndRefresh={closeRetentionConfig}
/>
)}
{enableVersioningOpen && (
<EnableVersioningModal
closeVersioningModalAndRefresh={closeEnableVersioning}
modalOpen={enableVersioningOpen}
selectedBucket={bucketName}
versioningCurrentState={isVersioned}
/>
)}
<br />
<Paper className={classes.paperContainer}>
<Grid container>
<Grid item xs={9}>
<h2>Details</h2>
<hr className={classes.hrClass} />
<table width={"100%"}>
<tbody>
<tr>
<td className={classes.titleCol}>Access Policy:</td>
<td className={classes.capitalizeFirst}>
<Button
color="primary"
className={classes.anchorButton}
onClick={() => {
setAccessPolicyScreenOpen(true);
}}
>
{loadingBucket ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
accessPolicy.toLowerCase()
)}
</Button>
</td>
<td className={classes.titleCol}>Encryption:</td>
<td>
{loadingEncryption ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
<Button
color="primary"
className={classes.anchorButton}
onClick={() => {
setEnableEncryptionScreenOpen(true);
}}
>
{encryptionEnabled ? "Enabled" : "Disabled"}
</Button>
)}
</td>
</tr>
<tr>
<td className={classes.titleCol}>Replication:</td>
<td className={classes.doubleElement}>
<span>{replicationRules ? "Enabled" : "Disabled"}</span>
</td>
{!hasObjectLocking ? (
<React.Fragment>
<td className={classes.titleCol}>Object Locking:</td>
<td>Disabled</td>
</React.Fragment>
) : (
<React.Fragment>
<td colSpan={2}></td>
</React.Fragment>
)}
</tr>
</tbody>
</table>
</Grid>
<Grid item xs={3} className={classes.reportedUsage}>
<Grid container direction="row" alignItems="center">
<Grid item className={classes.icon} xs={2}>
<UsageIcon />
</Grid>
<Grid item xs={10}>
<Typography className={classes.elementTitle}>
Reported Usage
</Typography>
</Grid>
</Grid>
<Typography className={classes.consumptionValue}>
{niceBytes(bucketSize)}
</Typography>
</Grid>
</Grid>
</Paper>
<br />
<br />
<Paper className={classes.paperContainer}>
<Grid container>
<Grid item xs={12}>
<h2>Versioning</h2>
<hr className={classes.hrClass} />
<table>
<tbody>
<tr>
<td className={classes.titleCol}>Versioning:</td>
<td>
{loadingVersioning ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
<Fragment>
<Button
color="primary"
className={classes.anchorButton}
onClick={setBucketVersioning}
>
{isVersioned ? "Enabled" : "Disabled"}
</Button>
</Fragment>
)}
</td>
</tr>
</tbody>
</table>
</Grid>
</Grid>
</Paper>
<br />
<br />
{hasObjectLocking && (
<Paper className={classes.paperContainer}>
<Grid container>
<Grid item xs={12}>
<h2>Object Locking</h2>
<hr className={classes.hrClass} />
<table>
<tbody>
<tr className={classes.gridContainer}>
<td className={classes.titleCol}>Retention:</td>
<td>
{loadingVersioning ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
<Fragment>
<Button
color="primary"
className={classes.anchorButton}
onClick={() => {
setRetentionConfigOpen(true);
}}
>
Configure
</Button>
</Fragment>
)}
</td>
</tr>
</tbody>
</table>
</Grid>
</Grid>
</Paper>
)}
</Fragment>
);
};
const mapState = (state: AppState) => ({
session: state.console.session,
});
const connector = connect(mapState, {
setErrorSnackMessage,
});
export default withStyles(styles)(connector(BucketSummary));

View File

@@ -34,6 +34,7 @@ const Buckets = () => {
return (
<Router history={history}>
<Switch>
<Route path="/buckets/:bucketName/*" component={BucketDetails} />
<Route path="/buckets/:bucketName" component={BucketDetails} />
<Route path="/" component={ListBuckets} />
<Route component={NotFoundPage} />

View File

@@ -36,6 +36,7 @@ import GenericWizard from "../../Common/GenericWizard/GenericWizard";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import { SelectorTypes } from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import { getBytes, k8sfactorForDropdown } from "../../../../common/utils";
interface IBulkReplicationModal {
open: boolean;
@@ -93,6 +94,10 @@ const AddBulkReplicationModal = ({
const [targetURL, setTargetURL] = useState<string>("");
const [region, setRegion] = useState<string>("");
const [useTLS, setUseTLS] = useState<boolean>(true);
const [replicationMode, setReplicationMode] = useState<string>("async");
const [bandwidthScalar, setBandwidthScalar] = useState<string>("100");
const [bandwidthUnit, setBandwidthUnit] = useState<string>("Gi");
const [healthCheck, setHealthCheck] = useState<string>("60");
const [relationBuckets, setRelationBuckets] = useState<string[]>([]);
const [remoteBucketsOpts, setRemoteBucketOpts] = useState<string[]>([]);
const [responseItem, setResponseItem] = useState<BulkReplicationItem[]>([]);
@@ -131,12 +136,20 @@ const AddBulkReplicationModal = ({
});
const endURL = `${useTLS ? "https://" : "http://"}${targetURL}`;
const hc = parseInt(healthCheck);
const remoteBucketsInfo = {
accessKey: accessKey,
secretKey: secretKey,
targetURL: endURL,
region: region,
bucketsRelation: replicate,
syncMode: replicationMode,
bandwidth:
replicationMode === "async"
? parseInt(getBytes(bandwidthScalar, bandwidthUnit, true))
: 0,
healthCheckPeriod: hc,
};
api
@@ -386,6 +399,67 @@ const AddBulkReplicationModal = ({
value={region}
/>
</Grid>
<Grid item xs={12}>
<SelectWrapper
id="replication_mode"
name="replication_mode"
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
setReplicationMode(e.target.value as string);
}}
label="Replication Mode"
value={replicationMode}
options={[
{ label: "Asynchronous", value: "async" },
{ label: "Synchronous", value: "sync" },
]}
/>
</Grid>
{replicationMode === "async" && (
<Grid item xs={12}>
<div className={classes.multiContainer}>
<div>
<InputBoxWrapper
type="number"
id="bandwidth_scalar"
name="bandwidth_scalar"
onChange={(
e: React.ChangeEvent<HTMLInputElement>
) => {
setBandwidthScalar(e.target.value as string);
}}
label="Bandwidth"
value={bandwidthScalar}
min="0"
/>
</div>
<div className={classes.sizeFactorContainer}>
<SelectWrapper
label={"Unit"}
id="bandwidth_unit"
name="bandwidth_unit"
value={bandwidthUnit}
onChange={(
e: React.ChangeEvent<{ value: unknown }>
) => {
setBandwidthUnit(e.target.value as string);
}}
options={k8sfactorForDropdown()}
/>
</div>
</div>
</Grid>
)}
<Grid item xs={12}>
<InputBoxWrapper
id="healthCheck"
name="healthCheck"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setHealthCheck(e.target.value as string);
}}
label="Health Check Duration"
value={healthCheck}
/>
</Grid>
</Fragment>
),
buttons: [

View File

@@ -27,6 +27,7 @@ export const ADD_BUCKET_RETENTION = "ADD_BUCKET_RETENTION";
export const ADD_BUCKET_RETENTION_MODE = "ADD_BUCKET_RETENTION_MODE";
export const ADD_BUCKET_RETENTION_UNIT = "ADD_BUCKET_RETENTION_UNIT";
export const ADD_BUCKET_RETENTION_VALIDITY = "ADD_BUCKET_RETENTION_VALIDITY";
export const BUCKET_DETAILS_SET_TAB = "BUCKET_DETAILS/SET_TAB";
interface AddBucketOpenAction {
type: typeof ADD_BUCKET_OPEN;
@@ -90,6 +91,11 @@ interface AddBucketRetentionValidityAction {
retentionValidity: number;
}
interface SetBucketDetailsTab {
type: typeof BUCKET_DETAILS_SET_TAB;
tab: string;
}
export type BucketActionTypes =
| AddBucketOpenAction
| AddBucketNameAction
@@ -103,7 +109,8 @@ export type BucketActionTypes =
| AddBucketRetentionAction
| AddBucketRetentionModeAction
| AddBucketRetentionUnitAction
| AddBucketRetentionValidityAction;
| AddBucketRetentionValidityAction
| SetBucketDetailsTab;
export function addBucketOpen(open: boolean) {
return {
@@ -193,3 +200,10 @@ export function addBucketRetentionValidity(validity: number) {
retentionValidity: validity,
};
}
export function setBucketDetailsTab(tab: string) {
return {
type: BUCKET_DETAILS_SET_TAB,
tab,
};
}

View File

@@ -29,6 +29,7 @@ import {
ADD_BUCKET_RETENTION_VALIDITY,
BucketActionTypes,
ADD_BUCKET_LOCKING,
BUCKET_DETAILS_SET_TAB,
} from "./actions";
export interface BucketsState {
@@ -44,6 +45,11 @@ export interface BucketsState {
addBucketRetentionMode: string;
addBucketRetentionUnit: string;
addBucketRetentionValidity: number;
bucketDetails: BucketDetailsState;
}
export interface BucketDetailsState {
selectedTab: string;
}
const initialState: BucketsState = {
@@ -59,6 +65,9 @@ const initialState: BucketsState = {
addBucketRetentionMode: "compliance",
addBucketRetentionUnit: "days",
addBucketRetentionValidity: 1,
bucketDetails: {
selectedTab: "summary",
},
};
export function bucketsReducer(
@@ -126,7 +135,14 @@ export function bucketsReducer(
...state,
addBucketRetentionValidity: action.retentionValidity,
};
case BUCKET_DETAILS_SET_TAB:
return {
...state,
bucketDetails: {
...state.bucketDetails,
selectedTab: action.tab,
},
};
case ADD_BUCKET_RESET:
return {
...state,

View File

@@ -73,8 +73,13 @@ export interface BucketReplicationRule {
id: string;
status: string;
priority: number;
delete_marker_replication: BucketReplicationRuleDeleteMarker;
delete_marker_replication: boolean;
deletes_replication: boolean;
metadata_replication: boolean;
prefix?: string;
tags?: string;
Destination: BucketReplicationDestination;
syncMode: string;
}
export interface BucketReplication {

View File

@@ -116,7 +116,7 @@ const QueryMultiSelector = ({
// Use effect to send new values to onChange
useEffect(() => {
const refScroll = bottomList.current;
if (refScroll) {
if (refScroll && currentKeys.length > 1) {
refScroll.scrollIntoView(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@@ -230,6 +230,38 @@ const Console = ({
component: Buckets,
path: "/buckets/:bucketName",
},
{
component: Buckets,
path: "/buckets/:bucketName/summary",
},
{
component: Buckets,
path: "/buckets/:bucketName/events",
},
{
component: Buckets,
path: "/buckets/:bucketName/replication",
},
{
component: Buckets,
path: "/buckets/:bucketName/lifecycle",
},
{
component: Buckets,
path: "/buckets/:bucketName/access",
},
{
component: Buckets,
path: "/buckets/:bucketName/access",
},
{
component: Buckets,
path: "/buckets/:bucketName/access/policies",
},
{
component: Buckets,
path: "/buckets/:bucketName/access/users",
},
{
component: ObjectBrowser,
path: "/object-browser",

View File

@@ -332,7 +332,7 @@ const Configure = ({
label="Console's Image"
value={consoleImage}
error={validationErrors["consoleImage"] || ""}
placeholder="E.g. minio/console:v0.7.2"
placeholder="E.g. minio/console:v0.7.4"
/>
</Grid>
<Grid item xs={12}>

View File

@@ -9268,10 +9268,10 @@ prepend-http@^1.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
prettier@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
prettier@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18"
integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==
pretty-bytes@^5.3.0:
version "5.5.0"

View File

@@ -187,14 +187,20 @@ func listRemoteBuckets(ctx context.Context, client MinioAdmin) ([]*models.Remote
}
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,
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,
SyncMode: "async",
Bandwidth: bucket.BandwidthLimit,
HealthCheckPeriod: int64(bucket.HealthCheckDuration.Seconds()),
}
if bucket.ReplicationSync {
remoteBucket.SyncMode = "sync"
}
remoteBuckets = append(remoteBuckets, remoteBucket)
}
@@ -244,21 +250,28 @@ func addRemoteBucket(ctx context.Context, client MinioAdmin, params models.Creat
}
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,
TargetBucket: *params.TargetBucket,
Secure: secure,
Credentials: creds,
Endpoint: host,
Path: "",
API: "s3v4",
Type: "replication",
Region: params.Region,
ReplicationSync: *params.SyncMode == "sync",
}
if *params.SyncMode == "async" {
remoteBucket.BandwidthLimit = params.Bandwidth
}
if params.HealthCheckPeriod > 0 {
remoteBucket.HealthCheckDuration = time.Duration(params.HealthCheckPeriod) * time.Second
}
bucketARN, err := client.addRemoteBucket(ctx, *params.SourceBucket, remoteBucket)
return bucketARN, err
}
func addBucketReplicationItem(ctx context.Context, session *models.Principal, minClient minioClient, bucketName, arn, destinationBucket string) error {
func addBucketReplicationItem(ctx context.Context, session *models.Principal, minClient minioClient, bucketName, prefix, arn, destinationBucket string, repDelMark, repDels, repMeta bool, tags string) error {
// we will tolerate this call failing
cfg, err := minClient.getBucketReplication(ctx, bucketName)
if err != nil {
@@ -274,7 +287,7 @@ func addBucketReplicationItem(ctx context.Context, session *models.Principal, mi
}
maxPrio++
s3Client, err := newS3BucketClient(session, bucketName, "")
s3Client, err := newS3BucketClient(session, bucketName, prefix)
if err != nil {
log.Println("error creating S3Client:", err)
return err
@@ -283,12 +296,32 @@ func addBucketReplicationItem(ctx context.Context, session *models.Principal, mi
// defining the client to be used
mcClient := mcClient{client: s3Client}
repDelMarkStatus := "disable"
if repDelMark {
repDelMarkStatus = "enable"
}
repDelsStatus := "disable"
if repDels {
repDelsStatus = "enable"
}
repMetaStatus := "disable"
if repMeta {
repMetaStatus = "enable"
}
log.Println("repMetaStatus is not yet implemented", repMetaStatus)
opts := replication.Options{
RoleArn: arn,
Priority: fmt.Sprintf("%d", maxPrio),
RuleStatus: "enable",
DestBucket: destinationBucket,
Op: replication.AddOption,
RoleArn: arn,
Priority: fmt.Sprintf("%d", maxPrio),
RuleStatus: "enable",
DestBucket: destinationBucket,
Op: replication.AddOption,
TagString: tags,
ReplicateDeleteMarkers: repDelMarkStatus,
ReplicateDeletes: repDelsStatus,
//ReplicaSync: repMetaStatus,
}
err2 := mcClient.setReplication(ctx, &cfg, opts)
@@ -318,13 +351,26 @@ func setMultiBucketReplication(ctx context.Context, session *models.Principal, c
TargetBucket: &targetBucket,
Region: params.Body.Region,
TargetURL: params.Body.TargetURL,
SyncMode: params.Body.SyncMode,
Bandwidth: params.Body.Bandwidth,
}
// We add the remote bucket reference & store the arn or errors returned
arn, err := addRemoteBucket(ctx, client, createRemoteBucketParams)
if err == nil {
err = addBucketReplicationItem(ctx, session, minClient, sourceBucket, arn, targetBucket)
err = addBucketReplicationItem(
ctx,
session,
minClient,
sourceBucket,
params.Body.Prefix,
arn,
targetBucket,
params.Body.ReplicateDeleteMarkers,
params.Body.ReplicateDeletes,
params.Body.ReplicateMetadata,
params.Body.Tags)
}
var errorReturn = ""

View File

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

View File

@@ -63,7 +63,7 @@ const (
// Image versions
const (
KESImageVersion = "minio/kes:v0.13.4"
ConsoleImageDefaultVersion = "minio/console:v0.7.2"
ConsoleImageDefaultVersion = "minio/console:v0.7.4"
)
// K8s

View File

@@ -4286,15 +4286,30 @@ func init() {
"bucketReplicationRule": {
"type": "object",
"properties": {
"bandwidth": {
"type": "string"
},
"delete_marker_replication": {
"$ref": "#/definitions/bucketReplicationRuleMarker"
"type": "boolean"
},
"deletes_replication": {
"type": "boolean"
},
"destination": {
"$ref": "#/definitions/bucketReplicationDestination"
},
"healthCheckPeriod": {
"type": "integer"
},
"id": {
"type": "string"
},
"metadata_replication": {
"type": "boolean"
},
"prefix": {
"type": "string"
},
"priority": {
"type": "integer",
"format": "int32"
@@ -4305,18 +4320,17 @@ func init() {
"Enabled",
"Disabled"
]
}
}
},
"bucketReplicationRuleMarker": {
"type": "object",
"properties": {
"status": {
},
"syncMode": {
"type": "string",
"default": "async",
"enum": [
"Enabled",
"Disabled"
"async",
"sync"
]
},
"tags": {
"type": "string"
}
}
},
@@ -4413,6 +4427,14 @@ func init() {
"type": "string",
"minLength": 3
},
"bandwidth": {
"type": "integer",
"format": "int64"
},
"healthCheckPeriod": {
"type": "integer",
"format": "int32"
},
"region": {
"type": "string"
},
@@ -4423,6 +4445,14 @@ func init() {
"sourceBucket": {
"type": "string"
},
"syncMode": {
"type": "string",
"default": "async",
"enum": [
"async",
"sync"
]
},
"targetBucket": {
"type": "string"
},
@@ -5372,6 +5402,10 @@ func init() {
"type": "string",
"minLength": 3
},
"bandwidth": {
"type": "integer",
"format": "int64"
},
"bucketsRelation": {
"type": "array",
"minLength": 1,
@@ -5379,13 +5413,40 @@ func init() {
"$ref": "#/definitions/multiBucketsRelation"
}
},
"healthCheckPeriod": {
"type": "integer",
"format": "int32"
},
"prefix": {
"type": "string"
},
"region": {
"type": "string"
},
"replicateDeleteMarkers": {
"type": "boolean"
},
"replicateDeletes": {
"type": "boolean"
},
"replicateMetadata": {
"type": "boolean"
},
"secretKey": {
"type": "string",
"minLength": 8
},
"syncMode": {
"type": "string",
"default": "async",
"enum": [
"async",
"sync"
]
},
"tags": {
"type": "string"
},
"targetURL": {
"type": "string"
}
@@ -6196,6 +6257,13 @@ func init() {
"type": "string",
"minLength": 3
},
"bandwidth": {
"type": "integer",
"format": "int64"
},
"healthCheckPeriod": {
"type": "integer"
},
"remoteARN": {
"type": "string"
},
@@ -6215,6 +6283,9 @@ func init() {
"status": {
"type": "string"
},
"syncMode": {
"type": "string"
},
"targetBucket": {
"type": "string"
},
@@ -11983,15 +12054,30 @@ func init() {
"bucketReplicationRule": {
"type": "object",
"properties": {
"bandwidth": {
"type": "string"
},
"delete_marker_replication": {
"$ref": "#/definitions/bucketReplicationRuleMarker"
"type": "boolean"
},
"deletes_replication": {
"type": "boolean"
},
"destination": {
"$ref": "#/definitions/bucketReplicationDestination"
},
"healthCheckPeriod": {
"type": "integer"
},
"id": {
"type": "string"
},
"metadata_replication": {
"type": "boolean"
},
"prefix": {
"type": "string"
},
"priority": {
"type": "integer",
"format": "int32"
@@ -12002,18 +12088,17 @@ func init() {
"Enabled",
"Disabled"
]
}
}
},
"bucketReplicationRuleMarker": {
"type": "object",
"properties": {
"status": {
},
"syncMode": {
"type": "string",
"default": "async",
"enum": [
"Enabled",
"Disabled"
"async",
"sync"
]
},
"tags": {
"type": "string"
}
}
},
@@ -12110,6 +12195,14 @@ func init() {
"type": "string",
"minLength": 3
},
"bandwidth": {
"type": "integer",
"format": "int64"
},
"healthCheckPeriod": {
"type": "integer",
"format": "int32"
},
"region": {
"type": "string"
},
@@ -12120,6 +12213,14 @@ func init() {
"sourceBucket": {
"type": "string"
},
"syncMode": {
"type": "string",
"default": "async",
"enum": [
"async",
"sync"
]
},
"targetBucket": {
"type": "string"
},
@@ -13057,6 +13158,10 @@ func init() {
"type": "string",
"minLength": 3
},
"bandwidth": {
"type": "integer",
"format": "int64"
},
"bucketsRelation": {
"type": "array",
"minLength": 1,
@@ -13064,13 +13169,40 @@ func init() {
"$ref": "#/definitions/multiBucketsRelation"
}
},
"healthCheckPeriod": {
"type": "integer",
"format": "int32"
},
"prefix": {
"type": "string"
},
"region": {
"type": "string"
},
"replicateDeleteMarkers": {
"type": "boolean"
},
"replicateDeletes": {
"type": "boolean"
},
"replicateMetadata": {
"type": "boolean"
},
"secretKey": {
"type": "string",
"minLength": 8
},
"syncMode": {
"type": "string",
"default": "async",
"enum": [
"async",
"sync"
]
},
"tags": {
"type": "string"
},
"targetURL": {
"type": "string"
}
@@ -13746,6 +13878,13 @@ func init() {
"type": "string",
"minLength": 3
},
"bandwidth": {
"type": "integer",
"format": "int64"
},
"healthCheckPeriod": {
"type": "integer"
},
"remoteARN": {
"type": "string"
},
@@ -13765,6 +13904,9 @@ func init() {
"status": {
"type": "string"
},
"syncMode": {
"type": "string"
},
"targetBucket": {
"type": "string"
},

View File

@@ -212,9 +212,22 @@ func getBucketReplicationdResponse(session *models.Principal, bucketName string)
var rules []*models.BucketReplicationRule
for _, rule := range res.Rules {
repDelMarkerStatus := false
if rule.DeleteMarkerReplication.Status == "enable" {
repDelMarkerStatus = true
}
repDelStatus := false
if rule.DeleteReplication.Status == "enable" {
repDelMarkerStatus = true
}
rules = append(rules, &models.BucketReplicationRule{
DeleteMarkerReplication: &models.BucketReplicationRuleMarker{Status: string(rule.DeleteMarkerReplication.Status)},
DeleteMarkerReplication: repDelMarkerStatus,
DeletesReplication: repDelStatus,
Destination: &models.BucketReplicationDestination{Bucket: rule.Destination.Bucket},
Tags: rule.Tags(),
Prefix: rule.Prefix(),
ID: rule.ID,
Priority: int32(rule.Priority),
Status: string(rule.Status),

View File

@@ -3020,14 +3020,6 @@ definitions:
$ref: "#/definitions/notificationConfig"
ignoreExisting:
type: boolean
bucketReplicationRuleMarker:
type: object
properties:
status:
type: string
enum:
- Enabled
- Disabled
bucketReplicationDestination:
type: object
properties:
@@ -3046,8 +3038,26 @@ definitions:
priority:
type: integer
format: int32
syncMode:
type: string
enum:
- async
- sync
default: async
bandwidth:
type: string
healthCheckPeriod:
type: integer
delete_marker_replication:
$ref: "#/definitions/bucketReplicationRuleMarker"
type: boolean
deletes_replication:
type: boolean
metadata_replication:
type: boolean
prefix:
type: string
tags:
type: string
destination:
$ref: "#/definitions/bucketReplicationDestination"
@@ -3103,6 +3113,28 @@ definitions:
type: string
region:
type: string
syncMode:
type: string
enum:
- async
- sync
default: async
bandwidth:
type: integer
format: int64
healthCheckPeriod:
type: integer
format: int32
prefix:
type: string
tags:
type: string
replicateDeleteMarkers:
type: boolean
replicateDeletes:
type: boolean
replicateMetadata:
type: boolean
bucketsRelation:
type: array
minLength: 1
@@ -3511,6 +3543,13 @@ definitions:
service:
type: string
enum: [ replication ]
syncMode:
type: string
bandwidth:
type: integer
format: int64
healthCheckPeriod:
type: integer
createRemoteBucket:
required:
- accessKey
@@ -3533,6 +3572,18 @@ definitions:
type: string
region:
type: string
syncMode:
type: string
enum:
- async
- sync
default: async
bandwidth:
type: integer
format: int64
healthCheckPeriod:
type: integer
format: int32
listRemoteBucketsResponse:
type: object
properties: