Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aec2d879e | ||
|
|
f77770bb6e | ||
|
|
34ff3d7157 | ||
|
|
4b6700d4ac | ||
|
|
f2c8f15fbf | ||
|
|
991204cd46 | ||
|
|
4bac7040a1 | ||
|
|
a247bf6a0c | ||
|
|
f4d08a7502 | ||
|
|
fb59e8c353 | ||
|
|
6e6ed300b7 | ||
|
|
48e6b1bb7c | ||
|
|
8949fbe245 | ||
|
|
d8e6bd7f4a | ||
|
|
4edfeb22c6 | ||
|
|
2d5d0d16ca | ||
|
|
16f8ee485a | ||
|
|
2d28f8bf35 | ||
|
|
8af3665ae2 | ||
|
|
0fa1d4bf7c | ||
|
|
8139416323 | ||
|
|
be5cd7f148 | ||
|
|
fa068b6d4a | ||
|
|
a805a49662 | ||
|
|
296e4ff5ce | ||
|
|
20749d2eae | ||
|
|
ff4e959d11 | ||
|
|
37195fefa8 | ||
|
|
13ef83cee4 | ||
|
|
b89b2d0c6a | ||
|
|
edf687fd8a | ||
|
|
cb60eba373 |
@@ -3,3 +3,4 @@ dist/
|
||||
target/
|
||||
mcs
|
||||
!mcs/
|
||||
portal-ui/node_modules/
|
||||
|
||||
52
.github/workflows/codeql.yml
vendored
Normal file
52
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: "Code scanning - action"
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 19 * * 0'
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
# CodeQL runs on ubuntu-latest and windows-latest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.14.x]
|
||||
go-version: [1.13.x, 1.14.x]
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
||||
|
||||
48
.github/workflows/release.yml
vendored
48
.github/workflows/release.yml
vendored
@@ -1,48 +0,0 @@
|
||||
name: goreleaser
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.14.x
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@53acad1befee355d46f71cccf6ab4d885eb4f77f
|
||||
with:
|
||||
version: latest
|
||||
args: release --skip-publish --rm-dist --snapshot
|
||||
-
|
||||
name: Upload Win64 Binaries
|
||||
uses: actions/upload-artifact@v1
|
||||
if: success()
|
||||
with:
|
||||
name: MCS-Snapshot-Build-Win64
|
||||
path: dist/mcs_windows_amd64
|
||||
-
|
||||
name: Upload Linux Binaries
|
||||
uses: actions/upload-artifact@v1
|
||||
if: success()
|
||||
with:
|
||||
name: MCS-Snapshot-Build-Linux-amd64
|
||||
path: dist/mcs_linux_amd64
|
||||
-
|
||||
name: Upload MacOS Binaries
|
||||
uses: actions/upload-artifact@v1
|
||||
if: success()
|
||||
with:
|
||||
name: MCS-Snapshot-Build-MacOSX-amd64
|
||||
path: dist/mcs_darwin_amd64
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,3 +31,4 @@ public.crt
|
||||
# Ignore VsCode files
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
*~
|
||||
@@ -23,7 +23,7 @@ builds:
|
||||
- -trimpath
|
||||
- --tags=kqueue
|
||||
ldflags:
|
||||
- -s -w -X github.com/minio/mcs/pkg.ReleaseTag={{.Tag}} -X github.com/minio/warp/pkg.CommitID={{.FullCommit}} -X github.com/minio/warp/pkg.Version={{.Version}} -X github.com/minio/warp/pkg.ShortCommitID={{.ShortCommit}} -X github.com/minio/warp/pkg.ReleaseTime={{.Date}}
|
||||
- -s -w -X github.com/minio/mcs/pkg.ReleaseTag={{.Tag}} -X github.com/minio/mcs/pkg.CommitID={{.FullCommit}} -X github.com/minio/mcs/pkg.Version={{.Version}} -X github.com/minio/mcs/pkg.ShortCommitID={{.ShortCommit}} -X github.com/minio/mcs/pkg.ReleaseTime={{.Date}}
|
||||
archives:
|
||||
-
|
||||
replacements:
|
||||
|
||||
84
CREDITS
84
CREDITS
@@ -20967,6 +20967,31 @@ THE SOFTWARE.
|
||||
|
||||
================================================================
|
||||
|
||||
go.uber.org/tools
|
||||
https://go.uber.org/tools
|
||||
----------------------------------------------------------------
|
||||
Copyright (c) 2017 Uber Technologies, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
================================================================
|
||||
|
||||
go.uber.org/zap
|
||||
https://go.uber.org/zap
|
||||
----------------------------------------------------------------
|
||||
@@ -21025,6 +21050,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================
|
||||
|
||||
golang.org/x/lint
|
||||
https://golang.org/x/lint
|
||||
----------------------------------------------------------------
|
||||
Copyright (c) 2013 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================
|
||||
|
||||
golang.org/x/mod
|
||||
https://golang.org/x/mod
|
||||
----------------------------------------------------------------
|
||||
@@ -23786,3 +23844,29 @@ https://gopkg.in/yaml.v2
|
||||
|
||||
================================================================
|
||||
|
||||
honnef.co/go/tools
|
||||
https://honnef.co/go/tools
|
||||
----------------------------------------------------------------
|
||||
Copyright (c) 2016 Dominik Honnef
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
FROM golang:1.14.1
|
||||
FROM golang:1.13
|
||||
|
||||
ADD go.mod /go/src/github.com/minio/mcs/go.mod
|
||||
ADD go.sum /go/src/github.com/minio/mcs/go.sum
|
||||
WORKDIR /go/src/github.com/minio/mcs/
|
||||
|
||||
# Get dependencies - will also be cached if we won't change mod/sum
|
||||
RUN go mod download
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@@ -31,6 +31,8 @@ install: mcs
|
||||
|
||||
swagger-gen:
|
||||
@echo "Generating swagger server code from yaml"
|
||||
@rm -rf models
|
||||
@rm -rf restapi/operations
|
||||
@swagger generate server -A mcs --main-package=mcs --exclude-main -P models.Principal -f ./swagger.yml -r NOTICE
|
||||
|
||||
assets:
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
A graphical user interface for [MinIO](https://github.com/minio/minio)
|
||||
|
||||
|
||||
| Dashboard | Adding A User |
|
||||
| ------------- | ------------- |
|
||||
|  |  |
|
||||
|
||||
## Setup
|
||||
|
||||
All `mcs` needs is a MinIO user with admin privileges and URL pointing to your MinIO deployment.
|
||||
@@ -57,7 +62,7 @@ Additionally, you can create policies to limit the privileges for `mcs` users, f
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Action": [
|
||||
"admin:ServerInfo",
|
||||
"admin:ServerInfo"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Sid": ""
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/minio/mcs/pkg"
|
||||
|
||||
@@ -103,10 +104,12 @@ func newApp(name string) *cli.App {
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = name
|
||||
app.Version = pkg.Version
|
||||
app.Version = pkg.Version + " - " + pkg.ShortCommitID
|
||||
app.Author = "MinIO, Inc."
|
||||
app.Usage = "mcs"
|
||||
app.Usage = "MinIO Console Server"
|
||||
app.Description = `MinIO Console Server`
|
||||
app.Copyright = "(c) 2020 MinIO, Inc."
|
||||
app.Compiled, _ = time.Parse(time.RFC3339, pkg.ReleaseTime)
|
||||
app.Commands = commands
|
||||
app.HideHelpCommand = true // Hide `help, h` command, we already have `minio --help`.
|
||||
app.CustomAppHelpTemplate = mcsHelpTemplate
|
||||
|
||||
2
go.mod
2
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/minio/mcs
|
||||
|
||||
go 1.14
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
|
||||
BIN
images/pic1.png
Normal file
BIN
images/pic1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 635 KiB |
BIN
images/pic2.png
Normal file
BIN
images/pic2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 688 KiB |
@@ -1,147 +0,0 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// AddNotificationEndpoint add notification endpoint
|
||||
//
|
||||
// swagger:model addNotificationEndpoint
|
||||
type AddNotificationEndpoint struct {
|
||||
|
||||
// account
|
||||
Account string `json:"account,omitempty"`
|
||||
|
||||
// properties
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
|
||||
// service
|
||||
// Enum: [webhook amqp kafka mqtt nats nsq mysql postgres elasticsearch redis]
|
||||
Service string `json:"service,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this add notification endpoint
|
||||
func (m *AddNotificationEndpoint) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateService(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var addNotificationEndpointTypeServicePropEnum []interface{}
|
||||
|
||||
func init() {
|
||||
var res []string
|
||||
if err := json.Unmarshal([]byte(`["webhook","amqp","kafka","mqtt","nats","nsq","mysql","postgres","elasticsearch","redis"]`), &res); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range res {
|
||||
addNotificationEndpointTypeServicePropEnum = append(addNotificationEndpointTypeServicePropEnum, v)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
// AddNotificationEndpointServiceWebhook captures enum value "webhook"
|
||||
AddNotificationEndpointServiceWebhook string = "webhook"
|
||||
|
||||
// AddNotificationEndpointServiceAmqp captures enum value "amqp"
|
||||
AddNotificationEndpointServiceAmqp string = "amqp"
|
||||
|
||||
// AddNotificationEndpointServiceKafka captures enum value "kafka"
|
||||
AddNotificationEndpointServiceKafka string = "kafka"
|
||||
|
||||
// AddNotificationEndpointServiceMqtt captures enum value "mqtt"
|
||||
AddNotificationEndpointServiceMqtt string = "mqtt"
|
||||
|
||||
// AddNotificationEndpointServiceNats captures enum value "nats"
|
||||
AddNotificationEndpointServiceNats string = "nats"
|
||||
|
||||
// AddNotificationEndpointServiceNsq captures enum value "nsq"
|
||||
AddNotificationEndpointServiceNsq string = "nsq"
|
||||
|
||||
// AddNotificationEndpointServiceMysql captures enum value "mysql"
|
||||
AddNotificationEndpointServiceMysql string = "mysql"
|
||||
|
||||
// AddNotificationEndpointServicePostgres captures enum value "postgres"
|
||||
AddNotificationEndpointServicePostgres string = "postgres"
|
||||
|
||||
// AddNotificationEndpointServiceElasticsearch captures enum value "elasticsearch"
|
||||
AddNotificationEndpointServiceElasticsearch string = "elasticsearch"
|
||||
|
||||
// AddNotificationEndpointServiceRedis captures enum value "redis"
|
||||
AddNotificationEndpointServiceRedis string = "redis"
|
||||
)
|
||||
|
||||
// prop value enum
|
||||
func (m *AddNotificationEndpoint) validateServiceEnum(path, location string, value string) error {
|
||||
if err := validate.Enum(path, location, value, addNotificationEndpointTypeServicePropEnum); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AddNotificationEndpoint) 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
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *AddNotificationEndpoint) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *AddNotificationEndpoint) UnmarshalBinary(b []byte) error {
|
||||
var res AddNotificationEndpoint
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// PolicyStatement policy statement
|
||||
//
|
||||
// swagger:model policyStatement
|
||||
type PolicyStatement struct {
|
||||
|
||||
// action
|
||||
Action []string `json:"Action"`
|
||||
|
||||
// condition
|
||||
Condition string `json:"Condition,omitempty"`
|
||||
|
||||
// effect
|
||||
Effect string `json:"Effect,omitempty"`
|
||||
|
||||
// resource
|
||||
Resource string `json:"Resource,omitempty"`
|
||||
|
||||
// sid
|
||||
Sid string `json:"Sid,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this policy statement
|
||||
func (m *PolicyStatement) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *PolicyStatement) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *PolicyStatement) UnmarshalBinary(b []byte) error {
|
||||
var res PolicyStatement
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// PolicyStatements policy statements
|
||||
//
|
||||
// swagger:model policyStatements
|
||||
type PolicyStatements []*PolicyStatement
|
||||
|
||||
// Validate validates this policy statements
|
||||
func (m PolicyStatements) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
for i := 0; i < len(m); i++ {
|
||||
if swag.IsZero(m[i]) { // not required
|
||||
continue
|
||||
}
|
||||
|
||||
if m[i] != nil {
|
||||
if err := m[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName(strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
29
pkg/acl/config.go
Normal file
29
pkg/acl/config.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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 acl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/pkg/env"
|
||||
)
|
||||
|
||||
// GetOperatorOnly gets mcs operator mode status set on env variable
|
||||
//or default one
|
||||
func GetOperatorOnly() string {
|
||||
return strings.ToLower(env.Get(McsOperatorOnly, "off"))
|
||||
}
|
||||
21
pkg/acl/const.go
Normal file
21
pkg/acl/const.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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 acl
|
||||
|
||||
const (
|
||||
McsOperatorOnly = "MCS_OPERATOR_ONLY"
|
||||
)
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
package acl
|
||||
|
||||
import iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
import (
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
// endpoints definition
|
||||
var (
|
||||
@@ -31,8 +33,11 @@ var (
|
||||
watch = "/watch"
|
||||
notifications = "/notification-endpoints"
|
||||
buckets = "/buckets"
|
||||
bucketDetails = "/buckets/:bucketName"
|
||||
bucketsDetail = "/buckets/:bucketName"
|
||||
serviceAccounts = "/service-accounts"
|
||||
tenants = "/tenants"
|
||||
tenantsDetail = "/tenants/:tenantName"
|
||||
heal = "/heal"
|
||||
)
|
||||
|
||||
type ConfigurationActionSet struct {
|
||||
@@ -181,12 +186,28 @@ var bucketsActionSet = ConfigurationActionSet{
|
||||
),
|
||||
}
|
||||
|
||||
// serviceAccountsActionSet contains the list of admin actions required for this endpoint to work
|
||||
// serviceAccountsActionSet no actions needed for this module to work
|
||||
var serviceAccountsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(),
|
||||
actions: iampolicy.NewActionSet(),
|
||||
}
|
||||
|
||||
// tenantsActionSet temporally no actions needed for tenants sections to work
|
||||
var tenantsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(),
|
||||
actions: iampolicy.NewActionSet(),
|
||||
}
|
||||
|
||||
// healActionSet contains the list of admin actions required for this endpoint to work
|
||||
var healActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.HealAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// endpointRules contains the mapping between endpoints and ActionSets, additional rules can be added here
|
||||
var endpointRules = map[string]ConfigurationActionSet{
|
||||
configuration: configurationActionSet,
|
||||
@@ -200,10 +221,20 @@ var endpointRules = map[string]ConfigurationActionSet{
|
||||
watch: watchActionSet,
|
||||
notifications: notificationsActionSet,
|
||||
buckets: bucketsActionSet,
|
||||
bucketDetails: bucketsActionSet,
|
||||
bucketsDetail: bucketsActionSet,
|
||||
serviceAccounts: serviceAccountsActionSet,
|
||||
heal: healActionSet,
|
||||
}
|
||||
|
||||
// operatorRules contains the mapping between endpoints and ActionSets for operator only mode
|
||||
var operatorRules = map[string]ConfigurationActionSet{
|
||||
tenants: tenantsActionSet,
|
||||
tenantsDetail: tenantsActionSet,
|
||||
}
|
||||
|
||||
// operatorOnly ENV variable
|
||||
var operatorOnly = GetOperatorOnly()
|
||||
|
||||
// GetActionsStringFromPolicy extract the admin/s3 actions from a given policy and return them in []string format
|
||||
//
|
||||
// ie:
|
||||
@@ -253,13 +284,19 @@ func actionsStringToActionSet(actions []string) iampolicy.ActionSet {
|
||||
// GetAuthorizedEndpoints return a list of allowed endpoint based on a provided *iampolicy.Policy
|
||||
// ie: pages the user should have access based on his current privileges
|
||||
func GetAuthorizedEndpoints(actions []string) []string {
|
||||
rangeTake := endpointRules
|
||||
|
||||
if operatorOnly == "on" {
|
||||
rangeTake = operatorRules
|
||||
}
|
||||
|
||||
if len(actions) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
// Prepare new ActionSet structure that will hold all the user actions
|
||||
userAllowedAction := actionsStringToActionSet(actions)
|
||||
allowedEndpoints := []string{}
|
||||
for endpoint, rules := range endpointRules {
|
||||
for endpoint, rules := range rangeTake {
|
||||
// check if user policy matches s3:* or admin:* typesIntersection
|
||||
endpointActionTypes := rules.actionTypes
|
||||
typesIntersection := endpointActionTypes.Intersection(userAllowedAction)
|
||||
|
||||
@@ -23,15 +23,28 @@ import (
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
type args struct {
|
||||
actions []string
|
||||
type args struct {
|
||||
actions []string
|
||||
}
|
||||
|
||||
type endpoint struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}
|
||||
|
||||
func validateEndpoints(t *testing.T, configs []endpoint) {
|
||||
for _, tt := range configs {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := GetAuthorizedEndpoints(tt.args.actions); !reflect.DeepEqual(len(got), tt.want) {
|
||||
t.Errorf("GetAuthorizedEndpoints() = %v, want %v", len(got), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
}
|
||||
|
||||
func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
tests := []endpoint{
|
||||
{
|
||||
name: "dashboard endpoint",
|
||||
args: args{
|
||||
@@ -59,7 +72,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"admin:*",
|
||||
},
|
||||
},
|
||||
want: 10,
|
||||
want: 11,
|
||||
},
|
||||
{
|
||||
name: "all s3 endpoints",
|
||||
@@ -78,7 +91,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 13,
|
||||
want: 14,
|
||||
},
|
||||
{
|
||||
name: "no endpoints",
|
||||
@@ -88,13 +101,52 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := GetAuthorizedEndpoints(tt.args.actions); !reflect.DeepEqual(len(got), tt.want) {
|
||||
t.Errorf("GetAuthorizedEndpoints() = %v, want %v", len(got), tt.want)
|
||||
}
|
||||
})
|
||||
|
||||
validateEndpoints(t, tests)
|
||||
}
|
||||
|
||||
func TestOperatorOnlyEndpoints(t *testing.T) {
|
||||
operatorOnly = "on"
|
||||
|
||||
tests := []endpoint{
|
||||
{
|
||||
name: "Operator Only - all admin endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"admin:*",
|
||||
},
|
||||
},
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "Operator Only - all s3 endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "Operator Only - all admin and s3 endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"admin:*",
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "Operator Only - no endpoints",
|
||||
args: args{
|
||||
[]string{},
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
|
||||
validateEndpoints(t, tests)
|
||||
}
|
||||
|
||||
func TestGetActionsStringFromPolicy(t *testing.T) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
5969
portal-ui/package-lock.json
generated
5969
portal-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
||||
"@types/webpack-env": "^1.14.1",
|
||||
"@types/websocket": "^1.0.0",
|
||||
"ansi-to-react": "^6.0.5",
|
||||
"chart.js": "^2.9.3",
|
||||
"codemirror": "^5.52.2",
|
||||
"history": "^4.10.1",
|
||||
"local-storage-fallback": "^4.1.1",
|
||||
@@ -28,6 +29,7 @@
|
||||
"moment": "^2.24.0",
|
||||
"npm": "^6.14.4",
|
||||
"react": "^16.13.1",
|
||||
"react-chartjs-2": "^2.9.0",
|
||||
"react-codemirror2": "^7.1.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-moment": "^0.9.7",
|
||||
|
||||
@@ -25,6 +25,7 @@ export const units = [
|
||||
"ZiB",
|
||||
"YiB",
|
||||
];
|
||||
export const k8sUnits = ["Ki", "Mi", "Gi", "Ti", "Pi", "Ei"];
|
||||
export const niceBytes = (x: string) => {
|
||||
let l = 0,
|
||||
n = parseInt(x, 10) || 0;
|
||||
@@ -57,3 +58,37 @@ export const timeFromDate = (d: Date) => {
|
||||
|
||||
return `${h}:${m}:${s}:${d.getMilliseconds()}`;
|
||||
};
|
||||
|
||||
// units to be used in a dropdown
|
||||
export const factorForDropdown = () => {
|
||||
return units.map((unit) => {
|
||||
return { label: unit, value: unit };
|
||||
});
|
||||
};
|
||||
|
||||
// units to be used in a dropdown
|
||||
export const k8sfactorForDropdown = () => {
|
||||
return k8sUnits.map((unit) => {
|
||||
return { label: unit, value: unit };
|
||||
});
|
||||
};
|
||||
|
||||
//getBytes, converts from a value and a unit from units array to bytes
|
||||
export const getBytes = (value: string, unit: string) => {
|
||||
const vl: number = parseFloat(value);
|
||||
const powFactor = units.findIndex((element) => element === unit);
|
||||
|
||||
if (powFactor == -1) {
|
||||
return 0;
|
||||
}
|
||||
const factor = Math.pow(1024, powFactor);
|
||||
const total = vl * factor;
|
||||
|
||||
return total.toString(10);
|
||||
};
|
||||
|
||||
//getTotalSize gets the total size of a value & unit
|
||||
export const getTotalSize = (value: string, unit: string) => {
|
||||
const bytes = getBytes(value, unit).toString(10);
|
||||
return niceBytes(bytes);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -14,22 +14,18 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import React from "react";
|
||||
import {SvgIcon} from "@material-ui/core";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class BucketsIcon extends React.Component {
|
||||
render() {
|
||||
return (<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<title>ic_h_buckets</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<polygon className="cls-1" points="13.428 16 2.572 16 0 0 16 0 13.428 16"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>)
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
|
||||
<path d="M8.392,10H1.608L0,0H10Z" />
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default BucketsIcon;
|
||||
|
||||
123
portal-ui/src/icons/ClustersIcon.tsx
Normal file
123
portal-ui/src/icons/ClustersIcon.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class ClustersIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 9">
|
||||
<g transform="translate(79 438.479)">
|
||||
<g>
|
||||
<g>
|
||||
<rect x="-77.9" y="-434.5" width="7.8" height="1" />
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect
|
||||
x="-77.9"
|
||||
y="-434.5"
|
||||
transform="matrix(0.4999 -0.8661 0.8661 0.4999 338.8698 -281.1237)"
|
||||
width="7.8"
|
||||
height="1"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect
|
||||
x="-74.5"
|
||||
y="-437.9"
|
||||
transform="matrix(0.866 -0.5001 0.5001 0.866 207.1129 -95.1668)"
|
||||
width="1"
|
||||
height="7.8"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d="M-71.8-430.1h-4.5l-2.2-3.9l2.2-3.9h4.5l2.2,3.9L-71.8-430.1z M-75.7-431.1h3.3l1.7-2.9l-1.7-2.9h-3.3
|
||||
l-1.7,2.9L-75.7-431.1z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d="M-72.3-434c0,0.9-0.7,1.7-1.7,1.7c-0.9,0-1.7-0.7-1.7-1.7c0-0.9,0.7-1.7,1.7-1.7
|
||||
C-73.1-435.7-72.3-434.9-72.3-434z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d="M-76.8-434c0,0.6-0.5,1.1-1.1,1.1c0,0,0,0,0,0c-0.6,0-1.1-0.5-1.1-1.1c0,0,0,0,0,0c0-0.6,0.5-1.1,1.1-1.1
|
||||
c0,0,0,0,0,0C-77.3-435.1-76.8-434.6-76.8-434C-76.8-434-76.8-434-76.8-434z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d="M-69-434c0,0.6-0.5,1.1-1.1,1.1c0,0,0,0,0,0c-0.6,0-1.1-0.5-1.1-1.1c0,0,0,0,0,0c0-0.6,0.5-1.1,1.1-1.1
|
||||
c0,0,0,0,0,0C-69.5-435.1-69-434.6-69-434C-69-434-69-434-69-434z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d="M-75.4-431.6c0.5,0.3,0.7,1,0.4,1.5c-0.3,0.5-1,0.7-1.5,0.4c0,0,0,0,0,0c-0.5-0.3-0.7-1-0.4-1.5
|
||||
C-76.6-431.7-75.9-431.9-75.4-431.6C-75.4-431.6-75.4-431.6-75.4-431.6z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d="M-71.5-438.3c0.5,0.3,0.7,1,0.4,1.5c-0.3,0.5-1,0.7-1.5,0.4c0,0,0,0,0,0c-0.5-0.3-0.7-1-0.4-1.5
|
||||
C-72.7-438.5-72-438.6-71.5-438.3C-71.5-438.3-71.5-438.3-71.5-438.3z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d="M-72.6-431.6c0.5-0.3,1.2-0.1,1.5,0.4c0,0,0,0,0,0c0.3,0.5,0.1,1.2-0.4,1.5c-0.5,0.3-1.2,0.1-1.5-0.4
|
||||
c0,0,0,0,0,0C-73.3-430.6-73.1-431.3-72.6-431.6z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d="M-76.5-438.3c0.5-0.3,1.2-0.1,1.5,0.4c0,0,0,0,0,0c0.3,0.5,0.1,1.2-0.4,1.5c-0.5,0.3-1.2,0.1-1.5-0.4
|
||||
c0,0,0,0,0,0C-77.2-437.3-77-438-76.5-438.3z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ClustersIcon;
|
||||
42
portal-ui/src/icons/ConfigurationsListIcon.tsx
Normal file
42
portal-ui/src/icons/ConfigurationsListIcon.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class ConfigurationsListIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
|
||||
<rect width="1.433" height="1" />
|
||||
<rect width="7.828" height="1" transform="translate(2.172)" />
|
||||
<rect width="1.433" height="1" transform="translate(0 6)" />
|
||||
<rect width="1.433" height="1" transform="translate(0 3)" />
|
||||
<rect width="1.433" height="1" transform="translate(0 9)" />
|
||||
<rect width="1.368" height="0.569" transform="translate(6.316 9)" />
|
||||
<path d="M5.566,9.569v-.31l-.238-.138-.269.155-.65.375L4.034,9V9H2.172v1H5.566Z" />
|
||||
<path d="M9.966,9l-.375.65-.65-.375-.269-.155-.238.138V10H10V9H9.967Z" />
|
||||
<path d="M3.625,6.793l.269-.155V6.362l-.269-.155L3.266,6H2.172V7H3.266Z" />
|
||||
<path d="M8.434,3.431v.31l.238.138.269-.155.649-.375L9.966,4V4H10V3H8.434Z" />
|
||||
<path d="M4.034,4l.375-.65.65.375.269.155.238-.138V3H2.172V4H4.033Z" />
|
||||
<path d="M9.356,5.929,10,5.558,9.316,4.373l-.644.372-.988-.571V3.431H6.316v.743l-.988.571-.644-.372L4,5.558l.644.371V7.071L4,7.442l.684,1.185.644-.372.988.571v.743H7.684V8.826l.988-.571.644.372L10,7.442l-.644-.371ZM7,7.278A.778.778,0,1,1,7.778,6.5.779.779,0,0,1,7,7.278Z" />
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfigurationsListIcon;
|
||||
@@ -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
|
||||
@@ -20,15 +20,24 @@ class DashboardIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<title>ic_h_dashboard</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<rect className="cls-1" x="9" width="7" height="7" />
|
||||
<rect className="cls-1" width="7" height="7" />
|
||||
<rect className="cls-1" x="9" y="9" width="7" height="7" />
|
||||
<rect className="cls-1" y="9" width="7" height="7" />
|
||||
</g>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
|
||||
<g transform="translate(249 720)">
|
||||
<rect
|
||||
width="6"
|
||||
height="5"
|
||||
transform="translate(-244 -720) rotate(90)"
|
||||
/>
|
||||
<rect width="4" height="4" transform="translate(-243 -720)" />
|
||||
<rect
|
||||
width="5"
|
||||
height="4"
|
||||
transform="translate(-239 -715) rotate(90)"
|
||||
/>
|
||||
<rect
|
||||
width="5"
|
||||
height="3"
|
||||
transform="translate(-244 -710) rotate(180)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
|
||||
41
portal-ui/src/icons/GroupsIcon.tsx
Normal file
41
portal-ui/src/icons/GroupsIcon.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class GroupsIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 9.787">
|
||||
<g transform="translate(177 719.787)">
|
||||
<g transform="translate(-105 -720)">
|
||||
<path d="M-65,5a3,3,0,0,0-1.131.224A3.981,3.981,0,0,1-65,8v2h3V8A3,3,0,0,0-65,5Z" />
|
||||
<path d="M-72,10h6V8a3,3,0,0,0-3-3,3,3,0,0,0-3,3Z" />
|
||||
<path
|
||||
className="a"
|
||||
d="M-65,.213a1.993,1.993,0,0,0-1.384.561A2.967,2.967,0,0,1-66,2.213a2.964,2.964,0,0,1-.384,1.439A1.989,1.989,0,0,0-65,4.213a2,2,0,0,0,2-2A2,2,0,0,0-65,.213Z"
|
||||
/>
|
||||
<circle cx="2" cy="2" r="2" transform="translate(-71 0.213)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default GroupsIcon;
|
||||
34
portal-ui/src/icons/IAMPoliciesIcon.tsx
Normal file
34
portal-ui/src/icons/IAMPoliciesIcon.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class BucketsIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8.75 10">
|
||||
<path
|
||||
d="M-44.625,10l-4.353-2.419L-53.375,10V0h8.75Z"
|
||||
transform="translate(53.375)"
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default BucketsIcon;
|
||||
34
portal-ui/src/icons/LambdaNotificationsIcon.tsx
Normal file
34
portal-ui/src/icons/LambdaNotificationsIcon.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class LambdaNotificationsIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
|
||||
<path
|
||||
d="M0,0v10l2.8-2.2H10V0H0z M6.6,6L5.6,6.4l-0.8-2l-1.5,2L2.5,5.9l1.9-2.6L4.1,2.4H3.2v-1h1.5l1.4,3.7l0.9-0.4
|
||||
l0.4,0.9L6.6,6z"
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LambdaNotificationsIcon;
|
||||
35
portal-ui/src/icons/MirroringIcon.tsx
Normal file
35
portal-ui/src/icons/MirroringIcon.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class MirroringIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
|
||||
<g transform="translate(61 439)">
|
||||
<rect width="1.5" height="10" transform="translate(-56.75 -439)" />
|
||||
<path d="M6.5,10V0h.572L10,10Z" transform="translate(-61 -439)" />
|
||||
<path d="M3.5,10V0H2.928L0,10Z" transform="translate(-61 -439)" />
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MirroringIcon;
|
||||
41
portal-ui/src/icons/ServiceAccountsIcon.tsx
Normal file
41
portal-ui/src/icons/ServiceAccountsIcon.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class ServiceAccountsIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 9.5">
|
||||
<g transform="translate(231 719.516)">
|
||||
<path
|
||||
d="M-125.5,7.984a4.5,4.5,0,0,1,4.5-4.5,4.5,4.5,0,0,1,4.5,4.5Z"
|
||||
transform="translate(-105 -720)"
|
||||
/>
|
||||
<rect width="10" height="1" transform="translate(-231 -711.016)" />
|
||||
<path
|
||||
d="M-119.5.484h-3v1h1v1h1v-1h1Z"
|
||||
transform="translate(-105 -720)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ServiceAccountsIcon;
|
||||
62
portal-ui/src/icons/TraceIcon.tsx
Normal file
62
portal-ui/src/icons/TraceIcon.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class TraceIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9.998 10">
|
||||
<g transform="translate(140.999 720)">
|
||||
<g transform="translate(-105 -720)">
|
||||
<rect
|
||||
width="1.114"
|
||||
height="1.667"
|
||||
transform="translate(-27.116 8.333)"
|
||||
/>
|
||||
<path d="M-28.184,10H-29.3V8.154l2.182-3.037V3.147H-26V5.476l-2.182,3.037Z" />
|
||||
<rect
|
||||
width="1.114"
|
||||
height="2.963"
|
||||
transform="translate(-31.531)"
|
||||
/>
|
||||
<rect
|
||||
width="1.114"
|
||||
height="2.132"
|
||||
transform="translate(-27.115 0)"
|
||||
/>
|
||||
<rect
|
||||
width="1.114"
|
||||
height="5.389"
|
||||
transform="translate(-29.298)"
|
||||
/>
|
||||
<path d="M-30.417,10h-1.114V5.722l-2.233-3V0h1.114V2.353l2.233,3Z" />
|
||||
<path d="M-32.65,10h-1.114V6.185l-2.234-3V0h1.114V2.815l2.234,3Z" />
|
||||
<rect
|
||||
width="1.114"
|
||||
height="4.463"
|
||||
transform="translate(-35.999 5.537)"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TraceIcon;
|
||||
@@ -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
|
||||
@@ -14,27 +14,32 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import React from "react";
|
||||
import {SvgIcon} from "@material-ui/core";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class UsersIcon extends React.Component {
|
||||
render() {
|
||||
return (<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 14.874">
|
||||
<title>ic_users</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path className="cls-1"
|
||||
d="M3.5,6.875h0a3.5,3.5,0,0,1,3.5,3.5v4.5a0,0,0,0,1,0,0H0a0,0,0,0,1,0,0v-4.5A3.5,3.5,0,0,1,3.5,6.875Z"/>
|
||||
<path className="cls-1"
|
||||
d="M12.5,6.875h0a3.5,3.5,0,0,1,3.5,3.5v4.5a0,0,0,0,1,0,0H9a0,0,0,0,1,0,0v-4.5A3.5,3.5,0,0,1,12.5,6.875Z"/>
|
||||
<circle className="cls-1" cx="3.498" cy="2.859" r="2.859"/>
|
||||
<circle className="cls-1" cx="12.502" cy="2.859" r="2.859"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>)
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6.131 10">
|
||||
<g transform="translate(193 719.787)">
|
||||
<g transform="translate(-193 -719.787)">
|
||||
<path
|
||||
d="M3,0h.131a3,3,0,0,1,3,3V5a0,0,0,0,1,0,0H0A0,0,0,0,1,0,5V3A3,3,0,0,1,3,0Z"
|
||||
transform="translate(0 5)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="2.065"
|
||||
cy="2"
|
||||
rx="2.065"
|
||||
ry="2"
|
||||
transform="translate(1 0)"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UsersIcon;
|
||||
|
||||
39
portal-ui/src/icons/WarpIcon.tsx
Normal file
39
portal-ui/src/icons/WarpIcon.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class WarpIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
|
||||
<g transform="translate(43 439)">
|
||||
<path d="M27.5,10" transform="translate(-61 -439)" />
|
||||
<rect width="1.5" height="2" transform="translate(-43 -431)" />
|
||||
<rect width="1.5" height="6" transform="translate(-38.75 -435)" />
|
||||
<rect width="1.5" height="8" transform="translate(-36.625 -437)" />
|
||||
<rect width="1.5" height="4" transform="translate(-40.875 -433)" />
|
||||
<rect width="1.5" height="10" transform="translate(-34.5 -439)" />
|
||||
<path d="M18.5,10" transform="translate(-61 -439)" />
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WarpIcon;
|
||||
59
portal-ui/src/icons/WatchIcon.tsx
Normal file
59
portal-ui/src/icons/WatchIcon.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
class WatchIcon extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
|
||||
<g transform="translate(213 720)">
|
||||
<g transform="translate(-105 -720)">
|
||||
<rect width="1.5" height="4" transform="translate(-108)" />
|
||||
<rect width="1.5" height="4" transform="translate(-108 6)" />
|
||||
<rect width="1.5" height="4" transform="translate(-99.5 6)" />
|
||||
<rect width="1.5" height="4" transform="translate(-99.5)" />
|
||||
<rect
|
||||
width="1.5"
|
||||
height="4"
|
||||
transform="translate(-98) rotate(90)"
|
||||
/>
|
||||
<rect
|
||||
width="1.5"
|
||||
height="4"
|
||||
transform="translate(-104) rotate(90)"
|
||||
/>
|
||||
<rect
|
||||
width="1.5"
|
||||
height="4"
|
||||
transform="translate(-104 8.5) rotate(90)"
|
||||
/>
|
||||
<rect
|
||||
width="1.5"
|
||||
height="4"
|
||||
transform="translate(-98 8.5) rotate(90)"
|
||||
/>
|
||||
<circle cx="2" cy="2" r="2" transform="translate(-105 3)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WatchIcon;
|
||||
@@ -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
|
||||
@@ -14,10 +14,20 @@
|
||||
// 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 { default as PermissionIcon } from './PermissionIcon';
|
||||
export { default as CreateIcon } from './CreateIcon';
|
||||
export { default as DeleteIcon } from './DeleteIcon';
|
||||
export { default as ServiceAccountIcon } from './ServiceAccountIcon';
|
||||
export { default as DashboardIcon } from './DashboardIcon';
|
||||
export { default as BucketsIcon } from './BucketsIcon';
|
||||
export { default as UsersIcon } from './UsersIcon';
|
||||
export { default as PermissionIcon } from "./PermissionIcon";
|
||||
export { default as CreateIcon } from "./CreateIcon";
|
||||
export { default as DeleteIcon } from "./DeleteIcon";
|
||||
export { default as ServiceAccountIcon } from "./ServiceAccountIcon";
|
||||
export { default as DashboardIcon } from "./DashboardIcon";
|
||||
export { default as BucketsIcon } from "./BucketsIcon";
|
||||
export { default as UsersIcon } from "./UsersIcon";
|
||||
export { default as ServiceAccountsIcon } from "./ServiceAccountsIcon";
|
||||
export { default as GroupsIcon } from "./GroupsIcon";
|
||||
export { default as IAMPoliciesIcon } from "./IAMPoliciesIcon";
|
||||
export { default as TraceIcon } from "./TraceIcon";
|
||||
export { default as LambdaNotificationsIcon } from "./LambdaNotificationsIcon";
|
||||
export { default as ConfigurationsListIcon } from "./ConfigurationsListIcon";
|
||||
export { default as ClustersIcon } from "./ClustersIcon";
|
||||
export { default as MirroringIcon } from "./MirroringIcon";
|
||||
export { default as WarpIcon } from "./WarpIcon";
|
||||
export { default as WatchIcon } from "./WatchIcon";
|
||||
|
||||
@@ -35,6 +35,7 @@ interface ISetAccessPolicyProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
bucketName: string;
|
||||
actualPolicy: string;
|
||||
closeModalAndRefresh: () => void;
|
||||
}
|
||||
|
||||
@@ -86,8 +87,14 @@ class SetAccessPolicy extends React.Component<
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { actualPolicy } = this.props;
|
||||
|
||||
this.setState({ accessPolicy: actualPolicy });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, open } = this.props;
|
||||
const { classes, open, actualPolicy } = this.props;
|
||||
const { addLoading, addError, accessPolicy } = this.state;
|
||||
return (
|
||||
<ModalWrapper
|
||||
|
||||
@@ -17,9 +17,13 @@
|
||||
import React from "react";
|
||||
import get from "lodash/get";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
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 } from "../types";
|
||||
import { BucketEvent, BucketEventList, BucketInfo, BucketList } from "../types";
|
||||
import { Button } from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import SetAccessPolicy from "./SetAccessPolicy";
|
||||
@@ -28,6 +32,7 @@ import { CreateIcon } from "../../../../icons";
|
||||
import AddEvent from "./AddEvent";
|
||||
import DeleteEvent from "./DeleteEvent";
|
||||
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
|
||||
import { niceBytes } from "../../../../common/utils";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -76,6 +81,42 @@ const styles = (theme: Theme) =>
|
||||
textAlign: "center",
|
||||
padding: "20px",
|
||||
},
|
||||
gridContainer: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "auto auto",
|
||||
gridGap: 8,
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
"& div:not(.MuiCircularProgress-root)": {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
"& div:nth-child(odd)": {
|
||||
justifyContent: "flex-end",
|
||||
fontWeight: 700,
|
||||
},
|
||||
"& div:nth-child(2n)": {
|
||||
minWidth: 150,
|
||||
},
|
||||
},
|
||||
masterActions: {
|
||||
width: "25%",
|
||||
minWidth: "120px",
|
||||
"& div": {
|
||||
margin: "5px 0px",
|
||||
},
|
||||
},
|
||||
paperContainer: {
|
||||
padding: 15,
|
||||
paddingLeft: 23,
|
||||
},
|
||||
headerContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
capitalizeFirst: {
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
});
|
||||
|
||||
interface IViewBucketProps {
|
||||
@@ -87,9 +128,12 @@ interface IViewBucketState {
|
||||
info: BucketInfo | null;
|
||||
records: BucketEvent[];
|
||||
totalRecords: number;
|
||||
loading: boolean;
|
||||
loadingBucket: boolean;
|
||||
loadingEvents: boolean;
|
||||
loadingSize: boolean;
|
||||
error: string;
|
||||
deleteError: string;
|
||||
errBucket: string;
|
||||
setAccessPolicyScreenOpen: boolean;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
@@ -97,6 +141,8 @@ interface IViewBucketState {
|
||||
deleteOpen: boolean;
|
||||
selectedBucket: string;
|
||||
selectedEvent: BucketEvent | null;
|
||||
bucketSize: string;
|
||||
errorSize: string;
|
||||
}
|
||||
|
||||
class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
@@ -104,9 +150,12 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
info: null,
|
||||
records: [],
|
||||
totalRecords: 0,
|
||||
loading: false,
|
||||
loadingBucket: true,
|
||||
loadingEvents: true,
|
||||
loadingSize: true,
|
||||
error: "",
|
||||
deleteError: "",
|
||||
errBucket: "",
|
||||
setAccessPolicyScreenOpen: false,
|
||||
page: 0,
|
||||
rowsPerPage: 10,
|
||||
@@ -114,10 +163,12 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
deleteOpen: false,
|
||||
selectedBucket: "",
|
||||
selectedEvent: null,
|
||||
bucketSize: "0",
|
||||
errorSize: "",
|
||||
};
|
||||
|
||||
fetchEvents() {
|
||||
this.setState({ loading: true }, () => {
|
||||
this.setState({ loadingBucket: true }, () => {
|
||||
const { page } = this.state;
|
||||
const { match } = this.props;
|
||||
const bucketName = match.params["bucketName"];
|
||||
@@ -128,7 +179,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
const total = get(res, "total", 0);
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
loadingEvents: false,
|
||||
records: events || [],
|
||||
totalRecords: total,
|
||||
error: "",
|
||||
@@ -142,7 +193,50 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.setState({ loading: false, error: err });
|
||||
this.setState({ loadingEvents: false, error: err });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchBucketsSize() {
|
||||
const { match } = this.props;
|
||||
const bucketName = match.params["bucketName"];
|
||||
|
||||
this.setState({ loadingSize: true }, () => {
|
||||
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");
|
||||
|
||||
this.setState({
|
||||
loadingSize: false,
|
||||
errorSize: "",
|
||||
bucketSize: size,
|
||||
});
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.setState({ loadingSize: false, errorSize: err });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
loadInfo() {
|
||||
const { match } = this.props;
|
||||
const bucketName = match.params["bucketName"];
|
||||
this.setState({ loadingBucket: true }, () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}`)
|
||||
.then((res: BucketInfo) => {
|
||||
this.setState({ loadingBucket: false, info: res });
|
||||
})
|
||||
.catch((err) => {
|
||||
this.setState({ loadingBucket: false, errBucket: err });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -161,20 +255,10 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
});
|
||||
}
|
||||
|
||||
loadInfo() {
|
||||
const { match } = this.props;
|
||||
const bucketName = match.params["bucketName"];
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}`)
|
||||
.then((res: BucketInfo) => {
|
||||
this.setState({ info: res });
|
||||
})
|
||||
.catch((err) => {});
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.loadInfo();
|
||||
this.fetchEvents();
|
||||
this.fetchBucketsSize();
|
||||
}
|
||||
|
||||
bucketFilter(): void {}
|
||||
@@ -186,12 +270,15 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
records,
|
||||
totalRecords,
|
||||
setAccessPolicyScreenOpen,
|
||||
loading,
|
||||
loadingEvents,
|
||||
loadingBucket,
|
||||
page,
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
addScreenOpen,
|
||||
selectedEvent,
|
||||
bucketSize,
|
||||
loadingSize,
|
||||
} = this.state;
|
||||
|
||||
const offset = page * rowsPerPage;
|
||||
@@ -242,6 +329,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
<SetAccessPolicy
|
||||
bucketName={bucketName}
|
||||
open={setAccessPolicyScreenOpen}
|
||||
actualPolicy={accessPolicy}
|
||||
closeModalAndRefresh={() => {
|
||||
this.closeAddModalAndRefresh();
|
||||
}}
|
||||
@@ -258,36 +346,75 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
Access Policy: {accessPolicy}
|
||||
{" "}
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
setAccessPolicyScreenOpen: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Change Access Policy
|
||||
</Button>
|
||||
<br />
|
||||
Reported Usage: 0 bytes
|
||||
<br />
|
||||
<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}>
|
||||
<div>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
size="medium"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
setAccessPolicyScreenOpen: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Change Access Policy
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="h6">Events</Typography>
|
||||
<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"
|
||||
size="small"
|
||||
startIcon={<CreateIcon />}
|
||||
size="medium"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
@@ -313,7 +440,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
{ label: "Prefix", elementKey: "prefix" },
|
||||
{ label: "Suffix", elementKey: "suffix" },
|
||||
]}
|
||||
isLoading={loading}
|
||||
isLoading={loadingEvents}
|
||||
records={filteredRecords}
|
||||
entityName="Events"
|
||||
idField="id"
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
// 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 {
|
||||
Checkbox,
|
||||
Grid,
|
||||
InputLabel,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
Tooltip,
|
||||
} from "@material-ui/core";
|
||||
import { OutlinedInputProps } from "@material-ui/core/OutlinedInput";
|
||||
import {
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
} from "@material-ui/core/styles";
|
||||
import {
|
||||
checkboxIcons,
|
||||
fieldBasic,
|
||||
tooltipHelper,
|
||||
} from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
interface CheckBoxProps {
|
||||
label: string;
|
||||
classes: any;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
value: string | boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
index?: number;
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
...checkboxIcons,
|
||||
labelContainer: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const CheckboxWrapper = ({
|
||||
label,
|
||||
onChange,
|
||||
value,
|
||||
id,
|
||||
name,
|
||||
checked = false,
|
||||
disabled = false,
|
||||
tooltip = "",
|
||||
classes,
|
||||
}: CheckBoxProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
)}
|
||||
<div className={classes.labelContainer}>
|
||||
<Checkbox
|
||||
name={name}
|
||||
id={id}
|
||||
value={value}
|
||||
color="primary"
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
checkedIcon={<span className={classes.checkedIcon} />}
|
||||
icon={<span className={classes.unCheckedIcon} />}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(CheckboxWrapper);
|
||||
@@ -44,6 +44,9 @@ interface InputBoxProps {
|
||||
tooltip?: string;
|
||||
autoComplete?: string;
|
||||
index?: number;
|
||||
error?: string;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -52,6 +55,14 @@ const styles = (theme: Theme) =>
|
||||
...tooltipHelper,
|
||||
textBoxContainer: {
|
||||
flexGrow: 1,
|
||||
position: "relative",
|
||||
},
|
||||
errorState: {
|
||||
color: "#b53b4b",
|
||||
fontSize: 14,
|
||||
position: "absolute",
|
||||
top: 7,
|
||||
right: 7,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -66,6 +77,10 @@ const inputStyles = makeStyles((theme: Theme) =>
|
||||
color: "#393939",
|
||||
fontSize: 14,
|
||||
},
|
||||
error: {
|
||||
color: "#b53b4b",
|
||||
boxShadow: "inset 0px 0px 1px 1px #b53b4b",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -92,14 +107,31 @@ const InputBoxWrapper = ({
|
||||
multiline = false,
|
||||
tooltip = "",
|
||||
index = 0,
|
||||
error = "",
|
||||
required = false,
|
||||
placeholder = "",
|
||||
classes,
|
||||
}: InputBoxProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={`${classes.fieldContainer} ${
|
||||
error !== "" ? classes.errorInField : ""
|
||||
}`}
|
||||
>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
<InputLabel
|
||||
htmlFor={id}
|
||||
className={`${error !== "" ? classes.fieldLabelError : ""} ${
|
||||
classes.inputLabel
|
||||
}`}
|
||||
>
|
||||
<span>
|
||||
{label}
|
||||
{required ? "*" : ""}
|
||||
</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
@@ -112,7 +144,6 @@ const InputBoxWrapper = ({
|
||||
|
||||
<div className={classes.textBoxContainer}>
|
||||
<InputField
|
||||
className={classes.boxDesign}
|
||||
id={id}
|
||||
name={name}
|
||||
variant="outlined"
|
||||
@@ -124,6 +155,9 @@ const InputBoxWrapper = ({
|
||||
multiline={multiline}
|
||||
autoComplete={autoComplete}
|
||||
inputProps={{ "data-index": index }}
|
||||
error={error !== ""}
|
||||
helperText={error}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
|
||||
@@ -92,16 +92,18 @@ const SelectWrapper = ({
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
)}
|
||||
<FormControl variant="outlined" fullWidth>
|
||||
<Select
|
||||
id={id}
|
||||
|
||||
@@ -33,6 +33,9 @@ export const fieldBasic = {
|
||||
alignItems: "center",
|
||||
},
|
||||
},
|
||||
fieldLabelError: {
|
||||
paddingBottom: 22,
|
||||
},
|
||||
fieldContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
@@ -61,3 +64,18 @@ export const tooltipHelper = {
|
||||
fontSize: 18,
|
||||
},
|
||||
};
|
||||
|
||||
const checkBoxBasic = {
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 3,
|
||||
};
|
||||
|
||||
export const checkboxIcons = {
|
||||
unCheckedIcon: { ...checkBoxBasic, border: "1px solid #d0d0d0" },
|
||||
checkedIcon: {
|
||||
...checkBoxBasic,
|
||||
border: "1px solid #201763",
|
||||
backgroundColor: "#201763",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { IWizardMain } from "./types";
|
||||
import WizardPage from "./WizardPage";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
wizardMain: {
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
flexGrow: 1,
|
||||
},
|
||||
wizardSteps: {
|
||||
marginRight: 10,
|
||||
"& ul": {
|
||||
padding: 15,
|
||||
|
||||
"& li": {
|
||||
listStyle: "lower-roman",
|
||||
marginBottom: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
buttonList: {
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
"&:not(:disabled):hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
"&:selected, &:active, &:focus, &:focus:active": {
|
||||
border: "none",
|
||||
outline: 0,
|
||||
boxShadow: "none",
|
||||
},
|
||||
},
|
||||
wizardContainer: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const GenericWizard = ({ classes, wizardSteps }: IWizardMain) => {
|
||||
const [currentStep, setCurrentStep] = useState<number>(0);
|
||||
|
||||
const pageChange = (toElement: string | number) => {
|
||||
const lastPage = wizardSteps.length - 1;
|
||||
|
||||
if (toElement === "++") {
|
||||
let nextPage = currentStep + 1;
|
||||
|
||||
if (nextPage > lastPage) {
|
||||
nextPage = lastPage;
|
||||
}
|
||||
|
||||
setCurrentStep(nextPage);
|
||||
}
|
||||
|
||||
if (toElement === "--") {
|
||||
let prevPage = currentStep - 1;
|
||||
|
||||
if (prevPage < 0) {
|
||||
prevPage = 0;
|
||||
}
|
||||
|
||||
setCurrentStep(prevPage);
|
||||
}
|
||||
|
||||
if (typeof toElement === "number") {
|
||||
let pg = toElement;
|
||||
if (toElement < 0) {
|
||||
pg = 0;
|
||||
}
|
||||
|
||||
if (toElement > lastPage) {
|
||||
pg = lastPage;
|
||||
}
|
||||
|
||||
setCurrentStep(pg);
|
||||
}
|
||||
};
|
||||
|
||||
if (wizardSteps.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.wizardMain}>
|
||||
<div className={classes.wizardSteps}>
|
||||
<ul>
|
||||
{wizardSteps.map((step, index) => {
|
||||
return (
|
||||
<li key={`wizard-${index.toString()}`}>
|
||||
<button
|
||||
onClick={() => pageChange(index)}
|
||||
disabled={index > currentStep}
|
||||
className={classes.buttonList}
|
||||
>
|
||||
{step.label}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
<div className={classes.wizardContainer}>
|
||||
<WizardPage page={wizardSteps[currentStep]} pageChange={pageChange} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(GenericWizard);
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { IWizardButton, IWizardPage } from "./types";
|
||||
import { Button } from "@material-ui/core";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
wizardStepContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
wizardComponent: {
|
||||
height: 375,
|
||||
overflowY: "auto",
|
||||
marginBottom: 10,
|
||||
},
|
||||
buttonsContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end" as const,
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const WizardPage = ({ classes, page, pageChange }: IWizardPage) => {
|
||||
const buttonAction = (btn: IWizardButton) => {
|
||||
switch (btn.type) {
|
||||
case "next":
|
||||
pageChange("++");
|
||||
break;
|
||||
case "back":
|
||||
pageChange("--");
|
||||
break;
|
||||
case "to":
|
||||
pageChange(btn.toPage || 0);
|
||||
default:
|
||||
}
|
||||
|
||||
if (btn.action) {
|
||||
btn.action();
|
||||
}
|
||||
};
|
||||
|
||||
console.log("buttons", page);
|
||||
|
||||
return (
|
||||
<div className={classes.wizardStepContainer}>
|
||||
<div className={classes.wizardComponent}>{page.componentRender}</div>
|
||||
<div className={classes.buttonsContainer}>
|
||||
{page.buttons.map((btn) => {
|
||||
return (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
buttonAction(btn);
|
||||
}}
|
||||
disabled={!btn.enabled}
|
||||
key={`button-${page.label}-${btn.label}`}
|
||||
>
|
||||
{btn.label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(WizardPage);
|
||||
40
portal-ui/src/screens/Console/Common/GenericWizard/types.ts
Normal file
40
portal-ui/src/screens/Console/Common/GenericWizard/types.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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 IWizardButton {
|
||||
label: string;
|
||||
type: string;
|
||||
action?: () => void;
|
||||
enabled?: boolean;
|
||||
toPage?: number;
|
||||
}
|
||||
|
||||
export interface IWizardElement {
|
||||
label: string;
|
||||
componentRender: any;
|
||||
buttons: IWizardButton[];
|
||||
}
|
||||
|
||||
export interface IWizardMain {
|
||||
classes: any;
|
||||
wizardSteps: IWizardElement[];
|
||||
}
|
||||
|
||||
export interface IWizardPage {
|
||||
classes: any;
|
||||
page: IWizardElement;
|
||||
pageChange: (to: string | number) => void;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { IconButton } from "@material-ui/core";
|
||||
import ViewIcon from "./TableActionIcons/ViewIcon";
|
||||
import PencilIcon from "./TableActionIcons/PencilIcon";
|
||||
import DeleteIcon from "./TableActionIcons/DeleteIcon";
|
||||
import DescriptionIcon from "./TableActionIcons/DescriptionIcon";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
interface IActionButton {
|
||||
@@ -39,6 +40,8 @@ const defineIcon = (type: string, selected: boolean) => {
|
||||
return <PencilIcon active={selected} />;
|
||||
case "delete":
|
||||
return <DeleteIcon active={selected} />;
|
||||
case "description":
|
||||
return <DescriptionIcon active={selected} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -51,13 +54,14 @@ const TableActionButton = ({
|
||||
idField,
|
||||
selected,
|
||||
to,
|
||||
sendOnlyId = false
|
||||
sendOnlyId = false,
|
||||
}: IActionButton) => {
|
||||
const valueClick = sendOnlyId ? valueToSend[idField] : valueToSend;
|
||||
|
||||
const buttonElement = (
|
||||
<IconButton
|
||||
aria-label={type}
|
||||
size={"small"}
|
||||
onClick={
|
||||
onClick
|
||||
? () => {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import { IIcon, selected, unSelected } from "./common";
|
||||
|
||||
const PencilIcon = ({ active = false }: IIcon) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill={active ? selected : unSelected}
|
||||
d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PencilIcon;
|
||||
@@ -27,10 +27,12 @@ import {
|
||||
Paper,
|
||||
Grid,
|
||||
Checkbox,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { TablePaginationActionsProps } from "@material-ui/core/TablePagination/TablePaginationActions";
|
||||
import TableActionButton from "./TableActionButton";
|
||||
import { checkboxIcons } from "../FormComponents/common/styleLibrary";
|
||||
|
||||
//Interfaces for table Items
|
||||
|
||||
@@ -87,12 +89,6 @@ const rowText = {
|
||||
borderColor: borderColor,
|
||||
};
|
||||
|
||||
const checkBoxBasic = {
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 3,
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
dialogContainer: {
|
||||
@@ -103,6 +99,7 @@ const styles = (theme: Theme) =>
|
||||
overflow: "auto",
|
||||
flexDirection: "column",
|
||||
padding: "19px 38px",
|
||||
minHeight: "200px",
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
@@ -133,21 +130,20 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
actionsContainer: {
|
||||
width: 120,
|
||||
width: 150,
|
||||
borderColor: borderColor,
|
||||
},
|
||||
paginatorComponent: {
|
||||
borderBottom: 0,
|
||||
},
|
||||
unCheckedIcon: { ...checkBoxBasic, border: "1px solid #d0d0d0" },
|
||||
checkedIcon: {
|
||||
...checkBoxBasic,
|
||||
border: "1px solid #201763",
|
||||
backgroundColor: "#201763",
|
||||
},
|
||||
checkBoxRow: {
|
||||
borderColor: borderColor,
|
||||
},
|
||||
loadingBox: {
|
||||
paddingTop: "100px",
|
||||
paddingBottom: "100px",
|
||||
},
|
||||
...checkboxIcons,
|
||||
});
|
||||
|
||||
// Function that renders Title Columns
|
||||
@@ -226,7 +222,16 @@ const TableWrapper = ({
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{isLoading && <LinearProgress />}
|
||||
{isLoading && (
|
||||
<Grid container className={classes.loadingBox}>
|
||||
<Grid item xs={12} style={{ textAlign: "center" }}>
|
||||
<Typography component="h3">Loading...</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
{records && records.length > 0 ? (
|
||||
<Table size="small" stickyHeader={stickyHeader}>
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
@@ -298,7 +303,9 @@ const TableWrapper = ({
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
<div>{`There are no ${entityName} yet.`}</div>
|
||||
<React.Fragment>
|
||||
{!isLoading && <div>{`There are no ${entityName} yet.`}</div>}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Paper>
|
||||
{paginatorConfig && (
|
||||
|
||||
@@ -42,10 +42,6 @@ export const configurationElements: IConfigurationElement[] = [
|
||||
configuration_id: "identity_ldap",
|
||||
configuration_label: "Identity LDAP Configuration",
|
||||
},
|
||||
{
|
||||
configuration_id: "policy_opa",
|
||||
configuration_label: "Policy OPA Configuration",
|
||||
},
|
||||
{
|
||||
configuration_id: "kms_vault",
|
||||
configuration_label: "KMS Vault Configuration",
|
||||
@@ -312,26 +308,6 @@ export const fieldsConfigurations: any = {
|
||||
multiline: true,
|
||||
},
|
||||
],
|
||||
policy_opa: [
|
||||
{
|
||||
name: "url",
|
||||
required: true,
|
||||
label: "OPA URL",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
required: true,
|
||||
label: "Auth Token",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "policy_opa",
|
||||
required: true,
|
||||
label: "Policy OPA",
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
kms_vault: [],
|
||||
kms_kes: [],
|
||||
logger_webhook: [
|
||||
|
||||
@@ -49,7 +49,7 @@ import Buckets from "./Buckets/Buckets";
|
||||
import Policies from "./Policies/Policies";
|
||||
import Permissions from "./Permissions/Permissions";
|
||||
import Dashboard from "./Dashboard/Dashboard";
|
||||
import Menu from "./Menu";
|
||||
import Menu from "./Menu/Menu";
|
||||
import api from "../../common/api";
|
||||
import storage from "local-storage-fallback";
|
||||
import NotFoundPage from "../NotFoundPage";
|
||||
@@ -62,9 +62,12 @@ import { Button, LinearProgress } from "@material-ui/core";
|
||||
import WebhookPanel from "./Configurations/ConfigurationPanels/WebhookPanel";
|
||||
import Trace from "./Trace/Trace";
|
||||
import Logs from "./Logs/Logs";
|
||||
import Heal from "./Heal/Heal";
|
||||
import Watch from "./Watch/Watch";
|
||||
import ListTenants from "./Tenants/ListTenants/ListTenants";
|
||||
import { ISessionResponse } from "./types";
|
||||
import { saveSessionResponse } from "./actions";
|
||||
import TenantDetails from "./Tenants/TenantDetails/TenantDetails";
|
||||
|
||||
function Copyright() {
|
||||
return (
|
||||
@@ -130,6 +133,7 @@ const styles = (theme: Theme) =>
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
overflowX: "hidden",
|
||||
},
|
||||
drawerPaperClose: {
|
||||
overflowX: "hidden",
|
||||
@@ -269,6 +273,10 @@ const Console = ({
|
||||
component: Logs,
|
||||
path: "/logs",
|
||||
},
|
||||
{
|
||||
component: Heal,
|
||||
path: "/heal",
|
||||
},
|
||||
{
|
||||
component: ListNotificationEndpoints,
|
||||
path: "/notification-endpoints",
|
||||
@@ -293,6 +301,14 @@ const Console = ({
|
||||
component: WebhookPanel,
|
||||
path: "/webhook/audit",
|
||||
},
|
||||
{
|
||||
component: ListTenants,
|
||||
path: "/tenants",
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/tenants/:tenantName",
|
||||
},
|
||||
];
|
||||
const allowedRoutes = routes.filter((route: any) => allowedPages[route.path]);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import { stringSort } from "../../../utils/sortFunctions";
|
||||
import AddGroup from "../Groups/AddGroup";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import SetPolicy from "../Policies/SetPolicy";
|
||||
|
||||
interface IGroupsProps {
|
||||
classes: any;
|
||||
@@ -96,6 +97,7 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const [policyOpen, setPolicyOpen] = useState<boolean>(false);
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
@@ -176,8 +178,14 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
setSelectedGroup(group);
|
||||
};
|
||||
|
||||
const setPolicyAction = (selectionElement: any): void => {
|
||||
setPolicyOpen(true);
|
||||
setSelectedGroup(selectionElement);
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: viewAction },
|
||||
{ type: "description", onClick: setPolicyAction },
|
||||
{ type: "delete", onClick: deleteAction },
|
||||
];
|
||||
|
||||
@@ -197,6 +205,16 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
{setPolicyOpen && (
|
||||
<SetPolicy
|
||||
open={policyOpen}
|
||||
selectedGroup={selectedGroup}
|
||||
selectedUser={null}
|
||||
closeModalAndRefresh={() => {
|
||||
setPolicyOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Groups</Typography>
|
||||
|
||||
327
portal-ui/src/screens/Console/Heal/Heal.tsx
Normal file
327
portal-ui/src/screens/Console/Heal/Heal.tsx
Normal file
@@ -0,0 +1,327 @@
|
||||
import { HorizontalBar } from "react-chartjs-2";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Grid,
|
||||
Typography,
|
||||
TextField,
|
||||
Checkbox,
|
||||
} from "@material-ui/core";
|
||||
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { wsProtocol } from "../../../utils/wsUtils";
|
||||
import api from "../../../common/api";
|
||||
import { FormControl, MenuItem, Select } from "@material-ui/core";
|
||||
import { BucketList, Bucket } from "../Watch/types";
|
||||
import { HealStatus, colorH } from "./types";
|
||||
import { niceBytes } from "../../../common/utils";
|
||||
|
||||
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",
|
||||
},
|
||||
});
|
||||
|
||||
interface IHeal {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const Heal = ({ classes }: IHeal) => {
|
||||
const [start, setStart] = useState(false);
|
||||
const [bucketName, setBucketName] = useState("Select Bucket");
|
||||
const [bucketList, setBucketList] = useState<Bucket[]>([]);
|
||||
const [prefix, setPrefix] = useState("");
|
||||
const [recursive, setRecursive] = useState(false);
|
||||
const [forceStart, setForceStart] = useState(false);
|
||||
const [forceStop, setForceStop] = useState(false);
|
||||
// healStatus states
|
||||
const [hStatus, setHStatus] = useState({
|
||||
beforeHeal: [0, 0, 0, 0],
|
||||
afterHeal: [0, 0, 0, 0],
|
||||
objectsHealed: 0,
|
||||
objectsScanned: 0,
|
||||
healDuration: 0,
|
||||
sizeScanned: "",
|
||||
});
|
||||
|
||||
const fetchBucketList = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets`)
|
||||
.then((res: BucketList) => {
|
||||
let buckets: Bucket[] = [];
|
||||
if (res.buckets !== null) {
|
||||
buckets = res.buckets;
|
||||
}
|
||||
setBucketList(buckets);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchBucketList();
|
||||
}, []);
|
||||
|
||||
// forceStart and forceStop need to be mutually exclusive
|
||||
useEffect(() => {
|
||||
if (forceStart === true) {
|
||||
setForceStop(false);
|
||||
}
|
||||
}, [forceStart]);
|
||||
|
||||
useEffect(() => {
|
||||
if (forceStop === true) {
|
||||
setForceStart(false);
|
||||
}
|
||||
}, [forceStop]);
|
||||
|
||||
const colorHealthArr = (color: colorH) => {
|
||||
return [color.Green, color.Yellow, color.Red, color.Grey];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// begin watch if bucketName in bucketList and start pressed
|
||||
if (start) {
|
||||
// values stored here to update chart
|
||||
const cB: colorH = { Green: 0, Yellow: 0, Red: 0, Grey: 0 };
|
||||
const cA: colorH = { Green: 0, Yellow: 0, Red: 0, Grey: 0 };
|
||||
|
||||
const url = new URL(window.location.toString());
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
const port = isDev ? "9090" : url.port;
|
||||
|
||||
const wsProt = wsProtocol(url.protocol);
|
||||
const c = new W3CWebSocket(
|
||||
`${wsProt}://${url.hostname}:${port}/ws/heal/${bucketName}?prefix=${prefix}&recursive=${recursive}&force-start=${forceStart}&force-stop=${forceStop}`
|
||||
);
|
||||
|
||||
if (c !== null) {
|
||||
c.onopen = () => {
|
||||
console.log("WebSocket Client Connected");
|
||||
c.send("ok");
|
||||
};
|
||||
c.onmessage = (message: IMessageEvent) => {
|
||||
let m: HealStatus = JSON.parse(message.data.toString());
|
||||
// Store percentage per health color
|
||||
for (const [key, value] of Object.entries(m.healthAfterCols)) {
|
||||
cA[key] = (value * 100) / m.itemsScanned;
|
||||
}
|
||||
for (const [key, value] of Object.entries(m.healthBeforeCols)) {
|
||||
cB[key] = (value * 100) / m.itemsScanned;
|
||||
}
|
||||
setHStatus({
|
||||
beforeHeal: colorHealthArr(cB),
|
||||
afterHeal: colorHealthArr(cA),
|
||||
objectsHealed: m.objectsHealed,
|
||||
objectsScanned: m.objectsScanned,
|
||||
healDuration: m.healDuration,
|
||||
sizeScanned: niceBytes(m.bytesScanned.toString()),
|
||||
});
|
||||
};
|
||||
c.onclose = () => {
|
||||
setStart(false);
|
||||
console.log("connection closed by server");
|
||||
};
|
||||
return () => {
|
||||
// close websocket on useEffect cleanup
|
||||
c.close(1000);
|
||||
console.log("closing websockets");
|
||||
};
|
||||
}
|
||||
}
|
||||
}, [start]);
|
||||
|
||||
let data = {
|
||||
labels: ["Green", "Yellow", "Red", "Grey"],
|
||||
datasets: [
|
||||
{
|
||||
label: "After Healing",
|
||||
data: hStatus.afterHeal,
|
||||
backgroundColor: "rgba(0, 0, 255, 0.2)",
|
||||
borderColor: "rgba(54, 162, 235, 1)",
|
||||
borderWidth: 1,
|
||||
},
|
||||
{
|
||||
label: "Before Healing",
|
||||
data: hStatus.beforeHeal,
|
||||
backgroundColor: "rgba(153, 102, 255, 0.2)",
|
||||
borderColor: "rgba(153, 102, 255, 1)",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
const bucketNames = bucketList.map((bucketName) => ({
|
||||
label: bucketName.name,
|
||||
value: bucketName.name,
|
||||
}));
|
||||
return (
|
||||
<React.Fragment>
|
||||
<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}
|
||||
</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",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Heal);
|
||||
64
portal-ui/src/screens/Console/Heal/types.ts
Normal file
64
portal-ui/src/screens/Console/Heal/types.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export interface HealDriveInfo {
|
||||
uuid: string;
|
||||
endpoint: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
export interface MomentHealth {
|
||||
color: string;
|
||||
offline: number;
|
||||
online: number;
|
||||
missing: number;
|
||||
corrupted: number;
|
||||
drives: HealDriveInfo[];
|
||||
}
|
||||
|
||||
export interface HealItemStatus {
|
||||
status: string;
|
||||
error: string;
|
||||
type: string;
|
||||
name: string;
|
||||
before: MomentHealth;
|
||||
after: MomentHealth;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface HealStatus {
|
||||
healDuration: number;
|
||||
bytesScanned: number;
|
||||
objectsScanned: number;
|
||||
itemsScanned: number;
|
||||
// Counters for healed objects and all kinds of healed items
|
||||
objectsHealed: number;
|
||||
itemsHealed: number;
|
||||
|
||||
itemsHealthStatus: HealItemStatus[];
|
||||
// Map of health color code to number of objects with that
|
||||
// health color code.
|
||||
healthBeforeCols: Map<string, number>;
|
||||
healthAfterCols: Map<string, number>;
|
||||
}
|
||||
|
||||
// colorH used to save health's percentage per color
|
||||
export interface colorH {
|
||||
[Green: string]: number;
|
||||
Yellow: number;
|
||||
Red: number;
|
||||
Grey: number;
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 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 ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import RoomServiceIcon from "@material-ui/icons/RoomService";
|
||||
import WebAssetIcon from "@material-ui/icons/WebAsset";
|
||||
import CenterFocusWeakIcon from "@material-ui/icons/CenterFocusWeak";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { Divider, Typography, withStyles } from "@material-ui/core";
|
||||
import { ExitToApp } from "@material-ui/icons";
|
||||
import { AppState } from "../../store";
|
||||
import { connect } from "react-redux";
|
||||
import { userLoggedIn } from "../../actions";
|
||||
import List from "@material-ui/core/List";
|
||||
import storage from "local-storage-fallback";
|
||||
import history from "../../history";
|
||||
import logo from "../../icons/minio_console_logo.svg";
|
||||
import {
|
||||
BucketsIcon,
|
||||
DashboardIcon,
|
||||
PermissionIcon,
|
||||
UsersIcon,
|
||||
} from "../../icons";
|
||||
import { createStyles, Theme } from "@material-ui/core/styles";
|
||||
import PersonIcon from "@material-ui/icons/Person";
|
||||
import api from "../../common/api";
|
||||
import NotificationsIcon from "@material-ui/icons/Notifications";
|
||||
import ListAltIcon from "@material-ui/icons/ListAlt";
|
||||
import LoopIcon from "@material-ui/icons/Loop";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
logo: {
|
||||
paddingTop: "42px",
|
||||
marginBottom: "20px",
|
||||
textAlign: "center",
|
||||
"& img": {
|
||||
width: "120px",
|
||||
},
|
||||
},
|
||||
menuList: {
|
||||
"& .active": {
|
||||
borderTopLeftRadius: "3px",
|
||||
borderBottomLeftRadius: "3px",
|
||||
color: "white",
|
||||
background:
|
||||
"transparent linear-gradient(90deg, #362585 0%, #362585 7%, #281B6F 39%, #1F1661 100%) 0% 0% no-repeat padding-box",
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: "white",
|
||||
},
|
||||
},
|
||||
"& .MuiListItem-root": {
|
||||
marginTop: "16px",
|
||||
},
|
||||
paddingLeft: "30px",
|
||||
"& .MuiSvgIcon-root": {
|
||||
fontSize: "18px",
|
||||
color: "#393939",
|
||||
},
|
||||
"& .MuiListItemIcon-root": {
|
||||
minWidth: "40px",
|
||||
},
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "14px",
|
||||
},
|
||||
"& .MuiListItem-gutters": {
|
||||
paddingRight: "0px",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
open: state.system.loggedIn,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, { userLoggedIn });
|
||||
|
||||
interface MenuProps {
|
||||
classes: any;
|
||||
userLoggedIn: typeof userLoggedIn;
|
||||
pages: string[];
|
||||
}
|
||||
|
||||
class Menu extends React.Component<MenuProps> {
|
||||
logout() {
|
||||
const deleteSession = () => {
|
||||
storage.removeItem("token");
|
||||
this.props.userLoggedIn(false);
|
||||
history.push("/");
|
||||
};
|
||||
api
|
||||
.invoke("POST", `/api/v1/logout`)
|
||||
.then(() => {
|
||||
deleteSession();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log(err);
|
||||
deleteSession();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, pages } = this.props;
|
||||
const allowedPages = pages.reduce((result: any, item: any, index: any) => {
|
||||
result[item] = true;
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
const menu = [
|
||||
{
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/dashboard",
|
||||
name: "Dashboard",
|
||||
icon: <DashboardIcon />,
|
||||
},
|
||||
{
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/buckets",
|
||||
name: "Buckets",
|
||||
icon: <BucketsIcon />,
|
||||
},
|
||||
{
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/service-accounts",
|
||||
name: "Service Accounts",
|
||||
icon: <RoomServiceIcon />,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/watch",
|
||||
name: "Watch",
|
||||
icon: <CenterFocusWeakIcon />,
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
key: "divider-1",
|
||||
},
|
||||
{
|
||||
type: "title",
|
||||
name: "Admin",
|
||||
component: Typography,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/users",
|
||||
name: "Users",
|
||||
icon: <PersonIcon />,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/groups",
|
||||
name: "Groups",
|
||||
icon: <UsersIcon />,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/policies",
|
||||
name: "IAM Policies",
|
||||
icon: <PermissionIcon />,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/trace",
|
||||
name: "Trace",
|
||||
icon: <LoopIcon />,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/logs",
|
||||
name: "Console Logs",
|
||||
icon: <WebAssetIcon />,
|
||||
},
|
||||
{
|
||||
type: "title",
|
||||
name: "Configuration",
|
||||
component: Typography,
|
||||
},
|
||||
{
|
||||
group: "Configuration",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/notification-endpoints",
|
||||
name: "Lambda Notifications",
|
||||
icon: <NotificationsIcon />,
|
||||
},
|
||||
{
|
||||
group: "Configuration",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/configurations-list",
|
||||
name: "Configurations List",
|
||||
icon: <ListAltIcon />,
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
key: "divider-2",
|
||||
},
|
||||
];
|
||||
|
||||
const allowedItems = menu.filter(
|
||||
(item: any) =>
|
||||
allowedPages[item.to] || item.forceDisplay || item.type !== "item"
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={classes.logo}>
|
||||
<img src={logo} alt="logo" />
|
||||
</div>
|
||||
<List className={classes.menuList}>
|
||||
{allowedItems.map((page: any) => {
|
||||
switch (page.type) {
|
||||
case "divider": {
|
||||
return <Divider key={page.key} />;
|
||||
}
|
||||
case "item": {
|
||||
return (
|
||||
<ListItem
|
||||
key={page.to}
|
||||
button
|
||||
component={page.component}
|
||||
to={page.to}
|
||||
>
|
||||
{page.icon && <ListItemIcon>{page.icon}</ListItemIcon>}
|
||||
{page.name && <ListItemText primary={page.name} />}
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
case "title": {
|
||||
return (
|
||||
(allowedItems || []).filter(
|
||||
(item: any) => item.group === page.name
|
||||
).length > 0 && (
|
||||
<ListItem key={page.name} component={page.component}>
|
||||
{page.name}
|
||||
</ListItem>
|
||||
)
|
||||
);
|
||||
}
|
||||
default:
|
||||
}
|
||||
})}
|
||||
<ListItem
|
||||
button
|
||||
onClick={() => {
|
||||
this.logout();
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<ExitToApp />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Logout" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connector(withStyles(styles)(Menu));
|
||||
424
portal-ui/src/screens/Console/Menu/Menu.tsx
Normal file
424
portal-ui/src/screens/Console/Menu/Menu.tsx
Normal file
@@ -0,0 +1,424 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import WebAssetIcon from "@material-ui/icons/WebAsset";
|
||||
import HealingIcon from "@material-ui/icons/Healing";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import List from "@material-ui/core/List";
|
||||
import { Divider, Typography, withStyles } from "@material-ui/core";
|
||||
import { ExitToApp } from "@material-ui/icons";
|
||||
import storage from "local-storage-fallback";
|
||||
import { createStyles, Theme } from "@material-ui/core/styles";
|
||||
import history from "../../../history";
|
||||
import logo from "../../../icons/minio_console_logo.svg";
|
||||
import { AppState } from "../../../store";
|
||||
import { userLoggedIn } from "../../../actions";
|
||||
import api from "../../../common/api";
|
||||
import WatchIcon from "../../../icons/WatchIcon";
|
||||
import { menuGroups } from "./utils";
|
||||
import { IMenuProps } from "./types";
|
||||
import {
|
||||
BucketsIcon,
|
||||
ClustersIcon,
|
||||
ConfigurationsListIcon,
|
||||
DashboardIcon,
|
||||
GroupsIcon,
|
||||
IAMPoliciesIcon,
|
||||
LambdaNotificationsIcon,
|
||||
MirroringIcon,
|
||||
ServiceAccountsIcon,
|
||||
TraceIcon,
|
||||
UsersIcon,
|
||||
WarpIcon,
|
||||
} from "../../../icons";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
logo: {
|
||||
paddingTop: "42px",
|
||||
marginBottom: "20px",
|
||||
textAlign: "center",
|
||||
"& img": {
|
||||
width: "120px",
|
||||
},
|
||||
},
|
||||
menuList: {
|
||||
"& .active": {
|
||||
borderTopLeftRadius: "3px",
|
||||
borderBottomLeftRadius: "3px",
|
||||
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,
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: "white",
|
||||
},
|
||||
"& .MuiTypography-root": {
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
paddingLeft: "30px",
|
||||
"& .MuiSvgIcon-root": {
|
||||
fontSize: 16,
|
||||
color: "#362585",
|
||||
maxWidth: 14,
|
||||
},
|
||||
"& .MuiListItemIcon-root": {
|
||||
minWidth: "25px",
|
||||
},
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "12px",
|
||||
color: "#2e2e2e",
|
||||
},
|
||||
"& .MuiListItem-gutters": {
|
||||
paddingRight: 0,
|
||||
},
|
||||
},
|
||||
extraMargin: {
|
||||
"&.MuiListItem-gutters": {
|
||||
marginLeft: 5,
|
||||
},
|
||||
},
|
||||
groupTitle: {
|
||||
color: "#220c7c",
|
||||
fontSize: 10,
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 700,
|
||||
marginBottom: 3,
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
},
|
||||
subTitleMenu: {
|
||||
fontWeight: 700,
|
||||
marginLeft: 10,
|
||||
"&.MuiTypography-root": {
|
||||
fontSize: 13,
|
||||
color: "#220c7c",
|
||||
},
|
||||
},
|
||||
selectorArrow: {
|
||||
marginLeft: 3,
|
||||
marginTop: 1,
|
||||
display: "inline-block",
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderStyle: "solid",
|
||||
borderWidth: "3px 2.5px 0 2.5px",
|
||||
borderColor: "#220C7C transparent transparent transparent",
|
||||
transform: "rotateZ(0deg)",
|
||||
transitionDuration: "0.2s",
|
||||
},
|
||||
selectorArrowOpen: {
|
||||
transform: "rotateZ(180deg)",
|
||||
},
|
||||
});
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
open: state.system.loggedIn,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, { userLoggedIn });
|
||||
|
||||
// Menu State builder for groups
|
||||
const menuStateBuilder = () => {
|
||||
let elements: any = [];
|
||||
menuGroups.forEach((menuItem) => {
|
||||
if (menuItem.collapsible) {
|
||||
elements[menuItem.group] = true;
|
||||
}
|
||||
});
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
|
||||
const [menuOpen, setMenuOpen] = useState<any>(menuStateBuilder());
|
||||
|
||||
const logout = () => {
|
||||
const deleteSession = () => {
|
||||
storage.removeItem("token");
|
||||
userLoggedIn(false);
|
||||
history.push("/");
|
||||
};
|
||||
api
|
||||
.invoke("POST", `/api/v1/logout`)
|
||||
.then(() => {
|
||||
deleteSession();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log(err);
|
||||
deleteSession();
|
||||
});
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
group: "common",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/dashboard",
|
||||
name: "Dashboard",
|
||||
icon: <DashboardIcon />,
|
||||
},
|
||||
{
|
||||
group: "User",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/buckets",
|
||||
name: "Buckets",
|
||||
icon: <BucketsIcon />,
|
||||
},
|
||||
{
|
||||
group: "User",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/service-accounts",
|
||||
name: "Service Accounts",
|
||||
icon: <ServiceAccountsIcon />,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/users",
|
||||
name: "Users",
|
||||
icon: <UsersIcon />,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/groups",
|
||||
name: "Groups",
|
||||
icon: <GroupsIcon />,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/policies",
|
||||
name: "IAM Policies",
|
||||
icon: <IAMPoliciesIcon />,
|
||||
},
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/logs",
|
||||
name: "Console Logs",
|
||||
icon: <WebAssetIcon />,
|
||||
},
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/watch",
|
||||
name: "Watch",
|
||||
icon: <WatchIcon />,
|
||||
},
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/trace",
|
||||
name: "Trace",
|
||||
icon: <TraceIcon />,
|
||||
},
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/heal",
|
||||
name: "Heal",
|
||||
icon: <HealingIcon />,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "title",
|
||||
name: "Configurations",
|
||||
component: Typography,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/notification-endpoints",
|
||||
name: "Lambda Notifications",
|
||||
icon: <LambdaNotificationsIcon />,
|
||||
extraMargin: true,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/configurations-list",
|
||||
name: "Configurations List",
|
||||
icon: <ConfigurationsListIcon />,
|
||||
extraMargin: true,
|
||||
},
|
||||
{
|
||||
group: "Operator",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/tenants",
|
||||
name: "Tenants",
|
||||
icon: <ClustersIcon />,
|
||||
},
|
||||
{
|
||||
group: "Operator",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/mirroring",
|
||||
name: "Mirroring",
|
||||
icon: <MirroringIcon />,
|
||||
},
|
||||
{
|
||||
group: "Operator",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/warp",
|
||||
name: "Warp",
|
||||
icon: <WarpIcon />,
|
||||
},
|
||||
];
|
||||
|
||||
const allowedPages = pages.reduce((result: any, item: any, index: any) => {
|
||||
result[item] = true;
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
const allowedItems = menuItems.filter(
|
||||
(item: any) =>
|
||||
allowedPages[item.to] || item.forceDisplay || item.type !== "item"
|
||||
);
|
||||
|
||||
const setMenuCollapse = (menuClicked: string) => {
|
||||
let newMenu: any = { ...menuOpen };
|
||||
|
||||
newMenu[menuClicked] = !newMenu[menuClicked];
|
||||
|
||||
setMenuOpen(newMenu);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={classes.logo}>
|
||||
<img src={logo} alt="logo" />
|
||||
</div>
|
||||
<List className={classes.menuList}>
|
||||
{menuGroups.map((groupMember, index) => {
|
||||
const filterByGroup = (allowedItems || []).filter(
|
||||
(item: any) => item.group === groupMember.group
|
||||
);
|
||||
|
||||
const countableElements = filterByGroup.filter(
|
||||
(menuItem: any) => menuItem.type !== "title"
|
||||
);
|
||||
|
||||
if (countableElements.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment key={`menuElem-${index.toString()}`}>
|
||||
{groupMember.label !== "" && (
|
||||
<ListItem
|
||||
className={classes.groupTitle}
|
||||
onClick={() => {
|
||||
if (groupMember.collapsible) {
|
||||
setMenuCollapse(groupMember.group);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{groupMember.label}
|
||||
{groupMember.collapsible && (
|
||||
<span
|
||||
className={`${classes.selectorArrow} ${
|
||||
menuOpen[groupMember.group]
|
||||
? classes.selectorArrowOpen
|
||||
: ""
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</ListItem>
|
||||
)}
|
||||
<Collapse
|
||||
in={
|
||||
groupMember.collapsible ? menuOpen[groupMember.group] : true
|
||||
}
|
||||
timeout="auto"
|
||||
unmountOnExit
|
||||
key={`menuGroup-${groupMember.group}`}
|
||||
>
|
||||
{filterByGroup.map((page: any) => {
|
||||
switch (page.type) {
|
||||
case "item": {
|
||||
return (
|
||||
<ListItem
|
||||
key={page.to}
|
||||
button
|
||||
component={page.component}
|
||||
to={page.to}
|
||||
className={
|
||||
page.extraMargin ? classes.extraMargin : null
|
||||
}
|
||||
>
|
||||
{page.icon && (
|
||||
<ListItemIcon>{page.icon}</ListItemIcon>
|
||||
)}
|
||||
{page.name && <ListItemText primary={page.name} />}
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
case "title": {
|
||||
return (
|
||||
<ListItem
|
||||
key={page.name}
|
||||
component={page.component}
|
||||
className={classes.subTitleMenu}
|
||||
>
|
||||
{page.name}
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
default:
|
||||
}
|
||||
})}
|
||||
<Divider />
|
||||
</Collapse>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
<ListItem button onClick={logout}>
|
||||
<ListItemIcon>
|
||||
<ExitToApp />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Logout" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default connector(withStyles(styles)(Menu));
|
||||
23
portal-ui/src/screens/Console/Menu/types.ts
Normal file
23
portal-ui/src/screens/Console/Menu/types.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// 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 { userLoggedIn } from "../../../actions";
|
||||
|
||||
export interface IMenuProps {
|
||||
classes: any;
|
||||
userLoggedIn: typeof userLoggedIn;
|
||||
pages: string[];
|
||||
}
|
||||
23
portal-ui/src/screens/Console/Menu/utils.ts
Normal file
23
portal-ui/src/screens/Console/Menu/utils.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// 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 menuGroups = [
|
||||
{ label: "", group: "common", collapsible: false },
|
||||
{ label: "User", group: "User", collapsible: true },
|
||||
{ label: "Admin", group: "Admin", collapsible: true },
|
||||
{ label: "Tools", group: "Tools", collapsible: true },
|
||||
{ label: "Operator", group: "Operator", collapsible: true },
|
||||
];
|
||||
@@ -51,7 +51,7 @@ const styles = (theme: Theme) =>
|
||||
interface IAddPolicyProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
closeModalAndRefresh: () => void;
|
||||
closeModalAndRefresh: (refresh: boolean) => void;
|
||||
policyEdit: Policy;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
addError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
this.props.closeModalAndRefresh(true);
|
||||
}
|
||||
);
|
||||
})
|
||||
@@ -120,7 +120,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
this.setState({ addError: "" }, () => {
|
||||
this.props.closeModalAndRefresh();
|
||||
this.props.closeModalAndRefresh(false);
|
||||
});
|
||||
}}
|
||||
title={`${policyEdit ? "Info" : "Create"} Policy`}
|
||||
|
||||
@@ -14,114 +14,114 @@
|
||||
// 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, useEffect } from "react";
|
||||
import get from "lodash/get";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { Button } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import { Policy, PolicyList } from "./types";
|
||||
import AddPolicy from "./AddPolicy";
|
||||
import DeletePolicy from "./DeletePolicy";
|
||||
import api from "../../../common/api";
|
||||
import { CreateIcon } from "../../../icons";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
import AddPolicy from "./AddPolicy";
|
||||
import DeletePolicy from "./DeletePolicy";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import api from "../../../common/api";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
paper: {
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
flexDirection: "column",
|
||||
},
|
||||
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px"
|
||||
padding: "20px",
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0)
|
||||
paddingRight: theme.spacing(0),
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
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",
|
||||
},
|
||||
});
|
||||
|
||||
interface IPoliciesProps {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
interface IPoliciesState {
|
||||
records: Policy[];
|
||||
totalRecords: number;
|
||||
loading: boolean;
|
||||
error: string;
|
||||
deleteError: string;
|
||||
addScreenOpen: boolean;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
deleteOpen: boolean;
|
||||
selectedPolicy: string;
|
||||
filterPolicies: string;
|
||||
policyEdit: any;
|
||||
}
|
||||
const Policies = ({ classes }: IPoliciesProps) => {
|
||||
const [records, setRecords] = useState<Policy[]>([]);
|
||||
const [totalRecords, setTotalRecords] = useState<number>(0);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [selectedPolicy, setSelectedPolicy] = useState<string>("");
|
||||
const [filterPolicies, setFilterPolicies] = useState<string>("");
|
||||
const [policyEdit, setPolicyEdit] = useState<any>(null);
|
||||
|
||||
class Policies extends React.Component<IPoliciesProps, IPoliciesState> {
|
||||
state: IPoliciesState = {
|
||||
records: [],
|
||||
totalRecords: 0,
|
||||
loading: false,
|
||||
error: "",
|
||||
deleteError: "",
|
||||
addScreenOpen: false,
|
||||
page: 0,
|
||||
rowsPerPage: 10,
|
||||
deleteOpen: false,
|
||||
selectedPolicy: "",
|
||||
filterPolicies: "",
|
||||
policyEdit: null
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchRecords();
|
||||
}, []);
|
||||
|
||||
fetchRecords() {
|
||||
this.setState({ loading: true }, () => {
|
||||
const { page, rowsPerPage } = this.state;
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
const offset = page * rowsPerPage;
|
||||
api
|
||||
.invoke("GET", `/api/v1/policies?offset=${offset}&limit=${rowsPerPage}`)
|
||||
.then((res: PolicyList) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
records: res.policies,
|
||||
totalRecords: res.total,
|
||||
error: ""
|
||||
const policies = get(res, "policies", []);
|
||||
const total = get(res, "total", 0);
|
||||
|
||||
policies.sort((pa, pb) => {
|
||||
if (pa.name > pb.name) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (pa.name < pb.name) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
setRecords(policies);
|
||||
setTotalRecords(total);
|
||||
setError("");
|
||||
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if (
|
||||
(res.policies === undefined ||
|
||||
@@ -130,187 +130,166 @@ class Policies extends React.Component<IPoliciesProps, IPoliciesState> {
|
||||
page > 0
|
||||
) {
|
||||
const newPage = page - 1;
|
||||
this.setState({ page: newPage }, () => {
|
||||
this.fetchRecords();
|
||||
});
|
||||
|
||||
setPage(newPage);
|
||||
fetchRecords();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({ loading: false, error: err });
|
||||
.catch((err) => {
|
||||
setLoading(false);
|
||||
setError(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [
|
||||
loading,
|
||||
setLoading,
|
||||
setRecords,
|
||||
setTotalRecords,
|
||||
setError,
|
||||
setPage,
|
||||
setError,
|
||||
]);
|
||||
|
||||
closeAddModalAndRefresh() {
|
||||
this.setState({ addScreenOpen: false }, () => {
|
||||
this.fetchRecords();
|
||||
});
|
||||
}
|
||||
const fetchRecords = () => {
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
closeDeleteModalAndRefresh(refresh: boolean) {
|
||||
this.setState({ deleteOpen: false }, () => {
|
||||
if (refresh) {
|
||||
this.fetchRecords();
|
||||
}
|
||||
});
|
||||
}
|
||||
const closeAddModalAndRefresh = (refresh: boolean) => {
|
||||
setAddScreenOpen(false);
|
||||
|
||||
policyFilter(): void {}
|
||||
if (refresh) {
|
||||
fetchRecords();
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.fetchRecords();
|
||||
}
|
||||
const closeDeleteModalAndRefresh = (refresh: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const {
|
||||
records,
|
||||
totalRecords,
|
||||
addScreenOpen,
|
||||
loading,
|
||||
page,
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
selectedPolicy,
|
||||
filterPolicies,
|
||||
policyEdit
|
||||
} = this.state;
|
||||
if (refresh) {
|
||||
fetchRecords();
|
||||
}
|
||||
};
|
||||
|
||||
const offset = page * rowsPerPage;
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
this.setState({ page: newPage });
|
||||
};
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const rPP = parseInt(event.target.value, 10);
|
||||
setPage(0);
|
||||
setRowsPerPage(rPP);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const rPP = parseInt(event.target.value, 10);
|
||||
this.setState({ page: 0, rowsPerPage: rPP });
|
||||
};
|
||||
const confirmDeletePolicy = (policy: string) => {
|
||||
setDeleteOpen(true);
|
||||
setSelectedPolicy(policy);
|
||||
};
|
||||
|
||||
const confirmDeletePolicy = (policy: string) => {
|
||||
this.setState({ deleteOpen: true, selectedPolicy: policy });
|
||||
};
|
||||
const viewAction = (row: any) => {
|
||||
setAddScreenOpen(true);
|
||||
setPolicyEdit(row);
|
||||
};
|
||||
|
||||
const viewAction = (row: any) => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
policyEdit: row
|
||||
});
|
||||
};
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: viewAction },
|
||||
{ type: "delete", onClick: confirmDeletePolicy, sendOnlyId: true },
|
||||
];
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: viewAction },
|
||||
{ type: "delete", onClick: confirmDeletePolicy, sendOnlyId: true }
|
||||
];
|
||||
const filteredRecords = records.filter((elementItem) =>
|
||||
elementItem.name.includes(filterPolicies)
|
||||
);
|
||||
|
||||
const filteredRecords = records
|
||||
.slice(offset, offset + rowsPerPage)
|
||||
.filter((b: Policy) => {
|
||||
if (filterPolicies === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterPolicies) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
const beginRecord = page * rowsPerPage;
|
||||
const endRecords = beginRecord + rowsPerPage;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addScreenOpen && (
|
||||
<AddPolicy
|
||||
open={addScreenOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
this.closeAddModalAndRefresh();
|
||||
}}
|
||||
policyEdit={policyEdit}
|
||||
/>
|
||||
)}
|
||||
{deleteOpen && (
|
||||
<DeletePolicy
|
||||
deleteOpen={deleteOpen}
|
||||
selectedPolicy={selectedPolicy}
|
||||
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
||||
this.closeDeleteModalAndRefresh(refresh);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<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 => {
|
||||
this.setState({
|
||||
filterPolicies: val.target.value
|
||||
});
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
policyEdit: 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={filteredRecords}
|
||||
entityName="Policies"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
const paginatedRecords = filteredRecords.slice(beginRecord, endRecords);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addScreenOpen && (
|
||||
<AddPolicy
|
||||
open={addScreenOpen}
|
||||
closeModalAndRefresh={closeAddModalAndRefresh}
|
||||
policyEdit={policyEdit}
|
||||
/>
|
||||
)}
|
||||
{deleteOpen && (
|
||||
<DeletePolicy
|
||||
deleteOpen={deleteOpen}
|
||||
selectedPolicy={selectedPolicy}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">IAM Policies</Typography>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
<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>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Policies);
|
||||
|
||||
190
portal-ui/src/screens/Console/Policies/SetPolicy.tsx
Normal file
190
portal-ui/src/screens/Console/Policies/SetPolicy.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
// 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, { useCallback, useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
Button,
|
||||
LinearProgress,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
|
||||
import { User } from "../Users/types";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import { Policy, PolicyList } from "./types";
|
||||
import api from "../../../common/api";
|
||||
import { policySort } from "../../../utils/sortFunctions";
|
||||
import { Group } from "../Groups/types";
|
||||
|
||||
interface ISetPolicyProps {
|
||||
classes: any;
|
||||
closeModalAndRefresh: () => void;
|
||||
selectedUser: User | null;
|
||||
selectedGroup: string | null;
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...modalBasic,
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
});
|
||||
|
||||
const SetPolicy = ({
|
||||
classes,
|
||||
closeModalAndRefresh,
|
||||
selectedUser,
|
||||
selectedGroup,
|
||||
open,
|
||||
}: ISetPolicyProps) => {
|
||||
//Local States
|
||||
const [records, setRecords] = useState<Policy[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const fetchRecords = () => {
|
||||
setLoading(true);
|
||||
|
||||
api
|
||||
.invoke("GET", `/api/v1/policies?limit=1000`)
|
||||
.then((res: PolicyList) => {
|
||||
const policies = res.policies === null ? [] : res.policies;
|
||||
setLoading(false);
|
||||
setRecords(policies.sort(policySort));
|
||||
setError("");
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoading(false);
|
||||
setError(err);
|
||||
});
|
||||
};
|
||||
|
||||
const setPolicyAction = (policyName: string) => {
|
||||
let entity = "user";
|
||||
let value = null;
|
||||
if (selectedGroup !== null) {
|
||||
entity = "group";
|
||||
value = selectedGroup;
|
||||
} else {
|
||||
if (selectedUser !== null) {
|
||||
value = selectedUser.accessKey;
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
api
|
||||
.invoke("PUT", `/api/v1/set-policy/${policyName}`, {
|
||||
entityName: value,
|
||||
entityType: entity,
|
||||
})
|
||||
.then((res: any) => {
|
||||
setLoading(false);
|
||||
setError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoading(false);
|
||||
setError(err);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
fetchRecords();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
onClose={() => {
|
||||
closeModalAndRefresh();
|
||||
}}
|
||||
modalOpen={open}
|
||||
title={
|
||||
selectedUser !== null ? "Set Policy to User" : "Set Policy to Group"
|
||||
}
|
||||
>
|
||||
<Grid container className={classes.formScrollable}>
|
||||
<Grid item xs={12}>
|
||||
<TableContainer component={Paper}>
|
||||
<Table
|
||||
className={classes.table}
|
||||
size="small"
|
||||
aria-label="a dense table"
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Policy</TableCell>
|
||||
<TableCell align="right"></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.map((row) => (
|
||||
<TableRow key={row.name}>
|
||||
<TableCell component="th" scope="row">
|
||||
{row.name}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size={"small"}
|
||||
onClick={() => {
|
||||
setPolicyAction(row.name);
|
||||
}}
|
||||
>
|
||||
Set
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
closeModalAndRefresh();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Grid>
|
||||
{loading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(SetPolicy);
|
||||
707
portal-ui/src/screens/Console/Tenants/ListTenants/AddTenant.tsx
Normal file
707
portal-ui/src/screens/Console/Tenants/ListTenants/AddTenant.tsx
Normal file
@@ -0,0 +1,707 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import { LinearProgress } from "@material-ui/core";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import api from "../../../../common/api";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { IVolumeConfiguration, IZone } from "./types";
|
||||
import CheckboxWrapper from "../../Common/FormComponents/CheckboxWrapper/CheckboxWrapper";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import { k8sfactorForDropdown } from "../../../../common/utils";
|
||||
import ZonesMultiSelector from "./ZonesMultiSelector";
|
||||
import {
|
||||
commonFormValidation,
|
||||
IValidation,
|
||||
} from "../../../../utils/validationFunctions";
|
||||
import GenericWizard from "../../Common/GenericWizard/GenericWizard";
|
||||
import { IWizardElement } from "../../Common/GenericWizard/types";
|
||||
|
||||
interface IAddTenantProps {
|
||||
open: boolean;
|
||||
closeModalAndRefresh: (reloadData: boolean) => any;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red",
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
multiContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center" as const,
|
||||
justifyContent: "flex-start" as const,
|
||||
},
|
||||
sizeFactorContainer: {
|
||||
marginLeft: 8,
|
||||
alignSelf: "flex-start" as const,
|
||||
},
|
||||
headerElement: {
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
paddingTop: 5,
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
tableTitle: {
|
||||
fontWeight: 700,
|
||||
width: "30%",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
interface Opts {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const AddTenant = ({
|
||||
open,
|
||||
closeModalAndRefresh,
|
||||
classes,
|
||||
}: IAddTenantProps) => {
|
||||
const [addSending, setAddSending] = useState<boolean>(false);
|
||||
const [addError, setAddError] = useState<string>("");
|
||||
const [tenantName, setTenantName] = useState<string>("");
|
||||
const [imageName, setImageName] = useState<string>("");
|
||||
const [serviceName, setServiceName] = useState<string>("");
|
||||
const [zones, setZones] = useState<IZone[]>([]);
|
||||
const [volumesPerServer, setVolumesPerServer] = useState<number>(0);
|
||||
const [volumeConfiguration, setVolumeConfiguration] = useState<
|
||||
IVolumeConfiguration
|
||||
>({ size: "", storage_class: "" });
|
||||
const [mountPath, setMountPath] = useState<string>("");
|
||||
const [accessKey, setAccessKey] = useState<string>("");
|
||||
const [secretKey, setSecretKey] = useState<string>("");
|
||||
const [enableMCS, setEnableMCS] = useState<boolean>(true);
|
||||
const [enableSSL, setEnableSSL] = useState<boolean>(false);
|
||||
const [sizeFactor, setSizeFactor] = useState<string>("Gi");
|
||||
const [storageClasses, setStorageClassesList] = useState<Opts[]>([]);
|
||||
const [validationErrors, setValidationErrors] = useState<any>({});
|
||||
const [namespace, setNamespace] = useState<string>("");
|
||||
const [nameTenantValid, setNameTenantValid] = useState<boolean>(false);
|
||||
const [configValid, setConfigValid] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStorageClassList();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const commonValidation = commonFormValidation([validationElements[0]]);
|
||||
|
||||
setNameTenantValid(!("tenant-name" in commonValidation));
|
||||
|
||||
setValidationErrors(commonValidation);
|
||||
}, [tenantName]);
|
||||
|
||||
useEffect(() => {
|
||||
const commonValidation = commonFormValidation(
|
||||
validationElements.slice(1, 3)
|
||||
);
|
||||
|
||||
setConfigValid(
|
||||
!("volumes_per_server" in commonValidation) &&
|
||||
!("volume_size" in commonValidation)
|
||||
);
|
||||
|
||||
setValidationErrors(commonValidation);
|
||||
}, [volumesPerServer, volumeConfiguration]);
|
||||
|
||||
const validationElements: IValidation[] = [
|
||||
{
|
||||
fieldKey: "tenant-name",
|
||||
required: true,
|
||||
pattern: /^[a-z0-9-]{3,63}$/,
|
||||
customPatternMessage:
|
||||
"Name only can contain lowercase letters, numbers and '-'. Min. Length: 3",
|
||||
value: tenantName,
|
||||
},
|
||||
{
|
||||
fieldKey: "volumes_per_server",
|
||||
required: true,
|
||||
value: volumesPerServer.toString(10),
|
||||
},
|
||||
{
|
||||
fieldKey: "volume_size",
|
||||
required: true,
|
||||
value: volumeConfiguration.size,
|
||||
},
|
||||
{
|
||||
fieldKey: "image",
|
||||
required: false,
|
||||
value: imageName,
|
||||
},
|
||||
{
|
||||
fieldKey: "service_name",
|
||||
required: false,
|
||||
value: serviceName,
|
||||
},
|
||||
|
||||
{
|
||||
fieldKey: "access_key",
|
||||
required: false,
|
||||
value: accessKey,
|
||||
},
|
||||
{
|
||||
fieldKey: "secret_key",
|
||||
required: false,
|
||||
value: secretKey,
|
||||
},
|
||||
];
|
||||
|
||||
const clearValidationError = (fieldKey: string) => {
|
||||
const newValidationElement = { ...validationErrors };
|
||||
delete newValidationElement[fieldKey];
|
||||
|
||||
setValidationErrors(newValidationElement);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (addSending) {
|
||||
let cleanZones: IZone[] = [];
|
||||
for (let zone of zones) {
|
||||
if (zone.name !== "") {
|
||||
cleanZones.push(zone);
|
||||
}
|
||||
}
|
||||
|
||||
const commonValidation = commonFormValidation(validationElements);
|
||||
|
||||
setValidationErrors(commonValidation);
|
||||
|
||||
if (Object.keys(commonValidation).length === 0) {
|
||||
api
|
||||
.invoke("POST", `/api/v1/mkube/tenants`, {
|
||||
name: tenantName,
|
||||
service_name: tenantName,
|
||||
image: imageName,
|
||||
enable_ssl: enableSSL,
|
||||
enable_mcs: enableMCS,
|
||||
access_key: accessKey,
|
||||
secret_key: secretKey,
|
||||
volumes_per_server: volumesPerServer,
|
||||
volume_configuration: {
|
||||
size: `${volumeConfiguration.size}${sizeFactor}`,
|
||||
storage_class: volumeConfiguration.storage_class,
|
||||
},
|
||||
zones: cleanZones,
|
||||
})
|
||||
.then(() => {
|
||||
setAddSending(false);
|
||||
setAddError("");
|
||||
closeModalAndRefresh(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddSending(false);
|
||||
setAddError(err);
|
||||
});
|
||||
} else {
|
||||
setAddSending(false);
|
||||
setAddError("Please fix the errors in the form and try again");
|
||||
}
|
||||
}
|
||||
}, [addSending]);
|
||||
|
||||
const setVolumeConfig = (item: string, value: string) => {
|
||||
const volumeCopy: IVolumeConfiguration = {
|
||||
size: item !== "size" ? volumeConfiguration.size : value,
|
||||
storage_class:
|
||||
item !== "storage_class" ? volumeConfiguration.storage_class : value,
|
||||
};
|
||||
|
||||
setVolumeConfiguration(volumeCopy);
|
||||
};
|
||||
|
||||
const fetchStorageClassList = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/mkube/storage-classes`)
|
||||
.then((res: string[]) => {
|
||||
let classes: string[] = [];
|
||||
if (res !== null) {
|
||||
classes = res;
|
||||
}
|
||||
setStorageClassesList(
|
||||
classes.map((s: string) => ({
|
||||
label: s,
|
||||
value: s,
|
||||
}))
|
||||
);
|
||||
|
||||
const newStorage = { ...volumeConfiguration };
|
||||
newStorage.storage_class = res[0];
|
||||
|
||||
setVolumeConfiguration(newStorage);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
const cancelButton = {
|
||||
label: "Cancel",
|
||||
type: "other",
|
||||
enabled: true,
|
||||
action: () => {
|
||||
closeModalAndRefresh(false);
|
||||
},
|
||||
};
|
||||
|
||||
const wizardSteps: IWizardElement[] = [
|
||||
{
|
||||
label: "Name Tenant",
|
||||
componentRender: (
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Name Tenant</h3>
|
||||
<span>How would you like to name this new tenant?</span>
|
||||
</div>
|
||||
<InputBoxWrapper
|
||||
id="tenant-name"
|
||||
name="tenant-name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTenantName(e.target.value);
|
||||
clearValidationError("tenant-name");
|
||||
}}
|
||||
label="Tenant Name"
|
||||
value={tenantName}
|
||||
required
|
||||
error={validationErrors["tenant-name"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
),
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Next", type: "next", enabled: nameTenantValid },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Configure",
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Configure</h3>
|
||||
<span>Basic configurations for tenant management</span>
|
||||
</div>
|
||||
Please enter your access & secret keys
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="access_key"
|
||||
name="access_key"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAccessKey(e.target.value);
|
||||
clearValidationError("access_key");
|
||||
}}
|
||||
label="Access Key"
|
||||
value={accessKey}
|
||||
error={validationErrors["access_key"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="secret_key"
|
||||
name="secret_key"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSecretKey(e.target.value);
|
||||
clearValidationError("secret_key");
|
||||
}}
|
||||
label="Secret Key"
|
||||
value={secretKey}
|
||||
error={validationErrors["secret_key"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
Please enter the MinIO image from dockerhub
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="image"
|
||||
name="image"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setImageName(e.target.value);
|
||||
clearValidationError("image");
|
||||
}}
|
||||
label="MinIO Image"
|
||||
value={imageName}
|
||||
error={validationErrors["image"] || ""}
|
||||
placeholder="Eg. minio/minio:RELEASE.2020-05-08T02-40-49Z"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="service_name"
|
||||
name="service_name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setServiceName(e.target.value);
|
||||
clearValidationError("service_name");
|
||||
}}
|
||||
label="Service Name"
|
||||
value={serviceName}
|
||||
error={validationErrors["service_name"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="namespace"
|
||||
name="namespace"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNamespace(e.target.value);
|
||||
clearValidationError("namespace");
|
||||
}}
|
||||
label="Namespace"
|
||||
value={namespace}
|
||||
error={validationErrors["namespace"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
),
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{ label: "Next", type: "next", enabled: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Storage Class",
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Choose your prefered Storage Class</h3>
|
||||
<span>
|
||||
Review the storage classes available in the tenant and decide
|
||||
which one to allocate the tenant to
|
||||
</span>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
id="storage_class"
|
||||
name="storage_class"
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
setVolumeConfig("storage_class", e.target.value as string);
|
||||
}}
|
||||
label="Storage Class"
|
||||
value={volumeConfiguration.storage_class}
|
||||
options={storageClasses}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
),
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{ label: "Next", type: "next", enabled: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Server Configuration",
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Server Configuration</h3>
|
||||
<span>Define the server configuration</span>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="mount_path"
|
||||
name="mount_path"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setMountPath(e.target.value);
|
||||
}}
|
||||
label="Mount Path"
|
||||
value={mountPath}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="volumes_per_server"
|
||||
name="volumes_per_server"
|
||||
type="number"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setVolumesPerServer(parseInt(e.target.value));
|
||||
clearValidationError("volumes_per_server");
|
||||
}}
|
||||
label="Volumes per Server"
|
||||
value={volumesPerServer.toString(10)}
|
||||
required
|
||||
error={validationErrors["volumes_per_server"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.multiContainer}>
|
||||
<div>
|
||||
<InputBoxWrapper
|
||||
id="volume_size"
|
||||
name="volume_size"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setVolumeConfig("size", e.target.value);
|
||||
clearValidationError("volume_size");
|
||||
}}
|
||||
label="Size"
|
||||
value={volumeConfiguration.size}
|
||||
required
|
||||
error={validationErrors["volume_size"] || ""}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.sizeFactorContainer}>
|
||||
<SelectWrapper
|
||||
label=""
|
||||
id="size_factor"
|
||||
name="size_factor"
|
||||
value={sizeFactor}
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
setSizeFactor(e.target.value as string);
|
||||
}}
|
||||
options={k8sfactorForDropdown()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
),
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{ label: "Next", type: "next", enabled: configValid },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Zones Definition",
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Zones Definition</h3>
|
||||
<span>Define the size of the tenant by defining the zone size</span>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<div>
|
||||
<ZonesMultiSelector
|
||||
label="Zones"
|
||||
name="zones_selector"
|
||||
onChange={(elements: IZone[]) => {
|
||||
setZones(elements);
|
||||
}}
|
||||
elements={zones}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
),
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{ label: "Next", type: "next", enabled: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Extra Configurations",
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Extra Configurations</h3>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<CheckboxWrapper
|
||||
value="enabled_mcs"
|
||||
id="enabled_mcs"
|
||||
name="enabled_mcs"
|
||||
checked={enableMCS}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
setEnableMCS(checked);
|
||||
}}
|
||||
label={"Enable mcs"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<CheckboxWrapper
|
||||
value="enable_ssl"
|
||||
id="enable_ssl"
|
||||
name="enable_ssl"
|
||||
checked={enableSSL}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
setEnableSSL(checked);
|
||||
}}
|
||||
label={"Enable SSL"}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
),
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{ label: "Next", type: "next", enabled: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Review",
|
||||
componentRender: (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3>Review</h3>
|
||||
<span>Review the details of the new tenant</span>
|
||||
</div>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Table size="small">
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Tenant Name
|
||||
</TableCell>
|
||||
<TableCell>{tenantName}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Access Key
|
||||
</TableCell>
|
||||
<TableCell>{accessKey}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Secret Key
|
||||
</TableCell>
|
||||
<TableCell>{secretKey}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
MinIO Image
|
||||
</TableCell>
|
||||
<TableCell>{imageName}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Service Name
|
||||
</TableCell>
|
||||
<TableCell>{serviceName}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Namespace
|
||||
</TableCell>
|
||||
<TableCell>{namespace}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Storage Class
|
||||
</TableCell>
|
||||
<TableCell>{volumeConfiguration.storage_class}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Mount Path
|
||||
</TableCell>
|
||||
<TableCell>{mountPath}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Volumes per Server
|
||||
</TableCell>
|
||||
<TableCell>{volumesPerServer}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Volume Size
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{volumeConfiguration.size} {sizeFactor}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Total Zones
|
||||
</TableCell>
|
||||
<TableCell>{zones.length}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Enable SSL
|
||||
</TableCell>
|
||||
<TableCell>{enableSSL ? "Enabled" : "Disabled"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Enable MCS
|
||||
</TableCell>
|
||||
<TableCell>{enableMCS ? "Enabled" : "Disabled"}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
{addSending && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</React.Fragment>
|
||||
),
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Save",
|
||||
type: "submit",
|
||||
enabled: !addSending,
|
||||
action: () => {
|
||||
console.log("Save");
|
||||
setAddSending(true);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
title="Create Tenant"
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
setAddError("");
|
||||
closeModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<GenericWizard wizardSteps={wizardSteps} />
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(AddTenant);
|
||||
@@ -0,0 +1,122 @@
|
||||
// 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 {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import api from "../../../../common/api";
|
||||
|
||||
interface IDeleteTenant {
|
||||
classes: any;
|
||||
deleteOpen: boolean;
|
||||
selectedTenant: string;
|
||||
closeDeleteModalAndRefresh: (refreshList: boolean) => any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
|
||||
const DeleteTenant = ({
|
||||
classes,
|
||||
deleteOpen,
|
||||
selectedTenant,
|
||||
closeDeleteModalAndRefresh,
|
||||
}: IDeleteTenant) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||
const [deleteError, setDeleteError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (deleteLoading) {
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/mkube/tenants/${selectedTenant}`)
|
||||
.then(() => {
|
||||
setDeleteLoading(false);
|
||||
setDeleteError("");
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
setDeleteLoading(false);
|
||||
setDeleteError(err);
|
||||
});
|
||||
}
|
||||
}, [deleteLoading]);
|
||||
|
||||
const removeRecord = () => {
|
||||
setDeleteLoading(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
setDeleteError("");
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Tenant</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete tenant <b>{selectedTenant}</b>?
|
||||
{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={removeRecord} color="secondary" autoFocus>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(DeleteTenant);
|
||||
@@ -0,0 +1,282 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import 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 TableWrapper from "../../Common/TableWrapper/TableWrapper";
|
||||
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import api from "../../../../common/api";
|
||||
import { ITenant, ITenantsResponse } from "./types";
|
||||
import { niceBytes } from "../../../../common/utils";
|
||||
import DeleteTenant from "./DeleteTenant";
|
||||
import AddTenant from "./AddTenant";
|
||||
|
||||
interface ITenantsList {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
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",
|
||||
},
|
||||
});
|
||||
|
||||
const ListTenants = ({ classes }: ITenantsList) => {
|
||||
const [createTenantOpen, setCreateTenantOpen] = useState<boolean>(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [selectedTenant, setSelectedTenant] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [filterTenants, setFilterTenants] = useState<string>("");
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
const [offset, setOffset] = useState<number>(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const closeAddModalAndRefresh = (reloadData: boolean) => {
|
||||
setCreateTenantOpen(false);
|
||||
|
||||
if (reloadData) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
};
|
||||
|
||||
const closeDeleteModalAndRefresh = (reloadData: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
if (reloadData) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmDeleteTenant = (tenant: string) => {
|
||||
setSelectedTenant(tenant);
|
||||
setDeleteOpen(true);
|
||||
};
|
||||
|
||||
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 tableActions = [
|
||||
{ type: "view", to: `/tenants`, sendOnlyId: true },
|
||||
{ type: "delete", onClick: confirmDeleteTenant, sendOnlyId: true },
|
||||
];
|
||||
|
||||
const filteredRecords = records
|
||||
.slice(offset, offset + rowsPerPage)
|
||||
.filter((b: any) => {
|
||||
if (filterTenants === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterTenants) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
const fetchRecords = () => {
|
||||
const offset = page * rowsPerPage;
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/mkube/tenants?offset=${offset}&limit=${rowsPerPage}`
|
||||
)
|
||||
.then((res: ITenantsResponse) => {
|
||||
if (res === null) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
let resTenants: ITenant[] = [];
|
||||
if (res.tenants !== null) {
|
||||
resTenants = res.tenants;
|
||||
}
|
||||
|
||||
for (let i = 0; i < resTenants.length; i++) {
|
||||
const total =
|
||||
resTenants[i].volume_count * resTenants[i].volume_size;
|
||||
resTenants[i].capacity = niceBytes(total + "");
|
||||
}
|
||||
|
||||
setRecords(resTenants);
|
||||
setError("");
|
||||
setIsLoading(false);
|
||||
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if ((!res.tenants || res.tenants.length === 0) && page > 0) {
|
||||
const newPage = page - 1;
|
||||
setPage(newPage);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
fetchRecords();
|
||||
}
|
||||
}, [isLoading, page, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{createTenantOpen && (
|
||||
<AddTenant
|
||||
open={createTenantOpen}
|
||||
closeModalAndRefresh={closeAddModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
{deleteOpen && (
|
||||
<DeleteTenant
|
||||
deleteOpen={deleteOpen}
|
||||
selectedTenant={selectedTenant}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Tenants</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Tenants"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterTenants(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setCreateTenantOpen(true);
|
||||
}}
|
||||
>
|
||||
Create Tenant
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Capacity", elementKey: "capacity" },
|
||||
{ label: "# of Zones", elementKey: "zone_count" },
|
||||
{ label: "State", elementKey: "currentState" },
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
records={filteredRecords}
|
||||
entityName="Tenants"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: filteredRecords.length,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ListTenants);
|
||||
@@ -0,0 +1,213 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 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, createRef, ChangeEvent } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import get from "lodash/get";
|
||||
import { InputLabel, Tooltip } from "@material-ui/core";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import {
|
||||
fieldBasic,
|
||||
tooltipHelper,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import DeleteIcon from "../../../../icons/DeleteIcon";
|
||||
import { IZone } from "./types";
|
||||
|
||||
interface IZonesMultiSelector {
|
||||
elements: IZone[];
|
||||
name: string;
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
classes: any;
|
||||
onChange: (elements: IZone[]) => void;
|
||||
}
|
||||
|
||||
const gridBasic = {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "calc(50% - 20px) calc(50% - 20px) 30px",
|
||||
gridGap: 8,
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
inputLabel: {
|
||||
...fieldBasic.inputLabel,
|
||||
width: 116,
|
||||
},
|
||||
inputContainer: {
|
||||
height: 150,
|
||||
overflowY: "auto",
|
||||
padding: 15,
|
||||
position: "relative",
|
||||
border: "1px solid #c4c4c4",
|
||||
},
|
||||
labelContainer: {
|
||||
display: "flex",
|
||||
},
|
||||
inputGrid: {
|
||||
...gridBasic,
|
||||
},
|
||||
inputTitles: {
|
||||
...gridBasic,
|
||||
marginBottom: 5,
|
||||
},
|
||||
deleteIconContainer: {
|
||||
alignSelf: "center",
|
||||
"& button": {
|
||||
padding: 0,
|
||||
marginBottom: 10,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const ZonesMultiSelector = ({
|
||||
elements,
|
||||
name,
|
||||
label,
|
||||
tooltip = "",
|
||||
onChange,
|
||||
classes,
|
||||
}: IZonesMultiSelector) => {
|
||||
const defaultZone: IZone = { name: "", servers: 0, capacity: "", volumes: 0 };
|
||||
|
||||
const [currentElements, setCurrentElements] = useState<IZone[]>([
|
||||
{ ...defaultZone },
|
||||
]);
|
||||
const bottomList = createRef<HTMLDivElement>();
|
||||
|
||||
// Use effect to send new values to onChange
|
||||
useEffect(() => {
|
||||
onChange(currentElements);
|
||||
}, [currentElements]);
|
||||
|
||||
// If the last input is not empty, we add a new one
|
||||
const addEmptyRow = (elementsUp: IZone[]) => {
|
||||
const lastElement = elementsUp[elementsUp.length - 1];
|
||||
if (lastElement.servers !== 0 && lastElement.name !== "") {
|
||||
elementsUp.push({ ...defaultZone });
|
||||
const refScroll = bottomList.current;
|
||||
|
||||
if (refScroll) {
|
||||
refScroll.scrollIntoView(false);
|
||||
}
|
||||
}
|
||||
|
||||
return elementsUp;
|
||||
};
|
||||
|
||||
// Onchange function for input box, we get the dataset-index & only update that value in the array
|
||||
const onChangeElement = (e: ChangeEvent<HTMLInputElement>, field: string) => {
|
||||
e.persist();
|
||||
|
||||
let updatedElement = [...currentElements];
|
||||
const index = get(e.target, "dataset.index", 0);
|
||||
|
||||
const rowPosition: IZone = updatedElement[index];
|
||||
|
||||
rowPosition.servers =
|
||||
field === "servers" ? parseInt(e.target.value) : rowPosition.servers;
|
||||
rowPosition.name = field === "name" ? e.target.value : rowPosition.name;
|
||||
|
||||
updatedElement[index] = rowPosition;
|
||||
|
||||
updatedElement = addEmptyRow(updatedElement);
|
||||
setCurrentElements(updatedElement);
|
||||
};
|
||||
|
||||
const deleteElement = (zoneToRemove: number) => {
|
||||
const zonesClone = [...currentElements];
|
||||
|
||||
const newArray = zonesClone
|
||||
.slice(0, zoneToRemove)
|
||||
.concat(zonesClone.slice(zoneToRemove + 1, zonesClone.length));
|
||||
|
||||
setCurrentElements(newArray);
|
||||
};
|
||||
|
||||
const inputs = currentElements.map((element, index) => {
|
||||
return (
|
||||
<React.Fragment key={`zone-${name}-${index.toString()}`}>
|
||||
<div>
|
||||
<InputBoxWrapper
|
||||
id={`${name}-${index.toString()}-name`}
|
||||
label={""}
|
||||
name={`${name}-${index.toString()}-name`}
|
||||
value={currentElements[index].name}
|
||||
onChange={(e) => onChangeElement(e, "name")}
|
||||
index={index}
|
||||
key={`Zones-${name}-${index.toString()}-name`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
id={`${name}-${index.toString()}-servers`}
|
||||
label={""}
|
||||
name={`${name}-${index.toString()}-servers`}
|
||||
value={currentElements[index].servers.toString(10)}
|
||||
onChange={(e) => onChangeElement(e, "servers")}
|
||||
index={index}
|
||||
key={`Zones-${name}-${index.toString()}-servers`}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.deleteIconContainer}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
deleteElement(index);
|
||||
}}
|
||||
disabled={index === currentElements.length - 1}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
<InputLabel className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.inputTitles}>
|
||||
<div>Name</div>
|
||||
<div>Servers</div>
|
||||
</div>
|
||||
<div className={classes.inputContainer}>
|
||||
<div className={classes.inputGrid}>{inputs}</div>
|
||||
</div>
|
||||
<div ref={bottomList} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ZonesMultiSelector);
|
||||
46
portal-ui/src/screens/Console/Tenants/ListTenants/types.ts
Normal file
46
portal-ui/src/screens/Console/Tenants/ListTenants/types.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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 IZone {
|
||||
name: string;
|
||||
servers: number;
|
||||
// computed
|
||||
capacity: string;
|
||||
volumes: number;
|
||||
}
|
||||
|
||||
export interface IVolumeConfiguration {
|
||||
size: string;
|
||||
storage_class: string;
|
||||
}
|
||||
|
||||
export interface ITenant {
|
||||
name: string;
|
||||
zone_count: number;
|
||||
currentState: string;
|
||||
instance_count: 4;
|
||||
creation_date: Date;
|
||||
volume_size: number;
|
||||
volume_count: number;
|
||||
volumes_per_server: number;
|
||||
zones: IZone[];
|
||||
// computed
|
||||
capacity: string;
|
||||
}
|
||||
|
||||
export interface ITenantsResponse {
|
||||
tenants: ITenant[];
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import React, { useState } from "react";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import {
|
||||
factorForDropdown,
|
||||
getTotalSize,
|
||||
niceBytes,
|
||||
} from "../../../../common/utils";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
|
||||
interface IAddZoneProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
onCloseZoneAndReload: (shouldReload: boolean) => void;
|
||||
volumesPerInstance: number;
|
||||
volumeSize: number;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red",
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
multiContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center" as const,
|
||||
justifyContent: "flex-start" as const,
|
||||
},
|
||||
sizeFactorContainer: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
bottomContainer: {
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
alignItems: "center",
|
||||
"& div": {
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
},
|
||||
},
|
||||
factorElements: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
sizeNumber: {
|
||||
fontSize: 35,
|
||||
fontWeight: 700,
|
||||
textAlign: "center",
|
||||
},
|
||||
sizeDescription: {
|
||||
fontSize: 14,
|
||||
color: "#777",
|
||||
textAlign: "center",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const AddZoneModal = ({
|
||||
classes,
|
||||
open,
|
||||
onCloseZoneAndReload,
|
||||
volumesPerInstance,
|
||||
volumeSize,
|
||||
}: IAddZoneProps) => {
|
||||
const [addSending, setAddSending] = useState<boolean>(false);
|
||||
const [zoneName, setZoneName] = useState<string>("");
|
||||
const [numberOfInstances, setNumberOfInstances] = useState<number>(0);
|
||||
|
||||
const instanceCapacity: number = volumeSize * volumesPerInstance;
|
||||
const totalCapacity: number = instanceCapacity * numberOfInstances;
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
onClose={() => onCloseZoneAndReload(false)}
|
||||
modalOpen={open}
|
||||
title="Add Zone"
|
||||
>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setAddSending(true);
|
||||
}}
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="zone_name"
|
||||
name="zone_name"
|
||||
type="string"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setZoneName(e.target.value);
|
||||
}}
|
||||
label="Name"
|
||||
value={zoneName}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="number_instances"
|
||||
name="number_instances"
|
||||
type="number"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNumberOfInstances(parseInt(e.target.value));
|
||||
}}
|
||||
label="Volumes per Server"
|
||||
value={numberOfInstances.toString(10)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} className={classes.bottomContainer}>
|
||||
<div className={classes.factorElements}>
|
||||
<div>
|
||||
<div className={classes.sizeNumber}>
|
||||
{niceBytes(instanceCapacity.toString(10))}
|
||||
</div>
|
||||
<div className={classes.sizeDescription}>Instance Capacity</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className={classes.sizeNumber}>
|
||||
{niceBytes(totalCapacity.toString(10))}
|
||||
</div>
|
||||
<div className={classes.sizeDescription}>Total Capacity</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addSending}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
{addSending && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(AddZoneModal);
|
||||
@@ -0,0 +1,218 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import Tab from "@material-ui/core/Tab";
|
||||
import Tabs from "@material-ui/core/Tabs";
|
||||
|
||||
interface IReplicationProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
closeModalAndRefresh: (refreshList: boolean) => void;
|
||||
}
|
||||
|
||||
interface IDropDownElements {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red",
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
multiContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center" as const,
|
||||
justifyContent: "flex-start" as const,
|
||||
},
|
||||
sizeFactorContainer: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const ReplicationSetup = ({
|
||||
classes,
|
||||
open,
|
||||
closeModalAndRefresh,
|
||||
}: IReplicationProps) => {
|
||||
const [addSending, setAddSending] = useState<boolean>(false);
|
||||
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||
const [sourceBucket, setSourceBucket] = useState<string>("");
|
||||
const [clusterSelected, setClusterSelected] = useState<string>("");
|
||||
const [destinationBucket, setDestinationBucket] = useState<string>("");
|
||||
const [address, setAddress] = useState<string>("");
|
||||
const [bucket, setBucket] = useState<string>("");
|
||||
const [accessKey, setAccessKey] = useState<string>("");
|
||||
const [secretKey, setSecretKey] = useState<string>("");
|
||||
|
||||
const clustersList: IDropDownElements[] = [];
|
||||
const sourceBuckets: IDropDownElements[] = [];
|
||||
const destinationBuckets: IDropDownElements[] = [];
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
title="Add Zone"
|
||||
onClose={() => {
|
||||
closeModalAndRefresh(false);
|
||||
}}
|
||||
>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setAddSending(true);
|
||||
}}
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
label="Source Bucket"
|
||||
options={sourceBuckets}
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
setSourceBucket(e.target.value as string);
|
||||
}}
|
||||
value={sourceBucket}
|
||||
name="source_bucket"
|
||||
id="source_bucket"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
onChange={(_, newValue: number) => {
|
||||
setSelectedTab(newValue);
|
||||
}}
|
||||
aria-label="cluster-tabs"
|
||||
>
|
||||
<Tab label="Local Cluster" />
|
||||
<Tab label="Remote Cluster" />
|
||||
</Tabs>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
{selectedTab === 0 && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
label="Cluster"
|
||||
options={clustersList}
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
setClusterSelected(e.target.value as string);
|
||||
}}
|
||||
value={clusterSelected}
|
||||
name="cluster"
|
||||
id="cluster"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
label="Destination Bucket"
|
||||
options={destinationBuckets}
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
setDestinationBucket(e.target.value as string);
|
||||
}}
|
||||
value={destinationBucket}
|
||||
name="destination_bucket"
|
||||
id="destination_bucket"
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{selectedTab === 1 && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="address"
|
||||
name="address"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAddress(e.target.value);
|
||||
}}
|
||||
label="Address"
|
||||
value={address}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="bucket"
|
||||
name="bucket"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setBucket(e.target.value);
|
||||
}}
|
||||
label="Bucket"
|
||||
value={bucket}
|
||||
/>
|
||||
</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>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addSending}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{addSending && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ReplicationSetup);
|
||||
@@ -0,0 +1,449 @@
|
||||
// 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 { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Button } from "@material-ui/core";
|
||||
import Tabs from "@material-ui/core/Tabs";
|
||||
import Tab from "@material-ui/core/Tab";
|
||||
import { CreateIcon } from "../../../../icons";
|
||||
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
|
||||
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import { niceBytes } from "../../../../common/utils";
|
||||
import AddZoneModal from "./AddZoneModal";
|
||||
import AddBucket from "../../Buckets/ListBuckets/AddBucket";
|
||||
import ReplicationSetup from "./ReplicationSetup";
|
||||
import api from "../../../../common/api";
|
||||
import { BucketInfo } from "../../Buckets/types";
|
||||
import { ITenant, IZone } from "../ListTenants/types";
|
||||
|
||||
interface ITenantDetailsProps {
|
||||
classes: any;
|
||||
match: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red",
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
multiContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center" as const,
|
||||
justifyContent: "flex-start" as const,
|
||||
},
|
||||
sizeFactorContainer: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
containerHeader: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
paperContainer: {
|
||||
padding: "15px 15px 15px 50px",
|
||||
},
|
||||
infoGrid: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "auto auto auto auto",
|
||||
gridGap: 8,
|
||||
"& div": {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
"& div:nth-child(odd)": {
|
||||
justifyContent: "flex-end",
|
||||
fontWeight: 700,
|
||||
},
|
||||
"& div:nth-child(2n)": {
|
||||
paddingRight: 35,
|
||||
},
|
||||
},
|
||||
masterActions: {
|
||||
width: "25%",
|
||||
minWidth: "120px",
|
||||
"& div": {
|
||||
margin: "5px 0px",
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...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);
|
||||
const [externalIDP, setExternalIDP] = useState<boolean>(false);
|
||||
const [externalKMS, setExternalKMS] = useState<boolean>(false);
|
||||
const [zoneCount, setZoneCount] = useState<number>(0);
|
||||
const [zones, setZones] = useState<IZone[]>([]);
|
||||
const [instances, setInstances] = useState<number>(0);
|
||||
const [volumes, setVolumes] = useState<number>(0);
|
||||
const [addZoneOpen, setAddZone] = useState<boolean>(false);
|
||||
const [addBucketOpen, setAddBucketOpen] = useState<boolean>(false);
|
||||
const [addReplicationOpen, setAddReplicationOpen] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [tenant, setTenant] = useState<ITenant | null>(null);
|
||||
|
||||
const onCloseZoneAndRefresh = (reload: boolean) => {
|
||||
setAddZone(false);
|
||||
|
||||
if (reload) {
|
||||
console.log("reload");
|
||||
}
|
||||
};
|
||||
|
||||
const closeBucketsAndRefresh = () => {
|
||||
setAddBucketOpen(false);
|
||||
};
|
||||
|
||||
const closeReplicationAndRefresh = (reload: boolean) => {
|
||||
setAddReplicationOpen(false);
|
||||
|
||||
if (reload) {
|
||||
console.log("reload");
|
||||
}
|
||||
};
|
||||
|
||||
const loadInfo = () => {
|
||||
const tenantName = match.params["tenantName"];
|
||||
|
||||
setLoading(true);
|
||||
|
||||
api
|
||||
.invoke("GET", `/api/v1/mkube/tenants/${tenantName}`)
|
||||
.then((res: ITenant) => {
|
||||
const total = res.volume_count * res.volume_size;
|
||||
|
||||
setCapacity(total);
|
||||
setZoneCount(res.zone_count);
|
||||
setVolumes(res.volume_count);
|
||||
setInstances(res.instance_count);
|
||||
const resZones = !res.zones ? [] : res.zones;
|
||||
for (let zone of resZones) {
|
||||
zone.volumes = res.volumes_per_server;
|
||||
const cap = res.volumes_per_server * res.volume_size * zone.servers;
|
||||
zone.capacity = niceBytes(cap + "");
|
||||
}
|
||||
setZones(resZones);
|
||||
|
||||
setTenant(res);
|
||||
setError("");
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadInfo();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addZoneOpen && tenant !== null && (
|
||||
<AddZoneModal
|
||||
open={addZoneOpen}
|
||||
onCloseZoneAndReload={onCloseZoneAndRefresh}
|
||||
volumeSize={tenant.volume_size}
|
||||
volumesPerInstance={tenant.volumes_per_server}
|
||||
/>
|
||||
)}
|
||||
{addBucketOpen && (
|
||||
<AddBucket
|
||||
open={addBucketOpen}
|
||||
closeModalAndRefresh={closeBucketsAndRefresh}
|
||||
/>
|
||||
)}
|
||||
{addReplicationOpen && (
|
||||
<ReplicationSetup
|
||||
open={addReplicationOpen}
|
||||
closeModalAndRefresh={closeReplicationAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">
|
||||
Tenant > {match.params["tenantName"]}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.containerHeader}>
|
||||
<Paper className={classes.paperContainer}>
|
||||
<div className={classes.infoGrid}>
|
||||
<div>Capacity:</div>
|
||||
<div>{niceBytes(capacity.toString(10))}</div>
|
||||
<div>Zones:</div>
|
||||
<div>{zoneCount}</div>
|
||||
<div>External IDP:</div>
|
||||
<div>
|
||||
{externalIDP ? "Yes" : "No"}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={() => {}}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
<div>Instances:</div>
|
||||
<div>{instances}</div>
|
||||
<div>External KMS:</div>
|
||||
<div>{externalKMS ? "Yes" : "No"} </div>
|
||||
<div>Volumes:</div>
|
||||
<div>{volumes}</div>
|
||||
</div>
|
||||
</Paper>
|
||||
<div className={classes.masterActions}>
|
||||
<div>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
onClick={() => {}}
|
||||
>
|
||||
Warp
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
onChange={(_, newValue: number) => {
|
||||
setSelectedTab(newValue);
|
||||
}}
|
||||
aria-label="tenant-tabs"
|
||||
>
|
||||
<Tab label="Zones" />
|
||||
<Tab label="Buckets" />
|
||||
<Tab label="Replication" />
|
||||
</Tabs>
|
||||
</Grid>
|
||||
<Grid item xs={6} className={classes.actionsTray}>
|
||||
{selectedTab === 0 && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setAddZone(true);
|
||||
}}
|
||||
>
|
||||
Add Zone
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{selectedTab === 1 && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setAddBucketOpen(true);
|
||||
}}
|
||||
>
|
||||
Create Bucket
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{selectedTab === 2 && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setAddReplicationOpen(true);
|
||||
}}
|
||||
>
|
||||
Add Replication
|
||||
</Button>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
{selectedTab === 0 && (
|
||||
<TableWrapper
|
||||
itemActions={[
|
||||
{
|
||||
type: "view",
|
||||
onClick: (element) => {
|
||||
console.log(element);
|
||||
},
|
||||
sendOnlyId: true,
|
||||
},
|
||||
{
|
||||
type: "delete",
|
||||
onClick: (element) => {
|
||||
console.log(element);
|
||||
},
|
||||
sendOnlyId: true,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Capacity", elementKey: "capacity" },
|
||||
{ label: "# of Instances", elementKey: "servers" },
|
||||
{ label: "# of Drives", elementKey: "volumes" },
|
||||
]}
|
||||
isLoading={false}
|
||||
records={zones}
|
||||
entityName="Zones"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
...mainPagination,
|
||||
onChangePage: () => {},
|
||||
onChangeRowsPerPage: () => {},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedTab === 1 && (
|
||||
<TableWrapper
|
||||
itemActions={[
|
||||
{
|
||||
type: "view",
|
||||
onClick: (element) => {
|
||||
console.log(element);
|
||||
},
|
||||
sendOnlyId: true,
|
||||
},
|
||||
{
|
||||
type: "replicate",
|
||||
onClick: (element) => {
|
||||
console.log(element);
|
||||
},
|
||||
sendOnlyId: true,
|
||||
},
|
||||
{
|
||||
type: "mirror",
|
||||
onClick: (element) => {
|
||||
console.log(element);
|
||||
},
|
||||
sendOnlyId: true,
|
||||
},
|
||||
{
|
||||
type: "delete",
|
||||
onClick: (element) => {
|
||||
console.log(element);
|
||||
},
|
||||
sendOnlyId: true,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
label: "Status",
|
||||
elementKey: "status",
|
||||
},
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "AccessPolicy", elementKey: "access_policy" },
|
||||
]}
|
||||
isLoading={false}
|
||||
records={[]}
|
||||
entityName="Buckets"
|
||||
idField="name"
|
||||
paginatorConfig={{
|
||||
...mainPagination,
|
||||
onChangePage: () => {},
|
||||
onChangeRowsPerPage: () => {},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedTab === 2 && (
|
||||
<TableWrapper
|
||||
itemActions={[
|
||||
{
|
||||
type: "view",
|
||||
onClick: (element) => {
|
||||
console.log(element);
|
||||
},
|
||||
sendOnlyId: true,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
label: "Source",
|
||||
elementKey: "source",
|
||||
},
|
||||
{ label: "Source Bucket", elementKey: "source_bucket" },
|
||||
{ label: "Destination", elementKey: "destination" },
|
||||
{
|
||||
label: "Destination Bucket",
|
||||
elementKey: "destination_bucket",
|
||||
},
|
||||
]}
|
||||
isLoading={false}
|
||||
records={[]}
|
||||
entityName="Replication"
|
||||
idField="id"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: 0,
|
||||
rowsPerPage: 0,
|
||||
page: 0,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: () => {},
|
||||
onChangeRowsPerPage: () => {},
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(TenantDetails);
|
||||
@@ -34,6 +34,8 @@ import AddUser from "./AddUser";
|
||||
import DeleteUser from "./DeleteUser";
|
||||
import AddToGroup from "./AddToGroup";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import DescriptionIcon from "@material-ui/icons/Description";
|
||||
import SetPolicy from "../Policies/SetPolicy";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -102,6 +104,7 @@ interface IUsersState {
|
||||
addGroupOpen: boolean;
|
||||
filter: string;
|
||||
checkedUsers: string[];
|
||||
setPolicyOpen: boolean;
|
||||
}
|
||||
|
||||
class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
@@ -119,6 +122,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
addGroupOpen: false,
|
||||
filter: "",
|
||||
checkedUsers: [],
|
||||
setPolicyOpen: false,
|
||||
};
|
||||
|
||||
fetchRecords() {
|
||||
@@ -192,6 +196,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
filter,
|
||||
checkedUsers,
|
||||
addGroupOpen,
|
||||
setPolicyOpen,
|
||||
} = this.state;
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
@@ -243,6 +248,13 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
});
|
||||
};
|
||||
|
||||
const setPolicyAction = (selectionElement: any): void => {
|
||||
this.setState({
|
||||
setPolicyOpen: true,
|
||||
selectedUser: selectionElement,
|
||||
});
|
||||
};
|
||||
|
||||
const deleteAction = (selectionElement: any): void => {
|
||||
this.setState({
|
||||
deleteOpen: true,
|
||||
@@ -252,6 +264,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: viewAction },
|
||||
{ type: "description", onClick: setPolicyAction },
|
||||
{ type: "delete", onClick: deleteAction },
|
||||
];
|
||||
|
||||
@@ -266,6 +279,16 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{setPolicyOpen && (
|
||||
<SetPolicy
|
||||
open={setPolicyOpen}
|
||||
selectedUser={selectedUser}
|
||||
selectedGroup={null}
|
||||
closeModalAndRefresh={() => {
|
||||
this.setState({ setPolicyOpen: false });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{deleteOpen && (
|
||||
<DeleteUser
|
||||
deleteOpen={deleteOpen}
|
||||
|
||||
@@ -18,6 +18,10 @@ interface userInterface {
|
||||
accessKey: string;
|
||||
}
|
||||
|
||||
interface policyInterface {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const usersSort = (a: userInterface, b: userInterface) => {
|
||||
if (a.accessKey > b.accessKey) {
|
||||
return 1;
|
||||
@@ -29,6 +33,17 @@ export const usersSort = (a: userInterface, b: userInterface) => {
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const policySort = (a: policyInterface, b: policyInterface) => {
|
||||
if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
// a must be equal to b
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const stringSort = (a: string, b: string) => {
|
||||
if (a > b) {
|
||||
return 1;
|
||||
|
||||
44
portal-ui/src/utils/validationFunctions.ts
Normal file
44
portal-ui/src/utils/validationFunctions.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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 IValidation {
|
||||
fieldKey: string;
|
||||
required: boolean;
|
||||
pattern?: RegExp;
|
||||
customPatternMessage?: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const commonFormValidation = (fieldsValidate: IValidation[]) => {
|
||||
let returnErrors: any = {};
|
||||
|
||||
fieldsValidate.forEach((field) => {
|
||||
if (field.required && field.value.trim() === "") {
|
||||
returnErrors[field.fieldKey] = "Field cannot be empty";
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.pattern && field.customPatternMessage) {
|
||||
const rgx = new RegExp(field.pattern, "g");
|
||||
|
||||
if (!field.value.match(rgx)) {
|
||||
returnErrors[field.fieldKey] = field.customPatternMessage;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return returnErrors;
|
||||
};
|
||||
@@ -3013,6 +3013,29 @@ chardet@^0.7.0:
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||
|
||||
chart.js@^2.9.3:
|
||||
version "2.9.3"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.3.tgz#ae3884114dafd381bc600f5b35a189138aac1ef7"
|
||||
integrity sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==
|
||||
dependencies:
|
||||
chartjs-color "^2.1.0"
|
||||
moment "^2.10.2"
|
||||
|
||||
chartjs-color-string@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
|
||||
integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==
|
||||
dependencies:
|
||||
color-name "^1.0.0"
|
||||
|
||||
chartjs-color@^2.1.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0"
|
||||
integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==
|
||||
dependencies:
|
||||
chartjs-color-string "^0.6.0"
|
||||
color-convert "^1.9.3"
|
||||
|
||||
chokidar@^2.1.8:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
||||
@@ -3243,7 +3266,7 @@ collection-visit@^1.0.0:
|
||||
map-visit "^1.0.0"
|
||||
object-visit "^1.0.0"
|
||||
|
||||
color-convert@^1.9.0, color-convert@^1.9.1:
|
||||
color-convert@^1.9.0, color-convert@^1.9.1, color-convert@^1.9.3:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
@@ -7632,7 +7655,7 @@ lodash.without@~4.4.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
|
||||
integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=
|
||||
|
||||
"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5, lodash@~4.17.4:
|
||||
"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.4:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
@@ -8028,6 +8051,11 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.0, mkdir
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
moment@^2.10.2:
|
||||
version "2.26.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"
|
||||
integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==
|
||||
|
||||
moment@^2.24.0:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||
@@ -9959,7 +9987,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.5.8, prop-types@^15.6.0, 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==
|
||||
@@ -10176,6 +10204,14 @@ react-app-polyfill@^1.0.6:
|
||||
regenerator-runtime "^0.13.3"
|
||||
whatwg-fetch "^3.0.0"
|
||||
|
||||
react-chartjs-2@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-2.9.0.tgz#d054dbdd763fbe9a76296a4ae0752ea549b76d9e"
|
||||
integrity sha512-IYwqUUnQRAJ9SNA978vxulHJTcUFTJk2LDVfbAyk0TnJFZZG7+6U/2flsE4MCw6WCbBjTTypy8T82Ch7XrPtRw==
|
||||
dependencies:
|
||||
lodash "^4.17.4"
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-codemirror2@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-7.1.0.tgz#b874a275ad4f6f2ee5adb23b550c0f4b8b82776d"
|
||||
@@ -12620,9 +12656,9 @@ websocket-driver@>=0.5.1:
|
||||
websocket-extensions ">=0.1.1"
|
||||
|
||||
websocket-extensions@>=0.1.1:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
|
||||
integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
|
||||
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
|
||||
|
||||
websocket@^1.0.31:
|
||||
version "1.0.31"
|
||||
|
||||
378
restapi/admin_heal.go
Normal file
378
restapi/admin_heal.go
Normal file
@@ -0,0 +1,378 @@
|
||||
// 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"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
// An alias of string to represent the health color code of an object
|
||||
type col string
|
||||
|
||||
const (
|
||||
colGrey col = "Grey"
|
||||
colRed col = "Red"
|
||||
colYellow col = "Yellow"
|
||||
colGreen col = "Green"
|
||||
)
|
||||
|
||||
var (
|
||||
hColOrder = []col{colRed, colYellow, colGreen}
|
||||
hColTable = map[int][]int{
|
||||
1: {0, -1, 1},
|
||||
2: {0, 1, 2},
|
||||
3: {1, 2, 3},
|
||||
4: {1, 2, 4},
|
||||
5: {1, 3, 5},
|
||||
6: {2, 4, 6},
|
||||
7: {2, 4, 7},
|
||||
8: {2, 5, 8},
|
||||
}
|
||||
)
|
||||
|
||||
type healItemStatus struct {
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Before struct {
|
||||
Color string `json:"color"`
|
||||
Offline int `json:"offline"`
|
||||
Online int `json:"online"`
|
||||
Missing int `json:"missing"`
|
||||
Corrupted int `json:"corrupted"`
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
} `json:"before"`
|
||||
After struct {
|
||||
Color string `json:"color"`
|
||||
Offline int `json:"offline"`
|
||||
Online int `json:"online"`
|
||||
Missing int `json:"missing"`
|
||||
Corrupted int `json:"corrupted"`
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
} `json:"after"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
type healStatus struct {
|
||||
// Total time since heal start in seconds
|
||||
HealDuration float64 `json:"healDuration"`
|
||||
|
||||
// Accumulated statistics of heal result records
|
||||
BytesScanned int64 `json:"bytesScanned"`
|
||||
|
||||
// Counter for objects, and another counter for all kinds of
|
||||
// items
|
||||
ObjectsScanned int64 `json:"objectsScanned"`
|
||||
ItemsScanned int64 `json:"itemsScanned"`
|
||||
|
||||
// Counters for healed objects and all kinds of healed items
|
||||
ObjectsHealed int64 `json:"objectsHealed"`
|
||||
ItemsHealed int64 `json:"itemsHealed"`
|
||||
|
||||
ItemsHealthStatus []healItemStatus `json:"itemsHealthStatus"`
|
||||
// Map of health color code to number of objects with that
|
||||
// health color code.
|
||||
HealthBeforeCols map[col]int64 `json:"healthBeforeCols"`
|
||||
HealthAfterCols map[col]int64 `json:"healthAfterCols"`
|
||||
}
|
||||
|
||||
type healOptions struct {
|
||||
BucketName string
|
||||
Prefix string
|
||||
ForceStart bool
|
||||
ForceStop bool
|
||||
madmin.HealOpts
|
||||
}
|
||||
|
||||
// startHeal starts healing of the servers based on heal options
|
||||
func startHeal(ctx context.Context, conn WSConn, client MinioAdmin, hOpts *healOptions) error {
|
||||
// Initialize heal
|
||||
healStart, _, err := client.heal(ctx, hOpts.BucketName, hOpts.Prefix, hOpts.HealOpts, "", hOpts.ForceStart, hOpts.ForceStop)
|
||||
if err != nil {
|
||||
log.Println("error initializing healing:", err)
|
||||
return err
|
||||
}
|
||||
if hOpts.ForceStop {
|
||||
log.Println("heal stopped successfully")
|
||||
return nil
|
||||
}
|
||||
clientToken := healStart.ClientToken
|
||||
hs := healStatus{
|
||||
HealthBeforeCols: make(map[col]int64),
|
||||
HealthAfterCols: make(map[col]int64),
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
_, res, err := client.heal(ctx, hOpts.BucketName, hOpts.Prefix, hOpts.HealOpts, clientToken, hOpts.ForceStart, hOpts.ForceStop)
|
||||
if err != nil {
|
||||
log.Println("error on heal:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
hs.writeStatus(&res, conn)
|
||||
|
||||
if res.Summary == "finished" {
|
||||
log.Println("heal finished")
|
||||
return nil
|
||||
}
|
||||
|
||||
if res.Summary == "stopped" {
|
||||
log.Println("heal stopped")
|
||||
return fmt.Errorf("heal had an error - %s", res.FailureDetail)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *healStatus) writeStatus(s *madmin.HealTaskStatus, conn WSConn) error {
|
||||
// Update state
|
||||
h.updateDuration(s)
|
||||
for _, item := range s.Items {
|
||||
err := h.updateStats(item)
|
||||
if err != nil {
|
||||
fmt.Println("error on updateStats:", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize message to be sent
|
||||
infoBytes, err := json.Marshal(h)
|
||||
if err != nil {
|
||||
fmt.Println("error on json.Marshal:", err)
|
||||
return err
|
||||
}
|
||||
// Send Message through websocket connection
|
||||
err = conn.writeMessage(websocket.TextMessage, infoBytes)
|
||||
if err != nil {
|
||||
log.Println("error writeMessage:", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *healStatus) updateDuration(s *madmin.HealTaskStatus) {
|
||||
h.HealDuration = time.Now().UTC().Sub(s.StartTime).Round(time.Second).Seconds()
|
||||
}
|
||||
|
||||
func (h *healStatus) updateStats(i madmin.HealResultItem) error {
|
||||
// update general status
|
||||
if i.Type == madmin.HealItemObject {
|
||||
// Objects whose size could not be found have -1 size
|
||||
// returned.
|
||||
if i.ObjectSize >= 0 {
|
||||
h.BytesScanned += i.ObjectSize
|
||||
}
|
||||
h.ObjectsScanned++
|
||||
}
|
||||
h.ItemsScanned++
|
||||
|
||||
beforeUp, afterUp := i.GetOnlineCounts()
|
||||
if afterUp > beforeUp {
|
||||
if i.Type == madmin.HealItemObject {
|
||||
h.ObjectsHealed++
|
||||
}
|
||||
h.ItemsHealed++
|
||||
}
|
||||
// update per item status
|
||||
itemStatus := healItemStatus{}
|
||||
// get color health status
|
||||
var beforeColor, afterColor col
|
||||
var err error
|
||||
switch i.Type {
|
||||
case madmin.HealItemMetadata, madmin.HealItemBucket:
|
||||
beforeColor, afterColor, err = getReplicatedFileHCCChange(i)
|
||||
default:
|
||||
if i.Type == madmin.HealItemObject {
|
||||
itemStatus.Size = i.ObjectSize
|
||||
}
|
||||
beforeColor, afterColor, err = getObjectHCCChange(i)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itemStatus.Status = "success"
|
||||
itemStatus.Before.Color = strings.ToLower(string(beforeColor))
|
||||
itemStatus.After.Color = strings.ToLower(string(afterColor))
|
||||
itemStatus.Type, itemStatus.Name = getHRITypeAndName(i)
|
||||
itemStatus.Before.Online, itemStatus.After.Online = beforeUp, afterUp
|
||||
itemStatus.Before.Missing, itemStatus.After.Missing = i.GetMissingCounts()
|
||||
itemStatus.Before.Corrupted, itemStatus.After.Corrupted = i.GetCorruptedCounts()
|
||||
itemStatus.Before.Offline, itemStatus.After.Offline = i.GetOfflineCounts()
|
||||
itemStatus.Before.Drives = i.Before.Drives
|
||||
itemStatus.After.Drives = i.After.Drives
|
||||
h.ItemsHealthStatus = append(h.ItemsHealthStatus, itemStatus)
|
||||
h.HealthBeforeCols[beforeColor]++
|
||||
h.HealthAfterCols[afterColor]++
|
||||
return nil
|
||||
}
|
||||
|
||||
// getObjectHCCChange - returns before and after color change for
|
||||
// objects
|
||||
func getObjectHCCChange(h madmin.HealResultItem) (b, a col, err error) {
|
||||
parityShards := h.ParityBlocks
|
||||
dataShards := h.DataBlocks
|
||||
|
||||
onlineBefore, onlineAfter := h.GetOnlineCounts()
|
||||
surplusShardsBeforeHeal := onlineBefore - dataShards
|
||||
surplusShardsAfterHeal := onlineAfter - dataShards
|
||||
b, err = getHColCode(surplusShardsBeforeHeal, parityShards)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a, err = getHColCode(surplusShardsAfterHeal, parityShards)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// getReplicatedFileHCCChange - fetches health color code for metadata
|
||||
// files that are replicated.
|
||||
func getReplicatedFileHCCChange(h madmin.HealResultItem) (b, a col, err error) {
|
||||
getColCode := func(numAvail int) (c col, err error) {
|
||||
// calculate color code for replicated object similar
|
||||
// to erasure coded objects
|
||||
quorum := h.DiskCount/h.SetCount/2 + 1
|
||||
surplus := numAvail/h.SetCount - quorum
|
||||
parity := h.DiskCount/h.SetCount - quorum
|
||||
c, err = getHColCode(surplus, parity)
|
||||
return
|
||||
}
|
||||
|
||||
onlineBefore, onlineAfter := h.GetOnlineCounts()
|
||||
b, err = getColCode(onlineBefore)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a, err = getColCode(onlineAfter)
|
||||
return
|
||||
}
|
||||
|
||||
func getHColCode(surplusShards, parityShards int) (c col, err error) {
|
||||
if parityShards < 1 || parityShards > 8 || surplusShards > parityShards {
|
||||
return c, fmt.Errorf("invalid parity shard count/surplus shard count given")
|
||||
}
|
||||
if surplusShards < 0 {
|
||||
return colGrey, err
|
||||
}
|
||||
colRow := hColTable[parityShards]
|
||||
for index, val := range colRow {
|
||||
if val != -1 && surplusShards <= val {
|
||||
return hColOrder[index], err
|
||||
}
|
||||
}
|
||||
return c, fmt.Errorf("cannot get a heal color code")
|
||||
}
|
||||
|
||||
func getHRITypeAndName(i madmin.HealResultItem) (typ, name string) {
|
||||
name = fmt.Sprintf("%s/%s", i.Bucket, i.Object)
|
||||
switch i.Type {
|
||||
case madmin.HealItemMetadata:
|
||||
typ = "system"
|
||||
name = i.Detail
|
||||
case madmin.HealItemBucketMetadata:
|
||||
typ = "system"
|
||||
name = "bucket-metadata:" + name
|
||||
case madmin.HealItemBucket:
|
||||
typ = "bucket"
|
||||
case madmin.HealItemObject:
|
||||
typ = "object"
|
||||
default:
|
||||
typ = fmt.Sprintf("!! Unknown heal result record %#v !!", i)
|
||||
name = typ
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getHealOptionsFromReq return options from request for healing process
|
||||
// path come as : `/heal/bucket1` and query params come on request form
|
||||
func getHealOptionsFromReq(req *http.Request) (*healOptions, error) {
|
||||
hOptions := healOptions{}
|
||||
re := regexp.MustCompile(`(/heal/)(.*?)(\?.*?$|$)`)
|
||||
matches := re.FindAllSubmatch([]byte(req.URL.Path), -1)
|
||||
// len matches is always 3
|
||||
// matches comes as e.g.
|
||||
// [["...", "/heal/" "bucket1"]]
|
||||
// [["/heal/" "/heal/" ""]]
|
||||
|
||||
// bucket name is on the second group, third position
|
||||
hOptions.BucketName = strings.TrimSpace(string(matches[0][2]))
|
||||
hOptions.Prefix = req.FormValue("prefix")
|
||||
hOptions.HealOpts.ScanMode = transformScanStr(req.FormValue("scan"))
|
||||
|
||||
if req.FormValue("force-start") != "" {
|
||||
boolVal, err := strconv.ParseBool(req.FormValue("force-start"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hOptions.ForceStart = boolVal
|
||||
}
|
||||
if req.FormValue("force-stop") != "" {
|
||||
boolVal, err := strconv.ParseBool(req.FormValue("force-stop"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hOptions.ForceStop = boolVal
|
||||
}
|
||||
// heal recursively
|
||||
if req.FormValue("recursive") != "" {
|
||||
boolVal, err := strconv.ParseBool(req.FormValue("recursive"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hOptions.HealOpts.Recursive = boolVal
|
||||
}
|
||||
// remove dangling objects in heal sequence
|
||||
if req.FormValue("remove") != "" {
|
||||
boolVal, err := strconv.ParseBool(req.FormValue("remove"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hOptions.HealOpts.Remove = boolVal
|
||||
}
|
||||
// only inspect data
|
||||
if req.FormValue("dry-run") != "" {
|
||||
boolVal, err := strconv.ParseBool(req.FormValue("dry-run"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hOptions.HealOpts.DryRun = boolVal
|
||||
}
|
||||
return &hOptions, nil
|
||||
}
|
||||
|
||||
func transformScanStr(scanStr string) madmin.HealScanMode {
|
||||
switch scanStr {
|
||||
case "deep":
|
||||
return madmin.HealDeepScan
|
||||
}
|
||||
return madmin.HealNormalScan
|
||||
}
|
||||
278
restapi/admin_heal_test.go
Normal file
278
restapi/admin_heal_test.go
Normal file
@@ -0,0 +1,278 @@
|
||||
// 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"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// assigning mock at runtime instead of compile time
|
||||
var minioHealMock func(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
|
||||
forceStart, forceStop bool) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error)
|
||||
|
||||
func (ac adminClientMock) heal(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
|
||||
forceStart, forceStop bool) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error) {
|
||||
return minioHealMock(ctx, bucket, prefix, healOpts, clientToken, forceStart, forceStop)
|
||||
}
|
||||
|
||||
func TestHeal(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := adminClientMock{}
|
||||
mockWSConn := mockConn{}
|
||||
ctx := context.Background()
|
||||
|
||||
function := "startHeal()"
|
||||
mockResultItem1 := madmin.HealResultItem{
|
||||
Type: madmin.HealItemObject,
|
||||
SetCount: 1,
|
||||
DiskCount: 4,
|
||||
ParityBlocks: 2,
|
||||
DataBlocks: 2,
|
||||
Before: struct {
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
}{
|
||||
Drives: []madmin.HealDriveInfo{
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateMissing,
|
||||
},
|
||||
},
|
||||
},
|
||||
After: struct {
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
}{
|
||||
Drives: []madmin.HealDriveInfo{
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
mockResultItem2 := madmin.HealResultItem{
|
||||
Type: madmin.HealItemBucket,
|
||||
SetCount: 1,
|
||||
DiskCount: 4,
|
||||
ParityBlocks: 2,
|
||||
DataBlocks: 2,
|
||||
Before: struct {
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
}{
|
||||
Drives: []madmin.HealDriveInfo{
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateMissing,
|
||||
},
|
||||
},
|
||||
},
|
||||
After: struct {
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
}{
|
||||
Drives: []madmin.HealDriveInfo{
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
mockHealTaskStatus := madmin.HealTaskStatus{
|
||||
StartTime: time.Now().UTC().Truncate(time.Second * 2), // mock 2 sec duration
|
||||
Items: []madmin.HealResultItem{
|
||||
mockResultItem1,
|
||||
mockResultItem2,
|
||||
},
|
||||
Summary: "finished",
|
||||
}
|
||||
|
||||
testStreamSize := 1
|
||||
testReceiver := make(chan healStatus, testStreamSize)
|
||||
isClosed := false // testReceiver is closed?
|
||||
|
||||
testOptions := &healOptions{
|
||||
BucketName: "testbucket",
|
||||
Prefix: "",
|
||||
ForceStart: false,
|
||||
ForceStop: false,
|
||||
}
|
||||
// Test-1: startHeal send simple stream of data, no errors
|
||||
minioHealMock = func(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
|
||||
forceStart, forceStop bool) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error) {
|
||||
|
||||
return healStart, mockHealTaskStatus, nil
|
||||
}
|
||||
writesCount := 1
|
||||
// mock connection WriteMessage() no error
|
||||
connWriteMessageMock = func(messageType int, data []byte) error {
|
||||
// emulate that receiver gets the message written
|
||||
var t healStatus
|
||||
_ = json.Unmarshal(data, &t)
|
||||
testReceiver <- t
|
||||
if writesCount == testStreamSize {
|
||||
// for testing we need to close the receiver channel
|
||||
if !isClosed {
|
||||
close(testReceiver)
|
||||
isClosed = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
writesCount++
|
||||
return nil
|
||||
}
|
||||
if err := startHeal(ctx, mockWSConn, client, testOptions); err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
|
||||
}
|
||||
// check that the TestReceiver got the same number of data from Console.
|
||||
for i := range testReceiver {
|
||||
assert.Equal(int64(1), i.ObjectsScanned)
|
||||
assert.Equal(int64(1), i.ObjectsHealed)
|
||||
assert.Equal(int64(2), i.ItemsScanned)
|
||||
assert.Equal(int64(2), i.ItemsHealed)
|
||||
assert.Equal(int64(0), i.HealthBeforeCols[colGreen])
|
||||
assert.Equal(int64(1), i.HealthBeforeCols[colYellow])
|
||||
assert.Equal(int64(1), i.HealthBeforeCols[colRed])
|
||||
assert.Equal(int64(0), i.HealthBeforeCols[colGrey])
|
||||
assert.Equal(int64(2), i.HealthAfterCols[colGreen])
|
||||
assert.Equal(int64(0), i.HealthAfterCols[colYellow])
|
||||
assert.Equal(int64(0), i.HealthAfterCols[colRed])
|
||||
assert.Equal(int64(0), i.HealthAfterCols[colGrey])
|
||||
}
|
||||
|
||||
// Test-2: startHeal error on init
|
||||
minioHealMock = func(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
|
||||
forceStart, forceStop bool) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error) {
|
||||
return healStart, mockHealTaskStatus, errors.New("error")
|
||||
}
|
||||
|
||||
if err := startHeal(ctx, mockWSConn, client, testOptions); assert.Error(err) {
|
||||
assert.Equal("error", err.Error())
|
||||
}
|
||||
|
||||
// Test-3: getHealOptionsFromReq return heal options from request
|
||||
u, _ := url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=true&force-start=true&force-stop=true&remove=true&dry-run=true&scan=deep")
|
||||
req := &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
opts, err := getHealOptionsFromReq(req)
|
||||
if err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", "getHealOptionsFromReq", err.Error())
|
||||
}
|
||||
expectedOptions := healOptions{
|
||||
BucketName: "bucket1",
|
||||
ForceStart: true,
|
||||
ForceStop: true,
|
||||
Prefix: "file/",
|
||||
HealOpts: madmin.HealOpts{
|
||||
Recursive: true,
|
||||
DryRun: true,
|
||||
ScanMode: madmin.HealDeepScan,
|
||||
},
|
||||
}
|
||||
assert.Equal(expectedOptions.BucketName, opts.BucketName)
|
||||
assert.Equal(expectedOptions.Prefix, opts.Prefix)
|
||||
assert.Equal(expectedOptions.Recursive, opts.Recursive)
|
||||
assert.Equal(expectedOptions.ForceStart, opts.ForceStart)
|
||||
assert.Equal(expectedOptions.DryRun, opts.DryRun)
|
||||
assert.Equal(expectedOptions.ScanMode, opts.ScanMode)
|
||||
// Test-3: getHealOptionsFromReq return error if boolean value not valid
|
||||
u, _ = url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=nonbool&force-start=true&force-stop=true&remove=true&dry-run=true&scan=deep")
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
opts, err = getHealOptionsFromReq(req)
|
||||
if assert.Error(err) {
|
||||
assert.Equal("strconv.ParseBool: parsing \"nonbool\": invalid syntax", err.Error())
|
||||
}
|
||||
// Test-4: getHealOptionsFromReq return error if boolean value not valid
|
||||
u, _ = url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=true&force-start=true&force-stop=true&remove=nonbool&dry-run=true&scan=deep")
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
opts, err = getHealOptionsFromReq(req)
|
||||
if assert.Error(err) {
|
||||
assert.Equal("strconv.ParseBool: parsing \"nonbool\": invalid syntax", err.Error())
|
||||
}
|
||||
// Test-5: getHealOptionsFromReq return error if boolean value not valid
|
||||
u, _ = url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=true&force-start=nonbool&force-stop=true&remove=true&dry-run=true&scan=deep")
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
opts, err = getHealOptionsFromReq(req)
|
||||
if assert.Error(err) {
|
||||
assert.Equal("strconv.ParseBool: parsing \"nonbool\": invalid syntax", err.Error())
|
||||
}
|
||||
// Test-6: getHealOptionsFromReq return error if boolean value not valid
|
||||
u, _ = url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=true&force-start=true&force-stop=nonbool&remove=true&dry-run=true&scan=deep")
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
opts, err = getHealOptionsFromReq(req)
|
||||
if assert.Error(err) {
|
||||
assert.Equal("strconv.ParseBool: parsing \"nonbool\": invalid syntax", err.Error())
|
||||
}
|
||||
// Test-7: getHealOptionsFromReq return error if boolean value not valid
|
||||
u, _ = url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=true&force-start=true&force-stop=true&remove=true&dry-run=nonbool&scan=deep")
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
opts, err = getHealOptionsFromReq(req)
|
||||
if assert.Error(err) {
|
||||
assert.Equal("strconv.ParseBool: parsing \"nonbool\": invalid syntax", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -74,10 +74,10 @@ func registerUsersHandlers(api *operations.McsAPI) {
|
||||
sessionID := string(*principal)
|
||||
userInfoResponse, err := getUserInfoResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return admin_api.NewGetUserDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
return admin_api.NewGetUserInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
|
||||
return admin_api.NewGetUserOK().WithPayload(userInfoResponse)
|
||||
return admin_api.NewGetUserInfoOK().WithPayload(userInfoResponse)
|
||||
})
|
||||
// Update User
|
||||
api.AdminAPIUpdateUserInfoHandler = admin_api.UpdateUserInfoHandlerFunc(func(params admin_api.UpdateUserInfoParams, principal *models.Principal) middleware.Responder {
|
||||
|
||||
@@ -85,6 +85,8 @@ type MinioAdmin interface {
|
||||
serviceTrace(ctx context.Context, allTrace, errTrace bool) <-chan madmin.ServiceTraceInfo
|
||||
getLogs(ctx context.Context, node string, lineCnt int, logKind string) <-chan madmin.LogInfo
|
||||
accountUsageInfo(ctx context.Context) (madmin.AccountUsageInfo, error)
|
||||
heal(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
|
||||
forceStart, forceStop bool) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error)
|
||||
// Service Accounts
|
||||
addServiceAccount(ctx context.Context, policy *iampolicy.Policy) (mauth.Credentials, error)
|
||||
listServiceAccounts(ctx context.Context) (madmin.ListServiceAccountsResp, error)
|
||||
@@ -234,6 +236,11 @@ func (ac adminClient) accountUsageInfo(ctx context.Context) (madmin.AccountUsage
|
||||
return ac.client.AccountUsageInfo(ctx)
|
||||
}
|
||||
|
||||
func (ac adminClient) heal(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
|
||||
forceStart, forceStop bool) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error) {
|
||||
return ac.client.Heal(ctx, bucket, prefix, healOpts, clientToken, forceStart, forceStop)
|
||||
}
|
||||
|
||||
func newMAdminClient(jwt string) (*madmin.AdminClient, error) {
|
||||
claims, err := auth.JWTAuthenticate(jwt)
|
||||
if err != nil {
|
||||
@@ -249,7 +256,9 @@ func newMAdminClient(jwt string) (*madmin.AdminClient, error) {
|
||||
// newAdminFromClaims creates a minio admin from Decrypted claims using Assume role credentials
|
||||
func newAdminFromClaims(claims *auth.DecryptedClaims) (*madmin.AdminClient, error) {
|
||||
tlsEnabled := getMinIOEndpointIsSecure()
|
||||
adminClient, err := madmin.NewWithOptions(getMinIOEndpoint(), &madmin.Options{
|
||||
endpoint := getMinIOEndpoint()
|
||||
|
||||
adminClient, err := madmin.NewWithOptions(endpoint, &madmin.Options{
|
||||
Creds: credentials.NewStaticV4(claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken),
|
||||
Secure: tlsEnabled,
|
||||
})
|
||||
|
||||
@@ -28,10 +28,10 @@ import (
|
||||
var Port = "9090"
|
||||
|
||||
// Hostname mcs hostname
|
||||
var Hostname = "localhost"
|
||||
var Hostname = "0.0.0.0"
|
||||
|
||||
// TLSHostname mcs tls hostname
|
||||
var TLSHostname = "localhost"
|
||||
var TLSHostname = "0.0.0.0"
|
||||
|
||||
// TLSPort mcs tls port
|
||||
var TLSPort = "9443"
|
||||
@@ -228,3 +228,8 @@ func getSecureFeaturePolicy() string {
|
||||
func getSecureExpectCTHeader() string {
|
||||
return env.Get(McsSecureExpectCTHeader, "")
|
||||
}
|
||||
|
||||
// getM3Host returns the hostname of mkube
|
||||
func getM3Host() string {
|
||||
return env.Get(McsM3Host, "http://m3:8787")
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/minio/mcs/models"
|
||||
"github.com/minio/mcs/pkg"
|
||||
"github.com/minio/mcs/pkg/auth"
|
||||
|
||||
assetFS "github.com/elazarl/go-bindata-assetfs"
|
||||
@@ -161,9 +162,13 @@ func setupGlobalMiddleware(handler http.Handler) http.Handler {
|
||||
// FileServerMiddleware serves files from the static folder
|
||||
func FileServerMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Server", "mcs/"+pkg.Version) // add HTTP Server header
|
||||
switch {
|
||||
case strings.HasPrefix(r.URL.Path, "/ws"):
|
||||
serveWS(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/v1/mkube"):
|
||||
client := &http.Client{}
|
||||
serverMkube(client, w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api"):
|
||||
next.ServeHTTP(w, r)
|
||||
default:
|
||||
|
||||
@@ -49,4 +49,5 @@ const (
|
||||
McsSecureReferrerPolicy = "MCS_SECURE_REFERRER_POLICY"
|
||||
McsSecureFeaturePolicy = "MCS_SECURE_FEATURE_POLICY"
|
||||
McsSecureExpectCTHeader = "MCS_SECURE_EXPECT_CT_HEADER"
|
||||
McsM3Host = "MCS_M3_HOSTNAME"
|
||||
)
|
||||
|
||||
68
restapi/mkube.go
Normal file
68
restapi/mkube.go
Normal file
@@ -0,0 +1,68 @@
|
||||
// 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 (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
apiErrors "github.com/go-openapi/errors"
|
||||
)
|
||||
|
||||
// serverMkube handles calls for mkube
|
||||
func serverMkube(client *http.Client, w http.ResponseWriter, req *http.Request) {
|
||||
// destination of the request, the mkube server
|
||||
req.URL.Path = strings.Replace(req.URL.Path, "/mkube", "", 1)
|
||||
targetURL := fmt.Sprintf("%s%s", getM3Host(), req.URL.String())
|
||||
|
||||
// set the HTTP method, url, and m3Req body
|
||||
m3Req, err := http.NewRequest(req.Method, targetURL, req.Body)
|
||||
if err != nil {
|
||||
apiErrors.ServeError(w, req, err)
|
||||
log.Println("error creating m3 request:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// set the m3Req headers
|
||||
m3Req.Header = req.Header
|
||||
resp, err := client.Do(m3Req)
|
||||
if err != nil {
|
||||
log.Println("error on m3 request:", err)
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
apiErrors.ServeError(w, req, errors.New("service M3 is not available"))
|
||||
return
|
||||
}
|
||||
apiErrors.ServeError(w, req, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
w.Header().Add("Content-Type", resp.Header.Get("Content-Type"))
|
||||
// Write the m3 response to the response writer
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
for scanner.Scan() {
|
||||
w.Write(scanner.Bytes())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Println("error scanning m3 response:", err)
|
||||
apiErrors.ServeError(w, req, err)
|
||||
}
|
||||
|
||||
}
|
||||
119
restapi/mkube_test.go
Normal file
119
restapi/mkube_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// RoundTripFunc .
|
||||
type RoundTripFunc func(req *http.Request) (*http.Response, error)
|
||||
|
||||
// RoundTrip .
|
||||
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return f(req)
|
||||
}
|
||||
|
||||
//NewTestClient returns *http.Client with Transport replaced to avoid making real calls
|
||||
func NewTestClient(fn RoundTripFunc) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: fn,
|
||||
}
|
||||
}
|
||||
|
||||
func Test_serverMkube(t *testing.T) {
|
||||
|
||||
OKclient := NewTestClient(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`OK`)),
|
||||
Header: make(http.Header),
|
||||
}, nil
|
||||
})
|
||||
|
||||
badClient := NewTestClient(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: 500,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`NOTOK`)),
|
||||
Header: make(http.Header),
|
||||
}, errors.New("something wrong")
|
||||
})
|
||||
|
||||
refusedClient := NewTestClient(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: 500,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`NOTOK`)),
|
||||
Header: make(http.Header),
|
||||
}, errors.New("connection refused")
|
||||
})
|
||||
|
||||
testURL, _ := url.Parse("/api/v1/clusters")
|
||||
type args struct {
|
||||
client *http.Client
|
||||
recorder *httptest.ResponseRecorder
|
||||
req *http.Request
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantCode int
|
||||
}{
|
||||
{
|
||||
name: "Successful request",
|
||||
args: args{
|
||||
client: OKclient,
|
||||
recorder: httptest.NewRecorder(),
|
||||
req: &http.Request{URL: testURL},
|
||||
},
|
||||
wantCode: 200,
|
||||
},
|
||||
{
|
||||
name: "Unsuccessful request",
|
||||
args: args{
|
||||
client: badClient,
|
||||
recorder: httptest.NewRecorder(),
|
||||
req: &http.Request{URL: testURL},
|
||||
},
|
||||
wantCode: 500,
|
||||
},
|
||||
{
|
||||
name: "refused request",
|
||||
args: args{
|
||||
client: refusedClient,
|
||||
recorder: httptest.NewRecorder(),
|
||||
req: &http.Request{URL: testURL},
|
||||
},
|
||||
wantCode: 500,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
serverMkube(tt.args.client, tt.args.recorder, tt.args.req)
|
||||
resp := tt.args.recorder.Result()
|
||||
if resp.StatusCode != tt.wantCode {
|
||||
t.Errorf("Invalid code returned")
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
// 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/mcs/models"
|
||||
)
|
||||
|
||||
// GetUserHandlerFunc turns a function with the right signature into a get user handler
|
||||
type GetUserHandlerFunc func(GetUserParams, *models.Principal) middleware.Responder
|
||||
|
||||
// Handle executing the request and returning a response
|
||||
func (fn GetUserHandlerFunc) Handle(params GetUserParams, principal *models.Principal) middleware.Responder {
|
||||
return fn(params, principal)
|
||||
}
|
||||
|
||||
// GetUserHandler interface for that can handle valid get user params
|
||||
type GetUserHandler interface {
|
||||
Handle(GetUserParams, *models.Principal) middleware.Responder
|
||||
}
|
||||
|
||||
// NewGetUser creates a new http.Handler for the get user operation
|
||||
func NewGetUser(ctx *middleware.Context, handler GetUserHandler) *GetUser {
|
||||
return &GetUser{Context: ctx, Handler: handler}
|
||||
}
|
||||
|
||||
/*GetUser swagger:route GET /users/{name} AdminAPI getUser
|
||||
|
||||
Get User
|
||||
|
||||
*/
|
||||
type GetUser struct {
|
||||
Context *middleware.Context
|
||||
Handler GetUserHandler
|
||||
}
|
||||
|
||||
func (o *GetUser) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
route, rCtx, _ := o.Context.RouteInfo(r)
|
||||
if rCtx != nil {
|
||||
r = rCtx
|
||||
}
|
||||
var Params = NewGetUserParams()
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// NewGetUserParams creates a new GetUserParams object
|
||||
// no default values defined in spec.
|
||||
func NewGetUserParams() GetUserParams {
|
||||
|
||||
return GetUserParams{}
|
||||
}
|
||||
|
||||
// GetUserParams contains all the bound params for the get user operation
|
||||
// typically these are obtained from a http.Request
|
||||
//
|
||||
// swagger:parameters GetUser
|
||||
type GetUserParams struct {
|
||||
|
||||
// HTTP Request Object
|
||||
HTTPRequest *http.Request `json:"-"`
|
||||
|
||||
/*
|
||||
Required: true
|
||||
In: path
|
||||
*/
|
||||
Name string
|
||||
}
|
||||
|
||||
// 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 NewGetUserParams() beforehand.
|
||||
func (o *GetUserParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
|
||||
var res []error
|
||||
|
||||
o.HTTPRequest = r
|
||||
|
||||
rName, rhkName, _ := route.Params.GetOK("name")
|
||||
if err := o.bindName(rName, rhkName, route.Formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindName binds and validates parameter Name from path.
|
||||
func (o *GetUserParams) bindName(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
|
||||
|
||||
o.Name = raw
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
// 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/mcs/models"
|
||||
)
|
||||
|
||||
// GetUserOKCode is the HTTP code returned for type GetUserOK
|
||||
const GetUserOKCode int = 200
|
||||
|
||||
/*GetUserOK A successful response.
|
||||
|
||||
swagger:response getUserOK
|
||||
*/
|
||||
type GetUserOK struct {
|
||||
|
||||
/*
|
||||
In: Body
|
||||
*/
|
||||
Payload *models.User `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
// NewGetUserOK creates GetUserOK with default headers values
|
||||
func NewGetUserOK() *GetUserOK {
|
||||
|
||||
return &GetUserOK{}
|
||||
}
|
||||
|
||||
// WithPayload adds the payload to the get user o k response
|
||||
func (o *GetUserOK) WithPayload(payload *models.User) *GetUserOK {
|
||||
o.Payload = payload
|
||||
return o
|
||||
}
|
||||
|
||||
// SetPayload sets the payload to the get user o k response
|
||||
func (o *GetUserOK) SetPayload(payload *models.User) {
|
||||
o.Payload = payload
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *GetUserOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
|
||||
|
||||
rw.WriteHeader(200)
|
||||
if o.Payload != nil {
|
||||
payload := o.Payload
|
||||
if err := producer.Produce(rw, payload); err != nil {
|
||||
panic(err) // let the recovery middleware deal with this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*GetUserDefault Generic error response.
|
||||
|
||||
swagger:response getUserDefault
|
||||
*/
|
||||
type GetUserDefault struct {
|
||||
_statusCode int
|
||||
|
||||
/*
|
||||
In: Body
|
||||
*/
|
||||
Payload *models.Error `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
// NewGetUserDefault creates GetUserDefault with default headers values
|
||||
func NewGetUserDefault(code int) *GetUserDefault {
|
||||
if code <= 0 {
|
||||
code = 500
|
||||
}
|
||||
|
||||
return &GetUserDefault{
|
||||
_statusCode: code,
|
||||
}
|
||||
}
|
||||
|
||||
// WithStatusCode adds the status to the get user default response
|
||||
func (o *GetUserDefault) WithStatusCode(code int) *GetUserDefault {
|
||||
o._statusCode = code
|
||||
return o
|
||||
}
|
||||
|
||||
// SetStatusCode sets the status to the get user default response
|
||||
func (o *GetUserDefault) SetStatusCode(code int) {
|
||||
o._statusCode = code
|
||||
}
|
||||
|
||||
// WithPayload adds the payload to the get user default response
|
||||
func (o *GetUserDefault) WithPayload(payload *models.Error) *GetUserDefault {
|
||||
o.Payload = payload
|
||||
return o
|
||||
}
|
||||
|
||||
// SetPayload sets the payload to the get user default response
|
||||
func (o *GetUserDefault) SetPayload(payload *models.Error) {
|
||||
o.Payload = payload
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *GetUserDefault) 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// GetUserURL generates an URL for the get user operation
|
||||
type GetUserURL struct {
|
||||
Name string
|
||||
|
||||
_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 *GetUserURL) WithBasePath(bp string) *GetUserURL {
|
||||
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 *GetUserURL) SetBasePath(bp string) {
|
||||
o._basePath = bp
|
||||
}
|
||||
|
||||
// Build a url path and query string
|
||||
func (o *GetUserURL) Build() (*url.URL, error) {
|
||||
var _result url.URL
|
||||
|
||||
var _path = "/users/{name}"
|
||||
|
||||
name := o.Name
|
||||
if name != "" {
|
||||
_path = strings.Replace(_path, "{name}", name, -1)
|
||||
} else {
|
||||
return nil, errors.New("name is required on GetUserURL")
|
||||
}
|
||||
|
||||
_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 *GetUserURL) 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 *GetUserURL) String() string {
|
||||
return o.Must(o.Build()).String()
|
||||
}
|
||||
|
||||
// BuildFull builds a full url with scheme, host, path and query string
|
||||
func (o *GetUserURL) BuildFull(scheme, host string) (*url.URL, error) {
|
||||
if scheme == "" {
|
||||
return nil, errors.New("scheme is required for a full url on GetUserURL")
|
||||
}
|
||||
if host == "" {
|
||||
return nil, errors.New("host is required for a full url on GetUserURL")
|
||||
}
|
||||
|
||||
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 *GetUserURL) StringFull(scheme, host string) string {
|
||||
return o.Must(o.BuildFull(scheme, host)).String()
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// 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 user_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"
|
||||
)
|
||||
|
||||
// LoginOauth2CallbackHandlerFunc turns a function with the right signature into a login oauth2 callback handler
|
||||
type LoginOauth2CallbackHandlerFunc func(LoginOauth2CallbackParams) middleware.Responder
|
||||
|
||||
// Handle executing the request and returning a response
|
||||
func (fn LoginOauth2CallbackHandlerFunc) Handle(params LoginOauth2CallbackParams) middleware.Responder {
|
||||
return fn(params)
|
||||
}
|
||||
|
||||
// LoginOauth2CallbackHandler interface for that can handle valid login oauth2 callback params
|
||||
type LoginOauth2CallbackHandler interface {
|
||||
Handle(LoginOauth2CallbackParams) middleware.Responder
|
||||
}
|
||||
|
||||
// NewLoginOauth2Callback creates a new http.Handler for the login oauth2 callback operation
|
||||
func NewLoginOauth2Callback(ctx *middleware.Context, handler LoginOauth2CallbackHandler) *LoginOauth2Callback {
|
||||
return &LoginOauth2Callback{Context: ctx, Handler: handler}
|
||||
}
|
||||
|
||||
/*LoginOauth2Callback swagger:route GET /login/oauth2/callback UserAPI loginOauth2Callback
|
||||
|
||||
Identity Provider oauth2 callback endpoint.
|
||||
|
||||
*/
|
||||
type LoginOauth2Callback struct {
|
||||
Context *middleware.Context
|
||||
Handler LoginOauth2CallbackHandler
|
||||
}
|
||||
|
||||
func (o *LoginOauth2Callback) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
route, rCtx, _ := o.Context.RouteInfo(r)
|
||||
if rCtx != nil {
|
||||
r = rCtx
|
||||
}
|
||||
var Params = NewLoginOauth2CallbackParams()
|
||||
|
||||
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) // actually handle the request
|
||||
|
||||
o.Context.Respond(rw, r, route.Produces, route, res)
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// 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 user_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"
|
||||
)
|
||||
|
||||
// NewLoginOauth2CallbackParams creates a new LoginOauth2CallbackParams object
|
||||
// no default values defined in spec.
|
||||
func NewLoginOauth2CallbackParams() LoginOauth2CallbackParams {
|
||||
|
||||
return LoginOauth2CallbackParams{}
|
||||
}
|
||||
|
||||
// LoginOauth2CallbackParams contains all the bound params for the login oauth2 callback operation
|
||||
// typically these are obtained from a http.Request
|
||||
//
|
||||
// swagger:parameters LoginOauth2Callback
|
||||
type LoginOauth2CallbackParams struct {
|
||||
|
||||
// HTTP Request Object
|
||||
HTTPRequest *http.Request `json:"-"`
|
||||
}
|
||||
|
||||
// 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 NewLoginOauth2CallbackParams() beforehand.
|
||||
func (o *LoginOauth2CallbackParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
|
||||
var res []error
|
||||
|
||||
o.HTTPRequest = r
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// 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 user_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/mcs/models"
|
||||
)
|
||||
|
||||
// LoginOauth2CallbackOKCode is the HTTP code returned for type LoginOauth2CallbackOK
|
||||
const LoginOauth2CallbackOKCode int = 200
|
||||
|
||||
/*LoginOauth2CallbackOK A successful response.
|
||||
|
||||
swagger:response loginOauth2CallbackOK
|
||||
*/
|
||||
type LoginOauth2CallbackOK struct {
|
||||
}
|
||||
|
||||
// NewLoginOauth2CallbackOK creates LoginOauth2CallbackOK with default headers values
|
||||
func NewLoginOauth2CallbackOK() *LoginOauth2CallbackOK {
|
||||
|
||||
return &LoginOauth2CallbackOK{}
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *LoginOauth2CallbackOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
|
||||
|
||||
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
|
||||
|
||||
rw.WriteHeader(200)
|
||||
}
|
||||
|
||||
/*LoginOauth2CallbackDefault Generic error response.
|
||||
|
||||
swagger:response loginOauth2CallbackDefault
|
||||
*/
|
||||
type LoginOauth2CallbackDefault struct {
|
||||
_statusCode int
|
||||
|
||||
/*
|
||||
In: Body
|
||||
*/
|
||||
Payload *models.Error `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
// NewLoginOauth2CallbackDefault creates LoginOauth2CallbackDefault with default headers values
|
||||
func NewLoginOauth2CallbackDefault(code int) *LoginOauth2CallbackDefault {
|
||||
if code <= 0 {
|
||||
code = 500
|
||||
}
|
||||
|
||||
return &LoginOauth2CallbackDefault{
|
||||
_statusCode: code,
|
||||
}
|
||||
}
|
||||
|
||||
// WithStatusCode adds the status to the login oauth2 callback default response
|
||||
func (o *LoginOauth2CallbackDefault) WithStatusCode(code int) *LoginOauth2CallbackDefault {
|
||||
o._statusCode = code
|
||||
return o
|
||||
}
|
||||
|
||||
// SetStatusCode sets the status to the login oauth2 callback default response
|
||||
func (o *LoginOauth2CallbackDefault) SetStatusCode(code int) {
|
||||
o._statusCode = code
|
||||
}
|
||||
|
||||
// WithPayload adds the payload to the login oauth2 callback default response
|
||||
func (o *LoginOauth2CallbackDefault) WithPayload(payload *models.Error) *LoginOauth2CallbackDefault {
|
||||
o.Payload = payload
|
||||
return o
|
||||
}
|
||||
|
||||
// SetPayload sets the payload to the login oauth2 callback default response
|
||||
func (o *LoginOauth2CallbackDefault) SetPayload(payload *models.Error) {
|
||||
o.Payload = payload
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *LoginOauth2CallbackDefault) 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
// 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 user_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"
|
||||
)
|
||||
|
||||
// LoginOauth2CallbackURL generates an URL for the login oauth2 callback operation
|
||||
type LoginOauth2CallbackURL struct {
|
||||
_basePath string
|
||||
}
|
||||
|
||||
// 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 *LoginOauth2CallbackURL) WithBasePath(bp string) *LoginOauth2CallbackURL {
|
||||
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 *LoginOauth2CallbackURL) SetBasePath(bp string) {
|
||||
o._basePath = bp
|
||||
}
|
||||
|
||||
// Build a url path and query string
|
||||
func (o *LoginOauth2CallbackURL) Build() (*url.URL, error) {
|
||||
var _result url.URL
|
||||
|
||||
var _path = "/login/oauth2/callback"
|
||||
|
||||
_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 *LoginOauth2CallbackURL) 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 *LoginOauth2CallbackURL) String() string {
|
||||
return o.Must(o.Build()).String()
|
||||
}
|
||||
|
||||
// BuildFull builds a full url with scheme, host, path and query string
|
||||
func (o *LoginOauth2CallbackURL) BuildFull(scheme, host string) (*url.URL, error) {
|
||||
if scheme == "" {
|
||||
return nil, errors.New("scheme is required for a full url on LoginOauth2CallbackURL")
|
||||
}
|
||||
if host == "" {
|
||||
return nil, errors.New("host is required for a full url on LoginOauth2CallbackURL")
|
||||
}
|
||||
|
||||
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 *LoginOauth2CallbackURL) StringFull(scheme, host string) string {
|
||||
return o.Must(o.BuildFull(scheme, host)).String()
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// 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 user_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"
|
||||
)
|
||||
|
||||
// Oauth2CallbackHandlerFunc turns a function with the right signature into a oauth2 callback handler
|
||||
type Oauth2CallbackHandlerFunc func(Oauth2CallbackParams) middleware.Responder
|
||||
|
||||
// Handle executing the request and returning a response
|
||||
func (fn Oauth2CallbackHandlerFunc) Handle(params Oauth2CallbackParams) middleware.Responder {
|
||||
return fn(params)
|
||||
}
|
||||
|
||||
// Oauth2CallbackHandler interface for that can handle valid oauth2 callback params
|
||||
type Oauth2CallbackHandler interface {
|
||||
Handle(Oauth2CallbackParams) middleware.Responder
|
||||
}
|
||||
|
||||
// NewOauth2Callback creates a new http.Handler for the oauth2 callback operation
|
||||
func NewOauth2Callback(ctx *middleware.Context, handler Oauth2CallbackHandler) *Oauth2Callback {
|
||||
return &Oauth2Callback{Context: ctx, Handler: handler}
|
||||
}
|
||||
|
||||
/*Oauth2Callback swagger:route GET /login/oauth2/callback UserAPI oauth2Callback
|
||||
|
||||
Identity Provider oauth2 callback endpoint.
|
||||
|
||||
*/
|
||||
type Oauth2Callback struct {
|
||||
Context *middleware.Context
|
||||
Handler Oauth2CallbackHandler
|
||||
}
|
||||
|
||||
func (o *Oauth2Callback) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
route, rCtx, _ := o.Context.RouteInfo(r)
|
||||
if rCtx != nil {
|
||||
r = rCtx
|
||||
}
|
||||
var Params = NewOauth2CallbackParams()
|
||||
|
||||
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) // actually handle the request
|
||||
|
||||
o.Context.Respond(rw, r, route.Produces, route, res)
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// 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 user_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"
|
||||
)
|
||||
|
||||
// NewOauth2CallbackParams creates a new Oauth2CallbackParams object
|
||||
// no default values defined in spec.
|
||||
func NewOauth2CallbackParams() Oauth2CallbackParams {
|
||||
|
||||
return Oauth2CallbackParams{}
|
||||
}
|
||||
|
||||
// Oauth2CallbackParams contains all the bound params for the oauth2 callback operation
|
||||
// typically these are obtained from a http.Request
|
||||
//
|
||||
// swagger:parameters Oauth2Callback
|
||||
type Oauth2CallbackParams struct {
|
||||
|
||||
// HTTP Request Object
|
||||
HTTPRequest *http.Request `json:"-"`
|
||||
}
|
||||
|
||||
// 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 NewOauth2CallbackParams() beforehand.
|
||||
func (o *Oauth2CallbackParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
|
||||
var res []error
|
||||
|
||||
o.HTTPRequest = r
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// 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 user_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/mcs/models"
|
||||
)
|
||||
|
||||
// Oauth2CallbackOKCode is the HTTP code returned for type Oauth2CallbackOK
|
||||
const Oauth2CallbackOKCode int = 200
|
||||
|
||||
/*Oauth2CallbackOK A successful response.
|
||||
|
||||
swagger:response oauth2CallbackOK
|
||||
*/
|
||||
type Oauth2CallbackOK struct {
|
||||
}
|
||||
|
||||
// NewOauth2CallbackOK creates Oauth2CallbackOK with default headers values
|
||||
func NewOauth2CallbackOK() *Oauth2CallbackOK {
|
||||
|
||||
return &Oauth2CallbackOK{}
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *Oauth2CallbackOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
|
||||
|
||||
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
|
||||
|
||||
rw.WriteHeader(200)
|
||||
}
|
||||
|
||||
/*Oauth2CallbackDefault Generic error response.
|
||||
|
||||
swagger:response oauth2CallbackDefault
|
||||
*/
|
||||
type Oauth2CallbackDefault struct {
|
||||
_statusCode int
|
||||
|
||||
/*
|
||||
In: Body
|
||||
*/
|
||||
Payload *models.Error `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
// NewOauth2CallbackDefault creates Oauth2CallbackDefault with default headers values
|
||||
func NewOauth2CallbackDefault(code int) *Oauth2CallbackDefault {
|
||||
if code <= 0 {
|
||||
code = 500
|
||||
}
|
||||
|
||||
return &Oauth2CallbackDefault{
|
||||
_statusCode: code,
|
||||
}
|
||||
}
|
||||
|
||||
// WithStatusCode adds the status to the oauth2 callback default response
|
||||
func (o *Oauth2CallbackDefault) WithStatusCode(code int) *Oauth2CallbackDefault {
|
||||
o._statusCode = code
|
||||
return o
|
||||
}
|
||||
|
||||
// SetStatusCode sets the status to the oauth2 callback default response
|
||||
func (o *Oauth2CallbackDefault) SetStatusCode(code int) {
|
||||
o._statusCode = code
|
||||
}
|
||||
|
||||
// WithPayload adds the payload to the oauth2 callback default response
|
||||
func (o *Oauth2CallbackDefault) WithPayload(payload *models.Error) *Oauth2CallbackDefault {
|
||||
o.Payload = payload
|
||||
return o
|
||||
}
|
||||
|
||||
// SetPayload sets the payload to the oauth2 callback default response
|
||||
func (o *Oauth2CallbackDefault) SetPayload(payload *models.Error) {
|
||||
o.Payload = payload
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *Oauth2CallbackDefault) 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
// 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 user_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"
|
||||
)
|
||||
|
||||
// Oauth2CallbackURL generates an URL for the oauth2 callback operation
|
||||
type Oauth2CallbackURL struct {
|
||||
_basePath string
|
||||
}
|
||||
|
||||
// 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 *Oauth2CallbackURL) WithBasePath(bp string) *Oauth2CallbackURL {
|
||||
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 *Oauth2CallbackURL) SetBasePath(bp string) {
|
||||
o._basePath = bp
|
||||
}
|
||||
|
||||
// Build a url path and query string
|
||||
func (o *Oauth2CallbackURL) Build() (*url.URL, error) {
|
||||
var _result url.URL
|
||||
|
||||
var _path = "/login/oauth2/callback"
|
||||
|
||||
_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 *Oauth2CallbackURL) 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 *Oauth2CallbackURL) String() string {
|
||||
return o.Must(o.Build()).String()
|
||||
}
|
||||
|
||||
// BuildFull builds a full url with scheme, host, path and query string
|
||||
func (o *Oauth2CallbackURL) BuildFull(scheme, host string) (*url.URL, error) {
|
||||
if scheme == "" {
|
||||
return nil, errors.New("scheme is required for a full url on Oauth2CallbackURL")
|
||||
}
|
||||
if host == "" {
|
||||
return nil, errors.New("host is required for a full url on Oauth2CallbackURL")
|
||||
}
|
||||
|
||||
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 *Oauth2CallbackURL) StringFull(scheme, host string) string {
|
||||
return o.Must(o.BuildFull(scheme, host)).String()
|
||||
}
|
||||
@@ -53,6 +53,7 @@ func getSessionResponse(sessionID string) (*models.SessionResponse, error) {
|
||||
log.Println("error getting claims from JWT", err)
|
||||
return nil, errorGenericInvalidSession
|
||||
}
|
||||
|
||||
sessionResp := &models.SessionResponse{
|
||||
Pages: acl.GetAuthorizedEndpoints(claims.Actions),
|
||||
Status: models.SessionResponseStatusOk,
|
||||
|
||||
@@ -78,10 +78,10 @@ func startWatch(ctx context.Context, conn WSConn, wsc MCS3Client, options watchO
|
||||
}
|
||||
}
|
||||
|
||||
// getOptionsFromReq gets bucket name, events, prefix, suffix from a websocket
|
||||
// getWatchOptionsFromReq gets bucket name, events, prefix, suffix from a websocket
|
||||
// watch path if defined.
|
||||
// path come as : `/watch/bucket1` and query params come on request form
|
||||
func getOptionsFromReq(req *http.Request) watchOptions {
|
||||
func getWatchOptionsFromReq(req *http.Request) watchOptions {
|
||||
wOptions := watchOptions{}
|
||||
// Default Events if not defined
|
||||
wOptions.Events = []string{"put", "get", "delete"}
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestWatch(t *testing.T) {
|
||||
mockWSConn := mockConn{}
|
||||
ctx := context.Background()
|
||||
|
||||
function := "startWatch(ctx, )"
|
||||
function := "startWatch()"
|
||||
|
||||
testStreamSize := 5
|
||||
testReceiver := make(chan []mc.EventInfo, testStreamSize)
|
||||
@@ -191,7 +191,7 @@ func TestWatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test-6: getOptionsFromReq return parameters from path
|
||||
// Test-6: getWatchOptionsFromReq return parameters from path
|
||||
u, err := url.Parse("http://localhost/api/v1/watch/bucket1?prefix=&suffix=.jpg&events=put,get")
|
||||
if err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", "url.Parse()", err.Error())
|
||||
@@ -199,7 +199,7 @@ func TestWatch(t *testing.T) {
|
||||
req := &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
opts := getOptionsFromReq(req)
|
||||
opts := getWatchOptionsFromReq(req)
|
||||
expectedOptions := watchOptions{
|
||||
BucketName: "bucket1",
|
||||
}
|
||||
@@ -211,7 +211,7 @@ func TestWatch(t *testing.T) {
|
||||
assert.Equal(expectedOptions.Suffix, opts.Suffix)
|
||||
assert.Equal(expectedOptions.Events, opts.Events)
|
||||
|
||||
// Test-7: getOptionsFromReq return default events if not defined
|
||||
// Test-7: getWatchOptionsFromReq return default events if not defined
|
||||
u, err = url.Parse("http://localhost/api/v1/watch/bucket1?prefix=&suffix=.jpg&events=")
|
||||
if err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", "url.Parse()", err.Error())
|
||||
@@ -219,7 +219,7 @@ func TestWatch(t *testing.T) {
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
opts = getOptionsFromReq(req)
|
||||
opts = getWatchOptionsFromReq(req)
|
||||
expectedOptions = watchOptions{
|
||||
BucketName: "bucket1",
|
||||
}
|
||||
@@ -231,7 +231,7 @@ func TestWatch(t *testing.T) {
|
||||
assert.Equal(expectedOptions.Suffix, opts.Suffix)
|
||||
assert.Equal(expectedOptions.Events, opts.Events)
|
||||
|
||||
// Test-8: getOptionsFromReq return default events if not defined
|
||||
// Test-8: getWatchOptionsFromReq return default events if not defined
|
||||
u, err = url.Parse("http://localhost/api/v1/watch/bucket2?prefix=&suffix=")
|
||||
if err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", "url.Parse()", err.Error())
|
||||
@@ -239,7 +239,7 @@ func TestWatch(t *testing.T) {
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
opts = getOptionsFromReq(req)
|
||||
opts = getWatchOptionsFromReq(req)
|
||||
expectedOptions = watchOptions{
|
||||
BucketName: "bucket2",
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user