Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27fd91ed37 | ||
|
|
7df0560104 | ||
|
|
2368199e03 | ||
|
|
bee98e1ba0 | ||
|
|
8abbbb4625 | ||
|
|
ef182fe75e | ||
|
|
613e93fdc6 | ||
|
|
a5e3c89a54 | ||
|
|
7f55b71495 | ||
|
|
d95d59e454 | ||
|
|
08ea069ed4 | ||
|
|
e7a41b4cd9 |
2
.github/workflows/jobs.yaml
vendored
2
.github/workflows/jobs.yaml
vendored
@@ -2517,7 +2517,7 @@ jobs:
|
||||
go tool cover -func=all.out | grep total > tmp2
|
||||
result=`cat tmp2 | awk 'END {print $3}'`
|
||||
result=${result%\%}
|
||||
threshold=53.40
|
||||
threshold=54.40
|
||||
echo "Result:"
|
||||
echo "$result%"
|
||||
if (( $(echo "$result >= $threshold" |bc -l) )); then
|
||||
|
||||
@@ -116,8 +116,8 @@ func TestLogout(t *testing.T) {
|
||||
log.Println("authentication token not found in cookies response")
|
||||
return
|
||||
}
|
||||
|
||||
request, err = http.NewRequest("POST", "http://localhost:9090/api/v1/logout", requestDataBody)
|
||||
logoutRequest := bytes.NewReader([]byte("{}"))
|
||||
request, err = http.NewRequest("POST", "http://localhost:9090/api/v1/logout", logoutRequest)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
@@ -126,7 +126,6 @@ func TestLogout(t *testing.T) {
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
|
||||
response, err = client.Do(request)
|
||||
|
||||
assert.NotNil(response, "Logout response is nil")
|
||||
assert.Nil(err, "Logout errored out")
|
||||
assert.Equal(response.StatusCode, 200)
|
||||
|
||||
@@ -15,7 +15,7 @@ spec:
|
||||
serviceAccountName: console-sa
|
||||
containers:
|
||||
- name: console
|
||||
image: 'minio/console:v0.22.0'
|
||||
image: 'minio/console:v0.22.2'
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
env:
|
||||
- name: CONSOLE_OPERATOR_MODE
|
||||
|
||||
@@ -32,7 +32,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: console
|
||||
image: 'minio/console:v0.22.0'
|
||||
image: 'minio/console:v0.22.2'
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
env:
|
||||
- name: CONSOLE_MINIO_SERVER
|
||||
|
||||
133
models/idp_list_configurations_response.go
Normal file
133
models/idp_list_configurations_response.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// IdpListConfigurationsResponse idp list configurations response
|
||||
//
|
||||
// swagger:model idpListConfigurationsResponse
|
||||
type IdpListConfigurationsResponse struct {
|
||||
|
||||
// results
|
||||
Results []*IdpServerConfiguration `json:"results"`
|
||||
}
|
||||
|
||||
// Validate validates this idp list configurations response
|
||||
func (m *IdpListConfigurationsResponse) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateResults(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *IdpListConfigurationsResponse) validateResults(formats strfmt.Registry) error {
|
||||
if swag.IsZero(m.Results) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.Results); i++ {
|
||||
if swag.IsZero(m.Results[i]) { // not required
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Results[i] != nil {
|
||||
if err := m.Results[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("results" + "." + strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("results" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validate this idp list configurations response based on the context it is used
|
||||
func (m *IdpListConfigurationsResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.contextValidateResults(ctx, formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *IdpListConfigurationsResponse) contextValidateResults(ctx context.Context, formats strfmt.Registry) error {
|
||||
|
||||
for i := 0; i < len(m.Results); i++ {
|
||||
|
||||
if m.Results[i] != nil {
|
||||
if err := m.Results[i].ContextValidate(ctx, formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("results" + "." + strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("results" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *IdpListConfigurationsResponse) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *IdpListConfigurationsResponse) UnmarshalBinary(b []byte) error {
|
||||
var res IdpListConfigurationsResponse
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
145
models/idp_server_configuration.go
Normal file
145
models/idp_server_configuration.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// IdpServerConfiguration idp server configuration
|
||||
//
|
||||
// swagger:model idpServerConfiguration
|
||||
type IdpServerConfiguration struct {
|
||||
|
||||
// enabled
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
|
||||
// info
|
||||
Info []*IdpServerConfigurationInfo `json:"info"`
|
||||
|
||||
// input
|
||||
Input string `json:"input,omitempty"`
|
||||
|
||||
// name
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// type
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this idp server configuration
|
||||
func (m *IdpServerConfiguration) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateInfo(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *IdpServerConfiguration) validateInfo(formats strfmt.Registry) error {
|
||||
if swag.IsZero(m.Info) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.Info); i++ {
|
||||
if swag.IsZero(m.Info[i]) { // not required
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Info[i] != nil {
|
||||
if err := m.Info[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("info" + "." + strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("info" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validate this idp server configuration based on the context it is used
|
||||
func (m *IdpServerConfiguration) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.contextValidateInfo(ctx, formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *IdpServerConfiguration) contextValidateInfo(ctx context.Context, formats strfmt.Registry) error {
|
||||
|
||||
for i := 0; i < len(m.Info); i++ {
|
||||
|
||||
if m.Info[i] != nil {
|
||||
if err := m.Info[i].ContextValidate(ctx, formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("info" + "." + strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("info" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *IdpServerConfiguration) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *IdpServerConfiguration) UnmarshalBinary(b []byte) error {
|
||||
var res IdpServerConfiguration
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
76
models/idp_server_configuration_info.go
Normal file
76
models/idp_server_configuration_info.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 (
|
||||
"context"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// IdpServerConfigurationInfo idp server configuration info
|
||||
//
|
||||
// swagger:model idpServerConfigurationInfo
|
||||
type IdpServerConfigurationInfo struct {
|
||||
|
||||
// is cfg
|
||||
IsCfg bool `json:"isCfg,omitempty"`
|
||||
|
||||
// is env
|
||||
IsEnv bool `json:"isEnv,omitempty"`
|
||||
|
||||
// key
|
||||
Key string `json:"key,omitempty"`
|
||||
|
||||
// value
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this idp server configuration info
|
||||
func (m *IdpServerConfigurationInfo) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this idp server configuration info based on context it is used
|
||||
func (m *IdpServerConfigurationInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *IdpServerConfigurationInfo) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *IdpServerConfigurationInfo) UnmarshalBinary(b []byte) error {
|
||||
var res IdpServerConfigurationInfo
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
@@ -41,6 +41,9 @@ type LoginDetails struct {
|
||||
// is direct p v
|
||||
IsDirectPV bool `json:"isDirectPV,omitempty"`
|
||||
|
||||
// is k8 s
|
||||
IsK8S bool `json:"isK8S,omitempty"`
|
||||
|
||||
// login strategy
|
||||
// Enum: [form redirect service-account redirect-service-account]
|
||||
LoginStrategy string `json:"loginStrategy,omitempty"`
|
||||
|
||||
@@ -34,6 +34,9 @@ import (
|
||||
// swagger:model loginResponse
|
||||
type LoginResponse struct {
|
||||
|
||||
// ID p refresh token
|
||||
IDPRefreshToken string `json:"IDPRefreshToken,omitempty"`
|
||||
|
||||
// session Id
|
||||
SessionID string `json:"sessionId,omitempty"`
|
||||
}
|
||||
|
||||
67
models/logout_request.go
Normal file
67
models/logout_request.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 (
|
||||
"context"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// LogoutRequest logout request
|
||||
//
|
||||
// swagger:model logoutRequest
|
||||
type LogoutRequest struct {
|
||||
|
||||
// state
|
||||
State string `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this logout request
|
||||
func (m *LogoutRequest) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this logout request based on context it is used
|
||||
func (m *LogoutRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *LogoutRequest) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *LogoutRequest) UnmarshalBinary(b []byte) error {
|
||||
var res LogoutRequest
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
@@ -40,6 +40,9 @@ type RewindItem struct {
|
||||
// delete flag
|
||||
DeleteFlag bool `json:"delete_flag,omitempty"`
|
||||
|
||||
// is latest
|
||||
IsLatest bool `json:"is_latest,omitempty"`
|
||||
|
||||
// last modified
|
||||
LastModified string `json:"last_modified,omitempty"`
|
||||
|
||||
|
||||
67
models/set_id_p_response.go
Normal file
67
models/set_id_p_response.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 (
|
||||
"context"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// SetIDPResponse set ID p response
|
||||
//
|
||||
// swagger:model setIDPResponse
|
||||
type SetIDPResponse struct {
|
||||
|
||||
// restart
|
||||
Restart bool `json:"restart,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this set ID p response
|
||||
func (m *SetIDPResponse) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this set ID p response based on context it is used
|
||||
func (m *SetIDPResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *SetIDPResponse) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *SetIDPResponse) UnmarshalBinary(b []byte) error {
|
||||
var res SetIDPResponse
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
@@ -3626,6 +3626,9 @@ func init() {
|
||||
"isDirectPV": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isK8S": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"loginStrategy": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -9622,6 +9625,9 @@ func init() {
|
||||
"isDirectPV": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isK8S": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"loginStrategy": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
xoauth2 "golang.org/x/oauth2"
|
||||
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/pkg/env"
|
||||
|
||||
"github.com/minio/console/restapi"
|
||||
|
||||
@@ -93,6 +94,15 @@ func login(credentials restapi.ConsoleCredentialsI) (*string, error) {
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
// isKubernetes returns true if minio is running in kubernetes.
|
||||
func isKubernetes() bool {
|
||||
// Kubernetes env used to validate if we are
|
||||
// indeed running inside a kubernetes pod
|
||||
// is KUBERNETES_SERVICE_HOST
|
||||
// https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kubelet_pods.go#L541
|
||||
return env.Get("KUBERNETES_SERVICE_HOST", "") != ""
|
||||
}
|
||||
|
||||
// getLoginDetailsResponse returns information regarding the Console authentication mechanism.
|
||||
func getLoginDetailsResponse(params authApi.LoginDetailParams) (*models.LoginDetails, *models.Error) {
|
||||
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
|
||||
@@ -129,6 +139,7 @@ func getLoginDetailsResponse(params authApi.LoginDetailParams) (*models.LoginDet
|
||||
LoginStrategy: loginStrategy,
|
||||
RedirectRules: redirectRules,
|
||||
IsDirectPV: getDirectPVEnabled(),
|
||||
IsK8S: isKubernetes(),
|
||||
}
|
||||
return loginDetails, nil
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ type ProviderConfig struct {
|
||||
Userinfo bool
|
||||
RedirectCallbackDynamic bool
|
||||
RedirectCallback string
|
||||
EndSessionEndpoint string
|
||||
RoleArn string // can be empty
|
||||
}
|
||||
|
||||
|
||||
@@ -110,6 +110,7 @@ type Provider struct {
|
||||
IDPName string
|
||||
// if enabled means that we need extrace access_token as well
|
||||
UserInfo bool
|
||||
RefreshToken string
|
||||
oauth2Config Configuration
|
||||
provHTTPClient *http.Client
|
||||
}
|
||||
@@ -319,6 +320,7 @@ func (client *Provider) VerifyIdentity(ctx context.Context, code, state, roleARN
|
||||
getWebTokenExpiry := func() (*credentials.WebIdentityToken, error) {
|
||||
customCtx := context.WithValue(ctx, oauth2.HTTPClient, client.provHTTPClient)
|
||||
oauth2Token, err := client.oauth2Config.Exchange(customCtx, code)
|
||||
client.RefreshToken = oauth2Token.RefreshToken
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.b20a708b.css",
|
||||
"main.js": "./static/js/main.ef5eea8b.js",
|
||||
"static/js/1260.5248e62a.chunk.js": "./static/js/1260.5248e62a.chunk.js",
|
||||
"static/js/6914.8c73a010.chunk.js": "./static/js/6914.8c73a010.chunk.js",
|
||||
"main.js": "./static/js/main.2b781eaf.js",
|
||||
"static/js/1260.ded6e17b.chunk.js": "./static/js/1260.ded6e17b.chunk.js",
|
||||
"static/js/6914.1699f207.chunk.js": "./static/js/6914.1699f207.chunk.js",
|
||||
"static/js/4209.227aa3b5.chunk.js": "./static/js/4209.227aa3b5.chunk.js",
|
||||
"static/js/1829.be0f91b9.chunk.js": "./static/js/1829.be0f91b9.chunk.js",
|
||||
"static/js/4455.237b7d46.chunk.js": "./static/js/4455.237b7d46.chunk.js",
|
||||
@@ -28,7 +28,7 @@
|
||||
"static/css/5503.9cb5f34b.chunk.css": "./static/css/5503.9cb5f34b.chunk.css",
|
||||
"static/js/5503.8102c269.chunk.js": "./static/js/5503.8102c269.chunk.js",
|
||||
"static/js/5926.a85e7cf5.chunk.js": "./static/js/5926.a85e7cf5.chunk.js",
|
||||
"static/js/701.6d0fe16f.chunk.js": "./static/js/701.6d0fe16f.chunk.js",
|
||||
"static/js/701.34df1a0d.chunk.js": "./static/js/701.34df1a0d.chunk.js",
|
||||
"static/js/7821.461c5aa3.chunk.js": "./static/js/7821.461c5aa3.chunk.js",
|
||||
"static/css/2850.9cb5f34b.chunk.css": "./static/css/2850.9cb5f34b.chunk.css",
|
||||
"static/js/2850.c2c3a292.chunk.js": "./static/js/2850.c2c3a292.chunk.js",
|
||||
@@ -40,6 +40,12 @@
|
||||
"static/css/5517.9cb5f34b.chunk.css": "./static/css/5517.9cb5f34b.chunk.css",
|
||||
"static/js/5517.730e0aeb.chunk.js": "./static/js/5517.730e0aeb.chunk.js",
|
||||
"static/js/2555.47860755.chunk.js": "./static/js/2555.47860755.chunk.js",
|
||||
"static/js/7486.83e0d248.chunk.js": "./static/js/7486.83e0d248.chunk.js",
|
||||
"static/js/1377.6fbc40f3.chunk.js": "./static/js/1377.6fbc40f3.chunk.js",
|
||||
"static/js/4672.7e50d90b.chunk.js": "./static/js/4672.7e50d90b.chunk.js",
|
||||
"static/js/2516.5d118ef6.chunk.js": "./static/js/2516.5d118ef6.chunk.js",
|
||||
"static/js/2759.3ab5d8ec.chunk.js": "./static/js/2759.3ab5d8ec.chunk.js",
|
||||
"static/js/7295.c85034bf.chunk.js": "./static/js/7295.c85034bf.chunk.js",
|
||||
"static/js/7585.cd3ad44f.chunk.js": "./static/js/7585.cd3ad44f.chunk.js",
|
||||
"static/js/4902.b5c5ff07.chunk.js": "./static/js/4902.b5c5ff07.chunk.js",
|
||||
"static/js/7847.659fc617.chunk.js": "./static/js/7847.659fc617.chunk.js",
|
||||
@@ -61,9 +67,9 @@
|
||||
"static/js/8833.9dccd2d5.chunk.js": "./static/js/8833.9dccd2d5.chunk.js",
|
||||
"static/js/6526.e3612299.chunk.js": "./static/js/6526.e3612299.chunk.js",
|
||||
"static/js/483.5b997456.chunk.js": "./static/js/483.5b997456.chunk.js",
|
||||
"static/js/9467.42dc3ff9.chunk.js": "./static/js/9467.42dc3ff9.chunk.js",
|
||||
"static/js/9467.8e1b707f.chunk.js": "./static/js/9467.8e1b707f.chunk.js",
|
||||
"static/js/6895.488a4025.chunk.js": "./static/js/6895.488a4025.chunk.js",
|
||||
"static/js/1379.3fee0dea.chunk.js": "./static/js/1379.3fee0dea.chunk.js",
|
||||
"static/js/5882.41668ace.chunk.js": "./static/js/5882.41668ace.chunk.js",
|
||||
"static/js/8277.e772e8bd.chunk.js": "./static/js/8277.e772e8bd.chunk.js",
|
||||
"static/js/4133.2386eedc.chunk.js": "./static/js/4133.2386eedc.chunk.js",
|
||||
"static/css/1367.9cb5f34b.chunk.css": "./static/css/1367.9cb5f34b.chunk.css",
|
||||
@@ -104,8 +110,8 @@
|
||||
"static/js/9179.73844de6.chunk.js": "./static/js/9179.73844de6.chunk.js",
|
||||
"static/js/51.f63429fd.chunk.js": "./static/js/51.f63429fd.chunk.js",
|
||||
"static/js/711.7013b9d7.chunk.js": "./static/js/711.7013b9d7.chunk.js",
|
||||
"static/js/6901.e2472ebd.chunk.js": "./static/js/6901.e2472ebd.chunk.js",
|
||||
"static/js/2185.164ae281.chunk.js": "./static/js/2185.164ae281.chunk.js",
|
||||
"static/js/6901.cda3f1f9.chunk.js": "./static/js/6901.cda3f1f9.chunk.js",
|
||||
"static/js/2185.4baca582.chunk.js": "./static/js/2185.4baca582.chunk.js",
|
||||
"static/js/312.770148c8.chunk.js": "./static/js/312.770148c8.chunk.js",
|
||||
"static/js/2112.c85537ec.chunk.js": "./static/js/2112.c85537ec.chunk.js",
|
||||
"static/js/4619.bbf03503.chunk.js": "./static/js/4619.bbf03503.chunk.js",
|
||||
@@ -151,8 +157,10 @@
|
||||
"static/js/9515.a4e964be.chunk.js": "./static/js/9515.a4e964be.chunk.js",
|
||||
"static/js/2983.e248775f.chunk.js": "./static/js/2983.e248775f.chunk.js",
|
||||
"static/js/5861.65847210.chunk.js": "./static/js/5861.65847210.chunk.js",
|
||||
"static/js/537.85347210.chunk.js": "./static/js/537.85347210.chunk.js",
|
||||
"static/js/2763.08c6e1fd.chunk.js": "./static/js/2763.08c6e1fd.chunk.js",
|
||||
"static/media/LoginBG.png": "./static/media/LoginBG.143407455857f28ba809.png",
|
||||
"static/media/videoBG.mp4": "./static/media/videoBG.17363418b3c2246a0e27.mp4",
|
||||
"static/media/loginAnimationPoster.png": "./static/media/loginAnimationPoster.9aa924bfe619e71d5d29.png",
|
||||
"static/media/Inter-BoldItalic.woff": "./static/media/Inter-BoldItalic.b376885042f6c961a541.woff",
|
||||
"static/media/Inter-LightItalic.woff": "./static/media/Inter-LightItalic.ef9f65d91d2b0ba9b2e4.woff",
|
||||
"static/media/Inter-BlackItalic.woff": "./static/media/Inter-BlackItalic.ca1e738e4f349f27514d.woff",
|
||||
@@ -173,9 +181,9 @@
|
||||
"static/media/Inter-Regular.woff2": "./static/media/Inter-Regular.c8ba52b05a9ef10f4758.woff2",
|
||||
"index.html": "./index.html",
|
||||
"main.b20a708b.css.map": "./static/css/main.b20a708b.css.map",
|
||||
"main.ef5eea8b.js.map": "./static/js/main.ef5eea8b.js.map",
|
||||
"1260.5248e62a.chunk.js.map": "./static/js/1260.5248e62a.chunk.js.map",
|
||||
"6914.8c73a010.chunk.js.map": "./static/js/6914.8c73a010.chunk.js.map",
|
||||
"main.2b781eaf.js.map": "./static/js/main.2b781eaf.js.map",
|
||||
"1260.ded6e17b.chunk.js.map": "./static/js/1260.ded6e17b.chunk.js.map",
|
||||
"6914.1699f207.chunk.js.map": "./static/js/6914.1699f207.chunk.js.map",
|
||||
"4209.227aa3b5.chunk.js.map": "./static/js/4209.227aa3b5.chunk.js.map",
|
||||
"1829.be0f91b9.chunk.js.map": "./static/js/1829.be0f91b9.chunk.js.map",
|
||||
"4455.237b7d46.chunk.js.map": "./static/js/4455.237b7d46.chunk.js.map",
|
||||
@@ -200,7 +208,7 @@
|
||||
"5503.9cb5f34b.chunk.css.map": "./static/css/5503.9cb5f34b.chunk.css.map",
|
||||
"5503.8102c269.chunk.js.map": "./static/js/5503.8102c269.chunk.js.map",
|
||||
"5926.a85e7cf5.chunk.js.map": "./static/js/5926.a85e7cf5.chunk.js.map",
|
||||
"701.6d0fe16f.chunk.js.map": "./static/js/701.6d0fe16f.chunk.js.map",
|
||||
"701.34df1a0d.chunk.js.map": "./static/js/701.34df1a0d.chunk.js.map",
|
||||
"7821.461c5aa3.chunk.js.map": "./static/js/7821.461c5aa3.chunk.js.map",
|
||||
"2850.9cb5f34b.chunk.css.map": "./static/css/2850.9cb5f34b.chunk.css.map",
|
||||
"2850.c2c3a292.chunk.js.map": "./static/js/2850.c2c3a292.chunk.js.map",
|
||||
@@ -212,6 +220,12 @@
|
||||
"5517.9cb5f34b.chunk.css.map": "./static/css/5517.9cb5f34b.chunk.css.map",
|
||||
"5517.730e0aeb.chunk.js.map": "./static/js/5517.730e0aeb.chunk.js.map",
|
||||
"2555.47860755.chunk.js.map": "./static/js/2555.47860755.chunk.js.map",
|
||||
"7486.83e0d248.chunk.js.map": "./static/js/7486.83e0d248.chunk.js.map",
|
||||
"1377.6fbc40f3.chunk.js.map": "./static/js/1377.6fbc40f3.chunk.js.map",
|
||||
"4672.7e50d90b.chunk.js.map": "./static/js/4672.7e50d90b.chunk.js.map",
|
||||
"2516.5d118ef6.chunk.js.map": "./static/js/2516.5d118ef6.chunk.js.map",
|
||||
"2759.3ab5d8ec.chunk.js.map": "./static/js/2759.3ab5d8ec.chunk.js.map",
|
||||
"7295.c85034bf.chunk.js.map": "./static/js/7295.c85034bf.chunk.js.map",
|
||||
"7585.cd3ad44f.chunk.js.map": "./static/js/7585.cd3ad44f.chunk.js.map",
|
||||
"4902.b5c5ff07.chunk.js.map": "./static/js/4902.b5c5ff07.chunk.js.map",
|
||||
"7847.659fc617.chunk.js.map": "./static/js/7847.659fc617.chunk.js.map",
|
||||
@@ -233,9 +247,9 @@
|
||||
"8833.9dccd2d5.chunk.js.map": "./static/js/8833.9dccd2d5.chunk.js.map",
|
||||
"6526.e3612299.chunk.js.map": "./static/js/6526.e3612299.chunk.js.map",
|
||||
"483.5b997456.chunk.js.map": "./static/js/483.5b997456.chunk.js.map",
|
||||
"9467.42dc3ff9.chunk.js.map": "./static/js/9467.42dc3ff9.chunk.js.map",
|
||||
"9467.8e1b707f.chunk.js.map": "./static/js/9467.8e1b707f.chunk.js.map",
|
||||
"6895.488a4025.chunk.js.map": "./static/js/6895.488a4025.chunk.js.map",
|
||||
"1379.3fee0dea.chunk.js.map": "./static/js/1379.3fee0dea.chunk.js.map",
|
||||
"5882.41668ace.chunk.js.map": "./static/js/5882.41668ace.chunk.js.map",
|
||||
"8277.e772e8bd.chunk.js.map": "./static/js/8277.e772e8bd.chunk.js.map",
|
||||
"4133.2386eedc.chunk.js.map": "./static/js/4133.2386eedc.chunk.js.map",
|
||||
"1367.9cb5f34b.chunk.css.map": "./static/css/1367.9cb5f34b.chunk.css.map",
|
||||
@@ -276,8 +290,8 @@
|
||||
"9179.73844de6.chunk.js.map": "./static/js/9179.73844de6.chunk.js.map",
|
||||
"51.f63429fd.chunk.js.map": "./static/js/51.f63429fd.chunk.js.map",
|
||||
"711.7013b9d7.chunk.js.map": "./static/js/711.7013b9d7.chunk.js.map",
|
||||
"6901.e2472ebd.chunk.js.map": "./static/js/6901.e2472ebd.chunk.js.map",
|
||||
"2185.164ae281.chunk.js.map": "./static/js/2185.164ae281.chunk.js.map",
|
||||
"6901.cda3f1f9.chunk.js.map": "./static/js/6901.cda3f1f9.chunk.js.map",
|
||||
"2185.4baca582.chunk.js.map": "./static/js/2185.4baca582.chunk.js.map",
|
||||
"312.770148c8.chunk.js.map": "./static/js/312.770148c8.chunk.js.map",
|
||||
"2112.c85537ec.chunk.js.map": "./static/js/2112.c85537ec.chunk.js.map",
|
||||
"4619.bbf03503.chunk.js.map": "./static/js/4619.bbf03503.chunk.js.map",
|
||||
@@ -323,10 +337,11 @@
|
||||
"9515.a4e964be.chunk.js.map": "./static/js/9515.a4e964be.chunk.js.map",
|
||||
"2983.e248775f.chunk.js.map": "./static/js/2983.e248775f.chunk.js.map",
|
||||
"5861.65847210.chunk.js.map": "./static/js/5861.65847210.chunk.js.map",
|
||||
"537.85347210.chunk.js.map": "./static/js/537.85347210.chunk.js.map",
|
||||
"2763.08c6e1fd.chunk.js.map": "./static/js/2763.08c6e1fd.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.b20a708b.css",
|
||||
"static/js/main.ef5eea8b.js"
|
||||
"static/js/main.2b781eaf.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="/"/><meta content="width=device-width,initial-scale=1" name="viewport"/><meta content="#081C42" media="(prefers-color-scheme: light)" name="theme-color"/><meta content="#081C42" media="(prefers-color-scheme: dark)" name="theme-color"/><meta content="MinIO Console" name="description"/><link href="./styles/root-styles.css" rel="stylesheet"/><link href="./apple-icon-180x180.png" rel="apple-touch-icon" sizes="180x180"/><link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/><link href="./favicon-96x96.png" rel="icon" sizes="96x96" type="image/png"/><link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/><link href="./manifest.json" rel="manifest"/><link color="#3a4e54" href="./safari-pinned-tab.svg" rel="mask-icon"/><title>MinIO Console</title><script defer="defer" src="./static/js/main.ef5eea8b.js"></script><link href="./static/css/main.b20a708b.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="preload"><img src="./images/background.svg"/> <img src="./images/background-wave-orig2.svg"/></div><div id="loader-block"><img src="./Loader.svg"/></div></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="/"/><meta content="width=device-width,initial-scale=1" name="viewport"/><meta content="#081C42" media="(prefers-color-scheme: light)" name="theme-color"/><meta content="#081C42" media="(prefers-color-scheme: dark)" name="theme-color"/><meta content="MinIO Console" name="description"/><link href="./styles/root-styles.css" rel="stylesheet"/><link href="./apple-icon-180x180.png" rel="apple-touch-icon" sizes="180x180"/><link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/><link href="./favicon-96x96.png" rel="icon" sizes="96x96" type="image/png"/><link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/><link href="./manifest.json" rel="manifest"/><link color="#3a4e54" href="./safari-pinned-tab.svg" rel="mask-icon"/><title>MinIO Console</title><script defer="defer" src="./static/js/main.2b781eaf.js"></script><link href="./static/css/main.b20a708b.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="preload"><img src="./images/background.svg"/> <img src="./images/background-wave-orig2.svg"/></div><div id="loader-block"><img src="./Loader.svg"/></div></div></body></html>
|
||||
@@ -1,2 +0,0 @@
|
||||
"use strict";(self.webpackChunkportal_ui=self.webpackChunkportal_ui||[]).push([[1260],{1260:function(t,e,n){n.r(e);n(72791);var o=n(16871),a=n(25469),r=n(45248),u=n(81207),c=n(87995),l=n(46078),i=n(80184);e.default=function(){var t=(0,a.TL)(),e=(0,o.s0)();return function(){var n=function(){(0,r.Ov)(),t((0,c.wr)(!1)),localStorage.setItem("userLoggedIn",""),localStorage.setItem("redirect-path",""),t((0,l.lX)()),e("login")};u.Z.invoke("POST","/api/v1/logout").then((function(){n()})).catch((function(t){console.log(t),n()}))}(),(0,i.jsx)(i.Fragment,{})}}}]);
|
||||
//# sourceMappingURL=1260.5248e62a.chunk.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"static/js/1260.5248e62a.chunk.js","mappings":"6MAmDA,UA1BmB,WACjB,IAAMA,GAAWC,EAAAA,EAAAA,MACXC,GAAWC,EAAAA,EAAAA,MAqBjB,OApBe,WACb,IAAMC,EAAgB,YACpBC,EAAAA,EAAAA,MACAL,GAASM,EAAAA,EAAAA,KAAW,IACpBC,aAAaC,QAAQ,eAAgB,IACrCD,aAAaC,QAAQ,gBAAiB,IACtCR,GAASS,EAAAA,EAAAA,OACTP,EAAS,QACV,EACDQ,EAAAA,EAAAA,OACU,OADV,kBAEGC,MAAK,WACJP,GACD,IACAQ,OAAM,SAACC,GACNC,QAAQC,IAAIF,GACZT,GACD,GACJ,CACDY,IACO,uBACR,C","sources":["screens/LogoutPage/LogoutPage.tsx"],"sourcesContent":["// This file is part of MinIO Console Server\n// Copyright (c) 2022 MinIO, Inc.\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <http://www.gnu.org/licenses/>.\n\nimport React from \"react\";\nimport { useNavigate } from \"react-router-dom\";\nimport { useAppDispatch } from \"../../store\";\nimport { ErrorResponseHandler } from \"../../common/types\";\nimport { clearSession } from \"../../common/utils\";\nimport api from \"../../common/api\";\nimport { userLogged } from \"../../systemSlice\";\nimport { resetSession } from \"../Console/consoleSlice\";\n\nconst LogoutPage = () => {\n const dispatch = useAppDispatch();\n const navigate = useNavigate();\n const logout = () => {\n const deleteSession = () => {\n clearSession();\n dispatch(userLogged(false));\n localStorage.setItem(\"userLoggedIn\", \"\");\n localStorage.setItem(\"redirect-path\", \"\");\n dispatch(resetSession());\n navigate(`login`);\n };\n api\n .invoke(\"POST\", `/api/v1/logout`)\n .then(() => {\n deleteSession();\n })\n .catch((err: ErrorResponseHandler) => {\n console.log(err);\n deleteSession();\n });\n };\n logout();\n return <></>;\n};\n\nexport default LogoutPage;\n"],"names":["dispatch","useAppDispatch","navigate","useNavigate","deleteSession","clearSession","userLogged","localStorage","setItem","resetSession","api","then","catch","err","console","log","logout"],"sourceRoot":""}
|
||||
2
portal-ui/build/static/js/1260.ded6e17b.chunk.js
Normal file
2
portal-ui/build/static/js/1260.ded6e17b.chunk.js
Normal file
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkportal_ui=self.webpackChunkportal_ui||[]).push([[1260],{1260:function(t,e,o){o.r(e);o(72791);var a=o(16871),n=o(25469),u=o(45248),c=o(87995),l=o(46078),r=o(81207),i=o(7241),s=o(80184);e.default=function(){var t=(0,n.TL)(),e=(0,a.s0)();return function(){var o=function(){(0,u.Ov)(),t((0,c.wr)(!1)),localStorage.setItem("userLoggedIn",""),localStorage.setItem("redirect-path",""),t((0,l.lX)()),e("/login")},a=localStorage.getItem("auth-state");r.Z.invoke("POST","/api/v1/logout",{state:a}).then((function(){o()})).catch((function(t){console.log(t),o()}))}(),(0,s.jsx)(i.Z,{})}}}]);
|
||||
//# sourceMappingURL=1260.ded6e17b.chunk.js.map
|
||||
1
portal-ui/build/static/js/1260.ded6e17b.chunk.js.map
Normal file
1
portal-ui/build/static/js/1260.ded6e17b.chunk.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/js/1260.ded6e17b.chunk.js","mappings":"uNAqDA,UA3BmB,WACjB,IAAMA,GAAWC,EAAAA,EAAAA,MACXC,GAAWC,EAAAA,EAAAA,MAsBjB,OArBe,WACb,IAAMC,EAAgB,YACpBC,EAAAA,EAAAA,MACAL,GAASM,EAAAA,EAAAA,KAAW,IACpBC,aAAaC,QAAQ,eAAgB,IACrCD,aAAaC,QAAQ,gBAAiB,IACtCR,GAASS,EAAAA,EAAAA,OACTP,EAAS,SACV,EACKQ,EAAQH,aAAaI,QAAQ,cACnCC,EAAAA,EAAAA,OACU,OADV,iBACoC,CAAEF,MAAAA,IACnCG,MAAK,WACJT,GACD,IACAU,OAAM,SAACC,GACNC,QAAQC,IAAIF,GACZX,GACD,GACJ,CACDc,IACO,SAAC,IAAD,GACR,C","sources":["screens/LogoutPage/LogoutPage.tsx"],"sourcesContent":["// This file is part of MinIO Console Server\n// Copyright (c) 2022 MinIO, Inc.\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <http://www.gnu.org/licenses/>.\n\nimport React from \"react\";\nimport { useNavigate } from \"react-router-dom\";\nimport { useAppDispatch } from \"../../store\";\nimport { ErrorResponseHandler } from \"../../common/types\";\nimport { clearSession } from \"../../common/utils\";\nimport { userLogged } from \"../../systemSlice\";\nimport { resetSession } from \"../Console/consoleSlice\";\nimport api from \"../../common/api\";\nimport LoadingComponent from \"../../common/LoadingComponent\";\n\nconst LogoutPage = () => {\n const dispatch = useAppDispatch();\n const navigate = useNavigate();\n const logout = () => {\n const deleteSession = () => {\n clearSession();\n dispatch(userLogged(false));\n localStorage.setItem(\"userLoggedIn\", \"\");\n localStorage.setItem(\"redirect-path\", \"\");\n dispatch(resetSession());\n navigate(`/login`);\n };\n const state = localStorage.getItem(\"auth-state\");\n api\n .invoke(\"POST\", `/api/v1/logout`, { state })\n .then(() => {\n deleteSession();\n })\n .catch((err: ErrorResponseHandler) => {\n console.log(err);\n deleteSession();\n });\n };\n logout();\n return <LoadingComponent />;\n};\n\nexport default LogoutPage;\n"],"names":["dispatch","useAppDispatch","navigate","useNavigate","deleteSession","clearSession","userLogged","localStorage","setItem","resetSession","state","getItem","api","then","catch","err","console","log","logout"],"sourceRoot":""}
|
||||
2
portal-ui/build/static/js/1377.6fbc40f3.chunk.js
Normal file
2
portal-ui/build/static/js/1377.6fbc40f3.chunk.js
Normal file
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkportal_ui=self.webpackChunkportal_ui||[]).push([[1377],{81377:function(u,e,n){n.r(e);n(72791);var t=n(11135),r=n(25787),s=n(44959),i=n(80184);e.default=(0,r.Z)((function(u){return(0,t.Z)({})}))((function(u){u.classes;return(0,i.jsx)(s.Z,{idpType:"openid"})}))}}]);
|
||||
//# sourceMappingURL=1377.6fbc40f3.chunk.js.map
|
||||
1
portal-ui/build/static/js/1377.6fbc40f3.chunk.js.map
Normal file
1
portal-ui/build/static/js/1377.6fbc40f3.chunk.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/js/1377.6fbc40f3.chunk.js","mappings":"6KAiCA,WAAeA,EAAAA,EAAAA,IANA,SAACC,GAAD,OAAkBC,EAAAA,EAAAA,GAAa,CAAC,EAAhC,GAMf,EAJgC,SAAC,GAA8C,EAA5CC,QACjC,OAAO,SAAC,IAAD,CAAmBC,QAAS,UACpC,G","sources":["screens/Console/IDP/IDPOpenIDConfigurations.tsx"],"sourcesContent":["// This file is part of MinIO Console Server\n// Copyright (c) 2022 MinIO, Inc.\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <http://www.gnu.org/licenses/>.\n\nimport React from \"react\";\n\nimport { Theme } from \"@mui/material/styles\";\nimport createStyles from \"@mui/styles/createStyles\";\nimport withStyles from \"@mui/styles/withStyles\";\nimport IDPConfigurations from \"./IDPConfigurations\";\n\ntype IDPOpenIDConfigurationsProps = {\n classes?: any;\n};\n\nconst styles = (theme: Theme) => createStyles({});\n\nconst IDPOpenIDConfigurations = ({ classes }: IDPOpenIDConfigurationsProps) => {\n return <IDPConfigurations idpType={\"openid\"} />;\n};\n\nexport default withStyles(styles)(IDPOpenIDConfigurations);\n"],"names":["withStyles","theme","createStyles","classes","idpType"],"sourceRoot":""}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
||||
"use strict";(self.webpackChunkportal_ui=self.webpackChunkportal_ui||[]).push([[2185],{62185:function(e,t,n){n.r(t);var a=n(29439),r=n(1413),i=n(72791),l=n(16871),s=n(75952),o=n(56028),c=n(61889),u=n(21435),d=n(11135),f=n(25787),h=n(23814),m=n(60364),x=n(45248),p=n(56096),j=n(25469),v=n(87995),b=n(80184),Z=(0,m.$j)((function(e){return{simplePath:e.objectBrowser.simplePath}}));t.default=Z((0,f.Z)((function(e){return(0,d.Z)((0,r.Z)((0,r.Z)({},h.ID),h.DF))}))((function(e){var t=e.modalOpen,n=(e.folderName,e.bucketName),r=e.onClose,d=e.classes,f=e.existingFiles,h=e.simplePath,m=(0,j.TL)(),Z=(0,l.s0)(),w=(0,i.useState)(""),P=(0,a.Z)(w,2),C=P[0],g=P[1],k=(0,i.useState)(!1),F=(0,a.Z)(k,2),N=F[0],y=F[1],E=(0,i.useState)(n),S=(0,a.Z)(E,2),z=S[0],B=S[1];(0,i.useEffect)((function(){if(h){var e="".concat(n).concat(n.endsWith("/")||h.startsWith("/")?"":"/").concat(h);B(e)}}),[h,n]);var I=function(){var e="/";h&&(e=h.endsWith("/")?h:"".concat(h,"/"));if(-1===f.findIndex((function(t){return t.name===e+C}))){var t=C.split("/").filter((function(e){return""!==e.trim()})).join("/"),a="/buckets/".concat(n,"/browse/").concat((0,x.LL)("".concat(e).concat(t,"/")));Z(a),r()}else m((0,v.zb)({errorMessage:"Folder cannot have the same name as an existing file",detailedError:""}))};(0,i.useEffect)((function(){var e=!0;0===C.trim().length&&(e=!1),y(e)}),[C]);return(0,b.jsx)(i.Fragment,{children:(0,b.jsx)(o.Z,{modalOpen:t,title:"Choose or create a new path",onClose:r,titleIcon:(0,b.jsx)(p.Z9m,{}),children:(0,b.jsxs)(c.ZP,{container:!0,children:[(0,b.jsxs)(c.ZP,{item:!0,xs:12,className:d.formFieldRow,children:[(0,b.jsx)("strong",{children:"Current Path:"})," ",(0,b.jsx)("br",{}),(0,b.jsx)("div",{style:{textOverflow:"ellipsis",whiteSpace:"nowrap",overflow:"hidden",fontSize:14,textAlign:"left"},dir:"rtl",children:z})]}),(0,b.jsx)(c.ZP,{item:!0,xs:12,className:d.formFieldRow,children:(0,b.jsx)(u.Z,{value:C,label:"New Folder Path",id:"folderPath",name:"folderPath",placeholder:"Enter the new Folder Path",onChange:function(e){g(e.target.value)},onKeyPress:function(e){"Enter"===e.code&&""!==C&&I()},required:!0})}),(0,b.jsxs)(c.ZP,{item:!0,xs:12,className:d.modalButtonBar,children:[(0,b.jsx)(s.zx,{id:"clear",type:"button",color:"primary",variant:"regular",onClick:function(){g("")},label:"Clear"}),(0,b.jsx)(s.zx,{id:"create",type:"submit",variant:"callAction",disabled:!N,onClick:I,label:"Create"})]})]})})})})))}}]);
|
||||
//# sourceMappingURL=2185.164ae281.chunk.js.map
|
||||
File diff suppressed because one or more lines are too long
2
portal-ui/build/static/js/2185.4baca582.chunk.js
Normal file
2
portal-ui/build/static/js/2185.4baca582.chunk.js
Normal file
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkportal_ui=self.webpackChunkportal_ui||[]).push([[2185],{62185:function(e,t,n){n.r(t);var r=n(29439),a=n(1413),o=n(72791),i=n(16871),s=n(75952),l=n(56028),c=n(61889),u=n(21435),d=n(11135),f=n(25787),h=n(23814),m=n(60364),x=n(45248),p=n(56096),j=n(25469),v=n(87995),b=n(80184),w=(0,m.$j)((function(e){return{simplePath:e.objectBrowser.simplePath}}));t.default=w((0,f.Z)((function(e){return(0,d.Z)((0,a.Z)((0,a.Z)({},h.ID),h.DF))}))((function(e){var t=e.modalOpen,n=(e.folderName,e.bucketName),a=e.onClose,d=e.classes,f=e.simplePath,h=(0,j.TL)(),w=(0,i.s0)(),Z=(0,o.useState)(""),P=(0,r.Z)(Z,2),C=P[0],g=P[1],k=(0,o.useState)(!1),F=(0,r.Z)(k,2),N=F[0],y=F[1],E=(0,o.useState)(n),S=(0,r.Z)(E,2),z=S[0],B=S[1],I=(0,m.v9)((function(e){return e.objectBrowser.records}));(0,o.useEffect)((function(){if(f){var e="".concat(n).concat(n.endsWith("/")||f.startsWith("/")?"":"/").concat(f);B(e)}}),[f,n]);var L=function(){var e="/";f&&(e=f.endsWith("/")?f:"".concat(f,"/"));if(-1===I.findIndex((function(t){return t.name===e+C}))){var t=C.split("/").filter((function(e){return""!==e.trim()})).join("/"),r="/buckets/".concat(n,"/browse/").concat((0,x.LL)("".concat(e).concat(t,"/")));w(r),a()}else h((0,v.zb)({errorMessage:"Folder cannot have the same name as an existing file",detailedError:""}))};(0,o.useEffect)((function(){var e=!0;0===C.trim().length&&(e=!1),y(e)}),[C]);return(0,b.jsx)(o.Fragment,{children:(0,b.jsx)(l.Z,{modalOpen:t,title:"Choose or create a new path",onClose:a,titleIcon:(0,b.jsx)(p.Z9m,{}),children:(0,b.jsxs)(c.ZP,{container:!0,children:[(0,b.jsxs)(c.ZP,{item:!0,xs:12,className:d.formFieldRow,children:[(0,b.jsx)("strong",{children:"Current Path:"})," ",(0,b.jsx)("br",{}),(0,b.jsx)("div",{style:{textOverflow:"ellipsis",whiteSpace:"nowrap",overflow:"hidden",fontSize:14,textAlign:"left"},dir:"rtl",children:z})]}),(0,b.jsx)(c.ZP,{item:!0,xs:12,className:d.formFieldRow,children:(0,b.jsx)(u.Z,{value:C,label:"New Folder Path",id:"folderPath",name:"folderPath",placeholder:"Enter the new Folder Path",onChange:function(e){g(e.target.value)},onKeyPress:function(e){"Enter"===e.code&&""!==C&&L()},required:!0})}),(0,b.jsxs)(c.ZP,{item:!0,xs:12,className:d.modalButtonBar,children:[(0,b.jsx)(s.zx,{id:"clear",type:"button",color:"primary",variant:"regular",onClick:function(){g("")},label:"Clear"}),(0,b.jsx)(s.zx,{id:"create",type:"submit",variant:"callAction",disabled:!N,onClick:L,label:"Create"})]})]})})})})))}}]);
|
||||
//# sourceMappingURL=2185.4baca582.chunk.js.map
|
||||
1
portal-ui/build/static/js/2185.4baca582.chunk.js.map
Normal file
1
portal-ui/build/static/js/2185.4baca582.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
portal-ui/build/static/js/2516.5d118ef6.chunk.js
Normal file
2
portal-ui/build/static/js/2516.5d118ef6.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/2516.5d118ef6.chunk.js.map
Normal file
1
portal-ui/build/static/js/2516.5d118ef6.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
portal-ui/build/static/js/2759.3ab5d8ec.chunk.js
Normal file
2
portal-ui/build/static/js/2759.3ab5d8ec.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/2759.3ab5d8ec.chunk.js.map
Normal file
1
portal-ui/build/static/js/2759.3ab5d8ec.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
portal-ui/build/static/js/4672.7e50d90b.chunk.js
Normal file
2
portal-ui/build/static/js/4672.7e50d90b.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/4672.7e50d90b.chunk.js.map
Normal file
1
portal-ui/build/static/js/4672.7e50d90b.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
portal-ui/build/static/js/537.85347210.chunk.js
Normal file
2
portal-ui/build/static/js/537.85347210.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/537.85347210.chunk.js.map
Normal file
1
portal-ui/build/static/js/537.85347210.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
portal-ui/build/static/js/5882.41668ace.chunk.js
Normal file
2
portal-ui/build/static/js/5882.41668ace.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/5882.41668ace.chunk.js.map
Normal file
1
portal-ui/build/static/js/5882.41668ace.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/6901.cda3f1f9.chunk.js.map
Normal file
1
portal-ui/build/static/js/6901.cda3f1f9.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
"use strict";(self.webpackChunkportal_ui=self.webpackChunkportal_ui||[]).push([[6914],{76914:function(e,t,a){a.r(t);var r=a(29439),o=a(72791),i=a(16871),n=a(81207),s=a(25787),l=a(11135),c=a(62666),d=a(10703),p=a(61889),g=a(20890),h=a(75952),m=a(15146),u=a(80184);t.default=(0,s.Z)((function(e){return(0,l.Z)({paper:{borderRadius:8,display:"flex",flexDirection:"column",alignItems:"center",width:800,height:424,margin:"auto",position:"absolute",top:"50%",left:"50%",marginLeft:-400,marginTop:-212,"&.MuiPaper-root":{borderRadius:8}},submit:{margin:"30px 0px 16px",height:40,boxShadow:"none",padding:"16px 30px"},mainContainer:{position:"relative",height:424},theOcean:{borderTopLeftRadius:8,borderBottomLeftRadius:8,background:"transparent linear-gradient(to bottom, #073052 0%,#05122b 100%); 0% 0% no-repeat padding-box;"},oceanBg:{backgroundImage:"url(/images/BG_Illustration.svg)",backgroundRepeat:"no-repeat",backgroundPosition:"bottom left",height:"100%",width:324},theLogin:{padding:"40px 45px 20px 45px"},extraDetailsContainer:{fontStyle:"italic",color:"#9C9C9C",transition:"all .2s ease-in-out",padding:"0 5px",marginTop:5,overflow:"auto"},errorLabel:{color:"#000",fontSize:18,fontWeight:500,marginLeft:5},simpleError:{marginTop:5,padding:"2px 5px",fontSize:16,color:"#000"},messageIcon:{color:"#C72C48",display:"flex","& svg":{width:32,height:32}},errorTitle:{display:"flex",alignItems:"center"}})}))((function(e){var t=e.classes,a=(0,i.s0)(),s=(0,o.useState)(""),l=(0,r.Z)(s,2),x=l[0],f=l[1],b=(0,o.useState)(""),v=(0,r.Z)(b,2),j=v[0],C=v[1],S=(0,o.useState)(!0),Z=(0,r.Z)(S,2),k=Z[0],w=Z[1];return(0,o.useEffect)((function(){if(k){var e=window.location.search,t=new URLSearchParams(e),r=t.get("code"),o=t.get("state"),i=t.get("error"),s=t.get("errorDescription");i||s?(f(i||""),C(s||""),w(!1)):n.Z.invoke("POST","/api/v1/login/oauth2/auth",{code:r,state:o}).then((function(){var e="/";localStorage.getItem("redirect-path")&&""!==localStorage.getItem("redirect-path")&&(e="".concat(localStorage.getItem("redirect-path")),localStorage.setItem("redirect-path","")),w(!1),a(e)})).catch((function(e){f(e.errorMessage),C(e.detailedError),w(!1)}))}}),[k,a]),""!==x||""!==j?(0,u.jsx)(o.Fragment,{children:(0,u.jsx)(d.Z,{className:t.paper,children:(0,u.jsxs)(p.ZP,{container:!0,className:t.mainContainer,children:[(0,u.jsx)(p.ZP,{item:!0,xs:7,className:t.theOcean,children:(0,u.jsx)("div",{className:t.oceanBg})}),(0,u.jsxs)(p.ZP,{item:!0,xs:5,className:t.theLogin,children:[(0,u.jsxs)("div",{className:t.errorTitle,children:[(0,u.jsx)("span",{className:t.messageIcon,children:(0,u.jsx)(m.Z,{})}),(0,u.jsx)("span",{className:t.errorLabel,children:"Error from IDP"})]}),(0,u.jsx)("div",{className:t.simpleError,children:x}),(0,u.jsx)(g.Z,{variant:"body1",gutterBottom:!0,component:"div",className:t.extraDetailsContainer,children:j}),(0,u.jsx)(h.zx,{id:"back-to-login",onClick:function(){window.location.href="".concat(c.F,"login")},type:"submit",variant:"callAction",fullWidth:!0,children:"Back to Login"})]})]})})}):null}))},15146:function(e,t,a){var r=a(64836);t.Z=void 0;var o=r(a(45649)),i=a(80184),n=(0,o.default)((0,i.jsx)("path",{d:"M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"}),"ErrorOutline");t.Z=n}}]);
|
||||
//# sourceMappingURL=6914.8c73a010.chunk.js.map
|
||||
"use strict";(self.webpackChunkportal_ui=self.webpackChunkportal_ui||[]).push([[6914],{76914:function(e,t,a){a.r(t);var r=a(29439),o=a(72791),i=a(16871),n=a(81207),s=a(25787),l=a(11135),c=a(62666),d=a(10703),g=a(61889),p=a(20890),h=a(75952),m=a(15146),u=a(80184);t.default=(0,s.Z)((function(e){return(0,l.Z)({paper:{borderRadius:8,display:"flex",flexDirection:"column",alignItems:"center",width:800,height:424,margin:"auto",position:"absolute",top:"50%",left:"50%",marginLeft:-400,marginTop:-212,"&.MuiPaper-root":{borderRadius:8}},submit:{margin:"30px 0px 16px",height:40,boxShadow:"none",padding:"16px 30px"},mainContainer:{position:"relative",height:424},theOcean:{borderTopLeftRadius:8,borderBottomLeftRadius:8,background:"transparent linear-gradient(to bottom, #073052 0%,#05122b 100%); 0% 0% no-repeat padding-box;"},oceanBg:{backgroundImage:"url(/images/BG_Illustration.svg)",backgroundRepeat:"no-repeat",backgroundPosition:"bottom left",height:"100%",width:324},theLogin:{padding:"40px 45px 20px 45px"},extraDetailsContainer:{fontStyle:"italic",color:"#9C9C9C",transition:"all .2s ease-in-out",padding:"0 5px",marginTop:5,overflow:"auto"},errorLabel:{color:"#000",fontSize:18,fontWeight:500,marginLeft:5},simpleError:{marginTop:5,padding:"2px 5px",fontSize:16,color:"#000"},messageIcon:{color:"#C72C48",display:"flex","& svg":{width:32,height:32}},errorTitle:{display:"flex",alignItems:"center"}})}))((function(e){var t=e.classes,a=(0,i.s0)(),s=(0,o.useState)(""),l=(0,r.Z)(s,2),x=l[0],f=l[1],b=(0,o.useState)(""),v=(0,r.Z)(b,2),S=v[0],j=v[1],C=(0,o.useState)(!0),Z=(0,r.Z)(C,2),k=Z[0],w=Z[1];return(0,o.useEffect)((function(){if(k){var e=window.location.search,t=new URLSearchParams(e),r=t.get("code"),o=t.get("state"),i=t.get("error"),s=t.get("errorDescription");i||s?(f(i||""),j(s||""),w(!1)):n.Z.invoke("POST","/api/v1/login/oauth2/auth",{code:r,state:o}).then((function(){var e="/";localStorage.getItem("redirect-path")&&""!==localStorage.getItem("redirect-path")&&(e="".concat(localStorage.getItem("redirect-path")),localStorage.setItem("redirect-path","")),o&&localStorage.setItem("auth-state",o),w(!1),a(e)})).catch((function(e){f(e.errorMessage),j(e.detailedError),w(!1)}))}}),[k,a]),""!==x||""!==S?(0,u.jsx)(o.Fragment,{children:(0,u.jsx)(d.Z,{className:t.paper,children:(0,u.jsxs)(g.ZP,{container:!0,className:t.mainContainer,children:[(0,u.jsx)(g.ZP,{item:!0,xs:7,className:t.theOcean,children:(0,u.jsx)("div",{className:t.oceanBg})}),(0,u.jsxs)(g.ZP,{item:!0,xs:5,className:t.theLogin,children:[(0,u.jsxs)("div",{className:t.errorTitle,children:[(0,u.jsx)("span",{className:t.messageIcon,children:(0,u.jsx)(m.Z,{})}),(0,u.jsx)("span",{className:t.errorLabel,children:"Error from IDP"})]}),(0,u.jsx)("div",{className:t.simpleError,children:x}),(0,u.jsx)(p.Z,{variant:"body1",gutterBottom:!0,component:"div",className:t.extraDetailsContainer,children:S}),(0,u.jsx)(h.zx,{id:"back-to-login",onClick:function(){window.location.href="".concat(c.F,"login")},type:"submit",variant:"callAction",fullWidth:!0,children:"Back to Login"})]})]})})}):null}))},15146:function(e,t,a){var r=a(64836);t.Z=void 0;var o=r(a(45649)),i=a(80184),n=(0,o.default)((0,i.jsx)("path",{d:"M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"}),"ErrorOutline");t.Z=n}}]);
|
||||
//# sourceMappingURL=6914.1699f207.chunk.js.map
|
||||
1
portal-ui/build/static/js/6914.1699f207.chunk.js.map
Normal file
1
portal-ui/build/static/js/6914.1699f207.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
portal-ui/build/static/js/7295.c85034bf.chunk.js
Normal file
2
portal-ui/build/static/js/7295.c85034bf.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/7295.c85034bf.chunk.js.map
Normal file
1
portal-ui/build/static/js/7295.c85034bf.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
portal-ui/build/static/js/7486.83e0d248.chunk.js
Normal file
2
portal-ui/build/static/js/7486.83e0d248.chunk.js
Normal file
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkportal_ui=self.webpackChunkportal_ui||[]).push([[7486],{17486:function(u,e,n){n.r(e);n(72791);var t=n(11135),r=n(25787),s=n(44959),a=n(80184);e.default=(0,r.Z)((function(u){return(0,t.Z)({})}))((function(u){u.classes;return(0,a.jsx)(s.Z,{idpType:"ldap"})}))}}]);
|
||||
//# sourceMappingURL=7486.83e0d248.chunk.js.map
|
||||
1
portal-ui/build/static/js/7486.83e0d248.chunk.js.map
Normal file
1
portal-ui/build/static/js/7486.83e0d248.chunk.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/js/7486.83e0d248.chunk.js","mappings":"6KAiCA,WAAeA,EAAAA,EAAAA,IANA,SAACC,GAAD,OAAkBC,EAAAA,EAAAA,GAAa,CAAC,EAAhC,GAMf,EAJ8B,SAAC,GAA4C,EAA1CC,QAC/B,OAAO,SAAC,IAAD,CAAmBC,QAAS,QACpC,G","sources":["screens/Console/IDP/IDPLDAPConfigurations.tsx"],"sourcesContent":["// This file is part of MinIO Console Server\n// Copyright (c) 2022 MinIO, Inc.\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <http://www.gnu.org/licenses/>.\n\nimport React from \"react\";\n\nimport { Theme } from \"@mui/material/styles\";\nimport createStyles from \"@mui/styles/createStyles\";\nimport withStyles from \"@mui/styles/withStyles\";\nimport IDPConfigurations from \"./IDPConfigurations\";\n\ntype IDPLDAPConfigurationsProps = {\n classes?: any;\n};\n\nconst styles = (theme: Theme) => createStyles({});\n\nconst IDPLDAPConfigurations = ({ classes }: IDPLDAPConfigurationsProps) => {\n return <IDPConfigurations idpType={\"ldap\"} />;\n};\n\nexport default withStyles(styles)(IDPLDAPConfigurations);\n"],"names":["withStyles","theme","createStyles","classes","idpType"],"sourceRoot":""}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/9467.8e1b707f.chunk.js.map
Normal file
1
portal-ui/build/static/js/9467.8e1b707f.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
3
portal-ui/build/static/js/main.2b781eaf.js
Normal file
3
portal-ui/build/static/js/main.2b781eaf.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/main.2b781eaf.js.map
Normal file
1
portal-ui/build/static/js/main.2b781eaf.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 318 KiB |
BIN
portal-ui/build/static/media/videoBG.17363418b3c2246a0e27.mp4
Normal file
BIN
portal-ui/build/static/media/videoBG.17363418b3c2246a0e27.mp4
Normal file
Binary file not shown.
@@ -30,7 +30,7 @@
|
||||
"kbar": "^0.1.0-beta.34",
|
||||
"local-storage-fallback": "^4.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mds": "https://github.com/minio/mds.git#v0.0.5",
|
||||
"mds": "https://github.com/minio/mds.git#v0.0.7",
|
||||
"minio": "^7.0.28",
|
||||
"moment": "^2.29.4",
|
||||
"react": "^18.1.0",
|
||||
|
||||
@@ -39,7 +39,14 @@ const MainRouter = () => {
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route path="/logout" element={<Logout />} />
|
||||
<Route
|
||||
path="/logout"
|
||||
element={
|
||||
<Suspense fallback={<LoadingComponent />}>
|
||||
<Logout />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
|
||||
@@ -35,6 +35,7 @@ import { SRInfoStateType } from "./types";
|
||||
import { AppState, useAppDispatch } from "./store";
|
||||
import { saveSessionResponse } from "./screens/Console/consoleSlice";
|
||||
import { getOverrideColorVariants } from "./utils/stylesUtils";
|
||||
import LoadingComponent from "./common/LoadingComponent";
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
Component: any;
|
||||
@@ -119,7 +120,7 @@ const ProtectedRoute = ({ Component }: ProtectedRouteProps) => {
|
||||
|
||||
// if we're still trying to retrieve user session render nothing
|
||||
if (sessionLoading) {
|
||||
return null;
|
||||
return <LoadingComponent />;
|
||||
}
|
||||
// redirect user to the right page based on session status
|
||||
return userLoggedIn ? <Component /> : <StorePathAndRedirect />;
|
||||
|
||||
@@ -30,8 +30,6 @@ const LoadingComponent = () => {
|
||||
>
|
||||
<Grid item xs={3} style={{ textAlign: "center" }}>
|
||||
<Loader style={{ width: 35, height: 35 }} />
|
||||
<br />
|
||||
Loading...
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -130,6 +130,15 @@ export const IAM_PAGES = {
|
||||
ACCOUNT_ADD: "/access-keys/new-account",
|
||||
USER_SA_ACCOUNT_ADD: "/identity/users/new-user-sa/:userName",
|
||||
|
||||
/* IDP */
|
||||
IDP_LDAP_CONFIGURATIONS: "/idp/ldap/configurations",
|
||||
IDP_LDAP_CONFIGURATIONS_VIEW: "/idp/ldap/configurations/:idpName",
|
||||
IDP_LDAP_CONFIGURATIONS_ADD: "/idp/ldap/configurations/add-idp",
|
||||
|
||||
IDP_OPENID_CONFIGURATIONS: "/idp/openid/configurations",
|
||||
IDP_OPENID_CONFIGURATIONS_VIEW: "/idp/openid/configurations/:idpName",
|
||||
IDP_OPENID_CONFIGURATIONS_ADD: "/idp/openid/configurations/add-idp",
|
||||
|
||||
POLICIES: "/identity/policies",
|
||||
POLICY_ADD: "/identity/add-policy",
|
||||
POLICIES_VIEW: "/identity/policies/*",
|
||||
@@ -430,6 +439,30 @@ export const IAM_PAGES_PERMISSIONS = {
|
||||
IAM_SCOPES.ADMIN_SERVER_INFO,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_LDAP_CONFIGURATIONS]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_LDAP_CONFIGURATIONS_ADD]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_LDAP_CONFIGURATIONS_VIEW]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_OPENID_CONFIGURATIONS]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_OPENID_CONFIGURATIONS_ADD]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_OPENID_CONFIGURATIONS_VIEW]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
};
|
||||
|
||||
export const S3_ALL_RESOURCES = "arn:aws:s3:::*";
|
||||
|
||||
@@ -84,7 +84,9 @@ export const deleteCookie = (name: string) => {
|
||||
|
||||
export const clearSession = () => {
|
||||
storage.removeItem("token");
|
||||
storage.removeItem("auth-state");
|
||||
deleteCookie("token");
|
||||
deleteCookie("idp-refresh-token");
|
||||
};
|
||||
|
||||
// timeFromDate gets time string from date input
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment, useEffect } from "react";
|
||||
import React, { Fragment, useCallback, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
@@ -37,8 +37,22 @@ import {
|
||||
} from "../../../../common/SecureComponent/permissions";
|
||||
import BackLink from "../../../../common/BackLink";
|
||||
import {
|
||||
newMessage,
|
||||
resetMessages,
|
||||
setIsVersioned,
|
||||
setLoadingLocking,
|
||||
setLoadingObjectInfo,
|
||||
setLoadingObjects,
|
||||
setLoadingRecords,
|
||||
setLoadingVersioning,
|
||||
setLoadingVersions,
|
||||
setLockingEnabled,
|
||||
setObjectDetailsView,
|
||||
setRecords,
|
||||
setSearchObjects,
|
||||
setSearchVersions,
|
||||
setSelectedObjectView,
|
||||
setSimplePathHandler,
|
||||
setVersionsModeEnabled,
|
||||
} from "../../ObjectBrowser/objectBrowserSlice";
|
||||
import SearchBox from "../../Common/SearchBox";
|
||||
@@ -47,18 +61,99 @@ import AutoColorIcon from "../../Common/Components/AutoColorIcon";
|
||||
import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
|
||||
import { Button } from "mds";
|
||||
import hasPermission from "../../../../common/SecureComponent/accessControl";
|
||||
import { IMessageEvent } from "websocket";
|
||||
import { wsProtocol } from "../../../../utils/wsUtils";
|
||||
import {
|
||||
WebsocketRequest,
|
||||
WebsocketResponse,
|
||||
} from "../ListBuckets/Objects/ListObjects/types";
|
||||
import { decodeURLString, encodeURLString } from "../../../../common/utils";
|
||||
import { permissionItems } from "../ListBuckets/Objects/utils";
|
||||
import { setErrorSnackMessage } from "../../../../systemSlice";
|
||||
import api from "../../../../common/api";
|
||||
import { BucketObjectLocking, BucketVersioning } from "../types";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
let objectsWS: WebSocket;
|
||||
let currentRequestID: number = 0;
|
||||
let errorCounter: number = 0;
|
||||
let wsInFlight: boolean = false;
|
||||
|
||||
const initWSConnection = (
|
||||
openCallback?: () => void,
|
||||
onMessageCallback?: (message: IMessageEvent) => void
|
||||
) => {
|
||||
if (wsInFlight) {
|
||||
return;
|
||||
}
|
||||
wsInFlight = true;
|
||||
const url = new URL(window.location.toString());
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
const port = isDev ? "9090" : url.port;
|
||||
|
||||
// check if we are using base path, if not this always is `/`
|
||||
const baseLocation = new URL(document.baseURI);
|
||||
const baseUrl = baseLocation.pathname;
|
||||
|
||||
const wsProt = wsProtocol(url.protocol);
|
||||
|
||||
objectsWS = new WebSocket(
|
||||
`${wsProt}://${url.hostname}:${port}${baseUrl}ws/objectManager`
|
||||
);
|
||||
|
||||
objectsWS.onopen = () => {
|
||||
wsInFlight = false;
|
||||
if (openCallback) {
|
||||
openCallback();
|
||||
}
|
||||
errorCounter = 0;
|
||||
};
|
||||
|
||||
if (onMessageCallback) {
|
||||
objectsWS.onmessage = onMessageCallback;
|
||||
}
|
||||
|
||||
const reconnectFn = () => {
|
||||
if (errorCounter <= 5) {
|
||||
initWSConnection(openCallback, onMessageCallback);
|
||||
errorCounter += 1;
|
||||
} else {
|
||||
console.error("Websocket not available.");
|
||||
}
|
||||
};
|
||||
|
||||
objectsWS.onclose = () => {
|
||||
wsInFlight = false;
|
||||
console.warn("Websocket Disconnected. Attempting Reconnection...");
|
||||
|
||||
// We reconnect after 3 seconds
|
||||
setTimeout(reconnectFn, 3000);
|
||||
};
|
||||
|
||||
objectsWS.onerror = () => {
|
||||
wsInFlight = false;
|
||||
console.error("Error in websocket connection. Attempting reconnection...");
|
||||
|
||||
// We reconnect after 3 seconds
|
||||
setTimeout(reconnectFn, 3000);
|
||||
};
|
||||
};
|
||||
|
||||
const BrowserHandler = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
|
||||
const loadingVersioning = useSelector(
|
||||
(state: AppState) => state.objectBrowser.loadingVersioning
|
||||
);
|
||||
|
||||
const versionsMode = useSelector(
|
||||
(state: AppState) => state.objectBrowser.versionsMode
|
||||
);
|
||||
@@ -71,6 +166,36 @@ const BrowserHandler = () => {
|
||||
const searchVersions = useSelector(
|
||||
(state: AppState) => state.objectBrowser.searchVersions
|
||||
);
|
||||
const rewindEnabled = useSelector(
|
||||
(state: AppState) => state.objectBrowser.rewind.rewindEnabled
|
||||
);
|
||||
const rewindDate = useSelector(
|
||||
(state: AppState) => state.objectBrowser.rewind.dateToRewind
|
||||
);
|
||||
const showDeleted = useSelector(
|
||||
(state: AppState) => state.objectBrowser.showDeleted
|
||||
);
|
||||
const allowResources = useSelector(
|
||||
(state: AppState) => state.console.session.allowResources
|
||||
);
|
||||
const loadingObjects = useSelector(
|
||||
(state: AppState) => state.objectBrowser.loadingObjects
|
||||
);
|
||||
const loadingLocking = useSelector(
|
||||
(state: AppState) => state.objectBrowser.loadingLocking
|
||||
);
|
||||
const bucketToRewind = useSelector(
|
||||
(state: AppState) => state.objectBrowser.rewind.bucketToRewind
|
||||
);
|
||||
const loadRecords = useSelector(
|
||||
(state: AppState) => state.objectBrowser.loadRecords
|
||||
);
|
||||
const detailsOpen = useSelector(
|
||||
(state: AppState) => state.objectBrowser.objectDetailsOpen
|
||||
);
|
||||
const selectedInternalPaths = useSelector(
|
||||
(state: AppState) => state.objectBrowser.selectedInternalPaths
|
||||
);
|
||||
|
||||
const features = useSelector(selFeatures);
|
||||
|
||||
@@ -81,10 +206,286 @@ const BrowserHandler = () => {
|
||||
|
||||
const obOnly = !!features?.includes("object-browser-only");
|
||||
|
||||
/*WS Request Handlers*/
|
||||
const onMessageCallBack = useCallback(
|
||||
(message: IMessageEvent) => {
|
||||
// reset start status
|
||||
dispatch(setLoadingObjects(false));
|
||||
|
||||
const response: WebsocketResponse = JSON.parse(message.data.toString());
|
||||
if (currentRequestID === response.request_id) {
|
||||
// If response is not from current request, we can omit
|
||||
if (response.request_id !== currentRequestID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
response.error ===
|
||||
"The Access Key Id you provided does not exist in our records."
|
||||
) {
|
||||
// Session expired.
|
||||
window.location.reload();
|
||||
} else if (response.error === "Access Denied.") {
|
||||
let pathPrefix = "";
|
||||
if (internalPaths) {
|
||||
const decodedPath = decodeURLString(internalPaths);
|
||||
pathPrefix = decodedPath.endsWith("/")
|
||||
? decodedPath
|
||||
: decodedPath + "/";
|
||||
}
|
||||
|
||||
const permitItems = permissionItems(
|
||||
bucketName,
|
||||
pathPrefix,
|
||||
allowResources || []
|
||||
);
|
||||
|
||||
if (!permitItems || permitItems.length === 0) {
|
||||
dispatch(
|
||||
setErrorSnackMessage({
|
||||
errorMessage: response.error,
|
||||
detailedError: response.error,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
dispatch(setRecords(permitItems));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This indicates final messages is received.
|
||||
if (response.request_end) {
|
||||
dispatch(setLoadingObjects(false));
|
||||
dispatch(setLoadingRecords(false));
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
dispatch(newMessage(response.data));
|
||||
}
|
||||
}
|
||||
},
|
||||
[dispatch, internalPaths, allowResources, bucketName]
|
||||
);
|
||||
|
||||
const initWSRequest = useCallback(
|
||||
(path: string, date: Date) => {
|
||||
if (objectsWS && objectsWS.readyState === 1) {
|
||||
try {
|
||||
const newRequestID = currentRequestID + 1;
|
||||
dispatch(resetMessages());
|
||||
|
||||
const request: WebsocketRequest = {
|
||||
bucket_name: bucketName,
|
||||
prefix: encodeURLString(path),
|
||||
mode: rewindEnabled || showDeleted ? "rewind" : "objects",
|
||||
date: date.toISOString(),
|
||||
request_id: newRequestID,
|
||||
};
|
||||
|
||||
objectsWS.send(JSON.stringify(request));
|
||||
|
||||
// We store the new ID for the requestID
|
||||
currentRequestID = newRequestID;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} else {
|
||||
// Socket is disconnected, we request reconnection but will need to recreate call
|
||||
const dupRequest = () => {
|
||||
initWSRequest(path, date);
|
||||
};
|
||||
|
||||
initWSConnection(dupRequest, onMessageCallBack);
|
||||
}
|
||||
},
|
||||
[bucketName, rewindEnabled, showDeleted, dispatch, onMessageCallBack]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
const request: WebsocketRequest = {
|
||||
mode: "cancel",
|
||||
request_id: currentRequestID,
|
||||
};
|
||||
|
||||
if (objectsWS && objectsWS.readyState === 1) {
|
||||
objectsWS.send(JSON.stringify(request));
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (objectsWS?.readyState === 1) {
|
||||
const decodedIPaths = decodeURLString(internalPaths);
|
||||
|
||||
if (decodedIPaths.endsWith("/") || decodedIPaths === "") {
|
||||
dispatch(setObjectDetailsView(false));
|
||||
dispatch(setSelectedObjectView(null));
|
||||
dispatch(
|
||||
setSimplePathHandler(decodedIPaths === "" ? "/" : decodedIPaths)
|
||||
);
|
||||
} else {
|
||||
dispatch(setLoadingObjectInfo(true));
|
||||
dispatch(setObjectDetailsView(true));
|
||||
dispatch(setLoadingVersions(true));
|
||||
dispatch(
|
||||
setSelectedObjectView(
|
||||
`${decodedIPaths ? `${encodeURLString(decodedIPaths)}` : ``}`
|
||||
)
|
||||
);
|
||||
dispatch(
|
||||
setSimplePathHandler(
|
||||
`${decodedIPaths.split("/").slice(0, -1).join("/")}/`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [internalPaths, rewindDate, rewindEnabled, dispatch]);
|
||||
|
||||
// Direct file access effect / prefix
|
||||
useEffect(() => {
|
||||
if (!loadingObjects && loadRecords && !rewindEnabled) {
|
||||
const parentPath = `${decodeURLString(internalPaths)
|
||||
.split("/")
|
||||
.slice(0, -1)
|
||||
.join("/")}/`;
|
||||
|
||||
initWSRequest(parentPath, new Date());
|
||||
}
|
||||
}, [
|
||||
loadingObjects,
|
||||
loadRecords,
|
||||
bucketName,
|
||||
bucketToRewind,
|
||||
dispatch,
|
||||
internalPaths,
|
||||
rewindDate,
|
||||
rewindEnabled,
|
||||
initWSRequest,
|
||||
detailsOpen,
|
||||
]);
|
||||
|
||||
const displayListObjects = hasPermission(bucketName, [
|
||||
IAM_SCOPES.S3_LIST_BUCKET,
|
||||
]);
|
||||
|
||||
// Common objects list
|
||||
useEffect(() => {
|
||||
// begin watch if bucketName in bucketList and start pressed
|
||||
if (loadingObjects && displayListObjects) {
|
||||
let pathPrefix = "";
|
||||
if (internalPaths) {
|
||||
const decodedPath = decodeURLString(internalPaths);
|
||||
|
||||
// internalPaths are selected (file details), we split and get parent folder
|
||||
if (selectedInternalPaths === internalPaths) {
|
||||
pathPrefix = `${decodeURLString(internalPaths)
|
||||
.split("/")
|
||||
.slice(0, -1)
|
||||
.join("/")}/`;
|
||||
} else {
|
||||
pathPrefix = decodedPath.endsWith("/")
|
||||
? decodedPath
|
||||
: decodedPath + "/";
|
||||
}
|
||||
}
|
||||
|
||||
let requestDate = new Date();
|
||||
|
||||
if (rewindEnabled && rewindDate) {
|
||||
requestDate = rewindDate;
|
||||
}
|
||||
|
||||
initWSRequest(pathPrefix, requestDate);
|
||||
} else {
|
||||
dispatch(setLoadingObjects(false));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
loadingObjects,
|
||||
internalPaths,
|
||||
dispatch,
|
||||
rewindDate,
|
||||
rewindEnabled,
|
||||
displayListObjects,
|
||||
initWSRequest,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setVersionsModeEnabled({ status: false }));
|
||||
}, [internalPaths, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingVersioning) {
|
||||
if (displayListObjects) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/versioning`)
|
||||
.then((res: BucketVersioning) => {
|
||||
dispatch(setIsVersioned(res.is_versioned));
|
||||
dispatch(setLoadingVersioning(false));
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
console.error(
|
||||
"Error Getting Object Versioning Status: ",
|
||||
err.detailedError
|
||||
);
|
||||
dispatch(setLoadingVersioning(false));
|
||||
});
|
||||
} else {
|
||||
dispatch(setLoadingVersioning(false));
|
||||
dispatch(resetMessages());
|
||||
}
|
||||
}
|
||||
}, [bucketName, loadingVersioning, dispatch, displayListObjects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingLocking) {
|
||||
if (displayListObjects) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/object-locking`)
|
||||
.then((res: BucketObjectLocking) => {
|
||||
dispatch(setLockingEnabled(res.object_locking_enabled));
|
||||
dispatch(setLoadingLocking(false));
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
console.error(
|
||||
"Error Getting Object Locking Status: ",
|
||||
err.detailedError
|
||||
);
|
||||
dispatch(setLoadingLocking(false));
|
||||
});
|
||||
} else {
|
||||
dispatch(resetMessages());
|
||||
dispatch(setLoadingLocking(false));
|
||||
}
|
||||
}
|
||||
}, [bucketName, loadingLocking, dispatch, displayListObjects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingLocking) {
|
||||
if (displayListObjects) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/object-locking`)
|
||||
.then((res: BucketObjectLocking) => {
|
||||
dispatch(setLockingEnabled(res.object_locking_enabled));
|
||||
setLoadingLocking(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
console.error(
|
||||
"Error Getting Object Locking Status: ",
|
||||
err.detailedError
|
||||
);
|
||||
setLoadingLocking(false);
|
||||
});
|
||||
} else {
|
||||
dispatch(resetMessages());
|
||||
setLoadingLocking(false);
|
||||
}
|
||||
}
|
||||
}, [bucketName, loadingLocking, dispatch, displayListObjects]);
|
||||
|
||||
const openBucketConfiguration = () => {
|
||||
navigate(`/buckets/${bucketName}/admin`);
|
||||
};
|
||||
|
||||
@@ -64,6 +64,7 @@ import { selFeatures } from "../../consoleSlice";
|
||||
import AutoColorIcon from "../../Common/Components/AutoColorIcon";
|
||||
import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
|
||||
import AButton from "../../Common/AButton/AButton";
|
||||
import { setLoadingObjects } from "../../ObjectBrowser/objectBrowserSlice";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -123,6 +124,7 @@ const ListBuckets = ({ classes }: IListBucketsProps) => {
|
||||
.then((res: BucketList) => {
|
||||
setLoading(false);
|
||||
setRecords(res.buckets || []);
|
||||
dispatch(setLoadingObjects(true));
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
formFieldStyles,
|
||||
modalStyleUtils,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { connect } from "react-redux";
|
||||
import { connect, useSelector } from "react-redux";
|
||||
import { encodeURLString } from "../../../../../../common/utils";
|
||||
|
||||
import { BucketObjectItem } from "./types";
|
||||
@@ -41,7 +41,6 @@ interface ICreatePath {
|
||||
bucketName: string;
|
||||
folderName: string;
|
||||
onClose: () => any;
|
||||
existingFiles: BucketObjectItem[];
|
||||
simplePath: string | null;
|
||||
}
|
||||
|
||||
@@ -57,7 +56,6 @@ const CreatePathModal = ({
|
||||
bucketName,
|
||||
onClose,
|
||||
classes,
|
||||
existingFiles,
|
||||
simplePath,
|
||||
}: ICreatePath) => {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -67,6 +65,8 @@ const CreatePathModal = ({
|
||||
const [isFormValid, setIsFormValid] = useState<boolean>(false);
|
||||
const [currentPath, setCurrentPath] = useState(bucketName);
|
||||
|
||||
const records = useSelector((state: AppState) => state.objectBrowser.records);
|
||||
|
||||
useEffect(() => {
|
||||
if (simplePath) {
|
||||
const newPath = `${bucketName}${
|
||||
@@ -91,7 +91,7 @@ const CreatePathModal = ({
|
||||
const sharesName = (record: BucketObjectItem) =>
|
||||
record.name === folderPath + pathUrl;
|
||||
|
||||
if (existingFiles.findIndex(sharesName) !== -1) {
|
||||
if (records.findIndex(sharesName) !== -1) {
|
||||
dispatch(
|
||||
setModalErrorSnackMessage({
|
||||
errorMessage: "Folder cannot have the same name as an existing file",
|
||||
|
||||
@@ -30,15 +30,10 @@ import { Button } from "mds";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import get from "lodash/get";
|
||||
import { BucketObjectItem, BucketObjectItemsList } from "./types";
|
||||
import api from "../../../../../../common/api";
|
||||
import TableWrapper, {
|
||||
ItemActions,
|
||||
} from "../../../../Common/TableWrapper/TableWrapper";
|
||||
import {
|
||||
decodeURLString,
|
||||
encodeURLString,
|
||||
getClientOS,
|
||||
niceBytesInt,
|
||||
} from "../../../../../../common/utils";
|
||||
|
||||
@@ -50,27 +45,16 @@ import {
|
||||
searchField,
|
||||
tableStyles,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { Badge, Typography } from "@mui/material";
|
||||
import { Badge } from "@mui/material";
|
||||
import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
|
||||
import {
|
||||
download,
|
||||
extensionPreview,
|
||||
permissionItems,
|
||||
sortListObjects,
|
||||
} from "../utils";
|
||||
import {
|
||||
BucketInfo,
|
||||
BucketObjectLocking,
|
||||
BucketQuota,
|
||||
BucketVersioning,
|
||||
} from "../../../types";
|
||||
import { extensionPreview } from "../utils";
|
||||
import { BucketInfo, BucketQuota } from "../../../types";
|
||||
import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
|
||||
import ScreenTitle from "../../../../Common/ScreenTitle/ScreenTitle";
|
||||
|
||||
import { AppState, useAppDispatch } from "../../../../../../store";
|
||||
import PageLayout from "../../../../Common/Layout/PageLayout";
|
||||
|
||||
import {
|
||||
IAM_SCOPES,
|
||||
permissionTooltipHelper,
|
||||
@@ -79,7 +63,6 @@ import {
|
||||
hasPermission,
|
||||
SecureComponent,
|
||||
} from "../../../../../../common/SecureComponent";
|
||||
|
||||
import withSuspense from "../../../../Common/Components/withSuspense";
|
||||
import {
|
||||
BucketsIcon,
|
||||
@@ -91,7 +74,6 @@ import UploadFilesButton from "../../UploadFilesButton";
|
||||
import DetailsListPanel from "./DetailsListPanel";
|
||||
import ObjectDetailPanel from "./ObjectDetailPanel";
|
||||
import ActionsListSection from "./ActionsListSection";
|
||||
import { listModeColumns, rewindModeColumns } from "./ListObjectsHelpers";
|
||||
import VersionsNavigator from "../ObjectDetails/VersionsNavigator";
|
||||
import CheckboxWrapper from "../../../../Common/FormComponents/CheckboxWrapper/CheckboxWrapper";
|
||||
|
||||
@@ -111,16 +93,21 @@ import {
|
||||
completeObject,
|
||||
failObject,
|
||||
openList,
|
||||
resetMessages,
|
||||
resetRewind,
|
||||
setLoadingObjectInfo,
|
||||
setLoadingObjectsList,
|
||||
setDownloadRenameModal,
|
||||
setLoadingObjects,
|
||||
setLoadingRecords,
|
||||
setLoadingVersions,
|
||||
setNewObject,
|
||||
setObjectDetailsView,
|
||||
setPreviewOpen,
|
||||
setSearchObjects,
|
||||
setSelectedObjects,
|
||||
setSelectedObjectView,
|
||||
setSelectedPreview,
|
||||
setShareFileModalOpen,
|
||||
setShowDeletedObjects,
|
||||
setSimplePathHandler,
|
||||
setVersionsModeEnabled,
|
||||
updateProgress,
|
||||
} from "../../../../ObjectBrowser/objectBrowserSlice";
|
||||
@@ -132,8 +119,13 @@ import {
|
||||
setBucketInfo,
|
||||
} from "../../../BucketDetails/bucketDetailsSlice";
|
||||
import RenameLongFileName from "../../../../ObjectBrowser/RenameLongFilename";
|
||||
import { selFeatures } from "../../../../consoleSlice";
|
||||
import TooltipWrapper from "../../../../Common/TooltipWrapper/TooltipWrapper";
|
||||
import ListObjectsTable from "./ListObjectsTable";
|
||||
import {
|
||||
downloadSelected,
|
||||
openPreview,
|
||||
openShare,
|
||||
} from "../../../../ObjectBrowser/objectBrowserThunks";
|
||||
|
||||
const HistoryIcon = React.lazy(
|
||||
() => import("../../../../../../icons/HistoryIcon")
|
||||
@@ -159,28 +151,6 @@ const PreviewFileModal = withSuspense(
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
browsePaper: {
|
||||
border: 0,
|
||||
height: "calc(100vh - 210px)",
|
||||
"&.isEmbedded": {
|
||||
height: "calc(100vh - 315px)",
|
||||
},
|
||||
"&.actionsPanelOpen": {
|
||||
minHeight: "100%",
|
||||
},
|
||||
"@media (max-width: 800px)": {
|
||||
width: 800,
|
||||
},
|
||||
},
|
||||
"@global": {
|
||||
".rowLine:hover .iconFileElm": {
|
||||
backgroundImage: "url(/images/ob_file_filled.svg)",
|
||||
},
|
||||
".rowLine:hover .iconFolderElm": {
|
||||
backgroundImage: "url(/images/ob_folder_filled.svg)",
|
||||
},
|
||||
},
|
||||
|
||||
badgeOverlap: {
|
||||
"& .MuiBadge-badge": {
|
||||
top: 10,
|
||||
@@ -215,12 +185,8 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
breadcrumbsContainer: {
|
||||
padding: "12px 14px 5px",
|
||||
},
|
||||
parentWrapper: {
|
||||
"@media (max-width: 800px)": {
|
||||
overflowX: "auto",
|
||||
},
|
||||
},
|
||||
fullContainer: {
|
||||
position: "relative",
|
||||
"@media (max-width: 799px)": {
|
||||
width: 0,
|
||||
},
|
||||
@@ -255,31 +221,6 @@ const acceptDnDStyle = {
|
||||
borderColor: "#00e676",
|
||||
};
|
||||
|
||||
function useInterval(callback: any, delay: number) {
|
||||
const savedCallback = useRef<Function | null>(null);
|
||||
|
||||
// Remember the latest callback.
|
||||
useEffect(() => {
|
||||
savedCallback.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
// Set up the interval.
|
||||
useEffect(() => {
|
||||
function tick() {
|
||||
if (savedCallback !== undefined && savedCallback.current) {
|
||||
savedCallback.current();
|
||||
}
|
||||
}
|
||||
|
||||
if (delay !== null) {
|
||||
let id = setInterval(tick, delay);
|
||||
return () => clearInterval(id);
|
||||
}
|
||||
}, [delay]);
|
||||
}
|
||||
|
||||
const defLoading = <Typography component="h3">Loading...</Typography>;
|
||||
|
||||
const ListObjects = () => {
|
||||
const classes = useStyles();
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -290,9 +231,6 @@ const ListObjects = () => {
|
||||
const rewindEnabled = useSelector(
|
||||
(state: AppState) => state.objectBrowser.rewind.rewindEnabled
|
||||
);
|
||||
const rewindDate = useSelector(
|
||||
(state: AppState) => state.objectBrowser.rewind.dateToRewind
|
||||
);
|
||||
const bucketToRewind = useSelector(
|
||||
(state: AppState) => state.objectBrowser.rewind.bucketToRewind
|
||||
);
|
||||
@@ -300,9 +238,6 @@ const ListObjects = () => {
|
||||
(state: AppState) => state.objectBrowser.versionsMode
|
||||
);
|
||||
|
||||
const searchObjects = useSelector(
|
||||
(state: AppState) => state.objectBrowser.searchObjects
|
||||
);
|
||||
const showDeleted = useSelector(
|
||||
(state: AppState) => state.objectBrowser.showDeleted
|
||||
);
|
||||
@@ -312,47 +247,41 @@ const ListObjects = () => {
|
||||
const selectedInternalPaths = useSelector(
|
||||
(state: AppState) => state.objectBrowser.selectedInternalPaths
|
||||
);
|
||||
const loading = useSelector(
|
||||
const loadingObjects = useSelector(
|
||||
(state: AppState) => state.objectBrowser.loadingObjects
|
||||
);
|
||||
const simplePath = useSelector(
|
||||
(state: AppState) => state.objectBrowser.simplePath
|
||||
);
|
||||
|
||||
const loadingBucket = useSelector(selBucketDetailsLoading);
|
||||
const bucketInfo = useSelector(selBucketDetailsInfo);
|
||||
const allowResources = useSelector(
|
||||
(state: AppState) => state.console.session.allowResources
|
||||
const isVersioned = useSelector(
|
||||
(state: AppState) => state.objectBrowser.isVersioned
|
||||
);
|
||||
const lockingEnabled = useSelector(
|
||||
(state: AppState) => state.objectBrowser.lockingEnabled
|
||||
);
|
||||
const downloadRenameModal = useSelector(
|
||||
(state: AppState) => state.objectBrowser.downloadRenameModal
|
||||
);
|
||||
const selectedPreview = useSelector(
|
||||
(state: AppState) => state.objectBrowser.selectedPreview
|
||||
);
|
||||
const shareFileModalOpen = useSelector(
|
||||
(state: AppState) => state.objectBrowser.shareFileModalOpen
|
||||
);
|
||||
const previewOpen = useSelector(
|
||||
(state: AppState) => state.objectBrowser.previewOpen
|
||||
);
|
||||
|
||||
const features = useSelector(selFeatures);
|
||||
const obOnly = !!features?.includes("object-browser-only");
|
||||
const loadingBucket = useSelector(selBucketDetailsLoading);
|
||||
const bucketInfo = useSelector(selBucketDetailsInfo);
|
||||
|
||||
const [records, setRecords] = useState<BucketObjectItem[]>([]);
|
||||
const [deleteMultipleOpen, setDeleteMultipleOpen] = useState<boolean>(false);
|
||||
const [loadingStartTime, setLoadingStartTime] = useState<number>(0);
|
||||
const [loadingMessage, setLoadingMessage] =
|
||||
useState<React.ReactNode>(defLoading);
|
||||
const [loadingVersioning, setLoadingVersioning] = useState<boolean>(true);
|
||||
const [isVersioned, setIsVersioned] = useState<boolean>(false);
|
||||
const [loadingLocking, setLoadingLocking] = useState<boolean>(true);
|
||||
const [lockingEnabled, setLockingEnabled] = useState<boolean>(false);
|
||||
const [rewindSelect, setRewindSelect] = useState<boolean>(false);
|
||||
const [selectedObjects, setSelectedObjects] = useState<string[]>([]);
|
||||
const [previewOpen, setPreviewOpen] = useState<boolean>(false);
|
||||
const [selectedPreview, setSelectedPreview] =
|
||||
useState<BucketObjectItem | null>(null);
|
||||
const [shareFileModalOpen, setShareFileModalOpen] = useState<boolean>(false);
|
||||
const [sortDirection, setSortDirection] = useState<
|
||||
"ASC" | "DESC" | undefined
|
||||
>("ASC");
|
||||
const [currentSortField, setCurrentSortField] = useState<string>("name");
|
||||
const [iniLoad, setIniLoad] = useState<boolean>(false);
|
||||
const [canShareFile, setCanShareFile] = useState<boolean>(false);
|
||||
const [canPreviewFile, setCanPreviewFile] = useState<boolean>(false);
|
||||
const [quota, setQuota] = useState<BucketQuota | null>(null);
|
||||
const [downloadRenameModal, setDownloadRenameModal] =
|
||||
useState<BucketObjectItem | null>(null);
|
||||
|
||||
const pathSegment = location.pathname.split("/browse/");
|
||||
|
||||
@@ -379,6 +308,30 @@ const ListObjects = () => {
|
||||
true
|
||||
);
|
||||
|
||||
const displayDeleteObject = hasPermission(bucketName, [
|
||||
IAM_SCOPES.S3_DELETE_OBJECT,
|
||||
]);
|
||||
const selectedObjects = useSelector(
|
||||
(state: AppState) => state.objectBrowser.selectedObjects
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setSearchObjects(""));
|
||||
dispatch(setLoadingObjects(true));
|
||||
dispatch(setSelectedObjects([]));
|
||||
}, [simplePath, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (rewindEnabled) {
|
||||
if (bucketToRewind !== bucketName) {
|
||||
dispatch(resetRewind());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, [rewindEnabled, bucketToRewind, bucketName, dispatch]);
|
||||
|
||||
// END OF WS HANDLERS
|
||||
|
||||
useEffect(() => {
|
||||
if (folderUpload.current !== null) {
|
||||
folderUpload.current.setAttribute("directory", "");
|
||||
@@ -433,39 +386,14 @@ const ListObjects = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedObjects.length === 0 && selectedInternalPaths === null) {
|
||||
if (
|
||||
selectedObjects.length === 0 &&
|
||||
selectedInternalPaths === null &&
|
||||
!loadingObjects
|
||||
) {
|
||||
dispatch(setObjectDetailsView(false));
|
||||
}
|
||||
}, [selectedObjects, selectedInternalPaths, dispatch]);
|
||||
|
||||
const displayDeleteObject = hasPermission(bucketName, [
|
||||
IAM_SCOPES.S3_DELETE_OBJECT,
|
||||
]);
|
||||
|
||||
const displayListObjects = hasPermission(bucketName, [
|
||||
IAM_SCOPES.S3_LIST_BUCKET,
|
||||
]);
|
||||
|
||||
const updateMessage = () => {
|
||||
let timeDelta = Date.now() - loadingStartTime;
|
||||
|
||||
if (timeDelta / 1000 >= 6) {
|
||||
setLoadingMessage(
|
||||
<Fragment>
|
||||
<Typography component="h3">
|
||||
This operation is taking longer than expected... (
|
||||
{Math.ceil(timeDelta / 1000)}s)
|
||||
</Typography>
|
||||
</Fragment>
|
||||
);
|
||||
} else if (timeDelta / 1000 >= 3) {
|
||||
setLoadingMessage(
|
||||
<Typography component="h3">
|
||||
This operation is taking longer than expected...
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [selectedObjects, selectedInternalPaths, dispatch, loadingObjects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!iniLoad) {
|
||||
@@ -474,280 +402,6 @@ const ListObjects = () => {
|
||||
}
|
||||
}, [iniLoad, dispatch, setIniLoad]);
|
||||
|
||||
useInterval(() => {
|
||||
// Your custom logic here
|
||||
if (loading) {
|
||||
updateMessage();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingVersioning) {
|
||||
if (displayListObjects) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/versioning`)
|
||||
.then((res: BucketVersioning) => {
|
||||
setIsVersioned(res.is_versioned);
|
||||
setLoadingVersioning(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
console.error(
|
||||
"Error Getting Object Versioning Status: ",
|
||||
err.detailedError
|
||||
);
|
||||
setLoadingVersioning(false);
|
||||
});
|
||||
} else {
|
||||
setLoadingVersioning(false);
|
||||
setRecords([]);
|
||||
}
|
||||
}
|
||||
}, [bucketName, loadingVersioning, dispatch, displayListObjects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingLocking) {
|
||||
if (displayListObjects) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/object-locking`)
|
||||
.then((res: BucketObjectLocking) => {
|
||||
setLockingEnabled(res.object_locking_enabled);
|
||||
setLoadingLocking(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
console.error(
|
||||
"Error Getting Object Locking Status: ",
|
||||
err.detailedError
|
||||
);
|
||||
setLoadingLocking(false);
|
||||
});
|
||||
} else {
|
||||
setRecords([]);
|
||||
setLoadingLocking(false);
|
||||
}
|
||||
}
|
||||
}, [bucketName, loadingLocking, dispatch, displayListObjects]);
|
||||
|
||||
useEffect(() => {
|
||||
const decodedIPaths = decodeURLString(internalPaths);
|
||||
|
||||
if (decodedIPaths.endsWith("/") || decodedIPaths === "") {
|
||||
dispatch(setObjectDetailsView(false));
|
||||
dispatch(setSelectedObjectView(null));
|
||||
dispatch(
|
||||
setSimplePathHandler(decodedIPaths === "" ? "/" : decodedIPaths)
|
||||
);
|
||||
} else {
|
||||
dispatch(setLoadingObjectInfo(true));
|
||||
dispatch(setObjectDetailsView(true));
|
||||
dispatch(setLoadingVersions(true));
|
||||
dispatch(
|
||||
setSelectedObjectView(
|
||||
`${decodedIPaths ? `${encodeURLString(decodedIPaths)}` : ``}`
|
||||
)
|
||||
);
|
||||
dispatch(
|
||||
setSimplePathHandler(
|
||||
`${decodedIPaths.split("/").slice(0, -1).join("/")}/`
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [internalPaths, rewindDate, rewindEnabled, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setSearchObjects(""));
|
||||
dispatch(setLoadingObjectsList(true));
|
||||
setSelectedObjects([]);
|
||||
}, [simplePath, dispatch, setSelectedObjects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
if (displayListObjects) {
|
||||
let pathPrefix = "";
|
||||
if (internalPaths) {
|
||||
const decodedPath = decodeURLString(internalPaths);
|
||||
pathPrefix = decodedPath.endsWith("/")
|
||||
? decodedPath
|
||||
: decodedPath + "/";
|
||||
}
|
||||
|
||||
let currentTimestamp = Date.now();
|
||||
setLoadingStartTime(currentTimestamp);
|
||||
setLoadingMessage(defLoading);
|
||||
|
||||
// We get URL to look into
|
||||
let urlTake = `/api/v1/buckets/${bucketName}/objects`;
|
||||
|
||||
// Is rewind enabled?, we use Rewind API
|
||||
if (rewindEnabled) {
|
||||
if (bucketToRewind !== bucketName) {
|
||||
dispatch(resetRewind());
|
||||
return;
|
||||
}
|
||||
|
||||
if (rewindDate) {
|
||||
const rewindParsed = rewindDate.toISOString();
|
||||
|
||||
urlTake = `/api/v1/buckets/${bucketName}/rewind/${rewindParsed}`;
|
||||
}
|
||||
} else if (showDeleted) {
|
||||
// Do we want to display deleted items too?, we use rewind to current time to show everything
|
||||
const currDate = new Date();
|
||||
const currDateISO = currDate.toISOString();
|
||||
|
||||
urlTake = `/api/v1/buckets/${bucketName}/rewind/${currDateISO}`;
|
||||
}
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`${urlTake}${
|
||||
pathPrefix ? `?prefix=${encodeURLString(pathPrefix)}` : ``
|
||||
}`
|
||||
)
|
||||
.then((res: BucketObjectItemsList) => {
|
||||
const records: BucketObjectItem[] = res.objects || [];
|
||||
const folders: BucketObjectItem[] = [];
|
||||
const files: BucketObjectItem[] = [];
|
||||
|
||||
// We separate items between folders or files to display folders at the beginning always.
|
||||
records.forEach((record) => {
|
||||
// We omit files from the same path
|
||||
if (record.name !== decodeURLString(internalPaths)) {
|
||||
// this is a folder
|
||||
if (record.name.endsWith("/")) {
|
||||
folders.push(record);
|
||||
} else {
|
||||
// this is a file
|
||||
files.push(record);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const recordsInElement = [...folders, ...files];
|
||||
|
||||
if (recordsInElement.length === 0 && pathPrefix !== "") {
|
||||
let pathTest = `/api/v1/buckets/${bucketName}/objects${
|
||||
internalPaths ? `?prefix=${internalPaths}` : ""
|
||||
}`;
|
||||
|
||||
if (rewindEnabled) {
|
||||
const rewindParsed = rewindDate.toISOString();
|
||||
|
||||
let pathPrefix = "";
|
||||
if (internalPaths) {
|
||||
const decodedPath = decodeURLString(internalPaths);
|
||||
pathPrefix = decodedPath.endsWith("/")
|
||||
? decodedPath
|
||||
: decodedPath + "/";
|
||||
}
|
||||
|
||||
pathTest = `/api/v1/buckets/${bucketName}/rewind/${rewindParsed}${
|
||||
pathPrefix ? `?prefix=${encodeURLString(pathPrefix)}` : ``
|
||||
}`;
|
||||
}
|
||||
|
||||
api
|
||||
.invoke("GET", pathTest)
|
||||
.then((res: BucketObjectItemsList) => {
|
||||
//It is a file since it has elements in the object, setting file flag and waiting for component mount
|
||||
if (!res.objects) {
|
||||
// It is a folder, we remove loader & set original results list
|
||||
dispatch(setLoadingObjectsList(false));
|
||||
setRecords(recordsInElement);
|
||||
} else {
|
||||
// This code prevents the program from opening a file when a substring of that file is entered as a new folder.
|
||||
// Previously, if there was a file test1.txt and the folder test was created with the same prefix, the program
|
||||
// would open test1.txt instead
|
||||
let found = false;
|
||||
let pathPrefixChopped = pathPrefix.slice(
|
||||
0,
|
||||
pathPrefix.length - 1
|
||||
);
|
||||
for (let i = 0; i < res.objects.length; i++) {
|
||||
if (res.objects[i].name === pathPrefixChopped) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (
|
||||
(res.objects.length === 1 &&
|
||||
res.objects[0].name.endsWith("/")) ||
|
||||
!found
|
||||
) {
|
||||
// This is a folder, we set the original results list
|
||||
setRecords(recordsInElement);
|
||||
} else {
|
||||
// This is a file. We change URL & Open file details view.
|
||||
dispatch(setObjectDetailsView(true));
|
||||
dispatch(setSelectedObjectView(internalPaths));
|
||||
|
||||
// We split the selected object URL & remove the last item to fetch the files list for the parent folder
|
||||
const parentPath = `${decodeURLString(internalPaths)
|
||||
.split("/")
|
||||
.slice(0, -1)
|
||||
.join("/")}/`;
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`${urlTake}${
|
||||
pathPrefix
|
||||
? `?prefix=${encodeURLString(parentPath)}`
|
||||
: ``
|
||||
}`
|
||||
)
|
||||
.then((res: BucketObjectItemsList) => {
|
||||
const records: BucketObjectItem[] = res.objects || [];
|
||||
|
||||
setRecords(records);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
dispatch(setLoadingObjectsList(false));
|
||||
}
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
dispatch(setLoadingObjectsList(false));
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
});
|
||||
} else {
|
||||
setRecords(recordsInElement);
|
||||
dispatch(setLoadingObjectsList(false));
|
||||
}
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
const permitItems = permissionItems(
|
||||
bucketName,
|
||||
pathPrefix,
|
||||
allowResources || []
|
||||
);
|
||||
|
||||
if (!permitItems || permitItems.length === 0) {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
} else {
|
||||
setRecords(permitItems);
|
||||
}
|
||||
|
||||
dispatch(setLoadingObjectsList(false));
|
||||
});
|
||||
} else {
|
||||
dispatch(setLoadingObjectsList(false));
|
||||
}
|
||||
}
|
||||
}, [
|
||||
loading,
|
||||
dispatch,
|
||||
bucketName,
|
||||
rewindEnabled,
|
||||
rewindDate,
|
||||
internalPaths,
|
||||
bucketInfo,
|
||||
showDeleted,
|
||||
displayListObjects,
|
||||
bucketToRewind,
|
||||
allowResources,
|
||||
]);
|
||||
|
||||
// bucket info
|
||||
useEffect(() => {
|
||||
if (loadingBucket) {
|
||||
@@ -769,8 +423,8 @@ const ListObjects = () => {
|
||||
|
||||
if (refresh) {
|
||||
dispatch(setSnackBarMessage(`Objects deleted successfully.`));
|
||||
setSelectedObjects([]);
|
||||
dispatch(setLoadingObjectsList(true));
|
||||
dispatch(setSelectedObjects([]));
|
||||
dispatch(setLoadingObjects(true));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -794,73 +448,6 @@ const ListObjects = () => {
|
||||
e.target.value = "";
|
||||
};
|
||||
|
||||
const downloadObject = (object: BucketObjectItem) => {
|
||||
const identityDownload = encodeURLString(
|
||||
`${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}`
|
||||
);
|
||||
|
||||
const ID = makeid(8);
|
||||
|
||||
const downloadCall = download(
|
||||
bucketName,
|
||||
encodeURLString(object.name),
|
||||
object.version_id,
|
||||
object.size,
|
||||
null,
|
||||
ID,
|
||||
(progress) => {
|
||||
dispatch(
|
||||
updateProgress({
|
||||
instanceID: identityDownload,
|
||||
progress: progress,
|
||||
})
|
||||
);
|
||||
},
|
||||
() => {
|
||||
dispatch(completeObject(identityDownload));
|
||||
},
|
||||
(msg: string) => {
|
||||
dispatch(failObject({ instanceID: identityDownload, msg }));
|
||||
},
|
||||
() => {
|
||||
dispatch(cancelObjectInList(identityDownload));
|
||||
}
|
||||
);
|
||||
storeCallForObjectWithID(ID, downloadCall);
|
||||
dispatch(
|
||||
setNewObject({
|
||||
ID,
|
||||
bucketName,
|
||||
done: false,
|
||||
instanceID: identityDownload,
|
||||
percentage: 0,
|
||||
prefix: object.name,
|
||||
type: "download",
|
||||
waitingForFile: true,
|
||||
failed: false,
|
||||
cancelled: false,
|
||||
errorMessage: "",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const openPath = (idElement: string) => {
|
||||
setSelectedObjects([]);
|
||||
|
||||
const newPath = `/buckets/${bucketName}/browse${
|
||||
idElement ? `/${encodeURLString(idElement)}` : ``
|
||||
}`;
|
||||
navigate(newPath);
|
||||
|
||||
dispatch(setObjectDetailsView(true));
|
||||
dispatch(setLoadingVersions(true));
|
||||
dispatch(
|
||||
setSelectedObjectView(
|
||||
`${idElement ? `${encodeURLString(idElement)}` : ``}`
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const uploadObject = useCallback(
|
||||
(files: File[], folderPath: string): void => {
|
||||
let pathPrefix = "";
|
||||
@@ -1008,7 +595,7 @@ const ListObjects = () => {
|
||||
};
|
||||
xhr.onloadend = () => {
|
||||
if (files.length === 0) {
|
||||
dispatch(setLoadingObjectsList(true));
|
||||
dispatch(setLoadingObjects(true));
|
||||
}
|
||||
};
|
||||
xhr.onabort = () => {
|
||||
@@ -1063,8 +650,8 @@ const ListObjects = () => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
}
|
||||
// We force objects list reload after all promises were handled
|
||||
dispatch(setLoadingObjectsList(true));
|
||||
setSelectedObjects([]);
|
||||
dispatch(setLoadingObjects(true));
|
||||
dispatch(setSelectedObjects([]));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1110,140 +697,18 @@ const ListObjects = () => {
|
||||
[isDragActive, isDragAccept]
|
||||
);
|
||||
|
||||
const openPreview = () => {
|
||||
if (selectedObjects.length === 1) {
|
||||
let fileObject: BucketObjectItem | undefined;
|
||||
|
||||
const findFunction = (currValue: BucketObjectItem) =>
|
||||
selectedObjects.includes(currValue.name);
|
||||
|
||||
fileObject = filteredRecords.find(findFunction);
|
||||
|
||||
if (fileObject) {
|
||||
setSelectedPreview(fileObject);
|
||||
setPreviewOpen(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const openShare = () => {
|
||||
if (selectedObjects.length === 1) {
|
||||
let fileObject: BucketObjectItem | undefined;
|
||||
|
||||
const findFunction = (currValue: BucketObjectItem) =>
|
||||
selectedObjects.includes(currValue.name);
|
||||
|
||||
fileObject = filteredRecords.find(findFunction);
|
||||
|
||||
if (fileObject) {
|
||||
setSelectedPreview(fileObject);
|
||||
setShareFileModalOpen(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const closeShareModal = () => {
|
||||
setShareFileModalOpen(false);
|
||||
setSelectedPreview(null);
|
||||
dispatch(setShareFileModalOpen(false));
|
||||
dispatch(setSelectedPreview(null));
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter((b: BucketObjectItem) => {
|
||||
if (searchObjects === "") {
|
||||
return true;
|
||||
} else {
|
||||
const objectName = b.name.toLowerCase();
|
||||
if (objectName.indexOf(searchObjects.toLowerCase()) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const rewindCloseModal = () => {
|
||||
setRewindSelect(false);
|
||||
};
|
||||
|
||||
const closePreviewWindow = () => {
|
||||
setPreviewOpen(false);
|
||||
setSelectedPreview(null);
|
||||
};
|
||||
|
||||
const selectListObjects = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const targetD = e.target;
|
||||
const value = targetD.value;
|
||||
const checked = targetD.checked;
|
||||
|
||||
let elements: string[] = [...selectedObjects]; // We clone the selectedBuckets array
|
||||
|
||||
if (checked) {
|
||||
// If the user has checked this field we need to push this to selectedBucketsList
|
||||
elements.push(value);
|
||||
} else {
|
||||
// User has unchecked this field, we need to remove it from the list
|
||||
elements = elements.filter((element) => element !== value);
|
||||
}
|
||||
setSelectedObjects(elements);
|
||||
dispatch(setSelectedObjectView(null));
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
const sortChange = (sortData: any) => {
|
||||
const newSortDirection = get(sortData, "sortDirection", "DESC");
|
||||
setCurrentSortField(sortData.sortBy);
|
||||
setSortDirection(newSortDirection);
|
||||
dispatch(setLoadingObjectsList(true));
|
||||
};
|
||||
|
||||
const plSelect = filteredRecords;
|
||||
const sortASC = plSelect.sort(sortListObjects(currentSortField));
|
||||
|
||||
let payload: BucketObjectItem[] = [];
|
||||
|
||||
if (sortDirection === "ASC") {
|
||||
payload = sortASC;
|
||||
} else {
|
||||
payload = sortASC.reverse();
|
||||
}
|
||||
|
||||
const selectAllItems = () => {
|
||||
dispatch(setSelectedObjectView(null));
|
||||
|
||||
if (selectedObjects.length === payload.length) {
|
||||
setSelectedObjects([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const elements = payload.map((item) => item.name);
|
||||
setSelectedObjects(elements);
|
||||
};
|
||||
|
||||
const downloadSelected = () => {
|
||||
if (selectedObjects.length !== 0) {
|
||||
let itemsToDownload: BucketObjectItem[] = [];
|
||||
|
||||
const filterFunction = (currValue: BucketObjectItem) =>
|
||||
selectedObjects.includes(currValue.name);
|
||||
|
||||
itemsToDownload = filteredRecords.filter(filterFunction);
|
||||
|
||||
// I case just one element is selected, then we trigger download modal validation.
|
||||
// We are going to enforce zip download when multiple files are selected
|
||||
if (itemsToDownload.length === 1) {
|
||||
if (
|
||||
itemsToDownload[0].name.length > 200 &&
|
||||
getClientOS().toLowerCase().includes("win")
|
||||
) {
|
||||
setDownloadRenameModal(itemsToDownload[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
itemsToDownload.forEach((filteredItem) => {
|
||||
downloadObject(filteredItem);
|
||||
});
|
||||
}
|
||||
dispatch(setPreviewOpen(false));
|
||||
dispatch(setSelectedPreview(null));
|
||||
};
|
||||
|
||||
const onClosePanel = (forceRefresh: boolean) => {
|
||||
@@ -1268,36 +733,30 @@ const ListObjects = () => {
|
||||
}
|
||||
|
||||
dispatch(setObjectDetailsView(false));
|
||||
setSelectedObjects([]);
|
||||
dispatch(setSelectedObjects([]));
|
||||
|
||||
if (forceRefresh) {
|
||||
dispatch(setLoadingObjectsList(true));
|
||||
dispatch(setLoadingObjects(true));
|
||||
}
|
||||
};
|
||||
|
||||
const setDeletedAction = () => {
|
||||
dispatch(resetMessages());
|
||||
dispatch(setShowDeletedObjects(!showDeleted));
|
||||
onClosePanel(true);
|
||||
};
|
||||
|
||||
const closeRenameModal = () => {
|
||||
setDownloadRenameModal(null);
|
||||
dispatch(setDownloadRenameModal(null));
|
||||
};
|
||||
|
||||
const tableActions: ItemActions[] = [
|
||||
{
|
||||
type: "view",
|
||||
label: "View",
|
||||
onClick: openPath,
|
||||
sendOnlyId: true,
|
||||
},
|
||||
];
|
||||
|
||||
const multiActionButtons = [
|
||||
{
|
||||
action: downloadSelected,
|
||||
action: () => {
|
||||
dispatch(downloadSelected(bucketName));
|
||||
},
|
||||
label: "Download",
|
||||
disabled: !canDownload || selectedObjects.length === 0,
|
||||
disabled: !canDownload || selectedObjects?.length === 0,
|
||||
icon: <DownloadIcon />,
|
||||
tooltip: canDownload
|
||||
? "Download Selected"
|
||||
@@ -1307,14 +766,18 @@ const ListObjects = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
action: openShare,
|
||||
action: () => {
|
||||
dispatch(openShare());
|
||||
},
|
||||
label: "Share",
|
||||
disabled: selectedObjects.length !== 1 || !canShareFile,
|
||||
icon: <ShareIcon />,
|
||||
tooltip: canShareFile ? "Share Selected File" : "Sharing unavailable",
|
||||
},
|
||||
{
|
||||
action: openPreview,
|
||||
action: () => {
|
||||
dispatch(openPreview());
|
||||
},
|
||||
label: "Preview",
|
||||
disabled: selectedObjects.length !== 1 || !canPreviewFile,
|
||||
icon: <PreviewIcon />,
|
||||
@@ -1484,7 +947,9 @@ const ListObjects = () => {
|
||||
if (versionsMode) {
|
||||
dispatch(setLoadingVersions(true));
|
||||
} else {
|
||||
dispatch(setLoadingObjectsList(true));
|
||||
dispatch(resetMessages());
|
||||
dispatch(setLoadingRecords(true));
|
||||
dispatch(setLoadingObjects(true));
|
||||
}
|
||||
}}
|
||||
disabled={
|
||||
@@ -1560,7 +1025,6 @@ const ListObjects = () => {
|
||||
<BrowserBreadcrumbs
|
||||
bucketName={bucketName}
|
||||
internalPaths={pageTitle}
|
||||
existingFiles={records || []}
|
||||
additionalOptions={
|
||||
!isVersioned || rewindEnabled ? null : (
|
||||
<div>
|
||||
@@ -1581,48 +1045,7 @@ const ListObjects = () => {
|
||||
hidePathButton={false}
|
||||
/>
|
||||
</Grid>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={
|
||||
rewindEnabled ? rewindModeColumns : listModeColumns
|
||||
}
|
||||
isLoading={loading}
|
||||
loadingMessage={loadingMessage}
|
||||
entityName="Objects"
|
||||
idField="name"
|
||||
records={payload}
|
||||
customPaperHeight={`${classes.browsePaper} ${
|
||||
obOnly ? "isEmbedded" : ""
|
||||
} ${detailsOpen ? "actionsPanelOpen" : ""}`}
|
||||
selectedItems={selectedObjects}
|
||||
onSelect={selectListObjects}
|
||||
customEmptyMessage={
|
||||
!displayListObjects
|
||||
? permissionTooltipHelper(
|
||||
[IAM_SCOPES.S3_LIST_BUCKET],
|
||||
"view Objects in this bucket"
|
||||
)
|
||||
: `This location is empty${
|
||||
!rewindEnabled
|
||||
? ", please try uploading a new file"
|
||||
: ""
|
||||
}`
|
||||
}
|
||||
sortConfig={{
|
||||
currentSort: currentSortField,
|
||||
currentDirection: sortDirection,
|
||||
triggerSort: sortChange,
|
||||
}}
|
||||
onSelectAll={selectAllItems}
|
||||
rowStyle={({ index }) => {
|
||||
if (payload[index]?.delete_flag) {
|
||||
return "deleted";
|
||||
}
|
||||
|
||||
return "";
|
||||
}}
|
||||
parentClassName={classes.parentWrapper}
|
||||
/>
|
||||
<ListObjectsTable />
|
||||
</Grid>
|
||||
</SecureComponent>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 { listModeColumns, rewindModeColumns } from "./ListObjectsHelpers";
|
||||
import TableWrapper, {
|
||||
ItemActions,
|
||||
} from "../../../../Common/TableWrapper/TableWrapper";
|
||||
import React, { useState } from "react";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import { useSelector } from "react-redux";
|
||||
import { AppState, useAppDispatch } from "../../../../../../store";
|
||||
import { selFeatures } from "../../../../consoleSlice";
|
||||
import { encodeURLString } from "../../../../../../common/utils";
|
||||
import {
|
||||
setLoadingObjects,
|
||||
setLoadingVersions,
|
||||
setObjectDetailsView,
|
||||
setSelectedObjects,
|
||||
setSelectedObjectView,
|
||||
} from "../../../../ObjectBrowser/objectBrowserSlice";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import get from "lodash/get";
|
||||
import { sortListObjects } from "../utils";
|
||||
import { BucketObjectItem } from "./types";
|
||||
import {
|
||||
IAM_SCOPES,
|
||||
permissionTooltipHelper,
|
||||
} from "../../../../../../common/SecureComponent/permissions";
|
||||
import { hasPermission } from "../../../../../../common/SecureComponent";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
browsePaper: {
|
||||
border: 0,
|
||||
height: "calc(100vh - 290px)",
|
||||
"&.isEmbedded": {
|
||||
height: "calc(100vh - 315px)",
|
||||
},
|
||||
"&.actionsPanelOpen": {
|
||||
minHeight: "100%",
|
||||
},
|
||||
"@media (max-width: 800px)": {
|
||||
width: 800,
|
||||
},
|
||||
},
|
||||
parentWrapper: {
|
||||
position: "relative",
|
||||
height: "calc(100% - 60px)",
|
||||
"@media (max-width: 800px)": {
|
||||
overflowX: "auto",
|
||||
},
|
||||
},
|
||||
"@global": {
|
||||
".rowLine:hover .iconFileElm": {
|
||||
backgroundImage: "url(/images/ob_file_filled.svg)",
|
||||
},
|
||||
".rowLine:hover .iconFolderElm": {
|
||||
backgroundImage: "url(/images/ob_folder_filled.svg)",
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const ListObjectsTable = () => {
|
||||
const classes = useStyles();
|
||||
const dispatch = useAppDispatch();
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [sortDirection, setSortDirection] = useState<
|
||||
"ASC" | "DESC" | undefined
|
||||
>("ASC");
|
||||
const [currentSortField, setCurrentSortField] = useState<string>("name");
|
||||
|
||||
const bucketName = params.bucketName || "";
|
||||
|
||||
const detailsOpen = useSelector(
|
||||
(state: AppState) => state.objectBrowser.objectDetailsOpen
|
||||
);
|
||||
|
||||
const loadingObjects = useSelector(
|
||||
(state: AppState) => state.objectBrowser.loadingObjects
|
||||
);
|
||||
|
||||
const features = useSelector(selFeatures);
|
||||
const obOnly = !!features?.includes("object-browser-only");
|
||||
|
||||
const rewindEnabled = useSelector(
|
||||
(state: AppState) => state.objectBrowser.rewind.rewindEnabled
|
||||
);
|
||||
const records = useSelector((state: AppState) => state.objectBrowser.records);
|
||||
const searchObjects = useSelector(
|
||||
(state: AppState) => state.objectBrowser.searchObjects
|
||||
);
|
||||
const selectedObjects = useSelector(
|
||||
(state: AppState) => state.objectBrowser.selectedObjects
|
||||
);
|
||||
|
||||
const displayListObjects = hasPermission(bucketName, [
|
||||
IAM_SCOPES.S3_LIST_BUCKET,
|
||||
]);
|
||||
|
||||
const filteredRecords = records.filter((b: BucketObjectItem) => {
|
||||
if (searchObjects === "") {
|
||||
return true;
|
||||
} else {
|
||||
const objectName = b.name.toLowerCase();
|
||||
if (objectName.indexOf(searchObjects.toLowerCase()) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const plSelect = filteredRecords;
|
||||
const sortASC = plSelect.sort(sortListObjects(currentSortField));
|
||||
|
||||
let payload: BucketObjectItem[] = [];
|
||||
|
||||
if (sortDirection === "ASC") {
|
||||
payload = sortASC;
|
||||
} else {
|
||||
payload = sortASC.reverse();
|
||||
}
|
||||
|
||||
const openPath = (idElement: string) => {
|
||||
dispatch(setSelectedObjects([]));
|
||||
|
||||
const newPath = `/buckets/${bucketName}/browse${
|
||||
idElement ? `/${encodeURLString(idElement)}` : ``
|
||||
}`;
|
||||
navigate(newPath);
|
||||
|
||||
dispatch(setObjectDetailsView(true));
|
||||
dispatch(setLoadingVersions(true));
|
||||
dispatch(
|
||||
setSelectedObjectView(
|
||||
`${idElement ? `${encodeURLString(idElement)}` : ``}`
|
||||
)
|
||||
);
|
||||
};
|
||||
const tableActions: ItemActions[] = [
|
||||
{
|
||||
type: "view",
|
||||
label: "View",
|
||||
onClick: openPath,
|
||||
sendOnlyId: true,
|
||||
},
|
||||
];
|
||||
|
||||
const sortChange = (sortData: any) => {
|
||||
const newSortDirection = get(sortData, "sortDirection", "DESC");
|
||||
setCurrentSortField(sortData.sortBy);
|
||||
setSortDirection(newSortDirection);
|
||||
dispatch(setLoadingObjects(true));
|
||||
};
|
||||
|
||||
const selectAllItems = () => {
|
||||
dispatch(setSelectedObjectView(null));
|
||||
|
||||
if (selectedObjects.length === payload.length) {
|
||||
dispatch(setSelectedObjects([]));
|
||||
return;
|
||||
}
|
||||
|
||||
const elements = payload.map((item) => item.name);
|
||||
dispatch(setSelectedObjects(elements));
|
||||
};
|
||||
|
||||
const selectListObjects = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const targetD = e.target;
|
||||
const value = targetD.value;
|
||||
const checked = targetD.checked;
|
||||
|
||||
let elements: string[] = [...selectedObjects]; // We clone the selectedBuckets array
|
||||
|
||||
if (checked) {
|
||||
// If the user has checked this field we need to push this to selectedBucketsList
|
||||
elements.push(value);
|
||||
} else {
|
||||
// User has unchecked this field, we need to remove it from the list
|
||||
elements = elements.filter((element) => element !== value);
|
||||
}
|
||||
dispatch(setSelectedObjects(elements));
|
||||
dispatch(setSelectedObjectView(null));
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
return (
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={rewindEnabled ? rewindModeColumns : listModeColumns}
|
||||
isLoading={loadingObjects}
|
||||
entityName="Objects"
|
||||
idField="name"
|
||||
records={payload}
|
||||
customPaperHeight={`${classes.browsePaper} ${
|
||||
obOnly ? "isEmbedded" : ""
|
||||
} ${detailsOpen ? "actionsPanelOpen" : ""}`}
|
||||
selectedItems={selectedObjects}
|
||||
onSelect={selectListObjects}
|
||||
customEmptyMessage={
|
||||
!displayListObjects
|
||||
? permissionTooltipHelper(
|
||||
[IAM_SCOPES.S3_LIST_BUCKET],
|
||||
"view Objects in this bucket"
|
||||
)
|
||||
: `This location is empty${
|
||||
!rewindEnabled ? ", please try uploading a new file" : ""
|
||||
}`
|
||||
}
|
||||
sortConfig={{
|
||||
currentSort: currentSortField,
|
||||
currentDirection: sortDirection,
|
||||
triggerSort: sortChange,
|
||||
}}
|
||||
onSelectAll={selectAllItems}
|
||||
rowStyle={({ index }) => {
|
||||
if (payload[index]?.delete_flag) {
|
||||
return "deleted";
|
||||
}
|
||||
|
||||
return "";
|
||||
}}
|
||||
parentClassName={classes.parentWrapper}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default ListObjectsTable;
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
spacingUtils,
|
||||
textStyleUtils,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { IFileInfo } from "../ObjectDetails/types";
|
||||
import { IFileInfo, MetadataResponse } from "../ObjectDetails/types";
|
||||
import { download, extensionPreview } from "../utils";
|
||||
import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
|
||||
@@ -189,6 +189,8 @@ const ObjectDetailPanel = ({
|
||||
const [previewOpen, setPreviewOpen] = useState<boolean>(false);
|
||||
const [totalVersionsSize, setTotalVersionsSize] = useState<number>(0);
|
||||
const [longFileOpen, setLongFileOpen] = useState<boolean>(false);
|
||||
const [metaData, setMetaData] = useState<any | null>(null);
|
||||
const [loadMetadata, setLoadingMetadata] = useState<boolean>(false);
|
||||
|
||||
const internalPathsDecoded = decodeURLString(internalPaths) || "";
|
||||
const allPathData = internalPathsDecoded.split("/");
|
||||
@@ -212,6 +214,10 @@ const ObjectDetailPanel = ({
|
||||
) || emptyFile;
|
||||
}
|
||||
|
||||
if (!infoElement.is_delete_marker) {
|
||||
setLoadingMetadata(true);
|
||||
}
|
||||
|
||||
setActualInfo(infoElement);
|
||||
}
|
||||
}, [selectedVersion, distributedSetup, allInfoElements]);
|
||||
@@ -242,8 +248,14 @@ const ObjectDetailPanel = ({
|
||||
|
||||
setTotalVersionsSize(tVersionSize);
|
||||
} else {
|
||||
setActualInfo(result[0]);
|
||||
const resInfo = result[0];
|
||||
|
||||
setActualInfo(resInfo);
|
||||
setVersions([]);
|
||||
|
||||
if (!resInfo.is_delete_marker) {
|
||||
setLoadingMetadata(true);
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(setLoadingObjectInfo(false));
|
||||
@@ -262,6 +274,26 @@ const ObjectDetailPanel = ({
|
||||
selectedVersion,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadMetadata && internalPaths !== "") {
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects/metadata?prefix=${internalPaths}`
|
||||
)
|
||||
.then((res: MetadataResponse) => {
|
||||
let metadata = get(res, "objectMetadata", {});
|
||||
|
||||
setMetaData(metadata);
|
||||
setLoadingMetadata(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error Getting Metadata Status: ", err.detailedError);
|
||||
setLoadingMetadata(false);
|
||||
});
|
||||
}
|
||||
}, [bucketName, internalPaths, loadMetadata]);
|
||||
|
||||
let tagKeys: string[] = [];
|
||||
|
||||
if (actualInfo && actualInfo.tags) {
|
||||
@@ -649,7 +681,7 @@ const ObjectDetailPanel = ({
|
||||
version_id: actualInfo.version_id || "null",
|
||||
size: parseInt(actualInfo.size || "0"),
|
||||
content_type: "",
|
||||
last_modified: new Date(actualInfo.last_modified),
|
||||
last_modified: actualInfo.last_modified,
|
||||
}}
|
||||
onClosePreview={() => {
|
||||
setPreviewOpen(false);
|
||||
@@ -838,20 +870,19 @@ const ObjectDetailPanel = ({
|
||||
</Fragment>
|
||||
</SecureComponent>
|
||||
</Box>
|
||||
<Grid item xs={12} className={classes.headerForSection}>
|
||||
<span>Metadata</span>
|
||||
<MetadataIcon />
|
||||
</Grid>
|
||||
<Box className={classes.detailContainer}>
|
||||
{actualInfo ? (
|
||||
<ObjectMetaData
|
||||
bucketName={bucketName}
|
||||
internalPaths={internalPaths}
|
||||
actualInfo={actualInfo}
|
||||
linear
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
{!actualInfo.is_delete_marker && (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.headerForSection}>
|
||||
<span>Metadata</span>
|
||||
<MetadataIcon />
|
||||
</Grid>
|
||||
<Box className={classes.detailContainer}>
|
||||
{actualInfo && metaData ? (
|
||||
<ObjectMetaData metaData={metaData} linear />
|
||||
) : null}
|
||||
</Box>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
|
||||
@@ -25,7 +25,7 @@ import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapp
|
||||
import { AppState, useAppDispatch } from "../../../../../../store";
|
||||
import {
|
||||
resetRewind,
|
||||
setLoadingObjectsList,
|
||||
setLoadingObjects,
|
||||
setRewindEnable,
|
||||
} from "../../../../ObjectBrowser/objectBrowserSlice";
|
||||
|
||||
@@ -73,7 +73,7 @@ const RewindEnable = ({
|
||||
})
|
||||
);
|
||||
}
|
||||
dispatch(setLoadingObjectsList(true));
|
||||
dispatch(setLoadingObjects(true));
|
||||
|
||||
closeModalAndRefresh();
|
||||
};
|
||||
|
||||
@@ -14,17 +14,48 @@
|
||||
// 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 { IFileInfo } from "../ObjectDetails/types";
|
||||
|
||||
export interface BucketObjectItem {
|
||||
name: string;
|
||||
size: number;
|
||||
etag?: string;
|
||||
last_modified: Date;
|
||||
last_modified: string;
|
||||
content_type?: string;
|
||||
version_id: string;
|
||||
delete_flag?: boolean;
|
||||
is_latest?: boolean;
|
||||
}
|
||||
|
||||
export interface BucketObjectItemsList {
|
||||
objects: BucketObjectItem[];
|
||||
total?: number;
|
||||
}
|
||||
|
||||
export interface WebsocketRequest {
|
||||
mode: "objects" | "rewind" | "close" | "cancel";
|
||||
bucket_name?: string;
|
||||
prefix?: string;
|
||||
date?: string;
|
||||
request_id: number;
|
||||
}
|
||||
|
||||
export interface WebsocketResponse {
|
||||
request_id: number;
|
||||
error?: string;
|
||||
request_end?: boolean;
|
||||
data?: ObjectResponse[];
|
||||
}
|
||||
export interface ObjectResponse {
|
||||
name: string;
|
||||
last_modified: string;
|
||||
size: number;
|
||||
version_id: string;
|
||||
delete_flag: boolean;
|
||||
is_latest: boolean;
|
||||
}
|
||||
|
||||
export interface IRestoreLocalObjectList {
|
||||
prefix: string;
|
||||
objectInfo: IFileInfo;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import React, { Fragment, useCallback, useEffect, useState } from "react";
|
||||
import useApi from "../../../../Common/Hooks/useApi";
|
||||
import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
import { MetadataResponse } from "./types";
|
||||
import get from "lodash/get";
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment } from "react";
|
||||
import { withStyles } from "@mui/styles";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { Box, Table, TableBody, TableCell, TableRow } from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
@@ -11,13 +24,10 @@ import {
|
||||
detailsPanel,
|
||||
spacingUtils,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { withStyles } from "@mui/styles";
|
||||
|
||||
interface IObjectMetadata {
|
||||
bucketName: string;
|
||||
internalPaths: string;
|
||||
metaData: any;
|
||||
classes?: any;
|
||||
actualInfo: any;
|
||||
linear?: boolean;
|
||||
}
|
||||
|
||||
@@ -45,38 +55,11 @@ const styles = (theme: Theme) =>
|
||||
});
|
||||
|
||||
const ObjectMetaData = ({
|
||||
bucketName,
|
||||
internalPaths,
|
||||
metaData,
|
||||
classes,
|
||||
actualInfo,
|
||||
linear = false,
|
||||
}: IObjectMetadata) => {
|
||||
const [metaData, setMetaData] = useState<any>({});
|
||||
|
||||
const onMetaDataSuccess = (res: MetadataResponse) => {
|
||||
let metadata = get(res, "objectMetadata", {});
|
||||
|
||||
setMetaData(metadata);
|
||||
};
|
||||
const onMetaDataError = (err: ErrorResponseHandler) => false;
|
||||
|
||||
const [, invokeMetaDataApi] = useApi(onMetaDataSuccess, onMetaDataError);
|
||||
|
||||
const metaKeys = Object.keys(metaData);
|
||||
const loadMetaData = useCallback(() => {
|
||||
invokeMetaDataApi(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects/metadata?prefix=${internalPaths}`
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [bucketName, internalPaths, actualInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (actualInfo) {
|
||||
loadMetaData();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [actualInfo, loadMetaData]);
|
||||
|
||||
if (linear) {
|
||||
return (
|
||||
|
||||
@@ -29,12 +29,14 @@ import ConfirmDialog from "../../../../Common/ModalWrapper/ConfirmDialog";
|
||||
import RecoverIcon from "../../../../../../icons/RecoverIcon";
|
||||
import { setErrorSnackMessage } from "../../../../../../systemSlice";
|
||||
import { useAppDispatch } from "../../../../../../store";
|
||||
import { IFileInfo } from "./types";
|
||||
import { restoreLocalObjectList } from "../../../../ObjectBrowser/objectBrowserSlice";
|
||||
|
||||
interface IRestoreFileVersion {
|
||||
classes: any;
|
||||
restoreOpen: boolean;
|
||||
bucketName: string;
|
||||
versionID: string;
|
||||
versionToRestore: IFileInfo;
|
||||
objectPath: string;
|
||||
onCloseAndUpdate: (refresh: boolean) => void;
|
||||
}
|
||||
@@ -46,7 +48,7 @@ const styles = (theme: Theme) =>
|
||||
|
||||
const RestoreFileVersion = ({
|
||||
classes,
|
||||
versionID,
|
||||
versionToRestore,
|
||||
bucketName,
|
||||
objectPath,
|
||||
restoreOpen,
|
||||
@@ -63,11 +65,18 @@ const RestoreFileVersion = ({
|
||||
"PUT",
|
||||
`/api/v1/buckets/${bucketName}/objects/restore?prefix=${encodeURLString(
|
||||
objectPath
|
||||
)}&version_id=${versionID}`
|
||||
)}&version_id=${versionToRestore.version_id}`
|
||||
)
|
||||
.then((res: any) => {
|
||||
console.log("REStORE", res);
|
||||
setRestoreLoading(false);
|
||||
onCloseAndUpdate(true);
|
||||
dispatch(
|
||||
restoreLocalObjectList({
|
||||
prefix: objectPath,
|
||||
objectInfo: versionToRestore,
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch((error: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(error));
|
||||
@@ -95,7 +104,7 @@ const RestoreFileVersion = ({
|
||||
Are you sure you want to restore <br />
|
||||
<b>{objectPath}</b> <br /> with Version ID:
|
||||
<br />
|
||||
<b className={classes.wrapText}>{versionID}</b>?
|
||||
<b className={classes.wrapText}>{versionToRestore.version_id}</b>?
|
||||
</DialogContentText>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -181,7 +181,7 @@ const VersionsNavigator = ({
|
||||
const [objectToShare, setObjectToShare] = useState<IFileInfo | null>(null);
|
||||
const [versions, setVersions] = useState<IFileInfo[]>([]);
|
||||
const [restoreVersionOpen, setRestoreVersionOpen] = useState<boolean>(false);
|
||||
const [restoreVersion, setRestoreVersion] = useState<string>("");
|
||||
const [restoreVersion, setRestoreVersion] = useState<IFileInfo | null>(null);
|
||||
const [sortValue, setSortValue] = useState<string>("date");
|
||||
const [previewOpen, setPreviewOpen] = useState<boolean>(false);
|
||||
const [deleteNonCurrentOpen, setDeleteNonCurrentOpen] =
|
||||
@@ -313,7 +313,7 @@ const VersionsNavigator = ({
|
||||
};
|
||||
|
||||
const onRestoreItem = (item: IFileInfo) => {
|
||||
setRestoreVersion(item.version_id || "");
|
||||
setRestoreVersion(item);
|
||||
setRestoreVersionOpen(true);
|
||||
};
|
||||
|
||||
@@ -334,7 +334,7 @@ const VersionsNavigator = ({
|
||||
|
||||
const closeRestoreModal = (reloadObjectData: boolean) => {
|
||||
setRestoreVersionOpen(false);
|
||||
setRestoreVersion("");
|
||||
setRestoreVersion(null);
|
||||
|
||||
if (reloadObjectData) {
|
||||
dispatch(setLoadingVersions(true));
|
||||
@@ -454,11 +454,11 @@ const VersionsNavigator = ({
|
||||
dataObject={objectToShare || actualInfo}
|
||||
/>
|
||||
)}
|
||||
{restoreVersionOpen && actualInfo && (
|
||||
{restoreVersionOpen && actualInfo && restoreVersion && (
|
||||
<RestoreFileVersion
|
||||
restoreOpen={restoreVersionOpen}
|
||||
bucketName={bucketName}
|
||||
versionID={restoreVersion}
|
||||
versionToRestore={restoreVersion}
|
||||
objectPath={actualInfo.name}
|
||||
onCloseAndUpdate={closeRestoreModal}
|
||||
/>
|
||||
@@ -477,7 +477,7 @@ const VersionsNavigator = ({
|
||||
objectToShare && objectToShare.size ? objectToShare.size : "0"
|
||||
),
|
||||
content_type: "",
|
||||
last_modified: new Date(actualInfo.last_modified),
|
||||
last_modified: actualInfo.last_modified,
|
||||
}}
|
||||
onClosePreview={() => {
|
||||
setPreviewOpen(false);
|
||||
@@ -514,7 +514,6 @@ const VersionsNavigator = ({
|
||||
<BrowserBreadcrumbs
|
||||
bucketName={bucketName}
|
||||
internalPaths={decodeURLString(internalPaths)}
|
||||
existingFiles={[]}
|
||||
hidePathButton={true}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -247,7 +247,7 @@ export const permissionItems = (
|
||||
returnElements.push({
|
||||
name: `${currentElementInPath}/`,
|
||||
size: 0,
|
||||
last_modified: new Date(),
|
||||
last_modified: "",
|
||||
version_id: "",
|
||||
});
|
||||
}
|
||||
@@ -276,7 +276,7 @@ export const permissionItems = (
|
||||
pathToRouteElements.length > 0 ? "/" : ""
|
||||
}${splitElement}/`,
|
||||
size: 0,
|
||||
last_modified: new Date(),
|
||||
last_modified: "",
|
||||
version_id: "",
|
||||
});
|
||||
return false;
|
||||
|
||||
@@ -122,6 +122,24 @@ const AccountCreate = React.lazy(
|
||||
|
||||
const Users = React.lazy(() => import("./Users/Users"));
|
||||
const Groups = React.lazy(() => import("./Groups/Groups"));
|
||||
const IDPLDAPConfigurations = React.lazy(
|
||||
() => import("./IDP/IDPLDAPConfigurations")
|
||||
);
|
||||
const IDPOpenIDConfigurations = React.lazy(
|
||||
() => import("./IDP/IDPOpenIDConfigurations")
|
||||
);
|
||||
const AddIDPLDAPConfiguration = React.lazy(
|
||||
() => import("./IDP/AddIDPLDAPConfiguration")
|
||||
);
|
||||
const AddIDPOpenIDConfiguration = React.lazy(
|
||||
() => import("./IDP/AddIDPOpenIDConfiguration")
|
||||
);
|
||||
const IDPLDAPConfigurationDetails = React.lazy(
|
||||
() => import("./IDP/IDPLDAPConfigurationDetails")
|
||||
);
|
||||
const IDPOpenIDConfigurationDetails = React.lazy(
|
||||
() => import("./IDP/IDPOpenIDConfigurationDetails")
|
||||
);
|
||||
|
||||
const TenantDetails = React.lazy(
|
||||
() => import("./Tenants/TenantDetails/TenantDetails")
|
||||
@@ -343,6 +361,30 @@ const Console = ({ classes }: IConsoleProps) => {
|
||||
component: Policies,
|
||||
path: IAM_PAGES.POLICIES,
|
||||
},
|
||||
{
|
||||
component: IDPLDAPConfigurations,
|
||||
path: IAM_PAGES.IDP_LDAP_CONFIGURATIONS,
|
||||
},
|
||||
{
|
||||
component: IDPOpenIDConfigurations,
|
||||
path: IAM_PAGES.IDP_OPENID_CONFIGURATIONS,
|
||||
},
|
||||
{
|
||||
component: AddIDPLDAPConfiguration,
|
||||
path: IAM_PAGES.IDP_LDAP_CONFIGURATIONS_ADD,
|
||||
},
|
||||
{
|
||||
component: AddIDPOpenIDConfiguration,
|
||||
path: IAM_PAGES.IDP_OPENID_CONFIGURATIONS_ADD,
|
||||
},
|
||||
{
|
||||
component: IDPLDAPConfigurationDetails,
|
||||
path: IAM_PAGES.IDP_LDAP_CONFIGURATIONS_VIEW,
|
||||
},
|
||||
{
|
||||
component: IDPOpenIDConfigurationDetails,
|
||||
path: IAM_PAGES.IDP_OPENID_CONFIGURATIONS_VIEW,
|
||||
},
|
||||
{
|
||||
component: Heal,
|
||||
path: IAM_PAGES.TOOLS_HEAL,
|
||||
|
||||
219
portal-ui/src/screens/Console/IDP/AddIDPConfiguration.tsx
Normal file
219
portal-ui/src/screens/Console/IDP/AddIDPConfiguration.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Box, Grid } from "@mui/material";
|
||||
import {
|
||||
formFieldStyles,
|
||||
modalBasic,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import { Button } from "mds";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import {
|
||||
setErrorSnackMessage,
|
||||
setServerNeedsRestart,
|
||||
} from "../../../systemSlice";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import BackLink from "../../../common/BackLink";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
import SectionTitle from "../Common/SectionTitle";
|
||||
|
||||
type AddIDPConfigurationProps = {
|
||||
classes?: any;
|
||||
icon: React.ReactNode;
|
||||
helpBox: React.ReactNode;
|
||||
header: string;
|
||||
title: string;
|
||||
backLink: string;
|
||||
formFields: object;
|
||||
endpoint: string;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...formFieldStyles,
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const AddIDPConfiguration = ({
|
||||
classes,
|
||||
icon,
|
||||
helpBox,
|
||||
header,
|
||||
backLink,
|
||||
title,
|
||||
formFields,
|
||||
endpoint,
|
||||
}: AddIDPConfigurationProps) => {
|
||||
const extraFormFields = {
|
||||
name: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Config Name is required" : "";
|
||||
},
|
||||
label: "Name",
|
||||
tooltip: "Name for identity provider configuration",
|
||||
placeholder: "Name",
|
||||
type: "text",
|
||||
},
|
||||
...formFields,
|
||||
};
|
||||
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [fields, setFields] = useState<any>({});
|
||||
|
||||
const onSuccess = (res: any) => {
|
||||
navigate(backLink);
|
||||
dispatch(setServerNeedsRestart(res.restart === true));
|
||||
};
|
||||
|
||||
const onError = (err: ErrorResponseHandler) =>
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
|
||||
const [loading, invokeApi] = useApi(onSuccess, onError);
|
||||
|
||||
const validSave = () => {
|
||||
for (const [key, value] of Object.entries(extraFormFields)) {
|
||||
if (
|
||||
value.required &&
|
||||
!(
|
||||
fields[key] !== undefined &&
|
||||
fields[key] !== null &&
|
||||
fields[key] !== ""
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setFields({});
|
||||
};
|
||||
|
||||
const addRecord = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
const name = fields["name"];
|
||||
let input = "";
|
||||
for (const key of Object.keys(formFields)) {
|
||||
if (fields[key]) {
|
||||
input += `${key}=${fields[key]} `;
|
||||
}
|
||||
}
|
||||
invokeApi("POST", endpoint, { name, input });
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<PageHeader label={<BackLink to={backLink} label={header} />} />
|
||||
<PageLayout>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
padding: "25px",
|
||||
gap: "25px",
|
||||
gridTemplateColumns: {
|
||||
md: "2fr 1.2fr",
|
||||
xs: "1fr",
|
||||
},
|
||||
border: "1px solid #eaeaea",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<SectionTitle icon={icon}>{title}</SectionTitle>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
addRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container item spacing="20" sx={{ marginTop: 1 }}>
|
||||
<Grid xs={12} item>
|
||||
{Object.entries(extraFormFields).map(([key, value]) => (
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={classes.formFieldRow}
|
||||
key={key}
|
||||
>
|
||||
<InputBoxWrapper
|
||||
id={key}
|
||||
required={value.required}
|
||||
name={key}
|
||||
label={value.label}
|
||||
tooltip={value.tooltip}
|
||||
error={value.hasError(fields[key], true)}
|
||||
value={fields[key] ? fields[key] : ""}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setFields({ ...fields, [key]: e.target.value })
|
||||
}
|
||||
placeholder={value.placeholder}
|
||||
type={value.type}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
<Grid item xs={12} textAlign={"right"}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
marginTop: "20px",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
id={"clear"}
|
||||
type="button"
|
||||
variant="regular"
|
||||
onClick={resetForm}
|
||||
label={"Clear"}
|
||||
/>
|
||||
|
||||
<Button
|
||||
id={"save-key"}
|
||||
type="submit"
|
||||
variant="callAction"
|
||||
color="primary"
|
||||
disabled={loading || !validSave()}
|
||||
label={"Save"}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</Box>
|
||||
{helpBox}
|
||||
</Box>
|
||||
</PageLayout>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(AddIDPConfiguration);
|
||||
106
portal-ui/src/screens/Console/IDP/AddIDPConfigurationHelpbox.tsx
Normal file
106
portal-ui/src/screens/Console/IDP/AddIDPConfigurationHelpbox.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React, { Fragment } from "react";
|
||||
|
||||
import { Box } from "@mui/material";
|
||||
import { HelpIconFilled } from "../../../icons";
|
||||
|
||||
interface IContent {
|
||||
icon: React.ReactNode;
|
||||
text: string;
|
||||
iconDescription: string;
|
||||
}
|
||||
|
||||
interface IAddIDPConfigurationHelpBoxProps {
|
||||
helpText: string;
|
||||
docLink: string;
|
||||
docText: string;
|
||||
contents: IContent[];
|
||||
}
|
||||
|
||||
const FeatureItem = ({
|
||||
icon,
|
||||
description,
|
||||
}: {
|
||||
icon: any;
|
||||
description: string;
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
"& .min-icon": {
|
||||
marginRight: "10px",
|
||||
height: "23px",
|
||||
width: "23px",
|
||||
marginBottom: "10px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{icon}{" "}
|
||||
<div style={{ fontSize: "14px", fontStyle: "italic", color: "#5E5E5E" }}>
|
||||
{description}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const AddIDPConfigurationHelpBox = ({
|
||||
helpText,
|
||||
docLink,
|
||||
docText,
|
||||
contents,
|
||||
}: IAddIDPConfigurationHelpBoxProps) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
padding: "20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontWeight: 600,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "16px",
|
||||
paddingBottom: "20px",
|
||||
|
||||
"& .min-icon": {
|
||||
height: "21px",
|
||||
width: "21px",
|
||||
marginRight: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<HelpIconFilled />
|
||||
<div>{helpText}</div>
|
||||
</Box>
|
||||
<Box sx={{ fontSize: "14px", marginBottom: "15px" }}>
|
||||
{contents.map((content) => (
|
||||
<Fragment>
|
||||
{content.icon && (
|
||||
<Box sx={{ paddingBottom: "20px" }}>
|
||||
<FeatureItem
|
||||
icon={content.icon}
|
||||
description={content.iconDescription}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ paddingBottom: "20px" }}>{content.text}</Box>
|
||||
</Fragment>
|
||||
))}
|
||||
<Box sx={{ paddingBottom: "20px" }}>
|
||||
<a href={docLink} target="_blank" rel="noreferrer">
|
||||
{docText}
|
||||
</a>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddIDPConfigurationHelpBox;
|
||||
@@ -0,0 +1,86 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import LoginIcon from "@mui/icons-material/Login";
|
||||
import {
|
||||
formFieldStyles,
|
||||
modalBasic,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
|
||||
import AddIDPConfiguration from "./AddIDPConfiguration";
|
||||
import { ldapFormFields } from "./utils";
|
||||
import AddIDPConfigurationHelpBox from "./AddIDPConfigurationHelpbox";
|
||||
|
||||
type AddIDPLDAPConfigurationProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...formFieldStyles,
|
||||
formFieldRow: {
|
||||
...formFieldStyles.formFieldRow,
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const AddIDPLDAPConfiguration = ({ classes }: AddIDPLDAPConfigurationProps) => {
|
||||
const helpBoxContents = [
|
||||
{
|
||||
text: "MinIO supports using an Active Directory or LDAP (AD/LDAP) service for external management of user identities. Configuring an external IDentity Provider (IDP) enables Single-Sign On (SSO) workflows, where applications authenticate against the external IDP before accessing MinIO.",
|
||||
icon: <LoginIcon />,
|
||||
iconDescription: "Create Configurations",
|
||||
},
|
||||
{
|
||||
text: "MinIO queries the configured Active Directory / LDAP server to verify the credentials specified by the application and optionally return a list of groups in which the user has membership. MinIO supports two modes (Lookup-Bind Mode and Username-Bind Mode) for performing these queries",
|
||||
icon: null,
|
||||
iconDescription: "",
|
||||
},
|
||||
{
|
||||
text: "MinIO recommends using Lookup-Bind mode as the preferred method for verifying AD/LDAP credentials. Username-Bind mode is a legacy method retained for backwards compatibility only.",
|
||||
icon: null,
|
||||
iconDescription: "",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<AddIDPConfiguration
|
||||
icon={<LoginIcon />}
|
||||
helpBox={
|
||||
<AddIDPConfigurationHelpBox
|
||||
helpText={"Learn more about LDAP Configurations"}
|
||||
contents={helpBoxContents}
|
||||
docLink={
|
||||
"https://min.io/docs/minio/linux/operations/external-iam.html?ref=con#minio-external-iam-ad-ldap"
|
||||
}
|
||||
docText={"Learn more about LDAP Configurations"}
|
||||
/>
|
||||
}
|
||||
header={"LDAP Configurations"}
|
||||
backLink={IAM_PAGES.IDP_LDAP_CONFIGURATIONS}
|
||||
title={"Create LDAP Configuration"}
|
||||
endpoint={"/api/v1/idp/ldap/"}
|
||||
formFields={ldapFormFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(AddIDPLDAPConfiguration);
|
||||
@@ -0,0 +1,71 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
|
||||
import { LockIcon } from "../../../icons";
|
||||
import AddIDPConfiguration from "./AddIDPConfiguration";
|
||||
import { openIDFormFields } from "./utils";
|
||||
import AddIDPConfigurationHelpBox from "./AddIDPConfigurationHelpbox";
|
||||
|
||||
type AddIDPOpenIDConfigurationProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const AddIDPOpenIDConfiguration = ({
|
||||
classes,
|
||||
}: AddIDPOpenIDConfigurationProps) => {
|
||||
const helpBoxContents = [
|
||||
{
|
||||
text: "MinIO supports using an OpenID Connect (OIDC) compatible IDentity Provider (IDP) such as Okta, KeyCloak, Dex, Google, or Facebook for external management of user identities.",
|
||||
icon: <LockIcon />,
|
||||
iconDescription: "Create Configurations",
|
||||
},
|
||||
{
|
||||
text: "Configuring an external IDP enables Single-Sign On workflows, where applications authenticate against the external IDP before accessing MinIO.",
|
||||
icon: null,
|
||||
iconDescription: "",
|
||||
},
|
||||
];
|
||||
return (
|
||||
<AddIDPConfiguration
|
||||
icon={<LockIcon />}
|
||||
helpBox={
|
||||
<AddIDPConfigurationHelpBox
|
||||
helpText={"Learn more about OpenID Connect Configurations"}
|
||||
contents={helpBoxContents}
|
||||
docLink={
|
||||
"https://min.io/docs/minio/linux/operations/external-iam.html?ref=con#minio-external-iam-oidc"
|
||||
}
|
||||
docText={"Learn more about OpenID Connect Configurations"}
|
||||
/>
|
||||
}
|
||||
header={"OpenID Configurations"}
|
||||
backLink={IAM_PAGES.IDP_OPENID_CONFIGURATIONS}
|
||||
title={"Create OpenID Configuration"}
|
||||
endpoint={"/api/v1/idp/openid/"}
|
||||
formFields={openIDFormFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(AddIDPOpenIDConfiguration);
|
||||
@@ -0,0 +1,86 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 { DialogContentText } from "@mui/material";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
|
||||
import { ConfirmDeleteIcon } from "../../../icons";
|
||||
import {
|
||||
setErrorSnackMessage,
|
||||
setServerNeedsRestart,
|
||||
} from "../../../systemSlice";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
|
||||
interface IDeleteIDPConfigurationModalProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
idp: string;
|
||||
idpType: string;
|
||||
}
|
||||
|
||||
const DeleteIDPConfigurationModal = ({
|
||||
closeDeleteModalAndRefresh,
|
||||
deleteOpen,
|
||||
idp,
|
||||
idpType,
|
||||
}: IDeleteIDPConfigurationModalProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const onDelSuccess = (res: any) => {
|
||||
closeDeleteModalAndRefresh(true);
|
||||
dispatch(setServerNeedsRestart(res.restart === true));
|
||||
};
|
||||
const onDelError = (err: ErrorResponseHandler) =>
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
if (!idp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
invokeDeleteApi("DELETE", `/api/v1/idp/${idpType}/${idp}`);
|
||||
};
|
||||
|
||||
const displayName = idp === "_" ? "Default" : idp;
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
title={`Delete ${displayName}`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
titleIcon={<ConfirmDeleteIcon />}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmButtonProps={{
|
||||
disabled: deleteLoading,
|
||||
}}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete IDP <b>{displayName}</b>{" "}
|
||||
configuration? <br />
|
||||
</DialogContentText>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteIDPConfigurationModal;
|
||||
364
portal-ui/src/screens/Console/IDP/IDPConfigurationDetails.tsx
Normal file
364
portal-ui/src/screens/Console/IDP/IDPConfigurationDetails.tsx
Normal file
@@ -0,0 +1,364 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Box, Grid } from "@mui/material";
|
||||
import {
|
||||
buttonsStyles,
|
||||
containerForHeader,
|
||||
formFieldStyles,
|
||||
hrClass,
|
||||
modalBasic,
|
||||
pageContentStyles,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { RefreshIcon, TrashIcon } from "../../../icons";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import { Button } from "mds";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import {
|
||||
setErrorSnackMessage,
|
||||
setServerNeedsRestart,
|
||||
} from "../../../systemSlice";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import api from "../../../common/api";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import BackLink from "../../../common/BackLink";
|
||||
import ScreenTitle from "../Common/ScreenTitle/ScreenTitle";
|
||||
import DeleteIDPConfigurationModal from "./DeleteIDPConfigurationModal";
|
||||
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
type IDPConfigurationDetailsProps = {
|
||||
classes?: any;
|
||||
formFields: object;
|
||||
endpoint: string;
|
||||
backLink: string;
|
||||
header: string;
|
||||
idpType: string;
|
||||
icon: React.ReactNode;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...formFieldStyles,
|
||||
formFieldRow: {
|
||||
...formFieldStyles.formFieldRow,
|
||||
},
|
||||
...modalBasic,
|
||||
pageContainer: {
|
||||
height: "100%",
|
||||
},
|
||||
screenTitle: {
|
||||
border: 0,
|
||||
paddingTop: 0,
|
||||
},
|
||||
...pageContentStyles,
|
||||
...searchField,
|
||||
capitalize: {
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
...hrClass,
|
||||
...buttonsStyles,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const IDPConfigurationDetails = ({
|
||||
classes,
|
||||
formFields,
|
||||
endpoint,
|
||||
backLink,
|
||||
header,
|
||||
idpType,
|
||||
icon,
|
||||
}: IDPConfigurationDetailsProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
|
||||
const configurationName = params.idpName;
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [isEnabled, setIsEnabled] = useState<boolean>(false);
|
||||
const [fields, setFields] = useState<any>({});
|
||||
const [originalFields, setOriginalFields] = useState<any>({});
|
||||
const [record, setRecord] = useState<any>({});
|
||||
const [editMode, setEditMode] = useState<boolean>(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
|
||||
const onSuccess = (res: any) => {
|
||||
dispatch(setServerNeedsRestart(res.restart === true));
|
||||
};
|
||||
|
||||
const onError = (err: ErrorResponseHandler) =>
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
|
||||
const [loadingSave, invokeApi] = useApi(onSuccess, onError);
|
||||
|
||||
const onEnabledSuccess = (res: any) => {
|
||||
setIsEnabled(!isEnabled);
|
||||
dispatch(setServerNeedsRestart(res.restart === true));
|
||||
};
|
||||
|
||||
const onEnabledError = (err: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
};
|
||||
|
||||
const [loadingEnabledSave, invokeEnabledApi] = useApi(
|
||||
onEnabledSuccess,
|
||||
onEnabledError
|
||||
);
|
||||
|
||||
const toggleEditMode = () => {
|
||||
if (editMode) {
|
||||
parseFields(record);
|
||||
}
|
||||
setEditMode(!editMode);
|
||||
};
|
||||
|
||||
const parseFields = (record: any) => {
|
||||
let fields: any = {};
|
||||
if (record.info) {
|
||||
record.info.forEach((item: any) => {
|
||||
if (item.key === "enable") {
|
||||
setIsEnabled(item.value === "on");
|
||||
}
|
||||
fields[item.key] = item.value;
|
||||
});
|
||||
}
|
||||
setFields(fields);
|
||||
};
|
||||
|
||||
const parseOriginalFields = (record: any) => {
|
||||
let fields: any = {};
|
||||
if (record.info) {
|
||||
record.info.forEach((item: any) => {
|
||||
fields[item.key] = item.value;
|
||||
});
|
||||
}
|
||||
setOriginalFields(fields);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loadRecord = () => {
|
||||
api
|
||||
.invoke("GET", `${endpoint}${configurationName}`)
|
||||
.then((result: any) => {
|
||||
if (result) {
|
||||
setRecord(result);
|
||||
parseFields(result);
|
||||
parseOriginalFields(result);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
if (loading) {
|
||||
loadRecord();
|
||||
}
|
||||
}, [dispatch, loading, configurationName, endpoint]);
|
||||
|
||||
const validSave = () => {
|
||||
for (const [key, value] of Object.entries(formFields)) {
|
||||
if (
|
||||
value.required &&
|
||||
!(
|
||||
fields[key] !== undefined &&
|
||||
fields[key] !== null &&
|
||||
fields[key] !== ""
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setFields({});
|
||||
};
|
||||
|
||||
const saveRecord = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
let input = "";
|
||||
for (const key of Object.keys(formFields)) {
|
||||
if (fields[key] || fields[key] !== originalFields[key]) {
|
||||
input += `${key}=${fields[key]} `;
|
||||
}
|
||||
}
|
||||
invokeApi("PUT", `${endpoint}${configurationName}`, { input });
|
||||
setEditMode(false);
|
||||
};
|
||||
|
||||
const closeDeleteModalAndRefresh = async (refresh: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
if (refresh) {
|
||||
navigate(backLink);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleConfiguration = (value: boolean) => {
|
||||
const input = `enable=${value ? "on" : "off"}`;
|
||||
invokeEnabledApi("PUT", `${endpoint}${configurationName}`, { input });
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
{deleteOpen && configurationName && (
|
||||
<DeleteIDPConfigurationModal
|
||||
deleteOpen={deleteOpen}
|
||||
idp={configurationName}
|
||||
idpType={idpType}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<PageHeader
|
||||
label={<BackLink to={backLink} label={header} />}
|
||||
actions={
|
||||
<FormSwitchWrapper
|
||||
label={""}
|
||||
indicatorLabels={["Enabled", "Disabled"]}
|
||||
checked={isEnabled}
|
||||
value={"is-configuration-enabled"}
|
||||
id={"is-configuration-enabled"}
|
||||
name={"is-configuration-enabled"}
|
||||
onChange={(e) => toggleConfiguration(e.target.checked)}
|
||||
description=""
|
||||
disabled={loadingEnabledSave}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<PageLayout className={classes.pageContainer}>
|
||||
<Grid item xs={12}>
|
||||
<ScreenTitle
|
||||
classes={{
|
||||
screenTitle: classes.screenTitle,
|
||||
}}
|
||||
icon={icon}
|
||||
title={configurationName === "_" ? "Default" : configurationName}
|
||||
actions={
|
||||
<Fragment>
|
||||
{configurationName !== "_" && (
|
||||
<Button
|
||||
id={"delete-idp-config"}
|
||||
onClick={() => {
|
||||
setDeleteOpen(true);
|
||||
}}
|
||||
label={"Delete Configuration"}
|
||||
icon={<TrashIcon />}
|
||||
variant={"secondary"}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
id={"refresh-idp-config"}
|
||||
onClick={() => setLoading(true)}
|
||||
label={"Refresh"}
|
||||
icon={<RefreshIcon />}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
saveRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container item spacing="20" sx={{ marginTop: 1 }}>
|
||||
<Grid xs={12} item className={classes.fieldBox}>
|
||||
{Object.entries(formFields).map(([key, value]) => (
|
||||
<Grid item xs={12} className={classes.formFieldRow} key={key}>
|
||||
<InputBoxWrapper
|
||||
id={key}
|
||||
required={value.required}
|
||||
name={key}
|
||||
label={value.label}
|
||||
tooltip={value.tooltip}
|
||||
error={value.hasError(fields[key], editMode)}
|
||||
value={fields[key] ? fields[key] : ""}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setFields({ ...fields, [key]: e.target.value })
|
||||
}
|
||||
placeholder={value.placeholder}
|
||||
disabled={!editMode}
|
||||
type={value.type}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
<Grid item xs={12} textAlign={"right"}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
marginTop: "20px",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
id={"edit"}
|
||||
type="button"
|
||||
variant={editMode ? "regular" : "callAction"}
|
||||
onClick={toggleEditMode}
|
||||
label={editMode ? "Cancel" : "Edit"}
|
||||
/>
|
||||
{editMode && (
|
||||
<Button
|
||||
id={"clear"}
|
||||
type="button"
|
||||
variant="regular"
|
||||
onClick={resetForm}
|
||||
label={"Clear"}
|
||||
/>
|
||||
)}
|
||||
|
||||
{editMode && (
|
||||
<Button
|
||||
id={"save-key"}
|
||||
type="submit"
|
||||
variant="callAction"
|
||||
color="primary"
|
||||
disabled={loading || loadingSave || !validSave()}
|
||||
label={"Save"}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</PageLayout>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPConfigurationDetails);
|
||||
225
portal-ui/src/screens/Console/IDP/IDPConfigurations.tsx
Normal file
225
portal-ui/src/screens/Console/IDP/IDPConfigurations.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_SCOPES,
|
||||
} from "../../../common/SecureComponent/permissions";
|
||||
import {
|
||||
hasPermission,
|
||||
SecureComponent,
|
||||
} from "../../../common/SecureComponent";
|
||||
import api from "../../../common/api";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { setErrorSnackMessage } from "../../../systemSlice";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
|
||||
import { Grid } from "@mui/material";
|
||||
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
|
||||
import { Button } from "mds";
|
||||
import { AddIcon, RefreshIcon } from "../../../icons";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import DeleteIDPConfigurationModal from "./DeleteIDPConfigurationModal";
|
||||
|
||||
type IDPConfigurationsProps = {
|
||||
classes?: any;
|
||||
idpType: string;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const IDPConfigurations = ({ classes, idpType }: IDPConfigurationsProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [selectedIDP, setSelectedIDP] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [records, setRecords] = useState<[]>([]);
|
||||
|
||||
const deleteIDP = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
]);
|
||||
|
||||
const viewIDP = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
]);
|
||||
|
||||
const displayIDPs = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRecords();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
if (displayIDPs) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/idp/${idpType}`)
|
||||
.then((res) => {
|
||||
setLoading(false);
|
||||
setRecords(
|
||||
res.results.map((r: any) => {
|
||||
r.name = r.name === "_" ? "Default" : r.name;
|
||||
r.enabled = r.enabled === true ? "Enabled" : "Disabled";
|
||||
return r;
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoading(false);
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}, [loading, setLoading, setRecords, dispatch, displayIDPs, idpType]);
|
||||
|
||||
const fetchRecords = () => {
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
const confirmDeleteIDP = (idp: string) => {
|
||||
setDeleteOpen(true);
|
||||
idp = idp === "Default" ? "_" : idp;
|
||||
setSelectedIDP(idp);
|
||||
};
|
||||
|
||||
const viewAction = (idp: any) => {
|
||||
let name = idp.name === "Default" ? "_" : idp.name;
|
||||
navigate(`/idp/${idpType}/configurations/${name}`);
|
||||
};
|
||||
|
||||
const closeDeleteModalAndRefresh = async (refresh: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
if (refresh) {
|
||||
fetchRecords();
|
||||
}
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{
|
||||
type: "view",
|
||||
onClick: viewAction,
|
||||
disableButtonFunction: () => !viewIDP,
|
||||
},
|
||||
{
|
||||
type: "delete",
|
||||
onClick: confirmDeleteIDP,
|
||||
sendOnlyId: true,
|
||||
disableButtonFunction: (idp: string) => !deleteIDP || idp === "Default",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{deleteOpen && (
|
||||
<DeleteIDPConfigurationModal
|
||||
deleteOpen={deleteOpen}
|
||||
idp={selectedIDP}
|
||||
idpType={idpType}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<PageHeader label={`${idpType.toUpperCase()} Configurations`} />
|
||||
<PageLayout className={classes.pageContainer}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
justifyContent={"flex-end"}
|
||||
sx={{
|
||||
"& button": {
|
||||
marginLeft: "8px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_CONFIG_UPDATE]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TooltipWrapper tooltip={"Refresh"}>
|
||||
<Button
|
||||
id={"refresh-keys"}
|
||||
variant="regular"
|
||||
icon={<RefreshIcon />}
|
||||
onClick={() => setLoading(true)}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</SecureComponent>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_CONFIG_UPDATE]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TooltipWrapper tooltip={`Create ${idpType} configuration`}>
|
||||
<Button
|
||||
id={"create-idp"}
|
||||
label={"Create Configuration"}
|
||||
variant={"callAction"}
|
||||
icon={<AddIcon />}
|
||||
onClick={() =>
|
||||
navigate(`/idp/${idpType}/configurations/add-idp`)
|
||||
}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</SecureComponent>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_CONFIG_UPDATE]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Type", elementKey: "type" },
|
||||
{ label: "Enabled", elementKey: "enabled" },
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={records}
|
||||
entityName="Keys"
|
||||
idField="name"
|
||||
/>
|
||||
</SecureComponent>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</PageLayout>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPConfigurations);
|
||||
@@ -0,0 +1,48 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { ldapFormFields } from "./utils";
|
||||
import LoginIcon from "@mui/icons-material/Login";
|
||||
import IDPConfigurationDetails from "./IDPConfigurationDetails";
|
||||
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
|
||||
|
||||
type IDPLDAPConfigurationDetailsProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const IDPLDAPConfigurationDetails = ({
|
||||
classes,
|
||||
}: IDPLDAPConfigurationDetailsProps) => {
|
||||
return (
|
||||
<IDPConfigurationDetails
|
||||
backLink={IAM_PAGES.IDP_LDAP_CONFIGURATIONS}
|
||||
header={"LDAP Configurations"}
|
||||
endpoint={"/api/v1/idp/ldap/"}
|
||||
idpType={"ldap"}
|
||||
formFields={ldapFormFields}
|
||||
icon={<LoginIcon width={40} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPLDAPConfigurationDetails);
|
||||
34
portal-ui/src/screens/Console/IDP/IDPLDAPConfigurations.tsx
Normal file
34
portal-ui/src/screens/Console/IDP/IDPLDAPConfigurations.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import IDPConfigurations from "./IDPConfigurations";
|
||||
|
||||
type IDPLDAPConfigurationsProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const IDPLDAPConfigurations = ({ classes }: IDPLDAPConfigurationsProps) => {
|
||||
return <IDPConfigurations idpType={"ldap"} />;
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPLDAPConfigurations);
|
||||
@@ -0,0 +1,48 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
|
||||
import { LockIcon } from "../../../icons";
|
||||
import { openIDFormFields } from "./utils";
|
||||
import IDPConfigurationDetails from "./IDPConfigurationDetails";
|
||||
|
||||
type IDPOpenIDConfigurationDetailsProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const IDPOpenIDConfigurationDetails = ({
|
||||
classes,
|
||||
}: IDPOpenIDConfigurationDetailsProps) => {
|
||||
return (
|
||||
<IDPConfigurationDetails
|
||||
backLink={IAM_PAGES.IDP_OPENID_CONFIGURATIONS}
|
||||
header={"OpenID Configurations"}
|
||||
endpoint={"/api/v1/idp/openid/"}
|
||||
idpType={"openid"}
|
||||
formFields={openIDFormFields}
|
||||
icon={<LockIcon width={40} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPOpenIDConfigurationDetails);
|
||||
@@ -0,0 +1,34 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import IDPConfigurations from "./IDPConfigurations";
|
||||
|
||||
type IDPOpenIDConfigurationsProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const IDPOpenIDConfigurations = ({ classes }: IDPOpenIDConfigurationsProps) => {
|
||||
return <IDPConfigurations idpType={"openid"} />;
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPOpenIDConfigurations);
|
||||
176
portal-ui/src/screens/Console/IDP/utils.tsx
Normal file
176
portal-ui/src/screens/Console/IDP/utils.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 openIDFormFields = {
|
||||
config_url: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Config URL is required" : "";
|
||||
},
|
||||
label: "Config URL",
|
||||
tooltip: "Config URL for identity provider configuration",
|
||||
placeholder:
|
||||
"https://identity-provider-url/.well-known/openid-configuration",
|
||||
type: "text",
|
||||
},
|
||||
client_id: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Client ID is required" : "";
|
||||
},
|
||||
label: "Client ID",
|
||||
tooltip: "Identity provider Client ID",
|
||||
placeholder: "Enter Client ID",
|
||||
type: "text",
|
||||
},
|
||||
client_secret: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Client Secret is required" : "";
|
||||
},
|
||||
label: "Client Secret",
|
||||
tooltip: "Identity provider Client Secret",
|
||||
placeholder: "Enter Client Secret",
|
||||
type: "password",
|
||||
},
|
||||
display_name: {
|
||||
required: false,
|
||||
label: "Display Name",
|
||||
tooltip: "Display Name",
|
||||
placeholder: "Enter Display Name",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
claim_name: {
|
||||
required: false,
|
||||
label: "Claim Name",
|
||||
tooltip: "Claim from which MinIO will read the policy or role to use",
|
||||
placeholder: "Enter Claim Name",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
claim_prefix: {
|
||||
required: false,
|
||||
label: "Claim Prefix",
|
||||
tooltip: "Claim Prefix",
|
||||
placeholder: "Enter Claim Prefix",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
scopes: {
|
||||
required: false,
|
||||
label: "Scopes",
|
||||
tooltip: "Scopes",
|
||||
placeholder: "openid,profile,email",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
redirect_uri: {
|
||||
required: false,
|
||||
label: "Redirect URI",
|
||||
tooltip: "Redirect URI",
|
||||
placeholder: "https://console-endpoint-url/oauth_callback",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
role_policy: {
|
||||
required: false,
|
||||
label: "Role Policy",
|
||||
tooltip: "Role Policy",
|
||||
placeholder: "readonly",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
};
|
||||
|
||||
export const ldapFormFields = {
|
||||
server_addr: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Server Address is required" : "";
|
||||
},
|
||||
label: "Server Address",
|
||||
tooltip: 'AD/LDAP server address e.g. "myldapserver.com:636"',
|
||||
placeholder: "myldapserver.com:636",
|
||||
type: "text",
|
||||
},
|
||||
lookup_bind_dn: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Lookup Bind DN is required" : "";
|
||||
},
|
||||
label: "Lookup Bind DN",
|
||||
tooltip:
|
||||
"DN for LDAP read-only service account used to perform DN and group lookups",
|
||||
placeholder: "cn=admin,dc=min,dc=io",
|
||||
type: "text",
|
||||
},
|
||||
lookup_bind_password: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Lookup Bind Password is required" : "";
|
||||
},
|
||||
label: "Lookup Bind Password",
|
||||
tooltip:
|
||||
"Password for LDAP read-only service account used to perform DN and group lookups",
|
||||
placeholder: "admin",
|
||||
type: "password",
|
||||
},
|
||||
user_dn_search_base_dn: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "User DN Search Base DN is required" : "";
|
||||
},
|
||||
label: "User DN Search Base",
|
||||
tooltip: "Base LDAP DN to search for user DN",
|
||||
placeholder: "DC=example,DC=net",
|
||||
type: "text",
|
||||
},
|
||||
user_dn_search_filter: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "User DN Search Filter is required" : "";
|
||||
},
|
||||
label: "User DN Search Filter",
|
||||
tooltip: "Search filter to lookup user DN",
|
||||
placeholder: "(sAMAcountName=%s)",
|
||||
type: "text",
|
||||
},
|
||||
display_name: {
|
||||
required: false,
|
||||
label: "Display Name",
|
||||
tooltip: "Display Name",
|
||||
placeholder: "Enter Display Name",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
group_search_base_dn: {
|
||||
required: false,
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
label: "Group Search Base DN",
|
||||
tooltip: "Group Search Base DN",
|
||||
placeholder: "ou=swengg,dc=min,dc=io",
|
||||
type: "text",
|
||||
},
|
||||
group_search_filter: {
|
||||
required: false,
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
label: "Group Search Filter",
|
||||
tooltip: "Group Search Filter",
|
||||
placeholder: "(&(objectclass=groupofnames)(member=%d))",
|
||||
type: "text",
|
||||
},
|
||||
};
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
IAM_SCOPES,
|
||||
permissionTooltipHelper,
|
||||
} from "../../../common/SecureComponent/permissions";
|
||||
import { BucketObjectItem } from "../Buckets/ListBuckets/Objects/ListObjects/types";
|
||||
import withSuspense from "../Common/Components/withSuspense";
|
||||
import { setSnackBarMessage } from "../../../systemSlice";
|
||||
import { AppState, useAppDispatch } from "../../../store";
|
||||
@@ -58,7 +57,6 @@ interface IObjectBrowser {
|
||||
bucketName: string;
|
||||
internalPaths: string;
|
||||
hidePathButton?: boolean;
|
||||
existingFiles: BucketObjectItem[];
|
||||
additionalOptions?: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -66,7 +64,6 @@ const BrowserBreadcrumbs = ({
|
||||
classes,
|
||||
bucketName,
|
||||
internalPaths,
|
||||
existingFiles,
|
||||
hidePathButton,
|
||||
additionalOptions,
|
||||
}: IObjectBrowser) => {
|
||||
@@ -176,7 +173,6 @@ const BrowserBreadcrumbs = ({
|
||||
bucketName={bucketName}
|
||||
folderName={internalPaths}
|
||||
onClose={closeAddFolderModal}
|
||||
existingFiles={existingFiles}
|
||||
/>
|
||||
)}
|
||||
<Grid item xs={12} className={`${classes.breadcrumbs}`}>
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { IFileItem, ObjectBrowserState } from "./types";
|
||||
import {
|
||||
BucketObjectItem,
|
||||
IRestoreLocalObjectList,
|
||||
} from "../Buckets/ListBuckets/Objects/ListObjects/types";
|
||||
|
||||
const defaultRewind = {
|
||||
rewindEnabled: false,
|
||||
@@ -47,6 +51,18 @@ const initialState: ObjectBrowserState = {
|
||||
showDeleted: false,
|
||||
selectedInternalPaths: null,
|
||||
simplePath: null,
|
||||
// object browser
|
||||
records: [],
|
||||
loadRecords: true,
|
||||
loadingVersioning: true,
|
||||
isVersioned: false,
|
||||
lockingEnabled: false,
|
||||
loadingLocking: false,
|
||||
selectedObjects: [],
|
||||
downloadRenameModal: null,
|
||||
selectedPreview: null,
|
||||
previewOpen: false,
|
||||
shareFileModalOpen: false,
|
||||
};
|
||||
|
||||
export const objectBrowserSlice = createSlice({
|
||||
@@ -217,7 +233,7 @@ export const objectBrowserSlice = createSlice({
|
||||
setSearchObjects: (state, action: PayloadAction<string>) => {
|
||||
state.searchObjects = action.payload;
|
||||
},
|
||||
setLoadingObjectsList: (state, action: PayloadAction<boolean>) => {
|
||||
setLoadingObjects: (state, action: PayloadAction<boolean>) => {
|
||||
state.loadingObjects = action.payload;
|
||||
},
|
||||
setSearchVersions: (state, action: PayloadAction<string>) => {
|
||||
@@ -259,6 +275,67 @@ export const objectBrowserSlice = createSlice({
|
||||
action.payload,
|
||||
];
|
||||
},
|
||||
setRecords: (state, action: PayloadAction<BucketObjectItem[]>) => {
|
||||
state.records = action.payload;
|
||||
},
|
||||
setLoadingVersioning: (state, action: PayloadAction<boolean>) => {
|
||||
state.loadingVersioning = action.payload;
|
||||
},
|
||||
setIsVersioned: (state, action: PayloadAction<boolean>) => {
|
||||
state.isVersioned = action.payload;
|
||||
},
|
||||
setLockingEnabled: (state, action: PayloadAction<boolean>) => {
|
||||
state.lockingEnabled = action.payload;
|
||||
},
|
||||
setLoadingLocking: (state, action: PayloadAction<boolean>) => {
|
||||
state.loadingLocking = action.payload;
|
||||
},
|
||||
newMessage: (state, action: PayloadAction<BucketObjectItem[]>) => {
|
||||
state.records = [...state.records, ...action.payload];
|
||||
},
|
||||
resetMessages: (state) => {
|
||||
state.records = [];
|
||||
},
|
||||
setLoadingRecords: (state, action: PayloadAction<boolean>) => {
|
||||
state.loadRecords = action.payload;
|
||||
},
|
||||
setSelectedObjects: (state, action: PayloadAction<string[]>) => {
|
||||
state.selectedObjects = action.payload;
|
||||
},
|
||||
setDownloadRenameModal: (
|
||||
state,
|
||||
action: PayloadAction<BucketObjectItem | null>
|
||||
) => {
|
||||
state.downloadRenameModal = action.payload;
|
||||
},
|
||||
setSelectedPreview: (
|
||||
state,
|
||||
action: PayloadAction<BucketObjectItem | null>
|
||||
) => {
|
||||
state.selectedPreview = action.payload;
|
||||
},
|
||||
setPreviewOpen: (state, action: PayloadAction<boolean>) => {
|
||||
state.previewOpen = action.payload;
|
||||
},
|
||||
setShareFileModalOpen: (state, action: PayloadAction<boolean>) => {
|
||||
state.shareFileModalOpen = action.payload;
|
||||
},
|
||||
restoreLocalObjectList: (
|
||||
state,
|
||||
action: PayloadAction<IRestoreLocalObjectList>
|
||||
) => {
|
||||
const indexToReplace = state.records.findIndex(
|
||||
(element) => element.name === action.payload.prefix
|
||||
);
|
||||
|
||||
if (indexToReplace >= 0) {
|
||||
state.records[indexToReplace].delete_flag =
|
||||
action.payload.objectInfo.is_delete_marker;
|
||||
state.records[indexToReplace].size = parseInt(
|
||||
action.payload.objectInfo.size || "0"
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
export const {
|
||||
@@ -275,7 +352,7 @@ export const {
|
||||
openList,
|
||||
closeList,
|
||||
setSearchObjects,
|
||||
setLoadingObjectsList,
|
||||
setLoadingObjects,
|
||||
cancelObjectInList,
|
||||
setSearchVersions,
|
||||
setSelectedVersion,
|
||||
@@ -287,6 +364,20 @@ export const {
|
||||
setSimplePathHandler,
|
||||
newDownloadInit,
|
||||
newUploadInit,
|
||||
setRecords,
|
||||
resetMessages,
|
||||
setLoadingVersioning,
|
||||
setIsVersioned,
|
||||
setLoadingLocking,
|
||||
setLockingEnabled,
|
||||
newMessage,
|
||||
setSelectedObjects,
|
||||
setDownloadRenameModal,
|
||||
setSelectedPreview,
|
||||
setPreviewOpen,
|
||||
setShareFileModalOpen,
|
||||
setLoadingRecords,
|
||||
restoreLocalObjectList,
|
||||
} = objectBrowserSlice.actions;
|
||||
|
||||
export default objectBrowserSlice.reducer;
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import { AppState } from "../../../store";
|
||||
import { encodeURLString, getClientOS } from "../../../common/utils";
|
||||
import { BucketObjectItem } from "../Buckets/ListBuckets/Objects/ListObjects/types";
|
||||
import { makeid, storeCallForObjectWithID } from "./transferManager";
|
||||
import { download } from "../Buckets/ListBuckets/Objects/utils";
|
||||
import {
|
||||
cancelObjectInList,
|
||||
completeObject,
|
||||
failObject,
|
||||
setDownloadRenameModal,
|
||||
setNewObject,
|
||||
setPreviewOpen,
|
||||
setSelectedPreview,
|
||||
setShareFileModalOpen,
|
||||
updateProgress,
|
||||
} from "./objectBrowserSlice";
|
||||
|
||||
export const downloadSelected = createAsyncThunk(
|
||||
"objectBrowser/downloadSelected",
|
||||
async (bucketName: string, { getState, rejectWithValue, dispatch }) => {
|
||||
const state = getState() as AppState;
|
||||
|
||||
const downloadObject = (object: BucketObjectItem) => {
|
||||
const identityDownload = encodeURLString(
|
||||
`${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}`
|
||||
);
|
||||
|
||||
const ID = makeid(8);
|
||||
|
||||
const downloadCall = download(
|
||||
bucketName,
|
||||
encodeURLString(object.name),
|
||||
object.version_id,
|
||||
object.size,
|
||||
null,
|
||||
ID,
|
||||
(progress) => {
|
||||
dispatch(
|
||||
updateProgress({
|
||||
instanceID: identityDownload,
|
||||
progress: progress,
|
||||
})
|
||||
);
|
||||
},
|
||||
() => {
|
||||
dispatch(completeObject(identityDownload));
|
||||
},
|
||||
(msg: string) => {
|
||||
dispatch(failObject({ instanceID: identityDownload, msg }));
|
||||
},
|
||||
() => {
|
||||
dispatch(cancelObjectInList(identityDownload));
|
||||
}
|
||||
);
|
||||
storeCallForObjectWithID(ID, downloadCall);
|
||||
dispatch(
|
||||
setNewObject({
|
||||
ID,
|
||||
bucketName,
|
||||
done: false,
|
||||
instanceID: identityDownload,
|
||||
percentage: 0,
|
||||
prefix: object.name,
|
||||
type: "download",
|
||||
waitingForFile: true,
|
||||
failed: false,
|
||||
cancelled: false,
|
||||
errorMessage: "",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
if (state.objectBrowser.selectedObjects.length !== 0) {
|
||||
let itemsToDownload: BucketObjectItem[] = [];
|
||||
|
||||
const filterFunction = (currValue: BucketObjectItem) =>
|
||||
state.objectBrowser.selectedObjects.includes(currValue.name);
|
||||
|
||||
itemsToDownload = state.objectBrowser.records.filter(filterFunction);
|
||||
|
||||
// I case just one element is selected, then we trigger download modal validation.
|
||||
// We are going to enforce zip download when multiple files are selected
|
||||
if (itemsToDownload.length === 1) {
|
||||
if (
|
||||
itemsToDownload[0].name.length > 200 &&
|
||||
getClientOS().toLowerCase().includes("win")
|
||||
) {
|
||||
dispatch(setDownloadRenameModal(itemsToDownload[0]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
itemsToDownload.forEach((filteredItem) => {
|
||||
downloadObject(filteredItem);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const openPreview = createAsyncThunk(
|
||||
"objectBrowser/openPreview",
|
||||
async (_, { getState, rejectWithValue, dispatch }) => {
|
||||
const state = getState() as AppState;
|
||||
|
||||
if (state.objectBrowser.selectedObjects.length === 1) {
|
||||
let fileObject: BucketObjectItem | undefined;
|
||||
|
||||
const findFunction = (currValue: BucketObjectItem) =>
|
||||
state.objectBrowser.selectedObjects.includes(currValue.name);
|
||||
|
||||
fileObject = state.objectBrowser.records.find(findFunction);
|
||||
|
||||
if (fileObject) {
|
||||
dispatch(setSelectedPreview(fileObject));
|
||||
dispatch(setPreviewOpen(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const openShare = createAsyncThunk(
|
||||
"objectBrowser/openShare",
|
||||
async (_, { getState, rejectWithValue, dispatch }) => {
|
||||
const state = getState() as AppState;
|
||||
|
||||
if (state.objectBrowser.selectedObjects.length === 1) {
|
||||
let fileObject: BucketObjectItem | undefined;
|
||||
|
||||
const findFunction = (currValue: BucketObjectItem) =>
|
||||
state.objectBrowser.selectedObjects.includes(currValue.name);
|
||||
|
||||
fileObject = state.objectBrowser.records.find(findFunction);
|
||||
|
||||
if (fileObject) {
|
||||
dispatch(setSelectedPreview(fileObject));
|
||||
dispatch(setShareFileModalOpen(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -14,6 +14,8 @@
|
||||
// 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 { BucketObjectItem } from "../Buckets/ListBuckets/Objects/ListObjects/types";
|
||||
|
||||
export const REWIND_SET_ENABLE = "REWIND/SET_ENABLE";
|
||||
export const REWIND_RESET_REWIND = "REWIND/RESET_REWIND";
|
||||
|
||||
@@ -76,6 +78,17 @@ export interface ObjectBrowserState {
|
||||
objectDetailsOpen: boolean;
|
||||
selectedInternalPaths: string | null;
|
||||
simplePath: string | null;
|
||||
records: BucketObjectItem[];
|
||||
loadRecords: boolean;
|
||||
loadingVersioning: boolean;
|
||||
isVersioned: boolean;
|
||||
lockingEnabled: boolean;
|
||||
loadingLocking: boolean;
|
||||
selectedObjects: string[];
|
||||
downloadRenameModal: BucketObjectItem | null;
|
||||
selectedPreview: BucketObjectItem | null;
|
||||
previewOpen: boolean;
|
||||
shareFileModalOpen: boolean;
|
||||
}
|
||||
|
||||
export interface ObjectManager {
|
||||
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
import SettingsIcon from "../../icons/SettingsIcon";
|
||||
import React from "react";
|
||||
import LicenseBadge from "./Menu/LicenseBadge";
|
||||
import { LockOpen, Login } from "@mui/icons-material";
|
||||
|
||||
export const validRoutes = (
|
||||
features: string[] | null | undefined,
|
||||
@@ -141,6 +142,20 @@ export const validRoutes = (
|
||||
to: IAM_PAGES.POLICIES,
|
||||
icon: AccessMenuIcon,
|
||||
},
|
||||
{
|
||||
name: "OpenID",
|
||||
component: NavLink,
|
||||
id: "openID",
|
||||
to: IAM_PAGES.IDP_OPENID_CONFIGURATIONS,
|
||||
icon: LockOpen,
|
||||
},
|
||||
{
|
||||
name: "LDAP",
|
||||
component: NavLink,
|
||||
id: "ldap",
|
||||
to: IAM_PAGES.IDP_LDAP_CONFIGURATIONS,
|
||||
icon: Login,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -142,6 +142,9 @@ const LoginCallback = ({ classes }: ILoginCallBackProps) => {
|
||||
targetPath = `${localStorage.getItem("redirect-path")}`;
|
||||
localStorage.setItem("redirect-path", "");
|
||||
}
|
||||
if (state) {
|
||||
localStorage.setItem("auth-state", state);
|
||||
}
|
||||
setLoading(false);
|
||||
navigate(targetPath);
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user