Compare commits
50 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 | ||
|
|
c49a7bbe3c | ||
|
|
3bb317535c | ||
|
|
989e6f3471 | ||
|
|
35d575e7ac | ||
|
|
92a8aab07d | ||
|
|
c5b2419191 | ||
|
|
732e0ef683 | ||
|
|
e8491d80cb | ||
|
|
35c3b53a23 | ||
|
|
6fef30f29d | ||
|
|
acf480fd25 | ||
|
|
8bbc4f0192 | ||
|
|
48e7991f11 | ||
|
|
5e9b0652b0 | ||
|
|
438211199d | ||
|
|
0f77a32656 | ||
|
|
9f3e99ede8 | ||
|
|
a8c07c0969 |
@@ -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
|
||||
12
.github/workflows/go.yml
vendored
12
.github/workflows/go.yml
vendored
@@ -14,25 +14,23 @@ 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 }}
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build on ${{ matrix.os }}
|
||||
env:
|
||||
GO111MODULE: on
|
||||
GOOS: linux
|
||||
run: |
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0
|
||||
$(go env GOPATH)/bin/golangci-lint run --timeout=5m --config ./.golangci.yml
|
||||
go mod vendor
|
||||
go test -v -race ./...
|
||||
make verifiers
|
||||
make test
|
||||
make mcs
|
||||
|
||||
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
|
||||
*~
|
||||
@@ -1,4 +1,7 @@
|
||||
linters-settings:
|
||||
golint:
|
||||
min-confidence: 0
|
||||
|
||||
misspell:
|
||||
locale: US
|
||||
|
||||
@@ -14,4 +17,8 @@ linters:
|
||||
- gosimple
|
||||
- deadcode
|
||||
- unparam
|
||||
- unused
|
||||
- structcheck
|
||||
|
||||
service:
|
||||
golangci-lint-version: 1.21.0 # use the fixed version to not introduce new linters unexpectedly
|
||||
|
||||
@@ -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:
|
||||
|
||||
885
CREDITS
885
CREDITS
@@ -940,6 +940,33 @@ https://github.com/Azure/go-autorest
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/BurntSushi/toml
|
||||
https://github.com/BurntSushi/toml
|
||||
----------------------------------------------------------------
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
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.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/PuerkitoBio/purell
|
||||
https://github.com/PuerkitoBio/purell
|
||||
----------------------------------------------------------------
|
||||
@@ -1148,33 +1175,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
================================================================
|
||||
|
||||
github.com/baiyubin/aliyun-sts-go-sdk
|
||||
https://github.com/baiyubin/aliyun-sts-go-sdk
|
||||
----------------------------------------------------------------
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Aliyun.
|
||||
|
||||
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.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/bcicen/jstream
|
||||
https://github.com/bcicen/jstream
|
||||
----------------------------------------------------------------
|
||||
@@ -7113,34 +7113,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/gorilla/websocket
|
||||
https://github.com/gorilla/websocket
|
||||
----------------------------------------------------------------
|
||||
Copyright (c) 2013 The Gorilla WebSocket 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.
|
||||
|
||||
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 HOLDER 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.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/grpc-ecosystem/go-grpc-middleware
|
||||
https://github.com/grpc-ecosystem/go-grpc-middleware
|
||||
----------------------------------------------------------------
|
||||
@@ -13042,33 +13014,6 @@ SOFTWARE.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/mattn/go-colorable
|
||||
https://github.com/mattn/go-colorable
|
||||
----------------------------------------------------------------
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Yasuhiro Matsumoto
|
||||
|
||||
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.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/mattn/go-ieproxy
|
||||
https://github.com/mattn/go-ieproxy
|
||||
----------------------------------------------------------------
|
||||
@@ -13113,21 +13058,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/mattn/go-isatty
|
||||
https://github.com/mattn/go-isatty
|
||||
----------------------------------------------------------------
|
||||
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
|
||||
|
||||
MIT License (Expat)
|
||||
|
||||
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.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/mattn/go-runewidth
|
||||
https://github.com/mattn/go-runewidth
|
||||
----------------------------------------------------------------
|
||||
@@ -14107,422 +14037,6 @@ https://github.com/minio/minio
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/minio/minio
|
||||
https://github.com/minio/minio
|
||||
----------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/minio/minio-go/v6
|
||||
https://github.com/minio/minio-go/v6
|
||||
----------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/minio/minio-go/v6
|
||||
https://github.com/minio/minio-go/v6
|
||||
----------------------------------------------------------------
|
||||
@@ -19918,33 +19432,6 @@ THE SOFTWARE.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/sirupsen/logrus
|
||||
https://github.com/sirupsen/logrus
|
||||
----------------------------------------------------------------
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Simon Eskildsen
|
||||
|
||||
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.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/smartystreets/assertions
|
||||
https://github.com/smartystreets/assertions
|
||||
----------------------------------------------------------------
|
||||
@@ -21480,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
|
||||
----------------------------------------------------------------
|
||||
@@ -21538,8 +21050,41 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================
|
||||
|
||||
golang.org/x/crypto
|
||||
https://golang.org/x/crypto
|
||||
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
|
||||
----------------------------------------------------------------
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
@@ -21802,6 +21347,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================
|
||||
|
||||
golang.org/x/xerrors
|
||||
https://golang.org/x/xerrors
|
||||
----------------------------------------------------------------
|
||||
Copyright (c) 2019 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.
|
||||
|
||||
================================================================
|
||||
|
||||
google.golang.org/api
|
||||
https://google.golang.org/api
|
||||
----------------------------------------------------------------
|
||||
@@ -22762,203 +22340,6 @@ third-party archives.
|
||||
|
||||
================================================================
|
||||
|
||||
gopkg.in/ini.v1
|
||||
https://gopkg.in/ini.v1
|
||||
----------------------------------------------------------------
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright 2014 Unknwon
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
================================================================
|
||||
|
||||
gopkg.in/jcmturner/aescts.v1
|
||||
https://gopkg.in/jcmturner/aescts.v1
|
||||
----------------------------------------------------------------
|
||||
@@ -24463,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.
|
||||
|
||||
================================================================
|
||||
|
||||
|
||||
127
DEVELOPMENT.md
Normal file
127
DEVELOPMENT.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# LDAP authentication with MCS
|
||||
|
||||
## Setup
|
||||
|
||||
Run openLDAP with docker.
|
||||
|
||||
```
|
||||
$ docker run --rm -p 389:389 -p 636:636 --name my-openldap-container --detach osixia/openldap:1.3.0
|
||||
```
|
||||
|
||||
Run the `billy.ldif` file using `ldapadd` command to create a new user and assign it to a group.
|
||||
|
||||
```
|
||||
$ cat > billy.ldif << EOF
|
||||
# LDIF fragment to create group branch under root
|
||||
dn: uid=billy,dc=example,dc=org
|
||||
uid: billy
|
||||
cn: billy
|
||||
sn: 3
|
||||
objectClass: top
|
||||
objectClass: posixAccount
|
||||
objectClass: inetOrgPerson
|
||||
loginShell: /bin/bash
|
||||
homeDirectory: /home/billy
|
||||
uidNumber: 14583102
|
||||
gidNumber: 14564100
|
||||
userPassword: {SSHA}j3lBh1Seqe4rqF1+NuWmjhvtAni1JC5A
|
||||
mail: billy@example.org
|
||||
gecos: Billy User
|
||||
# Create base group
|
||||
dn: ou=groups,dc=example,dc=org
|
||||
objectclass:organizationalunit
|
||||
ou: groups
|
||||
description: generic groups branch
|
||||
# create mcsAdmin group (this already exists on minio and have a policy of s3::*)
|
||||
dn: cn=mcsAdmin,ou=groups,dc=example,dc=org
|
||||
objectClass: top
|
||||
objectClass: posixGroup
|
||||
gidNumber: 678
|
||||
# Assing group to new user
|
||||
dn: cn=mcsAdmin,ou=groups,dc=example,dc=org
|
||||
changetype: modify
|
||||
add: memberuid
|
||||
memberuid: billy
|
||||
EOF
|
||||
|
||||
$ docker cp billy.ldif my-openldap-container:/container/service/slapd/assets/test/billy.ldif
|
||||
$ docker exec my-openldap-container ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin -f /container/service/slapd/assets/test/billy.ldif -H ldap://localhost -ZZ
|
||||
```
|
||||
|
||||
Query the ldap server to check the user billy was created correctly and got assigned to the mcsAdmin group, you should get a list
|
||||
containing ldap users and groups.
|
||||
|
||||
```
|
||||
$ docker exec my-openldap-container ldapsearch -x -H ldap://localhost -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin
|
||||
```
|
||||
|
||||
Query the ldap server again, this time filtering only for the user `billy`, you should see only 1 record.
|
||||
|
||||
```
|
||||
$ docker exec my-openldap-container ldapsearch -x -H ldap://localhost -b uid=billy,dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin
|
||||
```
|
||||
|
||||
### Change the password for user billy
|
||||
|
||||
Set the new password for `billy` to `minio123` and enter `admin` as the default `LDAP Password`
|
||||
|
||||
```
|
||||
$ docker exec -it my-openldap-container /bin/bash
|
||||
# ldappasswd -H ldap://localhost -x -D "cn=admin,dc=example,dc=org" -W -S "uid=billy,dc=example,dc=org"
|
||||
New password:
|
||||
Re-enter new password:
|
||||
Enter LDAP Password:
|
||||
```
|
||||
|
||||
### Add the mcsAdmin policy to user billy on MinIO
|
||||
```
|
||||
$ cat > mcsAdmin.json << EOF
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"admin:*"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Sid": ""
|
||||
},
|
||||
{
|
||||
"Action": [
|
||||
"s3:*"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": [
|
||||
"arn:aws:s3:::*"
|
||||
],
|
||||
"Sid": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
$ mc admin policy add myminio mcsAdmin mcsAdmin.json
|
||||
$ mc admin policy set myminio mcsAdmin user=billy
|
||||
```
|
||||
|
||||
## Run MinIO
|
||||
|
||||
```
|
||||
export MINIO_ACCESS_KEY=minio
|
||||
export MINIO_SECRET_KEY=minio123
|
||||
export MINIO_IDENTITY_LDAP_SERVER_ADDR='localhost:389'
|
||||
export MINIO_IDENTITY_LDAP_USERNAME_FORMAT='uid=%s,dc=example,dc=org'
|
||||
export MINIO_IDENTITY_LDAP_USERNAME_SEARCH_FILTER='(|(objectclass=posixAccount)(uid=%s))'
|
||||
export MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY=on
|
||||
export MINIO_IDENTITY_LDAP_SERVER_INSECURE=on
|
||||
./minio server ~/Data
|
||||
```
|
||||
|
||||
## Run MCS
|
||||
|
||||
```
|
||||
export MCS_ACCESS_KEY=minio
|
||||
export MCS_SECRET_KEY=minio123
|
||||
...
|
||||
export MCS_LDAP_ENABLED=on
|
||||
./mcs server
|
||||
```
|
||||
@@ -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
|
||||
|
||||
|
||||
26
Makefile
26
Makefile
@@ -6,7 +6,23 @@ default: mcs
|
||||
.PHONY: mcs
|
||||
mcs:
|
||||
@echo "Building mcs binary to './mcs'"
|
||||
@(CGO_ENABLED=0 go build -trimpath --tags=kqueue --ldflags "-s -w" -o mcs ./cmd/mcs)
|
||||
@(GO111MODULE=on CGO_ENABLED=0 go build -trimpath --tags=kqueue --ldflags "-s -w" -o mcs ./cmd/mcs)
|
||||
|
||||
getdeps:
|
||||
@mkdir -p ${GOPATH}/bin
|
||||
@which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.27.0)
|
||||
|
||||
verifiers: getdeps fmt lint
|
||||
|
||||
fmt:
|
||||
@echo "Running $@ check"
|
||||
@GO111MODULE=on gofmt -d cmd/
|
||||
@GO111MODULE=on gofmt -d pkg/
|
||||
|
||||
lint:
|
||||
@echo "Running $@ check"
|
||||
@GO111MODULE=on ${GOPATH}/bin/golangci-lint cache clean
|
||||
@GO111MODULE=on ${GOPATH}/bin/golangci-lint run --timeout=5m --config ./.golangci.yml
|
||||
|
||||
install: mcs
|
||||
@echo "Installing mcs binary to '$(GOPATH)/bin/mcs'"
|
||||
@@ -15,17 +31,19 @@ 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:
|
||||
@(cd portal-ui; yarn install; make build-static; cd ..)
|
||||
|
||||
test:
|
||||
@(go test -race -v github.com/minio/mcs/restapi/...)
|
||||
@(go test -race -v github.com/minio/mcs/pkg/auth/...)
|
||||
@(GO111MODULE=on go test -race -v github.com/minio/mcs/restapi/...)
|
||||
@(GO111MODULE=on go test -race -v github.com/minio/mcs/pkg/...)
|
||||
|
||||
coverage:
|
||||
@(go test -v -coverprofile=coverage.out github.com/minio/mcs/restapi/... && go tool cover -html=coverage.out && open coverage.html)
|
||||
@(GO111MODULE=on go test -v -coverprofile=coverage.out github.com/minio/mcs/restapi/... && go tool cover -html=coverage.out && open coverage.html)
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up all the generated files"
|
||||
|
||||
98
README.md
98
README.md
@@ -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.
|
||||
@@ -14,31 +19,30 @@ $ mc admin user add myminio mcs YOURMCSSECRET
|
||||
$ set -o history
|
||||
```
|
||||
|
||||
2. Create a policy for `mcs`
|
||||
2. Create a policy for `mcs` with access to everything (for testing and debugging)
|
||||
|
||||
```
|
||||
$ cat > mcsAdmin.json << EOF
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"admin:*"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Sid": ""
|
||||
},
|
||||
{
|
||||
"Action": [
|
||||
"s3:*"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": [
|
||||
"arn:aws:s3:::*"
|
||||
],
|
||||
"Sid": ""
|
||||
}
|
||||
]
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Action": [
|
||||
"admin:*"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Sid": ""
|
||||
},
|
||||
{
|
||||
"Action": [
|
||||
"s3:*"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": [
|
||||
"arn:aws:s3:::*"
|
||||
],
|
||||
"Sid": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
$ mc admin policy add myminio mcsAdmin mcsAdmin.json
|
||||
@@ -50,6 +54,49 @@ $ mc admin policy add myminio mcsAdmin mcsAdmin.json
|
||||
$ mc admin policy set myminio mcsAdmin user=mcs
|
||||
```
|
||||
|
||||
|
||||
### Note
|
||||
Additionally, you can create policies to limit the privileges for `mcs` users, for example, if you want the user to only have access to dashboard, buckets, notifications and watch page, the policy should look like this:
|
||||
```
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Action": [
|
||||
"admin:ServerInfo"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Sid": ""
|
||||
},
|
||||
{
|
||||
"Action": [
|
||||
"s3:ListenBucketNotification",
|
||||
"s3:PutBucketNotification",
|
||||
"s3:GetBucketNotification",
|
||||
"s3:ListMultipartUploadParts",
|
||||
"s3:ListBucketMultipartUploads",
|
||||
"s3:ListBucket",
|
||||
"s3:HeadBucket",
|
||||
"s3:GetObject",
|
||||
"s3:GetBucketLocation",
|
||||
"s3:AbortMultipartUpload",
|
||||
"s3:CreateBucket",
|
||||
"s3:PutObject",
|
||||
"s3:DeleteObject",
|
||||
"s3:DeleteBucket",
|
||||
"s3:PutBucketPolicy",
|
||||
"s3:DeleteBucketPolicy",
|
||||
"s3:GetBucketPolicy"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": [
|
||||
"arn:aws:s3:::*"
|
||||
],
|
||||
"Sid": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Run MCS server
|
||||
To run the server:
|
||||
|
||||
@@ -68,6 +115,15 @@ export MCS_MINIO_SERVER=http://localhost:9000
|
||||
./mcs server
|
||||
```
|
||||
|
||||
## Connect MCS to a Minio using TLS and a self-signed certificate
|
||||
|
||||
```
|
||||
...
|
||||
export MCS_MINIO_SERVER_TLS_SKIP_VERIFICATION=on
|
||||
export MCS_MINIO_SERVER=https://localhost:9000
|
||||
./mcs server
|
||||
```
|
||||
|
||||
You can verify that the apis work by doing the request on `localhost:9090/api/v1/...`
|
||||
|
||||
# Contribute to mcs Project
|
||||
|
||||
@@ -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
|
||||
|
||||
8
go.mod
8
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
|
||||
@@ -17,9 +17,9 @@ require (
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/json-iterator/go v1.1.9
|
||||
github.com/minio/cli v1.22.0
|
||||
github.com/minio/mc v0.0.0-20200415193718-68b638f2f96c
|
||||
github.com/minio/minio v0.0.0-20200501193630-d1c8e9f31ba0
|
||||
github.com/minio/minio-go/v6 v6.0.55-0.20200424204115-7506d2996b22
|
||||
github.com/minio/mc v0.0.0-20200515235434-3b479cf92ed6
|
||||
github.com/minio/minio v0.0.0-20200516011754-9cac385aecdb
|
||||
github.com/minio/minio-go/v6 v6.0.56-0.20200502013257-a81c8c13cc3f
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
|
||||
41
go.sum
41
go.sum
@@ -12,6 +12,7 @@ github.com/Azure/azure-storage-blob-go v0.8.0 h1:53qhf0Oxa0nOjgbDeeYPUeyiNmafAFE
|
||||
github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
|
||||
github.com/Azure/go-autorest v11.7.1+incompatible h1:M2YZIajBBVekV86x0rr1443Lc1F/Ylxb9w+5EtSyX3Q=
|
||||
github.com/Azure/go-autorest v11.7.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
@@ -30,7 +31,6 @@ github.com/alecthomas/participle v0.2.1 h1:4AVLj1viSGa4LG5HDXKXrm5xRx19SB/rS/skP
|
||||
github.com/alecthomas/participle v0.2.1/go.mod h1:SW6HZGeZgSIpcUWX3fXpfZhuaWHnmoD5KCVaqSaNTkk=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
|
||||
@@ -41,7 +41,6 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/aws/aws-sdk-go v1.20.21/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2 h1:M+TYzBcNIRyzPRg66ndEqUMd7oWDmhvdQmaPC6EZNwM=
|
||||
github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2/go.mod h1:RDu/qcrnpEdJC/p8tx34+YBFqqX71lB7dOX9QE+ZC4M=
|
||||
github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw=
|
||||
@@ -114,6 +113,7 @@ github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g=
|
||||
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
@@ -239,6 +239,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -338,12 +339,16 @@ github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eT
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/cpuid v1.2.2 h1:1xAgYebNnsb9LKCdLOvFWtAxGU/33mjJtyOVbmUa0Us=
|
||||
github.com/klauspost/cpuid v1.2.2/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.4 h1:EBfaK0SWSwk+fgk6efYFWdzl8MwRWoOO1gkmiaTXPW4=
|
||||
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM=
|
||||
github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/klauspost/readahead v1.3.1 h1:QqXNYvm+VvqYcbrRT4LojUciM0XrznFRIDrbHiJtu/0=
|
||||
github.com/klauspost/readahead v1.3.1/go.mod h1:AH9juHzNH7xqdqFHrMRSHeH2Ps+vFf+kblDqzPFiLJg=
|
||||
github.com/klauspost/reedsolomon v1.9.3 h1:N/VzgeMfHmLc+KHMD1UL/tNkfXAt8FnUqlgXGIduwAY=
|
||||
github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/klauspost/reedsolomon v1.9.7 h1:+azeqnT4iNG9UEcWC+7utJ4xXQ9S8pSlkZor0DOArEQ=
|
||||
github.com/klauspost/reedsolomon v1.9.7/go.mod h1:+8WD025Xpby8/kG5h/HDPIFhiiuGEtZOKw+5Y4drAD8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -399,15 +404,16 @@ github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2
|
||||
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
|
||||
github.com/minio/lsync v1.0.1 h1:AVvILxA976xc27hstd1oR+X9PQG0sPSom1MNb1ImfUs=
|
||||
github.com/minio/lsync v1.0.1/go.mod h1:tCFzfo0dlvdGl70IT4IAK/5Wtgb0/BrTmo/jE8pArKA=
|
||||
github.com/minio/mc v0.0.0-20200415193718-68b638f2f96c h1:JLr0fYpCleodj9nGB5hfsJU2zPdnNQKqa2bYsIvPhVw=
|
||||
github.com/minio/mc v0.0.0-20200415193718-68b638f2f96c/go.mod h1:l9PuOY62zT7AQJqopDjfo/T22AIBJSb2yXPVZf4RlhM=
|
||||
github.com/minio/minio v0.0.0-20200415191640-bde0f444dbab/go.mod h1:v8oQPMMaTkjDwp5cOz1WCElA4Ik+X+0y4On+VMk0fis=
|
||||
github.com/minio/minio v0.0.0-20200501193630-d1c8e9f31ba0 h1:QxIz36O01LbKqJiz6HKeKCOC2afgydspkpahQ807msY=
|
||||
github.com/minio/minio v0.0.0-20200501193630-d1c8e9f31ba0/go.mod h1:Vhlqz7Se0EgpgFiVxpvzF4Zz/h2LMx+EPKH96Aera5U=
|
||||
github.com/minio/mc v0.0.0-20200515235434-3b479cf92ed6 h1:2SrKe2vLDLwvnYkYrJelrzyGW8t/8HCbr9yDsw+8XSI=
|
||||
github.com/minio/mc v0.0.0-20200515235434-3b479cf92ed6/go.mod h1:U3Jgk0bcSjn+QPUMisrS6nxCWOoQ6rYWSvLCB30apuU=
|
||||
github.com/minio/minio v0.0.0-20200421050159-282c9f790a03/go.mod h1:zBua5AiljGs1Irdl2XEyiJjvZVCVDIG8gjozzRBcVlw=
|
||||
github.com/minio/minio v0.0.0-20200516011754-9cac385aecdb h1:CQC7D3UDnUycuxhwImcVhMSLet/RbShosAnYcvMtEB8=
|
||||
github.com/minio/minio v0.0.0-20200516011754-9cac385aecdb/go.mod h1:wymaytM/HELuwdz7BGZHmQ3XKq2SxPsLeGxyOCaCLiA=
|
||||
github.com/minio/minio-go/v6 v6.0.53 h1:8jzpwiOzZ5Iz7/goFWqNZRICbyWYShbb5rARjrnSCNI=
|
||||
github.com/minio/minio-go/v6 v6.0.53/go.mod h1:DIvC/IApeHX8q1BAMVCXSXwpmrmM+I+iBvhvztQorfI=
|
||||
github.com/minio/minio-go/v6 v6.0.55-0.20200424204115-7506d2996b22 h1:nZEve4vdUhwHBoV18zRvPDgjL6NYyDJE5QJvz3l9bRs=
|
||||
github.com/minio/minio-go/v6 v6.0.55-0.20200424204115-7506d2996b22/go.mod h1:KQMM+/44DSlSGSQWSfRrAZ12FVMmpWNuX37i2AX0jfI=
|
||||
github.com/minio/minio-go/v6 v6.0.55-0.20200425081427-89eebdef2af0/go.mod h1:KQMM+/44DSlSGSQWSfRrAZ12FVMmpWNuX37i2AX0jfI=
|
||||
github.com/minio/minio-go/v6 v6.0.56-0.20200502013257-a81c8c13cc3f h1:ifHrI8+exqLi5RztIWWKS5k+Wu+W7DJisVXwNaCH2zs=
|
||||
github.com/minio/minio-go/v6 v6.0.56-0.20200502013257-a81c8c13cc3f/go.mod h1:KQMM+/44DSlSGSQWSfRrAZ12FVMmpWNuX37i2AX0jfI=
|
||||
github.com/minio/parquet-go v0.0.0-20200414234858-838cfa8aae61 h1:pUSI/WKPdd77gcuoJkSzhJ4wdS8OMDOsOu99MtpXEQA=
|
||||
github.com/minio/parquet-go v0.0.0-20200414234858-838cfa8aae61/go.mod h1:4trzEJ7N1nBTd5Tt7OCZT5SEin+WiAXpdJ/WgPkESA8=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
@@ -607,11 +613,19 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.11.0 h1:gSmpCfs+R47a4yQPAI4xJ0IPDLTRGXskm6UelqNXpqE=
|
||||
go.uber.org/zap v1.11.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
|
||||
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -620,6 +634,7 @@ golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -635,6 +650,9 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -715,8 +733,11 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190914235951-31e00f45c22e h1:nOOVVcLC+/3MeovP40q5lCiWmP1Z1DaN8yn8ngU63hw=
|
||||
golang.org/x/tools v0.0.0-20190914235951-31e00f45c22e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200428211428-0c9eba77bc32 h1:Xvf3ZQTm5bjXPxhI7g+dwqsCqadK1rcNtwtszuatetk=
|
||||
golang.org/x/tools v0.0.0-20200428211428-0c9eba77bc32/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
@@ -786,4 +807,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
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
|
||||
}
|
||||
@@ -36,6 +36,9 @@ import (
|
||||
// swagger:model sessionResponse
|
||||
type SessionResponse struct {
|
||||
|
||||
// pages
|
||||
Pages []string `json:"pages"`
|
||||
|
||||
// status
|
||||
// Enum: [ok]
|
||||
Status string `json:"status,omitempty"`
|
||||
|
||||
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"
|
||||
)
|
||||
315
pkg/acl/endpoints.go
Normal file
315
pkg/acl/endpoints.go
Normal file
@@ -0,0 +1,315 @@
|
||||
// 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 (
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
// endpoints definition
|
||||
var (
|
||||
configuration = "/configurations-list"
|
||||
users = "/users"
|
||||
groups = "/groups"
|
||||
iamPolicies = "/policies"
|
||||
dashboard = "/dashboard"
|
||||
profiling = "/profiling"
|
||||
trace = "/trace"
|
||||
logs = "/logs"
|
||||
watch = "/watch"
|
||||
notifications = "/notification-endpoints"
|
||||
buckets = "/buckets"
|
||||
bucketsDetail = "/buckets/:bucketName"
|
||||
serviceAccounts = "/service-accounts"
|
||||
tenants = "/tenants"
|
||||
tenantsDetail = "/tenants/:tenantName"
|
||||
heal = "/heal"
|
||||
)
|
||||
|
||||
type ConfigurationActionSet struct {
|
||||
actionTypes iampolicy.ActionSet
|
||||
actions iampolicy.ActionSet
|
||||
}
|
||||
|
||||
// configurationActionSet contains the list of admin actions required for this endpoint to work
|
||||
var configurationActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ConfigUpdateAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// logsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var logsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ConsoleLogAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// dashboardActionSet contains the list of admin actions required for this endpoint to work
|
||||
var dashboardActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ServerInfoAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// groupsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var groupsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ListGroupsAdminAction,
|
||||
iampolicy.AddUserToGroupAdminAction,
|
||||
//iampolicy.GetGroupAdminAction,
|
||||
iampolicy.EnableGroupAdminAction,
|
||||
iampolicy.DisableGroupAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// iamPoliciesActionSet contains the list of admin actions required for this endpoint to work
|
||||
var iamPoliciesActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.GetPolicyAdminAction,
|
||||
iampolicy.DeletePolicyAdminAction,
|
||||
iampolicy.CreatePolicyAdminAction,
|
||||
iampolicy.AttachPolicyAdminAction,
|
||||
iampolicy.ListUserPoliciesAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// profilingActionSet contains the list of admin actions required for this endpoint to work
|
||||
var profilingActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ProfilingAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// traceActionSet contains the list of admin actions required for this endpoint to work
|
||||
var traceActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.TraceAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// usersActionSet contains the list of admin actions required for this endpoint to work
|
||||
var usersActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ListUsersAdminAction,
|
||||
iampolicy.CreateUserAdminAction,
|
||||
iampolicy.DeleteUserAdminAction,
|
||||
iampolicy.GetUserAdminAction,
|
||||
iampolicy.EnableUserAdminAction,
|
||||
iampolicy.DisableUserAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// watchActionSet contains the list of admin actions required for this endpoint to work
|
||||
var watchActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ListenBucketNotificationAction,
|
||||
),
|
||||
}
|
||||
|
||||
// notificationsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var notificationsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ListenBucketNotificationAction,
|
||||
iampolicy.PutBucketNotificationAction,
|
||||
iampolicy.GetBucketNotificationAction,
|
||||
),
|
||||
}
|
||||
|
||||
// bucketsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var bucketsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
// Read access to buckets
|
||||
iampolicy.ListMultipartUploadPartsAction,
|
||||
iampolicy.ListBucketMultipartUploadsAction,
|
||||
iampolicy.ListBucketAction,
|
||||
iampolicy.HeadBucketAction,
|
||||
iampolicy.GetObjectAction,
|
||||
iampolicy.GetBucketLocationAction,
|
||||
// Write access to buckets
|
||||
iampolicy.AbortMultipartUploadAction,
|
||||
iampolicy.CreateBucketAction,
|
||||
iampolicy.PutObjectAction,
|
||||
iampolicy.DeleteObjectAction,
|
||||
iampolicy.DeleteBucketAction,
|
||||
// Assign bucket policies
|
||||
iampolicy.PutBucketPolicyAction,
|
||||
iampolicy.DeleteBucketPolicyAction,
|
||||
iampolicy.GetBucketPolicyAction,
|
||||
),
|
||||
}
|
||||
|
||||
// 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,
|
||||
users: usersActionSet,
|
||||
groups: groupsActionSet,
|
||||
iamPolicies: iamPoliciesActionSet,
|
||||
dashboard: dashboardActionSet,
|
||||
profiling: profilingActionSet,
|
||||
trace: traceActionSet,
|
||||
logs: logsActionSet,
|
||||
watch: watchActionSet,
|
||||
notifications: notificationsActionSet,
|
||||
buckets: bucketsActionSet,
|
||||
bucketsDetail: bucketsActionSet,
|
||||
serviceAccounts: serviceAccountsActionSet,
|
||||
heal: healActionSet,
|
||||
}
|
||||
|
||||
// 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:
|
||||
// {
|
||||
// "Version": "2012-10-17",
|
||||
// "Statement": [{
|
||||
// "Action": [
|
||||
// "admin:ServerInfo",
|
||||
// "admin:CreatePolicy",
|
||||
// "admin:GetUser"
|
||||
// ],
|
||||
// ...
|
||||
// },
|
||||
// {
|
||||
// "Action": [
|
||||
// "s3:ListenBucketNotification",
|
||||
// "s3:PutBucketNotification"
|
||||
// ],
|
||||
// ...
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// Will produce an array like: ["admin:ServerInfo", "admin:CreatePolicy", "admin:GetUser", "s3:ListenBucketNotification", "s3:PutBucketNotification"]\
|
||||
func GetActionsStringFromPolicy(policy *iampolicy.Policy) []string {
|
||||
var actions []string
|
||||
for _, statement := range policy.Statements {
|
||||
// We only care about allowed actions
|
||||
if statement.Effect.IsAllowed(true) {
|
||||
for _, action := range statement.Actions.ToSlice() {
|
||||
actions = append(actions, string(action))
|
||||
}
|
||||
}
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
// actionsStringToActionSet convert a given string array to iampolicy.ActionSet structure
|
||||
// this avoids ending with duplicate actions
|
||||
func actionsStringToActionSet(actions []string) iampolicy.ActionSet {
|
||||
actionsSet := iampolicy.ActionSet{}
|
||||
for _, action := range actions {
|
||||
actionsSet.Add(iampolicy.Action(action))
|
||||
}
|
||||
return actionsSet
|
||||
}
|
||||
|
||||
// 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 rangeTake {
|
||||
// check if user policy matches s3:* or admin:* typesIntersection
|
||||
endpointActionTypes := rules.actionTypes
|
||||
typesIntersection := endpointActionTypes.Intersection(userAllowedAction)
|
||||
if len(typesIntersection) == len(endpointActionTypes.ToSlice()) {
|
||||
allowedEndpoints = append(allowedEndpoints, endpoint)
|
||||
continue
|
||||
}
|
||||
// check if user policy matches explicitly defined endpoint required actions
|
||||
endpointRequiredActions := rules.actions
|
||||
actionsIntersection := endpointRequiredActions.Intersection(userAllowedAction)
|
||||
if len(actionsIntersection) == len(endpointRequiredActions.ToSlice()) {
|
||||
allowedEndpoints = append(allowedEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return allowedEndpoints
|
||||
}
|
||||
190
pkg/acl/endpoints_test.go
Normal file
190
pkg/acl/endpoints_test.go
Normal file
@@ -0,0 +1,190 @@
|
||||
// This file is part of MinIO Orchestrator
|
||||
// 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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
tests := []endpoint{
|
||||
{
|
||||
name: "dashboard endpoint",
|
||||
args: args{
|
||||
[]string{"admin:ServerInfo"},
|
||||
},
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "policies endpoint",
|
||||
args: args{
|
||||
[]string{
|
||||
"admin:CreatePolicy",
|
||||
"admin:DeletePolicy",
|
||||
"admin:GetPolicy",
|
||||
"admin:AttachUserOrGroupPolicy",
|
||||
"admin:ListUserPolicies",
|
||||
},
|
||||
},
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "all admin endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"admin:*",
|
||||
},
|
||||
},
|
||||
want: 11,
|
||||
},
|
||||
{
|
||||
name: "all s3 endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 4,
|
||||
},
|
||||
{
|
||||
name: "all admin and s3 endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"admin:*",
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 14,
|
||||
},
|
||||
{
|
||||
name: "no endpoints",
|
||||
args: args{
|
||||
[]string{},
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
|
||||
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) {
|
||||
type args struct {
|
||||
policy *iampolicy.Policy
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "parse ReadOnly policy",
|
||||
args: args{
|
||||
policy: &iampolicy.ReadOnly,
|
||||
},
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "parse WriteOnly policy",
|
||||
args: args{
|
||||
policy: &iampolicy.WriteOnly,
|
||||
},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "parse AdminDiagnostics policy",
|
||||
args: args{
|
||||
policy: &iampolicy.AdminDiagnostics,
|
||||
},
|
||||
want: 6,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := GetActionsStringFromPolicy(tt.args.policy); !reflect.DeepEqual(len(got), tt.want) {
|
||||
t.Errorf("GetActionsStringFromPolicy() = %v, want %v", len(got), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errAuthentication = errors.New("Authentication failed, check your access credentials")
|
||||
errAuthentication = errors.New("authentication failed, check your access credentials")
|
||||
errNoAuthToken = errors.New("JWT token missing")
|
||||
errReadingToken = errors.New("JWT internal data is malformed")
|
||||
errClaimsFormat = errors.New("encrypted jwt claims not in the right format")
|
||||
@@ -57,6 +57,7 @@ type DecryptedClaims struct {
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
SessionToken string
|
||||
Actions []string
|
||||
}
|
||||
|
||||
// JWTAuthenticate takes a jwt, decode it, extract claims and validate the signature
|
||||
@@ -93,9 +94,9 @@ func JWTAuthenticate(token string) (*DecryptedClaims, error) {
|
||||
|
||||
// NewJWTWithClaimsForClient generates a new jwt with claims based on the provided STS credentials, first
|
||||
// encrypts the claims and the sign them
|
||||
func NewJWTWithClaimsForClient(credentials *credentials.Value, audience string) (string, error) {
|
||||
func NewJWTWithClaimsForClient(credentials *credentials.Value, actions []string, audience string) (string, error) {
|
||||
if credentials != nil {
|
||||
encryptedClaims, err := encryptClaims(credentials.AccessKeyID, credentials.SecretAccessKey, credentials.SessionToken)
|
||||
encryptedClaims, err := encryptClaims(credentials.AccessKeyID, credentials.SecretAccessKey, credentials.SessionToken, actions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -112,8 +113,8 @@ func NewJWTWithClaimsForClient(credentials *credentials.Value, audience string)
|
||||
|
||||
// encryptClaims() receives the 3 STS claims, concatenate them and encrypt them using AES-GCM
|
||||
// returns a base64 encoded ciphertext
|
||||
func encryptClaims(accessKeyID, secretAccessKey, sessionToken string) (string, error) {
|
||||
payload := []byte(fmt.Sprintf("%s:%s:%s", accessKeyID, secretAccessKey, sessionToken))
|
||||
func encryptClaims(accessKeyID, secretAccessKey, sessionToken string, actions []string) (string, error) {
|
||||
payload := []byte(fmt.Sprintf("%s#%s#%s#%s", accessKeyID, secretAccessKey, sessionToken, strings.Join(actions, ",")))
|
||||
ciphertext, err := encrypt(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -133,16 +134,18 @@ func decryptClaims(ciphertext string) (*DecryptedClaims, error) {
|
||||
log.Println(err)
|
||||
return nil, errClaimsFormat
|
||||
}
|
||||
s := strings.Split(string(plaintext), ":")
|
||||
s := strings.Split(string(plaintext), "#")
|
||||
// Validate that the decrypted string has the right format "accessKeyID:secretAccessKey:sessionToken"
|
||||
if len(s) != 3 {
|
||||
if len(s) != 4 {
|
||||
return nil, errClaimsFormat
|
||||
}
|
||||
accessKeyID, secretAccessKey, sessionToken := s[0], s[1], s[2]
|
||||
accessKeyID, secretAccessKey, sessionToken, actions := s[0], s[1], s[2], s[3]
|
||||
actionsList := strings.Split(actions, ",")
|
||||
return &DecryptedClaims{
|
||||
AccessKeyID: accessKeyID,
|
||||
SecretAccessKey: secretAccessKey,
|
||||
SessionToken: sessionToken,
|
||||
Actions: actionsList,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,14 @@ func TestNewJWTWithClaimsForClient(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : NewJWTWithClaimsForClient() is generated correctly without errors
|
||||
function := "NewJWTWithClaimsForClient()"
|
||||
jwt, err := NewJWTWithClaimsForClient(creds, audience)
|
||||
jwt, err := NewJWTWithClaimsForClient(creds, []string{""}, audience)
|
||||
if err != nil || jwt == "" {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err)
|
||||
}
|
||||
// saving jwt for future tests
|
||||
goodToken = jwt
|
||||
// Test-2 : NewJWTWithClaimsForClient() throws error because of empty credentials
|
||||
if _, err = NewJWTWithClaimsForClient(nil, audience); err != nil {
|
||||
if _, err = NewJWTWithClaimsForClient(nil, []string{""}, audience); err != nil {
|
||||
funcAssert.Equal("provided credentials are empty", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func TestJWTAuthenticate(t *testing.T) {
|
||||
}
|
||||
// Test-2 : JWTAuthenticate() return an error because of a tampered jwt
|
||||
if _, err := JWTAuthenticate(badToken); err != nil {
|
||||
funcAssert.Equal("Authentication failed, check your access credentials", err.Error())
|
||||
funcAssert.Equal("authentication failed, check your access credentials", err.Error())
|
||||
}
|
||||
// Test-3 : JWTAuthenticate() return an error because of an empty jwt
|
||||
if _, err := JWTAuthenticate(""); err != nil {
|
||||
|
||||
39
pkg/auth/ldap.go
Normal file
39
pkg/auth/ldap.go
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/>.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidCredentials = errors.New("invalid Credentials")
|
||||
)
|
||||
|
||||
// GetMcsCredentialsFromLDAP authenticates the user against MinIO when the LDAP integration is enabled
|
||||
// if the authentication succeed *credentials.Credentials object is returned and we continue with the normal STSAssumeRole flow
|
||||
func GetMcsCredentialsFromLDAP(endpoint, ldapUser, ldapPassword string) (*credentials.Credentials, error) {
|
||||
creds, err := credentials.NewLDAPIdentity(endpoint, ldapUser, ldapPassword)
|
||||
if err != nil {
|
||||
log.Println("LDAP authentication error: ", err)
|
||||
return nil, errInvalidCredentials
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
27
pkg/auth/ldap/config.go
Normal file
27
pkg/auth/ldap/config.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/pkg/env"
|
||||
)
|
||||
|
||||
func GetLDAPEnabled() bool {
|
||||
return strings.ToLower(env.Get(MCSLDAPEnabled, "off")) == "on"
|
||||
}
|
||||
22
pkg/auth/ldap/const.go
Normal file
22
pkg/auth/ldap/const.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// 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 ldap
|
||||
|
||||
const (
|
||||
// const for ldap configuration
|
||||
MCSLDAPEnabled = "MCS_LDAP_ENABLED"
|
||||
)
|
||||
@@ -22,13 +22,14 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/minio/mcs/pkg/auth"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// Authenticate validates websocket header and returns mcs jwt claims
|
||||
// GetTokenFromRequest returns a token from a http Request
|
||||
// either defined on a cookie `token` or on Authorization header.
|
||||
//
|
||||
// Authorization Header needs to be like "Authorization Bearer <jwt_token>"
|
||||
func Authenticate(r *http.Request) (*auth.DecryptedClaims, error) {
|
||||
func GetTokenFromRequest(r *http.Request) (*string, error) {
|
||||
// Get Auth token
|
||||
var reqToken string
|
||||
|
||||
@@ -46,11 +47,5 @@ func Authenticate(r *http.Request) (*auth.DecryptedClaims, error) {
|
||||
} else {
|
||||
reqToken = strings.TrimSpace(tokenCookie.Value)
|
||||
}
|
||||
|
||||
// Perform authentication before upgrading to a Websocket Connection
|
||||
claims, err := auth.JWTAuthenticate(reqToken)
|
||||
if err != nil {
|
||||
return nil, errors.New(http.StatusUnauthorized, err.Error())
|
||||
}
|
||||
return claims, nil
|
||||
return swag.String(reqToken), nil
|
||||
}
|
||||
|
||||
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",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Acme cloud storage"
|
||||
content="MinIO Console"
|
||||
/>
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
|
||||
@@ -35,7 +35,7 @@ const isLoggedIn = () => {
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
loggedIn: state.system.loggedIn
|
||||
loggedIn: state.system.loggedIn,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, { userLoggedIn });
|
||||
|
||||
@@ -24,8 +24,8 @@ export class API {
|
||||
return request(method, url)
|
||||
.set("Authorization", `Bearer ${token}`)
|
||||
.send(data)
|
||||
.then(res => res.body)
|
||||
.catch(err => {
|
||||
.then((res) => res.body)
|
||||
.catch((err) => {
|
||||
// if we get unauthorized, kick out the user
|
||||
if (err.status === 401) {
|
||||
storage.removeItem("token");
|
||||
|
||||
@@ -14,7 +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/>.
|
||||
|
||||
export const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
export const units = [
|
||||
"B",
|
||||
"KiB",
|
||||
"MiB",
|
||||
"GiB",
|
||||
"TiB",
|
||||
"PiB",
|
||||
"EiB",
|
||||
"ZiB",
|
||||
"YiB",
|
||||
];
|
||||
export const k8sUnits = ["Ki", "Mi", "Gi", "Ti", "Pi", "Ei"];
|
||||
export const niceBytes = (x: string) => {
|
||||
let l = 0,
|
||||
n = parseInt(x, 10) || 0;
|
||||
@@ -38,3 +49,46 @@ export const setCookie = (name: string, val: string) => {
|
||||
document.cookie =
|
||||
name + "=" + value + "; expires=" + date.toUTCString() + "; path=/";
|
||||
};
|
||||
|
||||
// timeFromdate gets time string from date input
|
||||
export const timeFromDate = (d: Date) => {
|
||||
let h = d.getHours() < 10 ? `0${d.getHours()}` : `${d.getHours()}`;
|
||||
let m = d.getMinutes() < 10 ? `0${d.getMinutes()}` : `${d.getMinutes()}`;
|
||||
let s = d.getSeconds() < 10 ? `0${d.getSeconds()}` : `${d.getSeconds()}`;
|
||||
|
||||
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";
|
||||
|
||||
@@ -19,6 +19,7 @@ import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import api from "../../../../common/api";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
@@ -26,11 +27,12 @@ import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBo
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
}
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
interface IAddBucketProps {
|
||||
@@ -49,7 +51,7 @@ class AddBucket extends React.Component<IAddBucketProps, IAddBucketState> {
|
||||
state: IAddBucketState = {
|
||||
addLoading: false,
|
||||
addError: "",
|
||||
bucketName: ""
|
||||
bucketName: "",
|
||||
};
|
||||
|
||||
addRecord(event: React.FormEvent) {
|
||||
@@ -61,23 +63,23 @@ class AddBucket extends React.Component<IAddBucketProps, IAddBucketState> {
|
||||
this.setState({ addLoading: true }, () => {
|
||||
api
|
||||
.invoke("POST", "/api/v1/buckets", {
|
||||
name: bucketName
|
||||
name: bucketName,
|
||||
})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
addError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
addError: err,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -106,31 +108,29 @@ class AddBucket extends React.Component<IAddBucketProps, IAddBucketState> {
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
<InputBoxWrapper
|
||||
id="bucket-name"
|
||||
name="bucket-name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ bucketName: e.target.value });
|
||||
}}
|
||||
label="Bucket Name"
|
||||
value={bucketName}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="bucket-name"
|
||||
name="bucket-name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ bucketName: e.target.value });
|
||||
}}
|
||||
label="Bucket Name"
|
||||
value={bucketName}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
|
||||
@@ -30,49 +30,50 @@ import AddBucket from "./AddBucket";
|
||||
import DeleteBucket from "./DeleteBucket";
|
||||
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
|
||||
import { CreateIcon } from "../../../../icons";
|
||||
import { niceBytes } from "../../../../common/utils";
|
||||
|
||||
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 IListBucketsProps {
|
||||
@@ -108,7 +109,7 @@ class ListBuckets extends React.Component<
|
||||
rowsPerPage: 10,
|
||||
deleteOpen: false,
|
||||
selectedBucket: "",
|
||||
filterBuckets: ""
|
||||
filterBuckets: "",
|
||||
};
|
||||
|
||||
fetchRecords() {
|
||||
@@ -122,7 +123,7 @@ class ListBuckets extends React.Component<
|
||||
loading: false,
|
||||
records: res.buckets || [],
|
||||
totalRecords: !res.buckets ? 0 : res.total,
|
||||
error: ""
|
||||
error: "",
|
||||
});
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if (
|
||||
@@ -174,7 +175,7 @@ class ListBuckets extends React.Component<
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
selectedBucket,
|
||||
filterBuckets
|
||||
filterBuckets,
|
||||
} = this.state;
|
||||
|
||||
const offset = page * rowsPerPage;
|
||||
@@ -196,7 +197,7 @@ class ListBuckets extends React.Component<
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", to: `/buckets`, sendOnlyId: true },
|
||||
{ type: "delete", onClick: confirmDeleteBucket, sendOnlyId: true }
|
||||
{ type: "delete", onClick: confirmDeleteBucket, sendOnlyId: true },
|
||||
];
|
||||
|
||||
const displayParsedDate = (date: string) => {
|
||||
@@ -249,9 +250,9 @@ class ListBuckets extends React.Component<
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={val => {
|
||||
onChange={(val) => {
|
||||
this.setState({
|
||||
filterBuckets: val.target.value
|
||||
filterBuckets: val.target.value,
|
||||
});
|
||||
}}
|
||||
InputProps={{
|
||||
@@ -260,7 +261,7 @@ class ListBuckets extends React.Component<
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
@@ -269,7 +270,7 @@ class ListBuckets extends React.Component<
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true
|
||||
addScreenOpen: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -287,8 +288,13 @@ class ListBuckets extends React.Component<
|
||||
{
|
||||
label: "Creation Date",
|
||||
elementKey: "creation_date",
|
||||
renderFunction: displayParsedDate
|
||||
}
|
||||
renderFunction: displayParsedDate,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
@@ -302,11 +308,11 @@ class ListBuckets extends React.Component<
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -27,6 +27,7 @@ import TableBody from "@material-ui/core/TableBody";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import { ArnList } from "../types";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
@@ -34,19 +35,20 @@ import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapp
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
}
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
interface IAddEventProps {
|
||||
@@ -74,7 +76,7 @@ class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
suffix: "",
|
||||
arn: "",
|
||||
selectedEvents: [],
|
||||
arnList: []
|
||||
arnList: [],
|
||||
};
|
||||
|
||||
addRecord(event: React.FormEvent) {
|
||||
@@ -91,25 +93,25 @@ class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
arn: arn,
|
||||
events: selectedEvents,
|
||||
prefix: prefix,
|
||||
suffix: suffix
|
||||
suffix: suffix,
|
||||
},
|
||||
ignoreExisting: true
|
||||
ignoreExisting: true,
|
||||
})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
addError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
addError: err,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -127,7 +129,7 @@ class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
arnList: arns,
|
||||
addError: ""
|
||||
addError: "",
|
||||
});
|
||||
})
|
||||
.catch((err: any) => {
|
||||
@@ -149,13 +151,13 @@ class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
selectedEvents,
|
||||
arnList,
|
||||
prefix,
|
||||
suffix
|
||||
suffix,
|
||||
} = this.state;
|
||||
|
||||
const events = [
|
||||
{ label: "PUT - Object Uploaded", value: "put" },
|
||||
{ label: "GET - Object accessed", value: "get" },
|
||||
{ label: "DELETE - Object Deleted", value: "delete" }
|
||||
{ label: "DELETE - Object Deleted", value: "delete" },
|
||||
];
|
||||
|
||||
const handleClick = (
|
||||
@@ -181,9 +183,9 @@ class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
this.setState({ selectedEvents: newSelected });
|
||||
};
|
||||
|
||||
const arnValues = arnList.map(arnConstant => ({
|
||||
const arnValues = arnList.map((arnConstant) => ({
|
||||
label: arnConstant,
|
||||
value: arnConstant
|
||||
value: arnConstant,
|
||||
}));
|
||||
|
||||
return (
|
||||
@@ -204,89 +206,91 @@ class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
<SelectWrapper
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ arn: e.target.value as string });
|
||||
}}
|
||||
id="select-access-policy"
|
||||
name="select-access-policy"
|
||||
label={"ARN"}
|
||||
value={arn}
|
||||
options={arnValues}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ arn: e.target.value as string });
|
||||
}}
|
||||
id="select-access-policy"
|
||||
name="select-access-policy"
|
||||
label={"ARN"}
|
||||
value={arn}
|
||||
options={arnValues}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Event</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{events.map(row => (
|
||||
<TableRow
|
||||
key={`group-${row.value}`}
|
||||
onClick={event => handleClick(event, row.value)}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value={row.value}
|
||||
color="primary"
|
||||
inputProps={{
|
||||
"aria-label": "secondary checkbox"
|
||||
}}
|
||||
onChange={event => handleClick(event, row.value)}
|
||||
checked={selectedEvents.includes(row.value)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.label}
|
||||
</TableCell>
|
||||
<Grid item xs={12}>
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Event</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="prefix-input"
|
||||
name="prefix-input"
|
||||
label="Prefix"
|
||||
value={prefix}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ prefix: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="suffix-input"
|
||||
name="suffix-input"
|
||||
label="Suffix"
|
||||
value={suffix}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ suffix: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{events.map((row) => (
|
||||
<TableRow
|
||||
key={`group-${row.value}`}
|
||||
onClick={(event) => handleClick(event, row.value)}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value={row.value}
|
||||
color="primary"
|
||||
inputProps={{
|
||||
"aria-label": "secondary checkbox",
|
||||
}}
|
||||
onChange={(event) => handleClick(event, row.value)}
|
||||
checked={selectedEvents.includes(row.value)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.label}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="prefix-input"
|
||||
name="prefix-input"
|
||||
label="Prefix"
|
||||
value={prefix}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ prefix: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="suffix-input"
|
||||
name="suffix-input"
|
||||
label="Suffix"
|
||||
value={suffix}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ suffix: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import React from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import get from "lodash/get";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -23,7 +24,7 @@ import {
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress
|
||||
LinearProgress,
|
||||
} from "@material-ui/core";
|
||||
import api from "../../../../common/api";
|
||||
import { BucketEvent, BucketList } from "../types";
|
||||
@@ -32,8 +33,8 @@ import Typography from "@material-ui/core/Typography";
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
|
||||
interface IDeleteEventProps {
|
||||
@@ -55,7 +56,7 @@ class DeleteEvent extends React.Component<
|
||||
> {
|
||||
state: IDeleteEventState = {
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
deleteError: "",
|
||||
};
|
||||
|
||||
removeRecord() {
|
||||
@@ -69,29 +70,34 @@ class DeleteEvent extends React.Component<
|
||||
}
|
||||
|
||||
this.setState({ deleteLoading: true }, () => {
|
||||
const events = get(bucketEvent, "events", []);
|
||||
const prefix = get(bucketEvent, "prefix", "");
|
||||
const suffix = get(bucketEvent, "suffix", "");
|
||||
api
|
||||
.invoke(
|
||||
"DELETE",
|
||||
`/api/v1/buckets/${selectedBucket}/events/${bucketEvent.id}`,
|
||||
`/api/v1/buckets/${selectedBucket}/events/${bucketEvent.arn}`,
|
||||
{
|
||||
name: selectedBucket
|
||||
events,
|
||||
prefix,
|
||||
suffix,
|
||||
}
|
||||
)
|
||||
.then((res: BucketList) => {
|
||||
this.setState(
|
||||
{
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
deleteError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeDeleteModalAndRefresh(true);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
deleteLoading: false,
|
||||
deleteError: err
|
||||
deleteError: err,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import api from "../../../../common/api";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
@@ -25,14 +26,16 @@ import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapp
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
color: "red",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
interface ISetAccessPolicyProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
bucketName: string;
|
||||
actualPolicy: string;
|
||||
closeModalAndRefresh: () => void;
|
||||
}
|
||||
|
||||
@@ -49,7 +52,7 @@ class SetAccessPolicy extends React.Component<
|
||||
state: ISetAccessPolicyState = {
|
||||
addLoading: false,
|
||||
addError: "",
|
||||
accessPolicy: ""
|
||||
accessPolicy: "",
|
||||
};
|
||||
|
||||
addRecord(event: React.FormEvent) {
|
||||
@@ -62,30 +65,36 @@ class SetAccessPolicy extends React.Component<
|
||||
this.setState({ addLoading: true }, () => {
|
||||
api
|
||||
.invoke("PUT", `/api/v1/buckets/${bucketName}/set-policy`, {
|
||||
access: accessPolicy
|
||||
access: accessPolicy,
|
||||
})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
addError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
addError: err,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
@@ -105,34 +114,33 @@ class SetAccessPolicy extends React.Component<
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
<SelectWrapper
|
||||
value={accessPolicy}
|
||||
label="Access Policy"
|
||||
id="select-access-policy"
|
||||
name="select-access-policy"
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ accessPolicy: e.target.value as string });
|
||||
}}
|
||||
options={[
|
||||
{ value: "PRIVATE", label: "Private" },
|
||||
{ value: "PUBLIC", label: "Public" },
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
value={accessPolicy}
|
||||
label="Access Policy"
|
||||
id="select-access-policy"
|
||||
name="select-access-policy"
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ accessPolicy: e.target.value as string });
|
||||
}}
|
||||
options={[
|
||||
{ value: "PRIVATE", label: "Private" },
|
||||
{ value: "PUBLIC", label: "Public" }
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
|
||||
@@ -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,54 +32,91 @@ 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({
|
||||
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",
|
||||
},
|
||||
noRecords: {
|
||||
lineHeight: "24px",
|
||||
textAlign: "center",
|
||||
padding: "20px"
|
||||
}
|
||||
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,20 +150,25 @@ 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,
|
||||
addScreenOpen: false,
|
||||
deleteOpen: false,
|
||||
selectedBucket: "",
|
||||
selectedEvent: null
|
||||
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,10 +179,10 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
const total = get(res, "total", 0);
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
loadingEvents: false,
|
||||
records: events || [],
|
||||
totalRecords: total,
|
||||
error: ""
|
||||
error: "",
|
||||
});
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if ((!events || res.events.length === 0) && page > 0) {
|
||||
@@ -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
|
||||
selectedEvent,
|
||||
bucketSize,
|
||||
loadingSize,
|
||||
} = this.state;
|
||||
|
||||
const offset = page * rowsPerPage;
|
||||
@@ -228,21 +315,27 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<AddEvent
|
||||
open={addScreenOpen}
|
||||
selectedBucket={bucketName}
|
||||
closeModalAndRefresh={() => {
|
||||
this.setState({ addScreenOpen: false });
|
||||
this.fetchEvents();
|
||||
}}
|
||||
/>
|
||||
<SetAccessPolicy
|
||||
bucketName={bucketName}
|
||||
open={setAccessPolicyScreenOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
this.closeAddModalAndRefresh();
|
||||
}}
|
||||
/>
|
||||
{addScreenOpen && (
|
||||
<AddEvent
|
||||
open={addScreenOpen}
|
||||
selectedBucket={bucketName}
|
||||
closeModalAndRefresh={() => {
|
||||
this.setState({ addScreenOpen: false });
|
||||
this.fetchEvents();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{setAccessPolicyScreenOpen && (
|
||||
<SetAccessPolicy
|
||||
bucketName={bucketName}
|
||||
open={setAccessPolicyScreenOpen}
|
||||
actualPolicy={accessPolicy}
|
||||
closeModalAndRefresh={() => {
|
||||
this.closeAddModalAndRefresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">
|
||||
@@ -253,39 +346,78 @@ 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
|
||||
addScreenOpen: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -303,12 +435,12 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
{
|
||||
label: "Events",
|
||||
elementKey: "events",
|
||||
renderFunction: eventsDisplay
|
||||
renderFunction: eventsDisplay,
|
||||
},
|
||||
{ label: "Prefix", elementKey: "prefix" },
|
||||
{ label: "Suffix", elementKey: "suffix" }
|
||||
{ label: "Suffix", elementKey: "suffix" },
|
||||
]}
|
||||
isLoading={loading}
|
||||
isLoading={loadingEvents}
|
||||
records={filteredRecords}
|
||||
entityName="Events"
|
||||
idField="id"
|
||||
@@ -320,11 +452,11 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -19,7 +19,7 @@ import Grid from "@material-ui/core/Grid";
|
||||
import get from "lodash/get";
|
||||
import InputBoxWrapper from "../InputBoxWrapper/InputBoxWrapper";
|
||||
import { InputLabel, Tooltip } from "@material-ui/core";
|
||||
import { fieldBasic } from "../common/styleLibrary";
|
||||
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
interface ICSVMultiSelector {
|
||||
@@ -34,9 +34,10 @@ interface ICSVMultiSelector {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
inputLabel: {
|
||||
...fieldBasic.inputLabel,
|
||||
marginBottom: 10
|
||||
width: 116,
|
||||
},
|
||||
inputContainer: {
|
||||
height: 150,
|
||||
@@ -44,11 +45,10 @@ const styles = (theme: Theme) =>
|
||||
padding: 15,
|
||||
position: "relative",
|
||||
border: "1px solid #c4c4c4",
|
||||
marginBottom: 10
|
||||
},
|
||||
labelContainer: {
|
||||
display: "flex"
|
||||
}
|
||||
display: "flex",
|
||||
},
|
||||
});
|
||||
|
||||
const CSVMultiSelector = ({
|
||||
@@ -57,7 +57,7 @@ const CSVMultiSelector = ({
|
||||
label,
|
||||
tooltip = "",
|
||||
onChange,
|
||||
classes
|
||||
classes,
|
||||
}: ICSVMultiSelector) => {
|
||||
const [currentElements, setCurrentElements] = useState<string[]>([""]);
|
||||
const bottomList = createRef<HTMLDivElement>();
|
||||
@@ -80,7 +80,7 @@ const CSVMultiSelector = ({
|
||||
// Use effect to send new values to onChange
|
||||
useEffect(() => {
|
||||
const elementsString = currentElements
|
||||
.filter(element => element.trim() !== "")
|
||||
.filter((element) => element.trim() !== "")
|
||||
.join(",");
|
||||
onChange(elementsString);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -129,18 +129,20 @@ const CSVMultiSelector = ({
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
<InputLabel className={classes.inputLabel}>{label}</InputLabel>
|
||||
<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} className={classes.inputContainer}>
|
||||
{inputs}
|
||||
<div ref={bottomList} />
|
||||
</Grid>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="left">
|
||||
<HelpIcon />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
@@ -19,16 +19,16 @@ import {
|
||||
InputLabel,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
} from "@material-ui/core";
|
||||
import { OutlinedInputProps } from "@material-ui/core/OutlinedInput";
|
||||
import {
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Theme,
|
||||
withStyles
|
||||
withStyles,
|
||||
} from "@material-ui/core/styles";
|
||||
import { fieldBasic } from "../common/styleLibrary";
|
||||
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
interface InputBoxProps {
|
||||
@@ -44,27 +44,43 @@ interface InputBoxProps {
|
||||
tooltip?: string;
|
||||
autoComplete?: string;
|
||||
index?: number;
|
||||
error?: string;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
textBoxContainer: {
|
||||
flexGrow: 1
|
||||
}
|
||||
flexGrow: 1,
|
||||
position: "relative",
|
||||
},
|
||||
errorState: {
|
||||
color: "#b53b4b",
|
||||
fontSize: 14,
|
||||
position: "absolute",
|
||||
top: 7,
|
||||
right: 7,
|
||||
},
|
||||
});
|
||||
|
||||
const inputStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
borderColor: "#393939",
|
||||
borderRadius: 0
|
||||
borderRadius: 0,
|
||||
},
|
||||
input: {
|
||||
padding: "11px 20px",
|
||||
color: "#393939",
|
||||
fontSize: 14
|
||||
}
|
||||
fontSize: 14,
|
||||
},
|
||||
error: {
|
||||
color: "#b53b4b",
|
||||
boxShadow: "inset 0px 0px 1px 1px #b53b4b",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -91,19 +107,43 @@ const InputBoxWrapper = ({
|
||||
multiline = false,
|
||||
tooltip = "",
|
||||
index = 0,
|
||||
classes
|
||||
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}>
|
||||
{label}
|
||||
<InputLabel
|
||||
htmlFor={id}
|
||||
className={`${error !== "" ? classes.fieldLabelError : ""} ${
|
||||
classes.inputLabel
|
||||
}`}
|
||||
>
|
||||
<span>
|
||||
{label}
|
||||
{required ? "*" : ""}
|
||||
</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
)}
|
||||
|
||||
<div className={classes.textBoxContainer}>
|
||||
<InputField
|
||||
className={classes.boxDesign}
|
||||
id={id}
|
||||
name={name}
|
||||
variant="outlined"
|
||||
@@ -115,15 +155,11 @@ const InputBoxWrapper = ({
|
||||
multiline={multiline}
|
||||
autoComplete={autoComplete}
|
||||
inputProps={{ "data-index": index }}
|
||||
error={error !== ""}
|
||||
helperText={error}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="left">
|
||||
<HelpIcon />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -23,9 +23,9 @@ import {
|
||||
createStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
makeStyles
|
||||
makeStyles,
|
||||
} from "@material-ui/core/styles";
|
||||
import { fieldBasic } from "../common/styleLibrary";
|
||||
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
export interface SelectorTypes {
|
||||
@@ -48,22 +48,23 @@ interface RadioGroupProps {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
radioBoxContainer: {
|
||||
flexGrow: 1
|
||||
}
|
||||
flexGrow: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const radioStyles = makeStyles({
|
||||
root: {
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent"
|
||||
}
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
borderRadius: "100%",
|
||||
width: 14,
|
||||
height: 14,
|
||||
border: "1px solid #000"
|
||||
border: "1px solid #000",
|
||||
},
|
||||
checkedIcon: {
|
||||
borderRadius: "100%",
|
||||
@@ -81,9 +82,9 @@ const radioStyles = makeStyles({
|
||||
position: "absolute",
|
||||
backgroundColor: "#000",
|
||||
top: 2,
|
||||
left: 2
|
||||
}
|
||||
}
|
||||
left: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const RadioButton = (props: RadioProps) => {
|
||||
@@ -110,14 +111,22 @@ export const RadioGroupSelector = ({
|
||||
onChange,
|
||||
tooltip = "",
|
||||
classes,
|
||||
displayInColumn = false
|
||||
displayInColumn = false,
|
||||
}: RadioGroupProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
{label}
|
||||
<span>{label}</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
|
||||
<div className={classes.radioBoxContainer}>
|
||||
<RadioGroup
|
||||
aria-label={id}
|
||||
@@ -127,7 +136,7 @@ export const RadioGroupSelector = ({
|
||||
onChange={onChange}
|
||||
row={!displayInColumn}
|
||||
>
|
||||
{selectorOptions.map(selectorOption => {
|
||||
{selectorOptions.map((selectorOption) => {
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={`rd-${name}-${selectorOption.value}`}
|
||||
@@ -139,13 +148,6 @@ export const RadioGroupSelector = ({
|
||||
})}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{tooltip !== "" && (
|
||||
<div>
|
||||
<Tooltip title={tooltip} placement="left">
|
||||
<HelpIcon />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -20,10 +20,12 @@ import {
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
InputBase
|
||||
InputBase,
|
||||
Tooltip,
|
||||
} from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { fieldBasic } from "../common/styleLibrary";
|
||||
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
interface selectorTypes {
|
||||
label: string;
|
||||
@@ -36,6 +38,7 @@ interface SelectProps {
|
||||
label: string;
|
||||
id: string;
|
||||
name: string;
|
||||
tooltip?: string;
|
||||
onChange: (
|
||||
e: React.ChangeEvent<{ name?: string | undefined; value: unknown }>
|
||||
) => void;
|
||||
@@ -44,15 +47,20 @@ interface SelectProps {
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
inputLabel: {
|
||||
...fieldBasic.inputLabel,
|
||||
width: 116,
|
||||
},
|
||||
});
|
||||
|
||||
const SelectStyled = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
"label + &": {
|
||||
marginTop: theme.spacing(3)
|
||||
}
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
},
|
||||
input: {
|
||||
borderRadius: 0,
|
||||
@@ -62,12 +70,12 @@ const SelectStyled = withStyles((theme: Theme) =>
|
||||
padding: "11px 20px",
|
||||
border: "1px solid #c4c4c4",
|
||||
"&:hover": {
|
||||
borderColor: "#393939"
|
||||
borderColor: "#393939",
|
||||
},
|
||||
"&:focus": {
|
||||
backgroundColor: "#fff"
|
||||
}
|
||||
}
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
},
|
||||
})
|
||||
)(InputBase);
|
||||
|
||||
@@ -78,14 +86,24 @@ const SelectWrapper = ({
|
||||
onChange,
|
||||
options,
|
||||
label,
|
||||
value
|
||||
tooltip = "",
|
||||
value,
|
||||
}: SelectProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
{label}
|
||||
</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}
|
||||
@@ -94,7 +112,7 @@ const SelectWrapper = ({
|
||||
onChange={onChange}
|
||||
input={<SelectStyled />}
|
||||
>
|
||||
{options.map(option => (
|
||||
{options.map((option) => (
|
||||
<MenuItem
|
||||
value={option.value}
|
||||
key={`select-${name}-${option.label}`}
|
||||
|
||||
@@ -19,17 +19,63 @@
|
||||
export const fieldBasic = {
|
||||
inputLabel: {
|
||||
fontWeight: 500,
|
||||
marginRight: 16,
|
||||
minWidth: 90,
|
||||
marginRight: 10,
|
||||
width: 100,
|
||||
fontSize: 14,
|
||||
color: "#393939"
|
||||
color: "#393939",
|
||||
textAlign: "right" as const,
|
||||
display: "flex",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
justifyContent: "flex-end",
|
||||
"& span": {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
},
|
||||
fieldLabelError: {
|
||||
paddingBottom: 22,
|
||||
},
|
||||
fieldContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: 10
|
||||
marginBottom: 10,
|
||||
},
|
||||
tooltipContainer: {
|
||||
marginLeft: 5
|
||||
}
|
||||
marginLeft: 5,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
};
|
||||
|
||||
export const modalBasic = {
|
||||
formScrollable: {
|
||||
maxHeight: "calc(100vh - 300px)" as const,
|
||||
overflowY: "auto" as const,
|
||||
marginBottom: 25,
|
||||
},
|
||||
formSlider: {
|
||||
marginLeft: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export const tooltipHelper = {
|
||||
tooltip: {
|
||||
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;
|
||||
@@ -26,11 +26,13 @@ import {
|
||||
TableRow,
|
||||
Paper,
|
||||
Grid,
|
||||
Checkbox
|
||||
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
|
||||
|
||||
@@ -84,25 +86,20 @@ const borderColor = "#eaeaea";
|
||||
const rowText = {
|
||||
fontWeight: 400,
|
||||
fontSize: 14,
|
||||
borderColor: borderColor
|
||||
};
|
||||
|
||||
const checkBoxBasic = {
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 3
|
||||
borderColor: borderColor,
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
dialogContainer: {
|
||||
padding: "12px 26px 22px"
|
||||
padding: "12px 26px 22px",
|
||||
},
|
||||
paper: {
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column",
|
||||
padding: "19px 38px"
|
||||
padding: "19px 38px",
|
||||
minHeight: "200px",
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
@@ -111,43 +108,42 @@ const styles = (theme: Theme) =>
|
||||
fontWeight: 700,
|
||||
fontSize: 14,
|
||||
paddingBottom: 15,
|
||||
borderColor: borderColor
|
||||
}
|
||||
}
|
||||
borderColor: borderColor,
|
||||
},
|
||||
},
|
||||
},
|
||||
rowUnselected: {
|
||||
...rowText
|
||||
...rowText,
|
||||
},
|
||||
rowSelected: {
|
||||
...rowText,
|
||||
color: "#201763"
|
||||
color: "#201763",
|
||||
},
|
||||
paginatorContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
padding: "5px 38px"
|
||||
padding: "5px 38px",
|
||||
},
|
||||
checkBoxHeader: {
|
||||
"&.MuiTableCell-paddingCheckbox": {
|
||||
paddingBottom: 9
|
||||
}
|
||||
paddingBottom: 9,
|
||||
},
|
||||
},
|
||||
actionsContainer: {
|
||||
width: 120,
|
||||
borderColor: borderColor
|
||||
width: 150,
|
||||
borderColor: borderColor,
|
||||
},
|
||||
paginatorComponent: {
|
||||
borderBottom: 0
|
||||
},
|
||||
unCheckedIcon: { ...checkBoxBasic, border: "1px solid #d0d0d0" },
|
||||
checkedIcon: {
|
||||
...checkBoxBasic,
|
||||
border: "1px solid #201763",
|
||||
backgroundColor: "#201763"
|
||||
borderBottom: 0,
|
||||
},
|
||||
checkBoxRow: {
|
||||
borderColor: borderColor
|
||||
}
|
||||
borderColor: borderColor,
|
||||
},
|
||||
loadingBox: {
|
||||
paddingTop: "100px",
|
||||
paddingBottom: "100px",
|
||||
},
|
||||
...checkboxIcons,
|
||||
});
|
||||
|
||||
// Function that renders Title Columns
|
||||
@@ -221,12 +217,21 @@ const TableWrapper = ({
|
||||
idField,
|
||||
classes,
|
||||
stickyHeader = false,
|
||||
paginatorConfig
|
||||
paginatorConfig,
|
||||
}: TableWrapperProps) => {
|
||||
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 && (
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { IElementValue, KVField } from "./types";
|
||||
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
import { IElementValue, KVField } from "./types";
|
||||
import CSVMultiSelector from "../Common/FormComponents/CSVMultiSelector/CSVMultiSelector";
|
||||
|
||||
interface IConfGenericProps {
|
||||
@@ -29,7 +30,10 @@ interface IConfGenericProps {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
// Function to get defined values,
|
||||
//we make this because the backed sometimes don't return all the keys when there is an initial configuration
|
||||
@@ -41,7 +45,7 @@ export const valueDef = (
|
||||
let defValue = type === "on|off" ? "false" : "";
|
||||
|
||||
if (defaults.length > 0) {
|
||||
const storedConfig = defaults.find(element => element.key === key);
|
||||
const storedConfig = defaults.find((element) => element.key === key);
|
||||
|
||||
if (storedConfig) {
|
||||
defValue = storedConfig.value;
|
||||
@@ -55,7 +59,7 @@ const ConfTargetGeneric = ({
|
||||
onChange,
|
||||
fields,
|
||||
defaultVals,
|
||||
classes
|
||||
classes,
|
||||
}: IConfGenericProps) => {
|
||||
const [valueHolder, setValueHolder] = useState<IElementValue[]>([]);
|
||||
const fieldsElements = !fields ? [] : fields;
|
||||
@@ -64,10 +68,10 @@ const ConfTargetGeneric = ({
|
||||
// Effect to create all the values to hold
|
||||
useEffect(() => {
|
||||
const values: IElementValue[] = [];
|
||||
fields.forEach(field => {
|
||||
fields.forEach((field) => {
|
||||
const stateInsert: IElementValue = {
|
||||
key: field.name,
|
||||
value: valueDef(field.name, field.type, defValList)
|
||||
value: valueDef(field.name, field.type, defValList),
|
||||
};
|
||||
values.push(stateInsert);
|
||||
});
|
||||
@@ -102,7 +106,7 @@ const ConfTargetGeneric = ({
|
||||
}
|
||||
selectorOptions={[
|
||||
{ label: "On", value: "true" },
|
||||
{ label: "Off", value: "false" }
|
||||
{ label: "Off", value: "false" },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
@@ -137,18 +141,14 @@ const ConfTargetGeneric = ({
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
{fieldsElements.map((field, item) => (
|
||||
<React.Fragment key={field.name}>
|
||||
<Grid item xs={12}>
|
||||
{fieldDefinition(field, item)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
<Grid xs={12} item className={classes.formScrollable}>
|
||||
{fieldsElements.map((field, item) => (
|
||||
<React.Fragment key={field.name}>
|
||||
<Grid item xs={12}>
|
||||
{fieldDefinition(field, item)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -35,36 +35,36 @@ interface IListConfiguration {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
fontWeight: 700,
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
marginLeft: 5,
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
iconText: {
|
||||
lineHeight: "24px"
|
||||
}
|
||||
lineHeight: "24px",
|
||||
},
|
||||
});
|
||||
|
||||
const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
const [editScreenOpen, setEditScreenOpen] = useState(false);
|
||||
const [selectedConfiguration, setSelectedConfiguration] = useState({
|
||||
configuration_id: "",
|
||||
configuration_label: ""
|
||||
configuration_label: "",
|
||||
});
|
||||
const [error, setError] = useState("");
|
||||
const [filter, setFilter] = useState("");
|
||||
@@ -81,12 +81,12 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
setSelectedConfiguration(element);
|
||||
setEditScreenOpen(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const filteredRecords: IConfigurationElement[] = configurationElements.filter(
|
||||
elementItem =>
|
||||
(elementItem) =>
|
||||
elementItem.configuration_id
|
||||
.toLocaleLowerCase()
|
||||
.includes(filter.toLocaleLowerCase())
|
||||
@@ -117,7 +117,7 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={event => {
|
||||
onChange={(event) => {
|
||||
setFilter(event.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
@@ -126,7 +126,7 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -137,7 +137,7 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Configuration", elementKey: "configuration_id" }
|
||||
{ label: "Configuration", elementKey: "configuration_id" },
|
||||
]}
|
||||
isLoading={false}
|
||||
records={filteredRecords}
|
||||
|
||||
@@ -44,29 +44,29 @@ interface IWebhook {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
fontWeight: 700,
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
marginLeft: 5,
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
iconText: {
|
||||
lineHeight: "24px"
|
||||
}
|
||||
lineHeight: "24px",
|
||||
},
|
||||
});
|
||||
|
||||
const panels = {
|
||||
@@ -77,8 +77,8 @@ const panels = {
|
||||
apiURL: "",
|
||||
configuration: {
|
||||
configuration_id: "logger_webhook",
|
||||
configuration_label: "Logger Webhook"
|
||||
}
|
||||
configuration_label: "Logger Webhook",
|
||||
},
|
||||
},
|
||||
audit: {
|
||||
main: "audit",
|
||||
@@ -87,9 +87,9 @@ const panels = {
|
||||
apiURL: "",
|
||||
configuration: {
|
||||
configuration_id: "audit_webhook",
|
||||
configuration_label: "Audit Webhook"
|
||||
}
|
||||
}
|
||||
configuration_label: "Audit Webhook",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
|
||||
@@ -107,15 +107,15 @@ const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filteredRecords: IWebhook[] = webhooks.filter(elementItem =>
|
||||
const filteredRecords: IWebhook[] = webhooks.filter((elementItem) =>
|
||||
elementItem.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())
|
||||
);
|
||||
|
||||
const tableActions = [
|
||||
{
|
||||
type: "edit",
|
||||
onClick: () => {}
|
||||
}
|
||||
onClick: () => {},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -144,7 +144,7 @@ const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={event => {
|
||||
onChange={(event) => {
|
||||
setFilter(event.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
@@ -153,7 +153,7 @@ const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -21,13 +21,17 @@ import Grid from "@material-ui/core/Grid";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
import { IElementValue } from "../types";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
interface IConfMySqlProps {
|
||||
onChange: (newValue: IElementValue[]) => void;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
//Local States
|
||||
@@ -88,7 +92,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
{ key: "format", value: format },
|
||||
{ key: "queue_dir", value: queueDir },
|
||||
{ key: "queue_limit", value: queueLimit },
|
||||
{ key: "comment", value: comment }
|
||||
{ key: "comment", value: comment },
|
||||
];
|
||||
|
||||
onChange(formValues);
|
||||
@@ -101,7 +105,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
}, [user, dbName, password, port, host, setDsnString, configToDsnString]);
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid container className={classes.formScrollable}>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
@@ -119,7 +123,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
"port",
|
||||
"dbname",
|
||||
"user",
|
||||
"password"
|
||||
"password",
|
||||
]);
|
||||
setHostname(kv.get("host") ? kv.get("host") + "" : "");
|
||||
setPort(kv.get("port") ? kv.get("port") + "" : "");
|
||||
@@ -137,6 +141,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
/>
|
||||
}
|
||||
label="Enter DSN String"
|
||||
className={classes.formSlider}
|
||||
/>
|
||||
</Grid>
|
||||
{useDsnString ? (
|
||||
@@ -232,13 +237,13 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
id="format"
|
||||
name="format"
|
||||
label="Format"
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
setFormat(e.target.value);
|
||||
}}
|
||||
tooltip="'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'"
|
||||
selectorOptions={[
|
||||
{ label: "Namespace", value: "namespace" },
|
||||
{ label: "Access", value: "access" }
|
||||
{ label: "Access", value: "access" },
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -279,12 +284,6 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,13 +22,17 @@ import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBo
|
||||
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import { IElementValue } from "../types";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
interface IConfPostgresProps {
|
||||
onChange: (newValue: IElementValue[]) => void;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
//Local States
|
||||
@@ -136,7 +140,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
{ key: "format", value: format },
|
||||
{ key: "queue_dir", value: queueDir },
|
||||
{ key: "queue_limit", value: queueLimit },
|
||||
{ key: "comment", value: comment }
|
||||
{ key: "comment", value: comment },
|
||||
];
|
||||
|
||||
onChange(formValues);
|
||||
@@ -148,7 +152,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
queueDir,
|
||||
queueLimit,
|
||||
comment,
|
||||
onChange
|
||||
onChange,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -162,11 +166,11 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
sslMode,
|
||||
host,
|
||||
setConnectionString,
|
||||
configToString
|
||||
configToString,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid container className={classes.formScrollable}>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
@@ -185,7 +189,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
"dbname",
|
||||
"user",
|
||||
"password",
|
||||
"sslmode"
|
||||
"sslmode",
|
||||
]);
|
||||
setHostname(kv.get("host") ? kv.get("host") + "" : "");
|
||||
setPort(kv.get("port") ? kv.get("port") + "" : "");
|
||||
@@ -206,6 +210,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
/>
|
||||
}
|
||||
label="Enter Connection String"
|
||||
className={classes.formSlider}
|
||||
/>
|
||||
</Grid>
|
||||
{useConnectionString ? (
|
||||
@@ -273,7 +278,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
{ label: "Require", value: "require" },
|
||||
{ label: "Disable", value: "disable" },
|
||||
{ label: "Verify CA", value: "verify-ca" },
|
||||
{ label: "Verify Full", value: "verify-full" }
|
||||
{ label: "Verify Full", value: "verify-full" },
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -320,13 +325,13 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
id="format"
|
||||
name="format"
|
||||
label="Format"
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
setFormat(e.target.value);
|
||||
}}
|
||||
tooltip="'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'"
|
||||
selectorOptions={[
|
||||
{ label: "Namespace", value: "namespace" },
|
||||
{ label: "Access", value: "access" }
|
||||
{ label: "Access", value: "access" },
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -367,12 +372,6 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,35 +16,37 @@
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import get from "lodash/get";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import api from "../../../../common/api";
|
||||
import { serverNeedsRestart } from "../../../../actions";
|
||||
import { connect } from "react-redux";
|
||||
import ConfTargetGeneric from "../ConfTargetGeneric";
|
||||
import { serverNeedsRestart } from "../../../../actions";
|
||||
import { fieldBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { fieldsConfigurations, removeEmptyFields } from "../utils";
|
||||
import { IConfigurationElement, IElementValue } from "../types";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
fontWeight: 700,
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
marginLeft: 5,
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
textAlign: "right",
|
||||
},
|
||||
logoButton: {
|
||||
height: "80px"
|
||||
}
|
||||
height: "80px",
|
||||
},
|
||||
});
|
||||
|
||||
interface IAddNotificationEndpointProps {
|
||||
@@ -60,7 +62,7 @@ const EditConfiguration = ({
|
||||
closeModalAndRefresh,
|
||||
serverNeedsRestart,
|
||||
selectedConfiguration,
|
||||
classes
|
||||
classes,
|
||||
}: IAddNotificationEndpointProps) => {
|
||||
//Local States
|
||||
const [valuesObj, setValueObj] = useState<IElementValue[]>([]);
|
||||
@@ -75,11 +77,11 @@ const EditConfiguration = ({
|
||||
if (configId) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/configs/${configId}`)
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
const keyVals = get(res, "key_values", []);
|
||||
setConfigValues(keyVals);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
setLoadingConfig(false);
|
||||
setErrorConfig(err);
|
||||
});
|
||||
@@ -90,7 +92,7 @@ const EditConfiguration = ({
|
||||
useEffect(() => {
|
||||
if (saving) {
|
||||
const payload = {
|
||||
key_values: removeEmptyFields(valuesObj)
|
||||
key_values: removeEmptyFields(valuesObj),
|
||||
};
|
||||
api
|
||||
.invoke(
|
||||
@@ -98,14 +100,14 @@ const EditConfiguration = ({
|
||||
`/api/v1/configs/${selectedConfiguration.configuration_id}`,
|
||||
payload
|
||||
)
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
setSaving(false);
|
||||
setErrorConfig("");
|
||||
serverNeedsRestart(true);
|
||||
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
setSaving(false);
|
||||
setErrorConfig(err);
|
||||
});
|
||||
@@ -115,7 +117,7 @@ const EditConfiguration = ({
|
||||
serverNeedsRestart,
|
||||
selectedConfiguration,
|
||||
valuesObj,
|
||||
closeModalAndRefresh
|
||||
closeModalAndRefresh,
|
||||
]);
|
||||
|
||||
//Fetch Actions
|
||||
@@ -125,7 +127,7 @@ const EditConfiguration = ({
|
||||
};
|
||||
|
||||
const onValueChange = useCallback(
|
||||
newValue => {
|
||||
(newValue) => {
|
||||
setValueObj(newValue);
|
||||
},
|
||||
[setValueObj]
|
||||
@@ -157,12 +159,11 @@ const EditConfiguration = ({
|
||||
onChange={onValueChange}
|
||||
defaultVals={configValues}
|
||||
/>
|
||||
<Grid item xs={3} className={classes.buttonContainer}>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={saving}
|
||||
>
|
||||
Save
|
||||
|
||||
@@ -31,34 +31,30 @@ export const configurationElements: IConfigurationElement[] = [
|
||||
{ configuration_id: "cache", configuration_label: "Cache Configuration" },
|
||||
{
|
||||
configuration_id: "compression",
|
||||
configuration_label: "Compression Configuration"
|
||||
configuration_label: "Compression Configuration",
|
||||
},
|
||||
{ configuration_id: "etcd", configuration_label: "Etcd Configuration" },
|
||||
{
|
||||
configuration_id: "identity_openid",
|
||||
configuration_label: "Identity Openid Configuration"
|
||||
configuration_label: "Identity Openid Configuration",
|
||||
},
|
||||
{
|
||||
configuration_id: "identity_ldap",
|
||||
configuration_label: "Identity LDAP Configuration"
|
||||
},
|
||||
{
|
||||
configuration_id: "policy_opa",
|
||||
configuration_label: "Policy OPA Configuration"
|
||||
configuration_label: "Identity LDAP Configuration",
|
||||
},
|
||||
{
|
||||
configuration_id: "kms_vault",
|
||||
configuration_label: "KMS Vault Configuration"
|
||||
configuration_label: "KMS Vault Configuration",
|
||||
},
|
||||
{ configuration_id: "kms_kes", configuration_label: "KMS KES Configuration" },
|
||||
{
|
||||
configuration_id: "logger_webhook",
|
||||
configuration_label: "Logger Webhook Configuration"
|
||||
configuration_label: "Logger Webhook Configuration",
|
||||
},
|
||||
{
|
||||
configuration_id: "audit_webhook",
|
||||
configuration_label: "Audit Webhook Configuration"
|
||||
}
|
||||
configuration_label: "Audit Webhook Configuration",
|
||||
},
|
||||
];
|
||||
|
||||
export const fieldsConfigurations: any = {
|
||||
@@ -68,7 +64,7 @@ export const fieldsConfigurations: any = {
|
||||
required: true,
|
||||
label: "name",
|
||||
tooltip: 'Name of the location of the server e.g. "us-west-rack2"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
@@ -76,8 +72,8 @@ export const fieldsConfigurations: any = {
|
||||
label: "comment",
|
||||
tooltip: "You can add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
],
|
||||
cache: [
|
||||
{
|
||||
@@ -86,21 +82,21 @@ export const fieldsConfigurations: any = {
|
||||
label: "Drives",
|
||||
tooltip:
|
||||
'Mountpoints e.g. "/optane1" or "/optane2", you can write one per field',
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "expiry",
|
||||
required: false,
|
||||
label: "Expiry",
|
||||
tooltip: 'Cache expiry duration in days e.g. "90"',
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "quota",
|
||||
required: false,
|
||||
label: "Quota",
|
||||
tooltip: 'Limit cache drive usage in percentage e.g. "90"',
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "exclude",
|
||||
@@ -108,28 +104,28 @@ export const fieldsConfigurations: any = {
|
||||
label: "Exclude",
|
||||
tooltip:
|
||||
'Wildcard exclusion patterns e.g. "bucket/*.tmp" or "*.exe", you can write one per field',
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "after",
|
||||
required: false,
|
||||
label: "After",
|
||||
tooltip: "Minimum number of access before caching an object",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "watermark_low",
|
||||
required: false,
|
||||
label: "Watermark Low",
|
||||
tooltip: "Watermark Low",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "watermark_high",
|
||||
required: false,
|
||||
label: "Watermark High",
|
||||
tooltip: "Watermark High",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
@@ -137,8 +133,8 @@ export const fieldsConfigurations: any = {
|
||||
label: "Comment",
|
||||
tooltip: "You can add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
],
|
||||
compression: [
|
||||
{
|
||||
@@ -147,7 +143,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "Extensions",
|
||||
tooltip:
|
||||
'Extensions to compress e.g. ".txt",".log" or ".csv", you can write one per field',
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "mime_types",
|
||||
@@ -155,8 +151,8 @@ export const fieldsConfigurations: any = {
|
||||
label: "Mime Types",
|
||||
tooltip:
|
||||
'Mime types e.g. "text/*","application/json" or "application/xml", you can write one per field',
|
||||
type: "csv"
|
||||
}
|
||||
type: "csv",
|
||||
},
|
||||
],
|
||||
etcd: [
|
||||
{
|
||||
@@ -165,35 +161,35 @@ export const fieldsConfigurations: any = {
|
||||
label: "Endpoints",
|
||||
tooltip:
|
||||
'List of etcd endpoints e.g. "http://localhost:2379", you can write one per field',
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "path_prefix",
|
||||
required: false,
|
||||
label: "Path Prefix",
|
||||
tooltip: 'namespace prefix to isolate tenants e.g. "customer1/"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "coredns_path",
|
||||
required: false,
|
||||
label: "Coredns Path",
|
||||
tooltip: 'Shared bucket DNS records, default is "/skydns"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "client_cert",
|
||||
required: false,
|
||||
label: "Client Cert",
|
||||
tooltip: "Client cert for mTLS authentication",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "client_cert_key",
|
||||
required: false,
|
||||
label: "Client Cert Key",
|
||||
tooltip: "Client cert key for mTLS authentication",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
@@ -201,8 +197,8 @@ export const fieldsConfigurations: any = {
|
||||
label: "Comment",
|
||||
tooltip: "You can add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
],
|
||||
identity_openid: [
|
||||
{
|
||||
@@ -210,28 +206,28 @@ export const fieldsConfigurations: any = {
|
||||
required: false,
|
||||
label: "Config URL",
|
||||
tooltip: "Config URL for Client ID configuration",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "client_id",
|
||||
required: false,
|
||||
label: "Client ID",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "claim_name",
|
||||
required: false,
|
||||
label: "Claim Name",
|
||||
tooltip: "Claim Name",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "claim_prefix",
|
||||
required: false,
|
||||
label: "Claim Prefix",
|
||||
tooltip: "Claim Prefix",
|
||||
type: "string"
|
||||
}
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
identity_ldap: [
|
||||
{
|
||||
@@ -239,7 +235,7 @@ export const fieldsConfigurations: any = {
|
||||
required: true,
|
||||
label: "Server ADDR",
|
||||
tooltip: 'AD/LDAP server address e.g. "myldapserver.com:636"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "username_format",
|
||||
@@ -247,7 +243,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "Username Format",
|
||||
tooltip:
|
||||
'List of username bind DNs e.g. "uid=%s","cn=accounts","dc=myldapserver" or "dc=com", you can write one per field',
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "username_search_filter",
|
||||
@@ -255,7 +251,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "Username Search Filter",
|
||||
tooltip:
|
||||
'User search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "group_search_filter",
|
||||
@@ -263,21 +259,21 @@ export const fieldsConfigurations: any = {
|
||||
label: "Group Search Filter",
|
||||
tooltip:
|
||||
'Search filter for groups e.g. "(&(objectclass=groupOfNames)(memberUid=%s))"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "username_search_base_dn",
|
||||
required: false,
|
||||
label: "Username Search Base DN",
|
||||
tooltip: "List of username search DNs, you can write one per field",
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "group_name_attribute",
|
||||
required: false,
|
||||
label: "Group Name Attribute",
|
||||
tooltip: 'Search attribute for group name e.g. "cn"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "sts_expiry",
|
||||
@@ -285,7 +281,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "STS Expiry",
|
||||
tooltip:
|
||||
'temporary credentials validity duration in s,m,h,d. Default is "1h"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
@@ -293,7 +289,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "TLS Skip Verify",
|
||||
tooltip:
|
||||
'Trust server TLS without verification, defaults to "off" (verify)',
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "server_insecure",
|
||||
@@ -301,7 +297,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "Server Insecure",
|
||||
tooltip:
|
||||
'Allow plain text connection to AD/LDAP server, defaults to "off"',
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
@@ -309,28 +305,8 @@ export const fieldsConfigurations: any = {
|
||||
label: "Comment",
|
||||
tooltip: "Optionally add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
],
|
||||
policy_opa: [
|
||||
{
|
||||
name: "url",
|
||||
required: true,
|
||||
label: "OPA URL",
|
||||
type: "string"
|
||||
multiline: true,
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
required: true,
|
||||
label: "Auth Token",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "policy_opa",
|
||||
required: true,
|
||||
label: "Policy OPA",
|
||||
type: "string"
|
||||
}
|
||||
],
|
||||
kms_vault: [],
|
||||
kms_kes: [],
|
||||
@@ -339,29 +315,29 @@ export const fieldsConfigurations: any = {
|
||||
name: "endpoint",
|
||||
required: true,
|
||||
label: "Endpoint",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
required: true,
|
||||
label: "Auth Token",
|
||||
type: "string"
|
||||
}
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
audit_webhook: [
|
||||
{
|
||||
name: "endpoint",
|
||||
required: true,
|
||||
label: "Endpoint",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
required: true,
|
||||
label: "Auth Token",
|
||||
type: "string"
|
||||
}
|
||||
]
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const commonFields = [
|
||||
@@ -371,7 +347,7 @@ const commonFields = [
|
||||
required: true,
|
||||
|
||||
tooltip: "staging dir for undelivered messages e.g. '/home/events'",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "queue-limit",
|
||||
@@ -379,15 +355,15 @@ const commonFields = [
|
||||
required: false,
|
||||
|
||||
tooltip: "maximum limit for undelivered messages, defaults to '10000'",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
label: "Comment",
|
||||
required: false,
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const notificationEndpointsFields: any = {
|
||||
@@ -398,77 +374,77 @@ export const notificationEndpointsFields: any = {
|
||||
required: true,
|
||||
|
||||
tooltip: "comma separated list of Kafka broker addresses",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "topic",
|
||||
label: "Topic",
|
||||
tooltip: "Kafka topic used for bucket notifications",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "sasl_username",
|
||||
label: "SASL Username",
|
||||
tooltip: "username for SASL/PLAIN or SASL/SCRAM authentication",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "sasl_password",
|
||||
label: "SASL Password",
|
||||
tooltip: "password for SASL/PLAIN or SASL/SCRAM authentication",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "sasl_mechanism",
|
||||
label: "SASL Mechanism",
|
||||
tooltip: "sasl authentication mechanism, default 'PLAIN'",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "tls_client_auth",
|
||||
label: "TLS Client Auth",
|
||||
tooltip:
|
||||
"clientAuth determines the Kafka server's policy for TLS client auth",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "sasl",
|
||||
label: "SASL",
|
||||
tooltip: "set to 'on' to enable SASL authentication",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "tls",
|
||||
label: "TLS",
|
||||
tooltip: "set to 'on' to enable TLS",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
label: "TLS skip verify",
|
||||
tooltip:
|
||||
'trust server TLS without verification, defaults to "on" (verify)',
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "client_tls_cert",
|
||||
label: "client TLS cert",
|
||||
tooltip: "path to client certificate for mTLS auth",
|
||||
type: "path"
|
||||
type: "path",
|
||||
},
|
||||
{
|
||||
name: "client_tls_key",
|
||||
label: "client TLS key",
|
||||
tooltip: "path to client key for mTLS auth",
|
||||
type: "path"
|
||||
type: "path",
|
||||
},
|
||||
{
|
||||
name: "version",
|
||||
label: "Version",
|
||||
tooltip: "specify the version of the Kafka cluster e.g '2.2.0'",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyAmqp]: [
|
||||
{
|
||||
@@ -477,68 +453,68 @@ export const notificationEndpointsFields: any = {
|
||||
label: "url",
|
||||
tooltip:
|
||||
"AMQP server endpoint e.g. `amqp://myuser:mypassword@localhost:5672`",
|
||||
type: "url"
|
||||
type: "url",
|
||||
},
|
||||
{
|
||||
name: "exchange",
|
||||
label: "exchange",
|
||||
tooltip: "name of the AMQP exchange",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "exchange_type",
|
||||
label: "exchange_type",
|
||||
tooltip: "AMQP exchange type",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "routing_key",
|
||||
label: "routing_key",
|
||||
tooltip: "routing key for publishing",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "mandatory",
|
||||
label: "mandatory",
|
||||
tooltip:
|
||||
"quietly ignore undelivered messages when set to 'off', default is 'on'",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "durable",
|
||||
label: "durable",
|
||||
tooltip:
|
||||
"persist queue across broker restarts when set to 'on', default is 'off'",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "no_wait",
|
||||
label: "no_wait",
|
||||
tooltip:
|
||||
"non-blocking message delivery when set to 'on', default is 'off'",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "internal",
|
||||
label: "internal",
|
||||
tooltip:
|
||||
"set to 'on' for exchange to be not used directly by publishers, but only when bound to other exchanges",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "auto_deleted",
|
||||
label: "auto_deleted",
|
||||
tooltip:
|
||||
"auto delete queue when set to 'on', when there are no consumers",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "delivery_mode",
|
||||
label: "delivery_mode",
|
||||
tooltip: "set to '1' for non-persistent or '2' for persistent queue",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyRedis]: [
|
||||
{
|
||||
@@ -546,22 +522,22 @@ export const notificationEndpointsFields: any = {
|
||||
required: true,
|
||||
label: "address",
|
||||
tooltip: "Redis server's address. For example: `localhost:6379`",
|
||||
type: "address"
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
name: "key",
|
||||
required: true,
|
||||
label: "key",
|
||||
tooltip: "Redis key to store/update events, key is auto-created",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
label: "password",
|
||||
tooltip: "Redis server password",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyMqtt]: [
|
||||
{
|
||||
@@ -569,46 +545,46 @@ export const notificationEndpointsFields: any = {
|
||||
required: true,
|
||||
label: "broker",
|
||||
tooltip: "MQTT server endpoint e.g. `tcp://localhost:1883`",
|
||||
type: "uri"
|
||||
type: "uri",
|
||||
},
|
||||
{
|
||||
name: "topic",
|
||||
required: true,
|
||||
label: "topic",
|
||||
tooltip: "name of the MQTT topic to publish",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "username",
|
||||
label: "username",
|
||||
tooltip: "MQTT username",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
label: "password",
|
||||
tooltip: "MQTT password",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "qos",
|
||||
label: "qos",
|
||||
tooltip: "set the quality of service priority, defaults to '0'",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "keep_alive_interval",
|
||||
label: "keep_alive_interval",
|
||||
tooltip: "keep-alive interval for MQTT connections in s,m,h,d",
|
||||
type: "duration"
|
||||
type: "duration",
|
||||
},
|
||||
{
|
||||
name: "reconnect_interval",
|
||||
label: "reconnect_interval",
|
||||
tooltip: "reconnect interval for MQTT connections in s,m,h,d",
|
||||
type: "duration"
|
||||
type: "duration",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyNats]: [
|
||||
{
|
||||
@@ -616,95 +592,95 @@ export const notificationEndpointsFields: any = {
|
||||
required: true,
|
||||
label: "address",
|
||||
tooltip: "NATS server address e.g. '0.0.0.0:4222'",
|
||||
type: "address"
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
name: "subject",
|
||||
required: true,
|
||||
label: "subject",
|
||||
tooltip: "NATS subscription subject",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "username",
|
||||
label: "username",
|
||||
tooltip: "NATS username",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
label: "password",
|
||||
tooltip: "NATS password",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "token",
|
||||
label: "token",
|
||||
tooltip: "NATS token",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "tls",
|
||||
label: "tls",
|
||||
tooltip: "set to 'on' to enable TLS",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
label: "tls_skip_verify",
|
||||
tooltip:
|
||||
'trust server TLS without verification, defaults to "on" (verify)',
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "ping_interval",
|
||||
label: "ping_interval",
|
||||
tooltip: "client ping commands interval in s,m,h,d. Disabled by default",
|
||||
type: "duration"
|
||||
type: "duration",
|
||||
},
|
||||
{
|
||||
name: "streaming",
|
||||
label: "streaming",
|
||||
tooltip: "set to 'on', to use streaming NATS server",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "streaming_async",
|
||||
label: "streaming_async",
|
||||
tooltip: "set to 'on', to enable asynchronous publish",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "streaming_max_pub_acks_in_flight",
|
||||
label: "streaming_max_pub_acks_in_flight",
|
||||
tooltip: "number of messages to publish without waiting for ACKs",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "streaming_cluster_id",
|
||||
label: "streaming_cluster_id",
|
||||
tooltip: "unique ID for NATS streaming cluster",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "cert_authority",
|
||||
label: "cert_authority",
|
||||
tooltip: "path to certificate chain of the target NATS server",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "client_cert",
|
||||
label: "client_cert",
|
||||
tooltip: "client cert for NATS mTLS auth",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "client_key",
|
||||
label: "client_key",
|
||||
tooltip: "client cert key for NATS mTLS auth",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyElasticsearch]: [
|
||||
{
|
||||
@@ -713,7 +689,7 @@ export const notificationEndpointsFields: any = {
|
||||
label: "url",
|
||||
tooltip:
|
||||
"Elasticsearch server's address, with optional authentication info",
|
||||
type: "url"
|
||||
type: "url",
|
||||
},
|
||||
{
|
||||
name: "index",
|
||||
@@ -721,7 +697,7 @@ export const notificationEndpointsFields: any = {
|
||||
label: "index",
|
||||
tooltip:
|
||||
"Elasticsearch index to store/update events, index is auto-created",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "format",
|
||||
@@ -729,9 +705,9 @@ export const notificationEndpointsFields: any = {
|
||||
label: "format",
|
||||
tooltip:
|
||||
"'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'",
|
||||
type: "enum"
|
||||
type: "enum",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyWebhooks]: [
|
||||
{
|
||||
@@ -740,15 +716,15 @@ export const notificationEndpointsFields: any = {
|
||||
label: "endpoint",
|
||||
tooltip:
|
||||
"webhook server endpoint e.g. http://localhost:8080/minio/events",
|
||||
type: "url"
|
||||
type: "url",
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
label: "auth_token",
|
||||
tooltip: "opaque string or JWT authorization token",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyNsq]: [
|
||||
{
|
||||
@@ -756,34 +732,34 @@ export const notificationEndpointsFields: any = {
|
||||
required: true,
|
||||
label: "nsqd_address",
|
||||
tooltip: "NSQ server address e.g. '127.0.0.1:4150'",
|
||||
type: "address"
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
name: "topic",
|
||||
required: true,
|
||||
label: "topic",
|
||||
tooltip: "NSQ topic",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "tls",
|
||||
label: "tls",
|
||||
tooltip: "set to 'on' to enable TLS",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
label: "tls_skip_verify",
|
||||
tooltip:
|
||||
'trust server TLS without verification, defaults to "on" (verify)',
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
...commonFields
|
||||
]
|
||||
...commonFields,
|
||||
],
|
||||
};
|
||||
|
||||
export const removeEmptyFields = (formFields: IElementValue[]) => {
|
||||
const nonEmptyFields = formFields.filter(field => field.value !== "");
|
||||
const nonEmptyFields = formFields.filter((field) => field.value !== "");
|
||||
|
||||
return nonEmptyFields;
|
||||
};
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
createStyles,
|
||||
StyledProps,
|
||||
Theme,
|
||||
withStyles
|
||||
withStyles,
|
||||
} from "@material-ui/core/styles";
|
||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||
import Drawer from "@material-ui/core/Drawer";
|
||||
@@ -28,7 +28,6 @@ import Box from "@material-ui/core/Box";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Container from "@material-ui/core/Container";
|
||||
import Link from "@material-ui/core/Link";
|
||||
|
||||
import history from "../../history";
|
||||
import {
|
||||
Redirect,
|
||||
@@ -36,21 +35,21 @@ import {
|
||||
RouteComponentProps,
|
||||
Router,
|
||||
Switch,
|
||||
withRouter
|
||||
withRouter,
|
||||
} from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
import { AppState } from "../../store";
|
||||
import {
|
||||
serverIsLoading,
|
||||
serverNeedsRestart,
|
||||
setMenuOpen
|
||||
setMenuOpen,
|
||||
} from "../../actions";
|
||||
import { ThemedComponentProps } from "@material-ui/core/styles/withTheme";
|
||||
import Buckets from "./Buckets/Buckets";
|
||||
import Policies from "./Policies/Policies";
|
||||
import Permissions from "./Permissions/Permissions";
|
||||
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";
|
||||
@@ -63,6 +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 (
|
||||
@@ -82,43 +87,43 @@ const drawerWidth = 254;
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
display: "flex"
|
||||
display: "flex",
|
||||
},
|
||||
toolbar: {
|
||||
background: theme.palette.background.default,
|
||||
color: "black",
|
||||
paddingRight: 24 // keep right padding when drawer closed
|
||||
paddingRight: 24, // keep right padding when drawer closed
|
||||
},
|
||||
toolbarIcon: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
padding: "0 8px",
|
||||
...theme.mixins.toolbar
|
||||
...theme.mixins.toolbar,
|
||||
},
|
||||
appBar: {
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
transition: theme.transitions.create(["width", "margin"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
})
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
},
|
||||
appBarShift: {
|
||||
marginLeft: drawerWidth,
|
||||
width: `calc(100% - ${drawerWidth}px)`,
|
||||
transition: theme.transitions.create(["width", "margin"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen
|
||||
})
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
},
|
||||
menuButton: {
|
||||
marginRight: 36
|
||||
marginRight: 36,
|
||||
},
|
||||
menuButtonHidden: {
|
||||
display: "none"
|
||||
display: "none",
|
||||
},
|
||||
title: {
|
||||
flexGrow: 1
|
||||
flexGrow: 1,
|
||||
},
|
||||
drawerPaper: {
|
||||
position: "relative",
|
||||
@@ -126,40 +131,41 @@ const styles = (theme: Theme) =>
|
||||
width: drawerWidth,
|
||||
transition: theme.transitions.create("width", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen
|
||||
})
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
overflowX: "hidden",
|
||||
},
|
||||
drawerPaperClose: {
|
||||
overflowX: "hidden",
|
||||
transition: theme.transitions.create("width", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
width: theme.spacing(7),
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
width: theme.spacing(9)
|
||||
}
|
||||
width: theme.spacing(9),
|
||||
},
|
||||
},
|
||||
appBarSpacer: {
|
||||
height: "5px"
|
||||
height: "5px",
|
||||
},
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
height: "100vh",
|
||||
overflow: "auto"
|
||||
overflow: "auto",
|
||||
},
|
||||
container: {
|
||||
paddingTop: theme.spacing(4),
|
||||
paddingBottom: theme.spacing(4)
|
||||
paddingBottom: theme.spacing(4),
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
flexDirection: "column",
|
||||
},
|
||||
fixedHeight: {
|
||||
minHeight: 240
|
||||
minHeight: 240,
|
||||
},
|
||||
warningBar: {
|
||||
background: theme.palette.primary.main,
|
||||
@@ -167,22 +173,10 @@ const styles = (theme: Theme) =>
|
||||
heigh: "60px",
|
||||
widht: "100%",
|
||||
lineHeight: "60px",
|
||||
textAlign: "center"
|
||||
}
|
||||
textAlign: "center",
|
||||
},
|
||||
});
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
open: state.system.sidebarOpen,
|
||||
needsRestart: state.system.serverNeedsRestart,
|
||||
isServerLoading: state.system.serverIsLoading
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
setMenuOpen,
|
||||
serverNeedsRestart,
|
||||
serverIsLoading
|
||||
});
|
||||
|
||||
interface IConsoleProps {
|
||||
open: boolean;
|
||||
needsRestart: boolean;
|
||||
@@ -192,142 +186,218 @@ interface IConsoleProps {
|
||||
setMenuOpen: typeof setMenuOpen;
|
||||
serverNeedsRestart: typeof serverNeedsRestart;
|
||||
serverIsLoading: typeof serverIsLoading;
|
||||
saveSessionResponse: typeof saveSessionResponse;
|
||||
session: ISessionResponse;
|
||||
}
|
||||
|
||||
class Console extends React.Component<
|
||||
IConsoleProps & RouteComponentProps & StyledProps & ThemedComponentProps
|
||||
> {
|
||||
componentDidMount(): void {
|
||||
const Console = ({
|
||||
classes,
|
||||
open,
|
||||
needsRestart,
|
||||
isServerLoading,
|
||||
serverNeedsRestart,
|
||||
serverIsLoading,
|
||||
saveSessionResponse,
|
||||
session,
|
||||
}: IConsoleProps) => {
|
||||
useEffect(() => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/session`)
|
||||
.then(res => {
|
||||
console.log(res);
|
||||
.then((res) => {
|
||||
saveSessionResponse(res);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
storage.removeItem("token");
|
||||
history.push("/");
|
||||
});
|
||||
}
|
||||
}, [saveSessionResponse]);
|
||||
|
||||
restartServer() {
|
||||
this.props.serverIsLoading(true);
|
||||
const restartServer = () => {
|
||||
serverIsLoading(true);
|
||||
api
|
||||
.invoke("POST", "/api/v1/service/restart", {})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
console.log("success restarting service");
|
||||
console.log(res);
|
||||
this.props.serverIsLoading(false);
|
||||
this.props.serverNeedsRestart(false);
|
||||
serverIsLoading(false);
|
||||
serverNeedsRestart(false);
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.serverIsLoading(false);
|
||||
.catch((err) => {
|
||||
serverIsLoading(false);
|
||||
console.log("failure restarting service");
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, open, needsRestart, isServerLoading } = this.props;
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<CssBaseline />
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
classes={{
|
||||
paper: clsx(classes.drawerPaper, !open && classes.drawerPaperClose)
|
||||
}}
|
||||
open={open}
|
||||
>
|
||||
{/*<div className={classes.toolbarIcon}>*/}
|
||||
{/* <IconButton*/}
|
||||
{/* onClick={() => {*/}
|
||||
{/* this.props.setMenuOpen(false);*/}
|
||||
{/* }}*/}
|
||||
{/* >*/}
|
||||
{/* <ChevronLeftIcon />*/}
|
||||
{/* </IconButton>*/}
|
||||
{/*</div>*/}
|
||||
{/*<Divider />*/}
|
||||
const allowedPages = session.pages.reduce(
|
||||
(result: any, item: any, index: any) => {
|
||||
result[item] = true;
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
const routes = [
|
||||
{
|
||||
component: Dashboard,
|
||||
path: "/dashboard",
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets",
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets/:bucketName",
|
||||
},
|
||||
{
|
||||
component: Watch,
|
||||
path: "/watch",
|
||||
},
|
||||
{
|
||||
component: Users,
|
||||
path: "/users",
|
||||
},
|
||||
{
|
||||
component: Groups,
|
||||
path: "/groups",
|
||||
},
|
||||
{
|
||||
component: Policies,
|
||||
path: "/policies",
|
||||
},
|
||||
{
|
||||
component: Trace,
|
||||
path: "/trace",
|
||||
},
|
||||
{
|
||||
component: Logs,
|
||||
path: "/logs",
|
||||
},
|
||||
{
|
||||
component: Heal,
|
||||
path: "/heal",
|
||||
},
|
||||
{
|
||||
component: ListNotificationEndpoints,
|
||||
path: "/notification-endpoints",
|
||||
},
|
||||
{
|
||||
component: ConfigurationsList,
|
||||
path: "/configurations-list",
|
||||
},
|
||||
{
|
||||
component: Permissions,
|
||||
path: "/permissions",
|
||||
},
|
||||
{
|
||||
component: ServiceAccounts,
|
||||
path: "/service-accounts",
|
||||
},
|
||||
{
|
||||
component: WebhookPanel,
|
||||
path: "/webhook/logger",
|
||||
},
|
||||
{
|
||||
component: WebhookPanel,
|
||||
path: "/webhook/audit",
|
||||
},
|
||||
{
|
||||
component: ListTenants,
|
||||
path: "/tenants",
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/tenants/:tenantName",
|
||||
},
|
||||
];
|
||||
const allowedRoutes = routes.filter((route: any) => allowedPages[route.path]);
|
||||
|
||||
<Menu />
|
||||
</Drawer>
|
||||
return (
|
||||
<React.Fragment>
|
||||
{session.status == "ok" ? (
|
||||
<div className={classes.root}>
|
||||
<CssBaseline />
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
classes={{
|
||||
paper: clsx(
|
||||
classes.drawerPaper,
|
||||
!open && classes.drawerPaperClose
|
||||
),
|
||||
}}
|
||||
open={open}
|
||||
>
|
||||
<Menu pages={session.pages} />
|
||||
</Drawer>
|
||||
|
||||
<main className={classes.content}>
|
||||
{needsRestart && (
|
||||
<div className={classes.warningBar}>
|
||||
{isServerLoading ? (
|
||||
<React.Fragment>
|
||||
The server is restarting.
|
||||
<LinearProgress />
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
The instance needs to be restarted for configuration changes
|
||||
to take effect.{" "}
|
||||
<Button
|
||||
color="secondary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
this.restartServer();
|
||||
}}
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={classes.appBarSpacer} />
|
||||
<Container maxWidth="lg" className={classes.container}>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path="/buckets" component={Buckets} />
|
||||
<Route exact path="/permissions" component={Permissions} />
|
||||
<Route exact path="/policies" component={Policies} />
|
||||
<Route
|
||||
exact
|
||||
path="/service_accounts"
|
||||
component={ServiceAccounts}
|
||||
/>
|
||||
<Route exact path="/users" component={Users} />
|
||||
<Route exact path="/dashboard" component={Dashboard} />
|
||||
<Route exct path="/groups" component={Groups} />
|
||||
<Route
|
||||
exact
|
||||
path="/notification-endpoints"
|
||||
component={ListNotificationEndpoints}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/configurations-list"
|
||||
component={ConfigurationsList}
|
||||
/>
|
||||
<Route exact path="/webhook/logger" component={WebhookPanel} />
|
||||
<Route exact path="/webhook/audit" component={WebhookPanel} />
|
||||
<Route exct path="/trace" component={Trace} />
|
||||
<Route exct path="/logs" component={Logs} />
|
||||
<Route exact path="/">
|
||||
<Redirect to="/dashboard" />
|
||||
</Route>
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</Router>
|
||||
<main className={classes.content}>
|
||||
{needsRestart && (
|
||||
<div className={classes.warningBar}>
|
||||
{isServerLoading ? (
|
||||
<React.Fragment>
|
||||
The server is restarting.
|
||||
<LinearProgress />
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
The instance needs to be restarted for configuration changes
|
||||
to take effect.{" "}
|
||||
<Button
|
||||
color="secondary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
restartServer();
|
||||
}}
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={classes.appBarSpacer} />
|
||||
<Container maxWidth="lg" className={classes.container}>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
{allowedRoutes.map((route: any) => (
|
||||
<Route
|
||||
key={route.path}
|
||||
exact
|
||||
path={route.path}
|
||||
component={route.component}
|
||||
/>
|
||||
))}
|
||||
{allowedRoutes.length > 0 ? (
|
||||
<Route exact path="/">
|
||||
<Redirect to={allowedRoutes[0].path} />
|
||||
</Route>
|
||||
) : null}
|
||||
</Switch>
|
||||
</Router>
|
||||
|
||||
<Box pt={4}>
|
||||
<Copyright />
|
||||
</Box>
|
||||
</Container>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
<Box pt={4}>
|
||||
<Copyright />
|
||||
</Box>
|
||||
</Container>
|
||||
</main>
|
||||
</div>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
// );
|
||||
const mapState = (state: AppState) => ({
|
||||
open: state.system.sidebarOpen,
|
||||
needsRestart: state.system.serverNeedsRestart,
|
||||
isServerLoading: state.system.serverIsLoading,
|
||||
session: state.console.session,
|
||||
});
|
||||
|
||||
export default withRouter(connector(withStyles(styles)(Console)));
|
||||
// export default withStyles(styles)(connector(Console));
|
||||
// export default compose(
|
||||
// withStyles(styles),
|
||||
// connector
|
||||
// )(withRouter(Console))
|
||||
const connector = connect(mapState, {
|
||||
setMenuOpen,
|
||||
serverNeedsRestart,
|
||||
serverIsLoading,
|
||||
saveSessionResponse,
|
||||
});
|
||||
|
||||
export default connector(withStyles(styles)(Console));
|
||||
|
||||
@@ -26,86 +26,87 @@ import ViewHeadlineIcon from "@material-ui/icons/ViewHeadline";
|
||||
import { Usage } from "./types";
|
||||
import api from "../../../common/api";
|
||||
import { niceBytes } from "../../../common/utils";
|
||||
import { LinearProgress } from "@material-ui/core";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
display: "flex"
|
||||
display: "flex",
|
||||
},
|
||||
toolbar: {
|
||||
paddingRight: 24 // keep right padding when drawer closed
|
||||
paddingRight: 24, // keep right padding when drawer closed
|
||||
},
|
||||
toolbarIcon: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
padding: "0 8px",
|
||||
...theme.mixins.toolbar
|
||||
...theme.mixins.toolbar,
|
||||
},
|
||||
appBar: {
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
transition: theme.transitions.create(["width", "margin"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
})
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
},
|
||||
|
||||
menuButton: {
|
||||
marginRight: 36
|
||||
marginRight: 36,
|
||||
},
|
||||
menuButtonHidden: {
|
||||
display: "none"
|
||||
display: "none",
|
||||
},
|
||||
title: {
|
||||
flexGrow: 1
|
||||
flexGrow: 1,
|
||||
},
|
||||
drawerPaperClose: {
|
||||
overflowX: "hidden",
|
||||
transition: theme.transitions.create("width", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
width: theme.spacing(7),
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
width: theme.spacing(9)
|
||||
}
|
||||
width: theme.spacing(9),
|
||||
},
|
||||
},
|
||||
appBarSpacer: theme.mixins.toolbar,
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
height: "100vh",
|
||||
overflow: "auto"
|
||||
overflow: "auto",
|
||||
},
|
||||
container: {
|
||||
paddingBottom: theme.spacing(4),
|
||||
"& h6": {
|
||||
color: "#777777",
|
||||
fontSize: 14
|
||||
fontSize: 14,
|
||||
},
|
||||
"& p": {
|
||||
"& span": {
|
||||
fontSize: 16
|
||||
}
|
||||
}
|
||||
fontSize: 16,
|
||||
},
|
||||
},
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
flexDirection: "column",
|
||||
},
|
||||
fixedHeight: {
|
||||
minHeight: 240
|
||||
minHeight: 240,
|
||||
},
|
||||
consumptionValue: {
|
||||
color: "#000000",
|
||||
fontSize: "60px",
|
||||
fontWeight: "bold"
|
||||
fontWeight: "bold",
|
||||
},
|
||||
icon: {
|
||||
marginRight: 10,
|
||||
color: "#777777"
|
||||
}
|
||||
color: "#777777",
|
||||
},
|
||||
});
|
||||
|
||||
interface IDashboardProps {
|
||||
@@ -115,7 +116,7 @@ interface IDashboardProps {
|
||||
const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
const fixedHeightPaper = clsx(classes.paper, classes.fixedHeight);
|
||||
const [usage, setUsage] = useState<Usage | null>(null);
|
||||
const [loading, isLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
@@ -130,11 +131,11 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
.then((res: Usage) => {
|
||||
setUsage(res);
|
||||
setError("");
|
||||
isLoading(false);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
const prettyUsage = (usage: string | undefined) => {
|
||||
@@ -143,7 +144,6 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
}
|
||||
return niceBytes(usage);
|
||||
};
|
||||
const units = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
|
||||
const prettyNumber = (usage: number | undefined) => {
|
||||
if (usage === undefined) {
|
||||
@@ -161,51 +161,59 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
<Typography variant="h2">MinIO Console</Typography>
|
||||
</Grid>
|
||||
{error !== "" && <Grid container>{error}</Grid>}
|
||||
<Grid item xs={12} md={4} lg={4}>
|
||||
<Paper className={fixedHeightPaper}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid item className={classes.icon}>
|
||||
<ViewHeadlineIcon />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h6">Total Buckets</Typography>
|
||||
</Grid>
|
||||
{loading ? (
|
||||
<Grid item xs={12} md={12} lg={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} md={4} lg={4}>
|
||||
<Paper className={fixedHeightPaper}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid item className={classes.icon}>
|
||||
<ViewHeadlineIcon />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h6">Total Buckets</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Typography className={classes.consumptionValue}>
|
||||
{usage ? prettyNumber(usage.buckets) : 0}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Typography className={classes.consumptionValue}>
|
||||
{usage ? prettyNumber(usage.buckets) : 0}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4} lg={4}>
|
||||
<Paper className={fixedHeightPaper}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid item className={classes.icon}>
|
||||
<NetworkCheckIcon />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h6"> Total Objects</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4} lg={4}>
|
||||
<Paper className={fixedHeightPaper}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid item className={classes.icon}>
|
||||
<NetworkCheckIcon />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h6"> Total Objects</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Typography className={classes.consumptionValue}>
|
||||
{usage ? prettyNumber(usage.objects) : 0}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Typography className={classes.consumptionValue}>
|
||||
{usage ? prettyNumber(usage.objects) : 0}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4} lg={4}>
|
||||
<Paper className={fixedHeightPaper}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid item className={classes.icon}>
|
||||
<PieChartIcon />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h6">Usage</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4} lg={4}>
|
||||
<Paper className={fixedHeightPaper}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid item className={classes.icon}>
|
||||
<PieChartIcon />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h6">Usage</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Typography className={classes.consumptionValue}>
|
||||
{usage ? prettyUsage(usage.usage + "") : 0}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Typography className={classes.consumptionValue}>
|
||||
{usage ? prettyUsage(usage.usage + "") : 0}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
|
||||
import api from "../../../common/api";
|
||||
import UsersSelectors from "./UsersSelectors";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
@@ -41,24 +42,25 @@ interface MainGroupProps {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
fontWeight: 700,
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
marginLeft: 5,
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
}
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const AddGroup = ({
|
||||
open,
|
||||
selectedGroup,
|
||||
closeModalAndRefresh,
|
||||
classes
|
||||
classes,
|
||||
}: IGroupProps) => {
|
||||
//Local States
|
||||
const [groupName, setGroupName] = useState<string>("");
|
||||
@@ -86,14 +88,14 @@ const AddGroup = ({
|
||||
.invoke("PUT", `/api/v1/groups/${groupName}`, {
|
||||
group: groupName,
|
||||
members: selectedUsers,
|
||||
status: groupEnabled
|
||||
status: groupEnabled,
|
||||
})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
isSaving(false);
|
||||
setError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
isSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
@@ -101,14 +103,14 @@ const AddGroup = ({
|
||||
api
|
||||
.invoke("POST", "/api/v1/groups", {
|
||||
group: groupName,
|
||||
members: selectedUsers
|
||||
members: selectedUsers,
|
||||
})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
isSaving(false);
|
||||
setError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
isSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
@@ -122,7 +124,7 @@ const AddGroup = ({
|
||||
selectedUsers,
|
||||
groupEnabled,
|
||||
selectedGroup,
|
||||
closeModalAndRefresh
|
||||
closeModalAndRefresh,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -135,7 +137,7 @@ const AddGroup = ({
|
||||
setGroupName(res.name);
|
||||
setSelectedUsers(res.members);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
isLoadingGroup(false);
|
||||
});
|
||||
@@ -159,62 +161,61 @@ const AddGroup = ({
|
||||
>
|
||||
<form noValidate autoComplete="off" onSubmit={setSaving}>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{selectedGroup !== null ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroupSelector
|
||||
currentSelection={groupEnabled}
|
||||
id="group-status"
|
||||
name="group-status"
|
||||
label="Status"
|
||||
onChange={e => {
|
||||
setGroupEnabled(e.target.value);
|
||||
}}
|
||||
selectorOptions={[
|
||||
{ label: "Enabled", value: "enabled" },
|
||||
{ label: "Disabled", value: "disabled" }
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="group-name"
|
||||
name="group-name"
|
||||
label="Name"
|
||||
value={groupName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setGroupName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<UsersSelectors
|
||||
selectedUsers={selectedUsers}
|
||||
setSelectedUsers={setSelectedUsers}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
{selectedGroup !== null ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroupSelector
|
||||
currentSelection={groupEnabled}
|
||||
id="group-status"
|
||||
name="group-status"
|
||||
label="Status"
|
||||
onChange={(e) => {
|
||||
setGroupEnabled(e.target.value);
|
||||
}}
|
||||
selectorOptions={[
|
||||
{ label: "Enabled", value: "enabled" },
|
||||
{ label: "Disabled", value: "disabled" },
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="group-name"
|
||||
name="group-name"
|
||||
label="Name"
|
||||
value={groupName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setGroupName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<UsersSelectors
|
||||
selectedUsers={selectedUsers}
|
||||
setSelectedUsers={setSelectedUsers}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
|
||||
@@ -26,10 +26,11 @@ import { CreateIcon } from "../../../icons";
|
||||
import api from "../../../common/api";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
import { GroupsList } from "./types";
|
||||
import { groupsSort } from "../../../utils/sortFunctions";
|
||||
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;
|
||||
@@ -39,50 +40,50 @@ interface IGroupsProps {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
paper: {
|
||||
// padding: theme.spacing(2),
|
||||
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),
|
||||
},
|
||||
wrapCell: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
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",
|
||||
},
|
||||
});
|
||||
|
||||
const Groups = ({ classes }: IGroupsProps) => {
|
||||
@@ -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);
|
||||
@@ -126,7 +128,7 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
.then((res: GroupsList) => {
|
||||
let resGroups: string[] = [];
|
||||
if (res.groups !== null) {
|
||||
resGroups = res.groups.sort(groupsSort);
|
||||
resGroups = res.groups.sort(stringSort);
|
||||
}
|
||||
setRecords(resGroups);
|
||||
const total = !res.total ? 0 : res.total;
|
||||
@@ -140,7 +142,7 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
setPage(newPage);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
@@ -162,7 +164,7 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter(elementItem =>
|
||||
const filteredRecords = records.filter((elementItem) =>
|
||||
elementItem.includes(filter)
|
||||
);
|
||||
|
||||
@@ -176,9 +178,15 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
setSelectedGroup(group);
|
||||
};
|
||||
|
||||
const setPolicyAction = (selectionElement: any): void => {
|
||||
setPolicyOpen(true);
|
||||
setSelectedGroup(selectionElement);
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: viewAction },
|
||||
{ type: "delete", onClick: deleteAction }
|
||||
{ type: "description", onClick: setPolicyAction },
|
||||
{ type: "delete", onClick: deleteAction },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -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>
|
||||
@@ -217,9 +235,9 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
setFilter(e.target.value);
|
||||
}}
|
||||
/>
|
||||
@@ -255,11 +273,11 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -38,43 +38,43 @@ interface IGroupsProps {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
paper: {
|
||||
// padding: theme.spacing(2),
|
||||
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),
|
||||
},
|
||||
wrapCell: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "left",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
filterField: {
|
||||
background: "#FFFFFF",
|
||||
@@ -82,24 +82,24 @@ const styles = (theme: Theme) =>
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
width: "100%",
|
||||
zIndex: 500
|
||||
zIndex: 500,
|
||||
},
|
||||
noFound: {
|
||||
textAlign: "center",
|
||||
padding: "10px 0"
|
||||
padding: "10px 0",
|
||||
},
|
||||
tableContainer: {
|
||||
maxHeight: 250
|
||||
maxHeight: 250,
|
||||
},
|
||||
stickyHeader: {
|
||||
backgroundColor: "#fff"
|
||||
}
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
});
|
||||
|
||||
const UsersSelectors = ({
|
||||
classes,
|
||||
selectedUsers,
|
||||
setSelectedUsers
|
||||
setSelectedUsers,
|
||||
}: IGroupsProps) => {
|
||||
//Local States
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
@@ -133,7 +133,7 @@ const UsersSelectors = ({
|
||||
elements.push(value);
|
||||
} else {
|
||||
// User has unchecked this field, we need to remove it from the list
|
||||
elements = elements.filter(element => element !== value);
|
||||
elements = elements.filter((element) => element !== value);
|
||||
}
|
||||
setSelectedUsers(elements);
|
||||
|
||||
@@ -154,13 +154,13 @@ const UsersSelectors = ({
|
||||
setError("");
|
||||
isLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter(elementItem =>
|
||||
const filteredRecords = records.filter((elementItem) =>
|
||||
elementItem.accessKey.includes(filter)
|
||||
);
|
||||
|
||||
@@ -185,9 +185,9 @@ const UsersSelectors = ({
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
setFilter(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -15,15 +15,13 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { useEffect } from "react";
|
||||
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
|
||||
import storage from "local-storage-fallback";
|
||||
import { AppState } from "../../../store";
|
||||
import { connect } from "react-redux";
|
||||
import { logMessageReceived, logResetMessages } from "./actions";
|
||||
import { LogMessage } from "./types";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { niceBytes } from "../../../common/utils";
|
||||
import Ansi from "ansi-to-react";
|
||||
import { isNull, isNullOrUndefined } from "util";
|
||||
import { timeFromDate } from "../../../common/utils";
|
||||
import { isNullOrUndefined } from "util";
|
||||
import { wsProtocol } from "../../../utils/wsUtils";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -34,31 +32,28 @@ const styles = (theme: Theme) =>
|
||||
overflow: "auto",
|
||||
"& ul": {
|
||||
margin: "4px",
|
||||
padding: "0px"
|
||||
padding: "0px",
|
||||
},
|
||||
"& ul li": {
|
||||
listStyle: "none",
|
||||
margin: "0px",
|
||||
padding: "0px",
|
||||
borderBottom: "1px solid #dedede"
|
||||
}
|
||||
borderBottom: "1px solid #dedede",
|
||||
},
|
||||
},
|
||||
tab: {
|
||||
padding: "25px"
|
||||
},
|
||||
ansiblue: {
|
||||
color: "blue"
|
||||
padding: "25px",
|
||||
},
|
||||
logerror: {
|
||||
color: "red"
|
||||
color: "#A52A2A",
|
||||
},
|
||||
logerror_tab: {
|
||||
color: "red",
|
||||
padding: "25px"
|
||||
color: "#A52A2A",
|
||||
padding: "25px",
|
||||
},
|
||||
ansidefault: {
|
||||
color: "black"
|
||||
}
|
||||
color: "black",
|
||||
},
|
||||
});
|
||||
|
||||
interface ILogs {
|
||||
@@ -72,7 +67,7 @@ const Logs = ({
|
||||
classes,
|
||||
logMessageReceived,
|
||||
logResetMessages,
|
||||
messages
|
||||
messages,
|
||||
}: ILogs) => {
|
||||
useEffect(() => {
|
||||
logResetMessages();
|
||||
@@ -114,14 +109,6 @@ const Logs = ({
|
||||
}
|
||||
}, [logMessageReceived]);
|
||||
|
||||
const timeFromdate = (d: Date) => {
|
||||
let h = d.getHours() < 10 ? `0${d.getHours()}` : `${d.getHours()}`;
|
||||
let m = d.getMinutes() < 10 ? `0${d.getMinutes()}` : `${d.getMinutes()}`;
|
||||
let s = d.getSeconds() < 10 ? `0${d.getSeconds()}` : `${d.getSeconds()}`;
|
||||
|
||||
return `${h}:${m}:${s}:${d.getMilliseconds()}`;
|
||||
};
|
||||
|
||||
// replaces a character of a string with other at a given index
|
||||
const replaceWeirdChar = (
|
||||
origString: string,
|
||||
@@ -135,23 +122,6 @@ const Logs = ({
|
||||
return newString;
|
||||
};
|
||||
|
||||
const colorify = (str: string) => {
|
||||
// matches strings starting like: `[34mEndpoint: [0m`
|
||||
const colorRegex = /(\[[0-9]+m)(.*?)(\[0+m)/g;
|
||||
let matches = colorRegex.exec(str);
|
||||
if (!isNullOrUndefined(matches)) {
|
||||
let start_color = matches[1];
|
||||
let text = matches[2];
|
||||
|
||||
if (start_color === "[34m") {
|
||||
return <span className={classes.ansiblue}>{text}</span>;
|
||||
}
|
||||
if (start_color === "[1m") {
|
||||
return <span className={classes.ansidarkblue}>{text}</span>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderError = (logElement: LogMessage) => {
|
||||
let errorElems = [];
|
||||
if (!isNullOrUndefined(logElement.error)) {
|
||||
@@ -166,7 +136,7 @@ const Logs = ({
|
||||
errorElems.push(
|
||||
<li key={`time-${logElement.key}`}>
|
||||
<span className={classes.logerror}>
|
||||
Time: {timeFromdate(logElement.time)}
|
||||
Time: {timeFromDate(logElement.time)}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
@@ -241,36 +211,22 @@ const Logs = ({
|
||||
|
||||
const renderLog = (logElement: LogMessage) => {
|
||||
let logMessage = logElement.ConsoleMsg;
|
||||
// if somehow after the color code starts with unicode 10 = Line feed
|
||||
// delete it
|
||||
const regexInit = /(\[[0-9]+m)/g;
|
||||
let match = regexInit.exec(logMessage);
|
||||
if (match) {
|
||||
if (logMessage.slice(match[0].length).codePointAt(1) == 10) {
|
||||
logMessage = replaceWeirdChar(logMessage, "", match[0].length + 1);
|
||||
}
|
||||
}
|
||||
// remove any non ascii characters, exclude any control codes
|
||||
logMessage = logMessage.replace(/([^\x20-\x7F])/g, "");
|
||||
|
||||
// Select what to add color and what not to.
|
||||
const colorRegex = /(\[[0-9]+m)(.*?)(\[0+m)/g;
|
||||
let m = colorRegex.exec(logMessage);
|
||||
// regex for terminal colors like e.g. `[31;4m `
|
||||
const tColorRegex = /((\[[0-9;]+m))/g;
|
||||
|
||||
// get substring if there was a match for to split what
|
||||
// is going to be colored and what not, here we add color
|
||||
// only to the first match.
|
||||
let substr = logMessage.slice(colorRegex.lastIndex);
|
||||
substr = substr.replace(regexInit, "");
|
||||
let substr = logMessage.replace(tColorRegex, "");
|
||||
|
||||
// strClean used for corner case when string has unicode 32 for
|
||||
// space instead of normal space.
|
||||
let strClean = logMessage.replace(regexInit, "");
|
||||
// if starts with multiple spaces add padding
|
||||
if (strClean.startsWith(" ") || strClean.codePointAt(1) === 32) {
|
||||
if (substr.startsWith(" ")) {
|
||||
return (
|
||||
<li key={logElement.key}>
|
||||
<span className={classes.tab}>
|
||||
{colorify(logMessage)} {substr}
|
||||
</span>
|
||||
<span className={classes.tab}>{substr}</span>
|
||||
</li>
|
||||
);
|
||||
} else if (!isNullOrUndefined(logElement.error)) {
|
||||
@@ -280,9 +236,7 @@ const Logs = ({
|
||||
// for all remaining set default class
|
||||
return (
|
||||
<li key={logElement.key}>
|
||||
<span className={classes.ansidefault}>
|
||||
{colorify(logMessage)} {substr}
|
||||
</span>
|
||||
<span className={classes.ansidefault}>{substr}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@@ -293,7 +247,7 @@ const Logs = ({
|
||||
<h1>Logs</h1>
|
||||
<div className={classes.logList}>
|
||||
<ul>
|
||||
{messages.map(m => {
|
||||
{messages.map((m) => {
|
||||
return renderLog(m);
|
||||
})}
|
||||
</ul>
|
||||
@@ -303,12 +257,12 @@ const Logs = ({
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
messages: state.logs.messages
|
||||
messages: state.logs.messages,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
logMessageReceived: logMessageReceived,
|
||||
logResetMessages: logResetMessages
|
||||
logResetMessages: logResetMessages,
|
||||
});
|
||||
|
||||
export default connector(withStyles(styles)(Logs));
|
||||
|
||||
@@ -33,12 +33,12 @@ export type LogActionTypes = LogMessageReceivedAction | LogResetMessagesAction;
|
||||
export function logMessageReceived(message: LogMessage) {
|
||||
return {
|
||||
type: LOG_MESSAGE_RECEIVED,
|
||||
message: message
|
||||
message: message,
|
||||
};
|
||||
}
|
||||
|
||||
export function logResetMessages() {
|
||||
return {
|
||||
type: LOG_RESET_MESSAGES
|
||||
type: LOG_RESET_MESSAGES,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import {
|
||||
LOG_MESSAGE_RECEIVED,
|
||||
LOG_RESET_MESSAGES,
|
||||
LogActionTypes
|
||||
LogActionTypes,
|
||||
} from "./actions";
|
||||
import { LogMessage } from "./types";
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface LogState {
|
||||
}
|
||||
|
||||
const initialState: LogState = {
|
||||
messages: []
|
||||
messages: [],
|
||||
};
|
||||
|
||||
export function logReducer(
|
||||
@@ -37,12 +37,12 @@ export function logReducer(
|
||||
case LOG_MESSAGE_RECEIVED:
|
||||
return {
|
||||
...state,
|
||||
messages: [...state.messages, action.message]
|
||||
messages: [...state.messages, action.message],
|
||||
};
|
||||
case LOG_RESET_MESSAGES:
|
||||
return {
|
||||
...state,
|
||||
messages: []
|
||||
messages: [],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -1,198 +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 WebAssetIcon from "@material-ui/icons/WebAsset";
|
||||
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;
|
||||
}
|
||||
|
||||
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 } = this.props;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={classes.logo}>
|
||||
<img src={logo} alt="logo" />
|
||||
</div>
|
||||
<List className={classes.menuList}>
|
||||
<ListItem button component={NavLink} to="/dashboard">
|
||||
<ListItemIcon>
|
||||
<DashboardIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Dashboard" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/buckets">
|
||||
<ListItemIcon>
|
||||
<BucketsIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Buckets" />
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem component={Typography}>Admin</ListItem>
|
||||
<ListItem button component={NavLink} to="/users">
|
||||
<ListItemIcon>
|
||||
<PersonIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Users" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/groups">
|
||||
<ListItemIcon>
|
||||
<UsersIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Groups" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/policies">
|
||||
<ListItemIcon>
|
||||
<PermissionIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="IAM Policies" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/trace">
|
||||
<ListItemIcon>
|
||||
<LoopIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Trace" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/logs">
|
||||
<ListItemIcon>
|
||||
<WebAssetIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Console Logs" />
|
||||
</ListItem>
|
||||
<ListItem component={Typography}>Configuration</ListItem>
|
||||
<ListItem button component={NavLink} to="/notification-endpoints">
|
||||
<ListItemIcon>
|
||||
<NotificationsIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Lambda Notifications" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/configurations-list">
|
||||
<ListItemIcon>
|
||||
<ListAltIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Configurations List" />
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<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 },
|
||||
];
|
||||
@@ -39,27 +39,27 @@ import {
|
||||
notifyElasticsearch,
|
||||
notifyWebhooks,
|
||||
notifyNsq,
|
||||
removeEmptyFields
|
||||
removeEmptyFields,
|
||||
} from "../Configurations/utils";
|
||||
import { IElementValue } from "../Configurations/types";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
fontWeight: 700,
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
marginLeft: 5,
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
textAlign: "right",
|
||||
},
|
||||
logoButton: {
|
||||
height: "80px"
|
||||
}
|
||||
height: "80px",
|
||||
},
|
||||
});
|
||||
|
||||
interface IAddNotificationEndpointProps {
|
||||
@@ -73,7 +73,7 @@ const AddNotificationEndpoint = ({
|
||||
open,
|
||||
closeModalAndRefresh,
|
||||
serverNeedsRestart,
|
||||
classes
|
||||
classes,
|
||||
}: IAddNotificationEndpointProps) => {
|
||||
//Local States
|
||||
const [service, setService] = useState<string>("");
|
||||
@@ -86,18 +86,18 @@ const AddNotificationEndpoint = ({
|
||||
useEffect(() => {
|
||||
if (saving) {
|
||||
const payload = {
|
||||
key_values: removeEmptyFields(valuesArr)
|
||||
key_values: removeEmptyFields(valuesArr),
|
||||
};
|
||||
api
|
||||
.invoke("PUT", `/api/v1/configs/${service}`, payload)
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
setSaving(false);
|
||||
setError("");
|
||||
serverNeedsRestart(true);
|
||||
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
setSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
@@ -111,7 +111,7 @@ const AddNotificationEndpoint = ({
|
||||
};
|
||||
|
||||
const onValueChange = useCallback(
|
||||
newValue => {
|
||||
(newValue) => {
|
||||
setValueArr(newValue);
|
||||
},
|
||||
[setValueArr]
|
||||
@@ -342,14 +342,14 @@ const AddNotificationEndpoint = ({
|
||||
</Grid>
|
||||
)}
|
||||
<form noValidate onSubmit={submitForm}>
|
||||
{srvComponent}
|
||||
|
||||
<Grid item xs={3} className={classes.buttonContainer}>
|
||||
<Grid item xs={12}>
|
||||
{srvComponent}
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={saving}
|
||||
>
|
||||
Save
|
||||
|
||||
@@ -31,14 +31,14 @@ import {
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
TextField
|
||||
TextField,
|
||||
} from "@material-ui/core";
|
||||
import {
|
||||
createStyles,
|
||||
lighten,
|
||||
makeStyles,
|
||||
Theme,
|
||||
withStyles
|
||||
withStyles,
|
||||
} from "@material-ui/core/styles";
|
||||
import api from "../../../common/api";
|
||||
import clsx from "clsx";
|
||||
@@ -61,21 +61,21 @@ const useToolbarStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(1)
|
||||
paddingRight: theme.spacing(1),
|
||||
},
|
||||
highlight:
|
||||
theme.palette.type === "light"
|
||||
? {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: lighten(theme.palette.secondary.light, 0.85)
|
||||
backgroundColor: lighten(theme.palette.secondary.light, 0.85),
|
||||
}
|
||||
: {
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.secondary.dark
|
||||
backgroundColor: theme.palette.secondary.dark,
|
||||
},
|
||||
title: {
|
||||
flex: "1 1 100%"
|
||||
}
|
||||
flex: "1 1 100%",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -90,7 +90,7 @@ const EnhancedTableToolbar = (props: EnhancedTableToolbarProps) => {
|
||||
return (
|
||||
<Toolbar
|
||||
className={clsx(classes.root, {
|
||||
[classes.highlight]: numSelected > 0
|
||||
[classes.highlight]: numSelected > 0,
|
||||
})}
|
||||
>
|
||||
{numSelected > 0 ? (
|
||||
@@ -122,8 +122,8 @@ const EnhancedTableToolbar = (props: EnhancedTableToolbarProps) => {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
|
||||
interface IAddPermissionContentProps {
|
||||
@@ -164,7 +164,7 @@ class AddPermissionContent extends React.Component<
|
||||
resources: [],
|
||||
buckets: [],
|
||||
bucketsError: "",
|
||||
loadingBuckets: false
|
||||
loadingBuckets: false,
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
@@ -176,10 +176,10 @@ class AddPermissionContent extends React.Component<
|
||||
this.setState({
|
||||
loadingBuckets: false,
|
||||
buckets: res.buckets,
|
||||
bucketsError: ""
|
||||
bucketsError: "",
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({ loadingBuckets: false, bucketsError: err });
|
||||
});
|
||||
});
|
||||
@@ -190,8 +190,8 @@ class AddPermissionContent extends React.Component<
|
||||
name: selectedPermission.name,
|
||||
description: selectedPermission.description,
|
||||
effect: selectedPermission.effect,
|
||||
resources: selectedPermission.resources.map(r => r.bucket_name),
|
||||
action: selectedPermission.actions[0].type
|
||||
resources: selectedPermission.resources.map((r) => r.bucket_name),
|
||||
action: selectedPermission.actions[0].type,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -204,7 +204,7 @@ class AddPermissionContent extends React.Component<
|
||||
resources,
|
||||
description,
|
||||
effect,
|
||||
action
|
||||
action,
|
||||
} = this.state;
|
||||
const { selectedPermission } = this.props;
|
||||
if (addLoading) {
|
||||
@@ -219,23 +219,23 @@ class AddPermissionContent extends React.Component<
|
||||
description: description,
|
||||
effect: effect,
|
||||
resources: resources,
|
||||
actions: [action]
|
||||
actions: [action],
|
||||
})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
addError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
addError: err,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -245,23 +245,23 @@ class AddPermissionContent extends React.Component<
|
||||
description: description,
|
||||
effect: effect,
|
||||
resources: resources,
|
||||
actions: [action]
|
||||
actions: [action],
|
||||
})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
addError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
addError: err,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -280,14 +280,14 @@ class AddPermissionContent extends React.Component<
|
||||
name,
|
||||
description,
|
||||
effect,
|
||||
action
|
||||
action,
|
||||
} = this.state;
|
||||
|
||||
const handleSelectAllClick = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
if (event.target.checked) {
|
||||
const newSelecteds = buckets.map(n => n.name);
|
||||
const newSelecteds = buckets.map((n) => n.name);
|
||||
this.setState({ resources: newSelecteds });
|
||||
return;
|
||||
}
|
||||
@@ -432,7 +432,7 @@ class AddPermissionContent extends React.Component<
|
||||
}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all desserts"
|
||||
"aria-label": "select all desserts",
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -452,7 +452,9 @@ class AddPermissionContent extends React.Component<
|
||||
return (
|
||||
<TableRow
|
||||
hover
|
||||
onClick={event => handleClick(event, row.name)}
|
||||
onClick={(event) =>
|
||||
handleClick(event, row.name)
|
||||
}
|
||||
role="checkbox"
|
||||
aria-checked={isItemSelected}
|
||||
tabIndex={-1}
|
||||
@@ -500,7 +502,7 @@ class AddPermissionContent extends React.Component<
|
||||
value={action}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
action: (event.target as HTMLInputElement).value
|
||||
action: (event.target as HTMLInputElement).value,
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -24,6 +24,7 @@ import api from "../../../common/api";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/theme/material.css";
|
||||
import { Policy } from "./types";
|
||||
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
|
||||
@@ -32,24 +33,25 @@ require("codemirror/mode/javascript/javascript");
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
jsonPolicyEditor: {
|
||||
minHeight: 400,
|
||||
width: "100%"
|
||||
width: "100%",
|
||||
},
|
||||
codeMirror: {
|
||||
fontSize: 14
|
||||
fontSize: 14,
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
}
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
interface IAddPolicyProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
closeModalAndRefresh: () => void;
|
||||
closeModalAndRefresh: (refresh: boolean) => void;
|
||||
policyEdit: Policy;
|
||||
}
|
||||
|
||||
@@ -65,7 +67,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
addLoading: false,
|
||||
addError: "",
|
||||
policyName: "",
|
||||
policyDefinition: ""
|
||||
policyDefinition: "",
|
||||
};
|
||||
|
||||
addRecord(event: React.FormEvent) {
|
||||
@@ -78,23 +80,23 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
api
|
||||
.invoke("POST", "/api/v1/policies", {
|
||||
name: policyName,
|
||||
policy: policyDefinition
|
||||
policy: policyDefinition,
|
||||
})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
addError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
this.props.closeModalAndRefresh(true);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
addError: err,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -105,7 +107,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
|
||||
if (policyEdit) {
|
||||
this.setState({
|
||||
policyName: policyEdit.name
|
||||
policyName: policyEdit.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -118,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`}
|
||||
@@ -131,51 +133,50 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
<InputBoxWrapper
|
||||
id="policy-name"
|
||||
name="policy-name"
|
||||
label="Policy Name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ policyName: e.target.value });
|
||||
}}
|
||||
value={policyName}
|
||||
disabled={!!policyEdit}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<CodeMirror
|
||||
className={classes.codeMirror}
|
||||
value={
|
||||
policyEdit
|
||||
? JSON.stringify(JSON.parse(policyEdit.policy), null, 4)
|
||||
: ""
|
||||
}
|
||||
options={{
|
||||
mode: "javascript",
|
||||
lineNumbers: true,
|
||||
}}
|
||||
onChange={(editor, data, value) => {
|
||||
this.setState({ policyDefinition: value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="policy-name"
|
||||
name="policy-name"
|
||||
label="Policy Name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ policyName: e.target.value });
|
||||
}}
|
||||
value={policyName}
|
||||
disabled={!!policyEdit}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<CodeMirror
|
||||
className={classes.codeMirror}
|
||||
value={
|
||||
policyEdit
|
||||
? JSON.stringify(JSON.parse(policyEdit.policy), null, 4)
|
||||
: ""
|
||||
}
|
||||
options={{
|
||||
mode: "javascript",
|
||||
lineNumbers: true
|
||||
}}
|
||||
onChange={(editor, data, value) => {
|
||||
this.setState({ policyDefinition: value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
{!policyEdit && (
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
|
||||
@@ -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);
|
||||
@@ -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,363 +14,101 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { UnControlled as CodeMirror } from "react-codemirror2";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Button, FormControlLabel, LinearProgress } from "@material-ui/core";
|
||||
import {
|
||||
createStyles,
|
||||
lighten,
|
||||
makeStyles,
|
||||
Theme,
|
||||
withStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import api from "../../../common/api";
|
||||
import clsx from "clsx";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import FilterListIcon from "@material-ui/icons/FilterList";
|
||||
import { Permission, PermissionList } from "../Permissions/types";
|
||||
import {
|
||||
NewServiceAccount,
|
||||
ServiceAccount,
|
||||
ServiceAccountDetails
|
||||
} from "./types";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import { Button, LinearProgress, Tooltip } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import api from "../../../common/api";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/theme/material.css";
|
||||
import { NewServiceAccount } from "./types";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
const useToolbarStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(1)
|
||||
},
|
||||
highlight:
|
||||
theme.palette.type === "light"
|
||||
? {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: lighten(theme.palette.secondary.light, 0.85)
|
||||
}
|
||||
: {
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.secondary.dark
|
||||
},
|
||||
title: {
|
||||
flex: "1 1 100%"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
interface EnhancedTableToolbarProps {
|
||||
numSelected: number;
|
||||
}
|
||||
|
||||
const EnhancedTableToolbar = (props: EnhancedTableToolbarProps) => {
|
||||
const classes = useToolbarStyles();
|
||||
const { numSelected } = props;
|
||||
|
||||
return (
|
||||
<Toolbar
|
||||
className={clsx(classes.root, {
|
||||
[classes.highlight]: numSelected > 0
|
||||
})}
|
||||
>
|
||||
{numSelected > 0 ? (
|
||||
<Typography
|
||||
className={classes.title}
|
||||
color="inherit"
|
||||
variant="subtitle1"
|
||||
>
|
||||
{numSelected} selected
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography className={classes.title} variant="h6" id="tableTitle">
|
||||
Permissions
|
||||
</Typography>
|
||||
)}
|
||||
{numSelected > 0 ? (
|
||||
<span />
|
||||
) : (
|
||||
<Tooltip title="Filter list">
|
||||
<IconButton aria-label="filter list">
|
||||
<FilterListIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Toolbar>
|
||||
);
|
||||
};
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
color: "red",
|
||||
},
|
||||
jsonPolicyEditor: {
|
||||
minHeight: 400,
|
||||
width: "100%",
|
||||
},
|
||||
codeMirror: {
|
||||
fontSize: 14,
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
interface IAddServiceAccountContentProps {
|
||||
interface IAddServiceAccountProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
closeModalAndRefresh: (res: NewServiceAccount | null) => void;
|
||||
selectedServiceAccount: ServiceAccount | null;
|
||||
}
|
||||
|
||||
interface IAddServiceAccountContentState {
|
||||
addLoading: boolean;
|
||||
addError: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
selectedPermissions: Permission[];
|
||||
rowsPerPage: number;
|
||||
page: number;
|
||||
permissions: Permission[];
|
||||
permissionsError: string;
|
||||
loadingPermissions: boolean;
|
||||
loadingServiceAccount: boolean;
|
||||
}
|
||||
const AddServiceAccount = ({
|
||||
classes,
|
||||
open,
|
||||
closeModalAndRefresh,
|
||||
}: IAddServiceAccountProps) => {
|
||||
const [addSending, setAddSending] = useState(false);
|
||||
const [addError, setAddError] = useState("");
|
||||
const [policyDefinition, setPolicyDefinition] = useState("");
|
||||
|
||||
class AddServiceAccountContent extends React.Component<
|
||||
IAddServiceAccountContentProps,
|
||||
IAddServiceAccountContentState
|
||||
> {
|
||||
state: IAddServiceAccountContentState = {
|
||||
addLoading: false,
|
||||
addError: "",
|
||||
name: "",
|
||||
enabled: true,
|
||||
selectedPermissions: [],
|
||||
rowsPerPage: 5,
|
||||
page: 0,
|
||||
permissions: [],
|
||||
permissionsError: "",
|
||||
loadingPermissions: false,
|
||||
loadingServiceAccount: false
|
||||
useEffect(() => {
|
||||
if (addSending) {
|
||||
api
|
||||
.invoke("POST", "/api/v1/service-accounts", {
|
||||
policy: policyDefinition,
|
||||
})
|
||||
.then((res) => {
|
||||
setAddSending(false);
|
||||
setAddError("");
|
||||
closeModalAndRefresh(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddSending(false);
|
||||
setAddError(err);
|
||||
});
|
||||
}
|
||||
}, [
|
||||
addSending,
|
||||
setAddSending,
|
||||
setAddError,
|
||||
policyDefinition,
|
||||
closeModalAndRefresh,
|
||||
]);
|
||||
|
||||
const addServiceAccount = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setAddSending(true);
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
// load a list of permissions
|
||||
this.setState({ loadingPermissions: true }, () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/permissions?limit=1000`)
|
||||
.then((res: PermissionList) => {
|
||||
this.setState({
|
||||
loadingPermissions: false,
|
||||
permissions: res.permissions,
|
||||
permissionsError: ""
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({ loadingPermissions: false, permissionsError: err });
|
||||
});
|
||||
});
|
||||
|
||||
const { selectedServiceAccount } = this.props;
|
||||
if (selectedServiceAccount !== null) {
|
||||
this.setState({ loadingServiceAccount: true }, () => {
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/service_accounts/${selectedServiceAccount.id}`
|
||||
)
|
||||
.then((res: ServiceAccountDetails) => {
|
||||
console.log(res);
|
||||
this.setState({
|
||||
loadingServiceAccount: false,
|
||||
name: selectedServiceAccount.name,
|
||||
enabled: selectedServiceAccount.enabled,
|
||||
selectedPermissions:
|
||||
res.permissions === undefined || res.permissions === null
|
||||
? []
|
||||
: res.permissions
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({ loadingServiceAccount: false });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
saveRecord(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
const { name, addLoading, selectedPermissions, enabled } = this.state;
|
||||
const { selectedServiceAccount } = this.props;
|
||||
if (addLoading) {
|
||||
return;
|
||||
}
|
||||
this.setState({ addLoading: true }, () => {
|
||||
if (selectedServiceAccount !== null) {
|
||||
api
|
||||
.invoke(
|
||||
"PUT",
|
||||
`/api/v1/service_accounts/${selectedServiceAccount.id}`,
|
||||
{
|
||||
id: selectedServiceAccount.id,
|
||||
name: name,
|
||||
enabled: enabled,
|
||||
permission_ids: selectedPermissions.map(p => p.id)
|
||||
}
|
||||
)
|
||||
.then(res => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh(null);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
});
|
||||
});
|
||||
} else {
|
||||
api
|
||||
.invoke("POST", "/api/v1/service_accounts", {
|
||||
name: name,
|
||||
permission_ids: selectedPermissions.map(p => p.id)
|
||||
})
|
||||
.then((res: NewServiceAccount) => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh(res);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
classes,
|
||||
selectedServiceAccount,
|
||||
open,
|
||||
closeModalAndRefresh
|
||||
} = this.props;
|
||||
const {
|
||||
addLoading,
|
||||
addError,
|
||||
page,
|
||||
rowsPerPage,
|
||||
permissions,
|
||||
selectedPermissions,
|
||||
name,
|
||||
loadingServiceAccount
|
||||
} = this.state;
|
||||
|
||||
const handleSelectAllClick = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
if (event.target.checked) {
|
||||
// const newSelecteds = permissions.map(n => n.name);
|
||||
const newSelecteds = [...permissions];
|
||||
this.setState({ selectedPermissions: newSelecteds });
|
||||
return;
|
||||
}
|
||||
this.setState({ selectedPermissions: [] });
|
||||
};
|
||||
|
||||
const handleClick = (
|
||||
event: React.MouseEvent<unknown>,
|
||||
perm: Permission
|
||||
) => {
|
||||
let newSelected: Permission[] = [...selectedPermissions];
|
||||
if (newSelected.filter(p => p.id === perm.id).length === 0) {
|
||||
newSelected.push(perm);
|
||||
} else {
|
||||
let selectedIndex = -1;
|
||||
for (let i = 0; i < newSelected.length; i++) {
|
||||
if (newSelected[i].id === perm.id) {
|
||||
selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (selectedIndex >= 0) {
|
||||
newSelected = [
|
||||
...newSelected.slice(0, selectedIndex),
|
||||
...newSelected.slice(selectedIndex + 1)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ selectedPermissions: newSelected });
|
||||
};
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
this.setState({ page: newPage });
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
this.setState({ page: 0, rowsPerPage: parseInt(event.target.value, 10) });
|
||||
};
|
||||
|
||||
const isSelected = (perm: Permission) =>
|
||||
selectedPermissions.filter(p => p.id === perm.id).length > 0;
|
||||
|
||||
const emptyRows =
|
||||
rowsPerPage -
|
||||
Math.min(rowsPerPage, permissions.length - page * rowsPerPage);
|
||||
|
||||
const handleChange = (name: string) => (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
this.setState({ enabled: event.target.checked });
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
this.setState({ addError: "" }, () => {
|
||||
closeModalAndRefresh(null);
|
||||
});
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
closeModalAndRefresh(null);
|
||||
}}
|
||||
title={`Create Service Account`}
|
||||
>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
addServiceAccount(e);
|
||||
}}
|
||||
title={
|
||||
selectedServiceAccount !== null
|
||||
? "Edit Service Account"
|
||||
: "Create Service Account"
|
||||
}
|
||||
>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
this.saveRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{loadingServiceAccount && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
@@ -383,155 +121,46 @@ class AddServiceAccountContent extends React.Component<
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="service-account-name"
|
||||
name="service-account-name"
|
||||
label="Name"
|
||||
value={name}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ name: e.target.value });
|
||||
<Typography component="h5">
|
||||
Optional Policy
|
||||
<Tooltip
|
||||
title="A policy that restricts this service account can be attached."
|
||||
placement="top-start"
|
||||
>
|
||||
<HelpIcon />
|
||||
</Tooltip>
|
||||
</Typography>
|
||||
<CodeMirror
|
||||
className={classes.codeMirror}
|
||||
options={{
|
||||
mode: "javascript",
|
||||
lineNumbers: true,
|
||||
}}
|
||||
onChange={(editor, data, value) => {
|
||||
setPolicyDefinition(value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.root}>
|
||||
<EnhancedTableToolbar
|
||||
numSelected={selectedPermissions.length}
|
||||
/>
|
||||
<TableContainer>
|
||||
<Table
|
||||
className={classes.table}
|
||||
aria-labelledby="tableTitle"
|
||||
size={"small"}
|
||||
aria-label="enhanced table"
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
indeterminate={
|
||||
selectedPermissions.length > 0 &&
|
||||
selectedPermissions.length < permissions.length
|
||||
}
|
||||
checked={
|
||||
selectedPermissions.length > 0 &&
|
||||
selectedPermissions.length === permissions.length
|
||||
}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all desserts"
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>Permission</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{permissions
|
||||
.slice(
|
||||
page * rowsPerPage,
|
||||
page * rowsPerPage + rowsPerPage
|
||||
)
|
||||
.map((row, index) => {
|
||||
const isItemSelected = isSelected(row);
|
||||
const labelId = `enhanced-table-checkbox-${index}`;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
hover
|
||||
onClick={event => handleClick(event, row)}
|
||||
role="checkbox"
|
||||
aria-checked={isItemSelected}
|
||||
tabIndex={-1}
|
||||
key={row.name}
|
||||
selected={isItemSelected}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isItemSelected}
|
||||
inputProps={{ "aria-labelledby": labelId }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell id={labelId}>{row.name}</TableCell>
|
||||
<TableCell>{row.description}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{emptyRows > 0 && (
|
||||
<TableRow style={{ height: 33 * emptyRows }}>
|
||||
<TableCell colSpan={6} />
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
component="div"
|
||||
count={permissions.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch onChange={handleChange("enabled")} value="checkedA" />
|
||||
}
|
||||
label="Enabled"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addLoading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addSending}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Grid>
|
||||
{addSending && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const AddServiceAccountWrapper = withStyles(styles)(AddServiceAccountContent);
|
||||
|
||||
interface IAddServiceAccountProps {
|
||||
open: boolean;
|
||||
closeModalAndRefresh: (res: NewServiceAccount | null) => void;
|
||||
selectedServiceAccount: ServiceAccount | null;
|
||||
}
|
||||
|
||||
interface IAddServiceAccountState {}
|
||||
|
||||
class AddServiceAccount extends React.Component<
|
||||
IAddServiceAccountProps,
|
||||
IAddServiceAccountState
|
||||
> {
|
||||
state: IAddServiceAccountState = {};
|
||||
|
||||
render() {
|
||||
return <AddServiceAccountWrapper {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default AddServiceAccount;
|
||||
export default withStyles(styles)(AddServiceAccount);
|
||||
|
||||
@@ -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
|
||||
@@ -17,20 +17,19 @@
|
||||
import React from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { NewServiceAccount } from "./types";
|
||||
import {
|
||||
Button,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText
|
||||
} from "@material-ui/core";
|
||||
import { Button } from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
color: "red",
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
});
|
||||
|
||||
interface ICredentialsPromptProps {
|
||||
@@ -40,76 +39,69 @@ interface ICredentialsPromptProps {
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
interface ICredentialsPromptState {}
|
||||
const download = (filename: string, text: string) => {
|
||||
let element = document.createElement("a");
|
||||
element.setAttribute(
|
||||
"href",
|
||||
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
|
||||
);
|
||||
element.setAttribute("download", filename);
|
||||
|
||||
class CredentialsPrompt extends React.Component<
|
||||
ICredentialsPromptProps,
|
||||
ICredentialsPromptState
|
||||
> {
|
||||
state: ICredentialsPromptState = {};
|
||||
element.style.display = "none";
|
||||
document.body.appendChild(element);
|
||||
|
||||
download(filename: string, text: string) {
|
||||
var element = document.createElement("a");
|
||||
element.setAttribute(
|
||||
"href",
|
||||
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
|
||||
);
|
||||
element.setAttribute("download", filename);
|
||||
element.click();
|
||||
|
||||
element.style.display = "none";
|
||||
document.body.appendChild(element);
|
||||
document.body.removeChild(element);
|
||||
};
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
const CredentialsPrompt = ({
|
||||
classes,
|
||||
newServiceAccount,
|
||||
open,
|
||||
closeModal,
|
||||
}: ICredentialsPromptProps) => {
|
||||
if (!newServiceAccount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, open, newServiceAccount } = this.props;
|
||||
|
||||
if (newServiceAccount === null) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
this.props.closeModal();
|
||||
}}
|
||||
title="New Service Account"
|
||||
>
|
||||
<React.Fragment>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
A new service account has been created with the following details:
|
||||
<ul>
|
||||
<li>
|
||||
<b>Access Key:</b>{" "}
|
||||
{newServiceAccount.service_account.access_key}
|
||||
</li>
|
||||
<li>
|
||||
<b>Secret Key:</b> {newServiceAccount.secret_key}
|
||||
</li>
|
||||
</ul>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
Write these down, as this is the only time the secret will be
|
||||
displayed.
|
||||
</Typography>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
title="New Service Account Created"
|
||||
>
|
||||
<React.Fragment>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
A new service account has been created with the following details:
|
||||
<ul>
|
||||
<li>
|
||||
<b>Access Key:</b> {newServiceAccount.accessKey}
|
||||
</li>
|
||||
<li>
|
||||
<b>Secret Key:</b> {newServiceAccount.secretKey}
|
||||
</li>
|
||||
</ul>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
Write these down, as this is the only time the secret will be
|
||||
displayed.
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.download(
|
||||
download(
|
||||
"credentials.json",
|
||||
JSON.stringify({
|
||||
access_key: newServiceAccount.service_account.access_key,
|
||||
secret_key: newServiceAccount.secret_key
|
||||
access_key: newServiceAccount.accessKey,
|
||||
secret_key: newServiceAccount.secretKey,
|
||||
})
|
||||
);
|
||||
}}
|
||||
@@ -119,18 +111,18 @@ class CredentialsPrompt extends React.Component<
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.props.closeModal();
|
||||
closeModal();
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(CredentialsPrompt);
|
||||
|
||||
@@ -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,152 +14,117 @@
|
||||
// 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 React from "react";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@material-ui/core";
|
||||
import api from "../../../common/api";
|
||||
import { ServiceAccount, ServiceAccountsList } from "./types";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
wrapText: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
}
|
||||
});
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red",
|
||||
},
|
||||
wrapText: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
});
|
||||
|
||||
interface IDeleteServiceAccountProps {
|
||||
classes: any;
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
selectedServiceAccount: ServiceAccount | null;
|
||||
classes: any;
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
selectedServiceAccount: string | null;
|
||||
}
|
||||
|
||||
interface IDeleteServiceAccountState {
|
||||
deleteLoading: boolean;
|
||||
deleteError: string;
|
||||
}
|
||||
const DeleteServiceAccount = ({
|
||||
classes,
|
||||
closeDeleteModalAndRefresh,
|
||||
deleteOpen,
|
||||
selectedServiceAccount,
|
||||
}: IDeleteServiceAccountProps) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||
const [deleteError, setDeleteError] = useState("");
|
||||
|
||||
class DeleteServiceAccount extends React.Component<
|
||||
IDeleteServiceAccountProps,
|
||||
IDeleteServiceAccountState
|
||||
> {
|
||||
state: IDeleteServiceAccountState = {
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
};
|
||||
|
||||
removeRecord() {
|
||||
const { deleteLoading } = this.state;
|
||||
const { selectedServiceAccount } = this.props;
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
if (selectedServiceAccount == null) {
|
||||
return;
|
||||
}
|
||||
this.setState({ deleteLoading: true }, () => {
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/service_accounts/${selectedServiceAccount.id}`, {
|
||||
id: selectedServiceAccount.id
|
||||
})
|
||||
.then((res: ServiceAccountsList) => {
|
||||
this.setState(
|
||||
{
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
},
|
||||
() => {
|
||||
this.props.closeDeleteModalAndRefresh(true);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
deleteLoading: false,
|
||||
deleteError: err
|
||||
});
|
||||
});
|
||||
useEffect(() => {
|
||||
if (deleteLoading) {
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/service-accounts/${selectedServiceAccount}`)
|
||||
.then(() => {
|
||||
setDeleteLoading(false);
|
||||
setDeleteError("");
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
setDeleteLoading(false);
|
||||
setDeleteError(err);
|
||||
});
|
||||
}
|
||||
}, [deleteLoading, closeDeleteModalAndRefresh, selectedServiceAccount]);
|
||||
|
||||
render() {
|
||||
const { classes, deleteOpen, selectedServiceAccount } = this.props;
|
||||
const { deleteLoading, deleteError } = this.state;
|
||||
|
||||
if (selectedServiceAccount === null) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
this.setState({ deleteError: "" }, () => {
|
||||
this.props.closeDeleteModalAndRefresh(false);
|
||||
});
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete ServiceAccount</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete service account{" "}
|
||||
<b className={classes.wrapText}>{selectedServiceAccount.name}</b>?
|
||||
{deleteError !== "" && (
|
||||
<React.Fragment>
|
||||
<br />
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{deleteError}
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.setState({ deleteError: "" }, () => {
|
||||
this.props.closeDeleteModalAndRefresh(false);
|
||||
});
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.removeRecord();
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
const removeRecord = () => {
|
||||
if (selectedServiceAccount == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setDeleteLoading(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete ServiceAccount</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete service account{" "}
|
||||
<b className={classes.wrapText}>{selectedServiceAccount}</b>?
|
||||
{deleteError !== "" && (
|
||||
<React.Fragment>
|
||||
<br />
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{deleteError}
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={removeRecord} color="secondary" autoFocus>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(DeleteServiceAccount);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user