Application routing now uses SecureComponent and hasPermission function (#1288)
- Some menu options were not showing even if the user has access to perform the operations (IAM Policies) - Deleted unecessary backend endpoints.go logic, instead using SecureComponent to validate application routes and Menu options rendering - All the logic related to routes and permissions is now in the permissions.ts file - Added SecureComponent to List Users page - Separated Menu options and routing logic for AdminConsole and OperatorConsole - Tools are hidden if user don't have access to them or MinIO is running in fs mode (heal, audit log, etc - Hide change-password button if user don't have access - Hide create user button if user don't have access - fixed some bugs when ldap/oidc is enabled Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
@@ -37,15 +37,9 @@ import (
|
||||
// swagger:model operatorSessionResponse
|
||||
type OperatorSessionResponse struct {
|
||||
|
||||
// features
|
||||
Features []string `json:"features"`
|
||||
|
||||
// operator
|
||||
Operator bool `json:"operator,omitempty"`
|
||||
|
||||
// pages
|
||||
Pages []string `json:"pages"`
|
||||
|
||||
// status
|
||||
// Enum: [ok]
|
||||
Status string `json:"status,omitempty"`
|
||||
|
||||
@@ -46,9 +46,6 @@ type SessionResponse struct {
|
||||
// operator
|
||||
Operator bool `json:"operator,omitempty"`
|
||||
|
||||
// pages
|
||||
Pages []string `json:"pages"`
|
||||
|
||||
// permissions
|
||||
Permissions map[string][]string `json:"permissions,omitempty"`
|
||||
|
||||
|
||||
@@ -2296,21 +2296,9 @@ func init() {
|
||||
"operatorSessionResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"features": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pages": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -6103,21 +6091,9 @@ func init() {
|
||||
"operatorSessionResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"features": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pages": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/minio/console/models"
|
||||
"github.com/minio/console/operatorapi/operations"
|
||||
"github.com/minio/console/operatorapi/operations/user_api"
|
||||
"github.com/minio/console/pkg/acl"
|
||||
)
|
||||
|
||||
func registerSessionHandlers(api *operations.OperatorAPI) {
|
||||
@@ -42,16 +41,8 @@ func getSessionResponse(session *models.Principal) (*models.OperatorSessionRespo
|
||||
return nil, prepareError(errorGenericInvalidSession)
|
||||
}
|
||||
sessionResp := &models.OperatorSessionResponse{
|
||||
Pages: acl.GetAuthorizedEndpoints([]string{}),
|
||||
Features: getListOfEnabledFeatures(),
|
||||
Status: models.OperatorSessionResponseStatusOk,
|
||||
Operator: true,
|
||||
}
|
||||
return sessionResp, nil
|
||||
}
|
||||
|
||||
// getListOfEnabledFeatures returns a list of features
|
||||
func getListOfEnabledFeatures() []string {
|
||||
var features []string
|
||||
return features
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package acl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/minio/pkg/env"
|
||||
)
|
||||
|
||||
// GetOperatorMode gets Console Operator mode status set on env variable or default one
|
||||
func GetOperatorMode() bool {
|
||||
return strings.ToLower(env.Get(consoleOperatorMode, "off")) == "on"
|
||||
}
|
||||
|
||||
func GetLDAPEnabled() bool {
|
||||
return strings.ToLower(env.Get(ConsoleLDAPEnabled, "off")) == "on"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package acl
|
||||
|
||||
const (
|
||||
consoleOperatorMode = "CONSOLE_OPERATOR_MODE"
|
||||
// const for ldap configuration
|
||||
ConsoleLDAPEnabled = "CONSOLE_LDAP_ENABLED"
|
||||
)
|
||||
@@ -1,469 +0,0 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package acl
|
||||
|
||||
import (
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
// endpoints definition
|
||||
var (
|
||||
configuration = "/settings"
|
||||
configurationItem = "/settings/:option"
|
||||
notificationEndpoints = "/notification-endpoints"
|
||||
notificationEndpointsAddAny = "/notification-endpoints/add/:service"
|
||||
notificationEndpointsAdd = "/notification-endpoints/add"
|
||||
tiers = "/tiers"
|
||||
tiersAddAny = "/tiers/add/:service"
|
||||
tiersAdd = "/tiers/add"
|
||||
users = "/users"
|
||||
usersDetail = "/users/:userName+"
|
||||
groups = "/groups"
|
||||
groupsDetails = "/groups/:groupName+"
|
||||
iamPolicies = "/policies"
|
||||
policiesDetail = "/policies/*"
|
||||
dashboard = "/dashboard"
|
||||
metrics = "/metrics"
|
||||
profiling = "/profiling"
|
||||
addBucket = "/add-bucket"
|
||||
buckets = "/buckets"
|
||||
bucketsGeneral = "/buckets/*"
|
||||
bucketsAdmin = "/buckets/:bucketName/admin/*"
|
||||
bucketsAdminMain = "/buckets/:bucketName/admin"
|
||||
bucketsBrowserMenu = "/buckets"
|
||||
bucketsBrowserList = "/buckets/*"
|
||||
bucketsBrowser = "/buckets/:bucketName/browse/*"
|
||||
bucketsBrowserMain = "/buckets/:bucketName/browse"
|
||||
serviceAccounts = "/account"
|
||||
changePassword = "/account/change-password"
|
||||
tenants = "/tenants"
|
||||
tenantsAdd = "/tenants/add"
|
||||
tenantsAddSub = "/tenants/add/*"
|
||||
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
|
||||
tenantHop = "/namespaces/:tenantNamespace/tenants/:tenantName/hop"
|
||||
podsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName"
|
||||
tenantsDetailSummary = "/namespaces/:tenantNamespace/tenants/:tenantName/summary"
|
||||
tenantsDetailMetrics = "/namespaces/:tenantNamespace/tenants/:tenantName/metrics"
|
||||
tenantsDetailPods = "/namespaces/:tenantNamespace/tenants/:tenantName/pods"
|
||||
tenantsDetailPools = "/namespaces/:tenantNamespace/tenants/:tenantName/pools"
|
||||
tenantsDetailVolumes = "/namespaces/:tenantNamespace/tenants/:tenantName/volumes"
|
||||
tenantsDetailLicense = "/namespaces/:tenantNamespace/tenants/:tenantName/license"
|
||||
tenantsDetailSecurity = "/namespaces/:tenantNamespace/tenants/:tenantName/security"
|
||||
storage = "/storage"
|
||||
storageVolumes = "/storage/volumes"
|
||||
storageDrives = "/storage/drives"
|
||||
remoteBuckets = "/remote-buckets"
|
||||
replication = "/replication"
|
||||
license = "/license"
|
||||
watch = "/tools/watch"
|
||||
heal = "/tools/heal"
|
||||
trace = "/tools/trace"
|
||||
tools = "/tools"
|
||||
logs = "/tools/logs"
|
||||
auditLogs = "/tools/audit-logs"
|
||||
speedtest = "/tools/speedtest"
|
||||
healthInfo = "/tools/diagnostics"
|
||||
)
|
||||
|
||||
type ConfigurationActionSet struct {
|
||||
actionTypes iampolicy.ActionSet
|
||||
actions iampolicy.ActionSet
|
||||
}
|
||||
|
||||
// configurationActionSet contains the list of admin actions required for this endpoint to work
|
||||
var configurationActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ConfigUpdateAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// dashboardActionSet contains the list of admin actions required for this endpoint to work
|
||||
var dashboardActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ServerInfoAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// groupsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var groupsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ListGroupsAdminAction,
|
||||
iampolicy.AddUserToGroupAdminAction,
|
||||
//iampolicy.GetGroupAdminAction,
|
||||
iampolicy.EnableGroupAdminAction,
|
||||
iampolicy.DisableGroupAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// iamPoliciesActionSet contains the list of admin actions required for this endpoint to work
|
||||
var iamPoliciesActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.GetPolicyAdminAction,
|
||||
iampolicy.DeletePolicyAdminAction,
|
||||
iampolicy.CreatePolicyAdminAction,
|
||||
iampolicy.AttachPolicyAdminAction,
|
||||
iampolicy.ListUserPoliciesAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// profilingActionSet contains the list of admin actions required for this endpoint to work
|
||||
var profilingActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ProfilingAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// usersActionSet contains the list of admin actions required for this endpoint to work
|
||||
var usersActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ListUsersAdminAction,
|
||||
iampolicy.CreateUserAdminAction,
|
||||
iampolicy.DeleteUserAdminAction,
|
||||
iampolicy.GetUserAdminAction,
|
||||
iampolicy.EnableUserAdminAction,
|
||||
iampolicy.DisableUserAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// bucketsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var bucketsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
// Read access to buckets
|
||||
iampolicy.ListMultipartUploadPartsAction,
|
||||
iampolicy.ListBucketMultipartUploadsAction,
|
||||
iampolicy.ListBucketAction,
|
||||
iampolicy.HeadBucketAction,
|
||||
iampolicy.GetObjectAction,
|
||||
iampolicy.GetBucketLocationAction,
|
||||
// Write access to buckets
|
||||
iampolicy.AbortMultipartUploadAction,
|
||||
iampolicy.CreateBucketAction,
|
||||
iampolicy.PutObjectAction,
|
||||
iampolicy.DeleteObjectAction,
|
||||
iampolicy.DeleteBucketAction,
|
||||
// Assign bucket policies
|
||||
iampolicy.PutBucketPolicyAction,
|
||||
iampolicy.DeleteBucketPolicyAction,
|
||||
iampolicy.GetBucketPolicyAction,
|
||||
),
|
||||
}
|
||||
|
||||
// serviceAccountsActionSet no actions needed for this module to work
|
||||
var serviceAccountsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(),
|
||||
actions: iampolicy.NewActionSet(),
|
||||
}
|
||||
|
||||
// changePasswordActionSet requires admin:CreateUser policy permission
|
||||
var changePasswordActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(),
|
||||
actions: iampolicy.NewActionSet(),
|
||||
}
|
||||
|
||||
// tenantsActionSet temporally no actions needed for tenants sections to work
|
||||
var tenantsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(),
|
||||
actions: iampolicy.NewActionSet(),
|
||||
}
|
||||
|
||||
var storageActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(),
|
||||
actions: iampolicy.NewActionSet(),
|
||||
}
|
||||
|
||||
var remoteBucketsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ConfigUpdateAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
var replicationActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ConfigUpdateAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// objectBrowserActionSet no actions needed for this module to work
|
||||
var objectBrowserActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(),
|
||||
actions: iampolicy.NewActionSet(),
|
||||
}
|
||||
|
||||
// licenseActionSet no actions needed for this module to work
|
||||
var licenseActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(),
|
||||
actions: iampolicy.NewActionSet(),
|
||||
}
|
||||
|
||||
// watchActionSet contains the list of admin actions required for this endpoint to work
|
||||
var watchActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ListenBucketNotificationAction,
|
||||
),
|
||||
}
|
||||
|
||||
// healActionSet contains the list of admin actions required for this endpoint to work
|
||||
var healActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.HealAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// logsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var logsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ConsoleLogAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// toolsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var toolsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.ConsoleLogAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// traceActionSet contains the list of admin actions required for this endpoint to work
|
||||
var traceActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.TraceAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// healthInfoActionSet contains the list of admin actions required for this endpoint to work
|
||||
var healthInfoActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.HealthInfoAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// logsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var speedtestActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.HealthInfoAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
var displayRules = map[string]func() bool{
|
||||
// disable users page if LDAP is enabled
|
||||
users: func() bool {
|
||||
return !GetLDAPEnabled()
|
||||
},
|
||||
// disable groups page if LDAP is enabled
|
||||
groups: func() bool {
|
||||
return !GetLDAPEnabled()
|
||||
},
|
||||
}
|
||||
|
||||
// endpointRules contains the mapping between endpoints and ActionSets, additional rules can be added here
|
||||
var endpointRules = map[string]ConfigurationActionSet{
|
||||
configuration: configurationActionSet,
|
||||
configurationItem: configurationActionSet,
|
||||
notificationEndpoints: configurationActionSet,
|
||||
notificationEndpointsAdd: configurationActionSet,
|
||||
notificationEndpointsAddAny: configurationActionSet,
|
||||
tiers: configurationActionSet,
|
||||
tiersAdd: configurationActionSet,
|
||||
tiersAddAny: configurationActionSet,
|
||||
users: usersActionSet,
|
||||
usersDetail: usersActionSet,
|
||||
groups: groupsActionSet,
|
||||
groupsDetails: groupsActionSet,
|
||||
iamPolicies: iamPoliciesActionSet,
|
||||
policiesDetail: iamPoliciesActionSet,
|
||||
dashboard: dashboardActionSet,
|
||||
metrics: dashboardActionSet,
|
||||
profiling: profilingActionSet,
|
||||
addBucket: bucketsActionSet,
|
||||
buckets: bucketsActionSet,
|
||||
bucketsGeneral: bucketsActionSet,
|
||||
bucketsAdmin: bucketsActionSet,
|
||||
bucketsAdminMain: bucketsActionSet,
|
||||
serviceAccounts: serviceAccountsActionSet,
|
||||
changePassword: changePasswordActionSet,
|
||||
remoteBuckets: remoteBucketsActionSet,
|
||||
replication: replicationActionSet,
|
||||
bucketsBrowser: objectBrowserActionSet,
|
||||
bucketsBrowserMenu: objectBrowserActionSet,
|
||||
bucketsBrowserList: objectBrowserActionSet,
|
||||
bucketsBrowserMain: objectBrowserActionSet,
|
||||
license: licenseActionSet,
|
||||
watch: watchActionSet,
|
||||
heal: healActionSet,
|
||||
trace: traceActionSet,
|
||||
logs: logsActionSet,
|
||||
auditLogs: logsActionSet,
|
||||
tools: toolsActionSet,
|
||||
healthInfo: healthInfoActionSet,
|
||||
speedtest: speedtestActionSet,
|
||||
}
|
||||
|
||||
// operatorRules contains the mapping between endpoints and ActionSets for operator only mode
|
||||
var operatorRules = map[string]ConfigurationActionSet{
|
||||
tenants: tenantsActionSet,
|
||||
tenantsAdd: tenantsActionSet,
|
||||
tenantsAddSub: tenantsActionSet,
|
||||
tenantsDetail: tenantsActionSet,
|
||||
tenantHop: tenantsActionSet,
|
||||
tenantsDetailSummary: tenantsActionSet,
|
||||
tenantsDetailMetrics: tenantsActionSet,
|
||||
tenantsDetailPods: tenantsActionSet,
|
||||
tenantsDetailPools: tenantsActionSet,
|
||||
tenantsDetailVolumes: tenantsActionSet,
|
||||
tenantsDetailLicense: tenantsActionSet,
|
||||
tenantsDetailSecurity: tenantsActionSet,
|
||||
podsDetail: tenantsActionSet,
|
||||
storage: storageActionSet,
|
||||
storageDrives: storageActionSet,
|
||||
storageVolumes: storageActionSet,
|
||||
license: licenseActionSet,
|
||||
}
|
||||
|
||||
// operatorOnly ENV variable
|
||||
var operatorOnly = GetOperatorMode()
|
||||
|
||||
// GetActionsStringFromPolicy extract the admin/s3 actions from a given policy and return them in []string format
|
||||
//
|
||||
// ie:
|
||||
// {
|
||||
// "Version": "2012-10-17",
|
||||
// "Statement": [{
|
||||
// "Action": [
|
||||
// "admin:ServerInfo",
|
||||
// "admin:CreatePolicy",
|
||||
// "admin:GetUser"
|
||||
// ],
|
||||
// ...
|
||||
// },
|
||||
// {
|
||||
// "Action": [
|
||||
// "s3:ListenBucketNotification",
|
||||
// "s3:PutBucketNotification"
|
||||
// ],
|
||||
// ...
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// Will produce an array like: ["admin:ServerInfo", "admin:CreatePolicy", "admin:GetUser", "s3:ListenBucketNotification", "s3:PutBucketNotification"]\
|
||||
func GetActionsStringFromPolicy(policy *iampolicy.Policy) []string {
|
||||
var actions []string
|
||||
for _, statement := range policy.Statements {
|
||||
// We only care about allowed actions
|
||||
if statement.Effect.IsAllowed(true) {
|
||||
for _, action := range statement.Actions.ToSlice() {
|
||||
actions = append(actions, string(action))
|
||||
}
|
||||
}
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
// actionsStringToActionSet convert a given string array to iampolicy.ActionSet structure
|
||||
// this avoids ending with duplicate actions
|
||||
func actionsStringToActionSet(actions []string) iampolicy.ActionSet {
|
||||
actionsSet := iampolicy.ActionSet{}
|
||||
for _, action := range actions {
|
||||
actionsSet.Add(iampolicy.Action(action))
|
||||
}
|
||||
return actionsSet
|
||||
}
|
||||
|
||||
// GetAuthorizedEndpoints return a list of allowed endpoint based on a provided *iampolicy.Policy
|
||||
// ie: pages the user should have access based on his current privileges
|
||||
func GetAuthorizedEndpoints(actions []string) []string {
|
||||
rangeTake := endpointRules
|
||||
|
||||
if operatorOnly {
|
||||
rangeTake = operatorRules
|
||||
}
|
||||
// Prepare new ActionSet structure that will hold all the user actions
|
||||
userAllowedAction := actionsStringToActionSet(actions)
|
||||
var allowedEndpoints []string
|
||||
for endpoint, rules := range rangeTake {
|
||||
|
||||
// check if display rule exists for this endpoint, this will control
|
||||
// what user sees on the console UI
|
||||
if rule, ok := displayRules[endpoint]; ok {
|
||||
if rule != nil && !rule() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// check if user policy matches s3:* or admin:* typesIntersection
|
||||
endpointActionTypes := rules.actionTypes
|
||||
typesIntersection := endpointActionTypes.Intersection(userAllowedAction)
|
||||
if len(typesIntersection) == len(endpointActionTypes.ToSlice()) {
|
||||
allowedEndpoints = append(allowedEndpoints, endpoint)
|
||||
continue
|
||||
}
|
||||
// check if user policy matches explicitly defined endpoint required actions
|
||||
endpointRequiredActions := rules.actions
|
||||
actionsIntersection := endpointRequiredActions.Intersection(userAllowedAction)
|
||||
if len(actionsIntersection) == len(endpointRequiredActions.ToSlice()) {
|
||||
allowedEndpoints = append(allowedEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return allowedEndpoints
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package acl
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type args struct {
|
||||
actions []string
|
||||
}
|
||||
|
||||
type endpoint struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}
|
||||
|
||||
func validateEndpoints(t *testing.T, configs []endpoint) {
|
||||
for _, tt := range configs {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := GetAuthorizedEndpoints(tt.args.actions); !reflect.DeepEqual(len(got), tt.want) {
|
||||
t.Errorf("GetAuthorizedEndpoints() = %v, want %v", len(got), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
tests := []endpoint{
|
||||
{
|
||||
name: "dashboard endpoint",
|
||||
args: args{
|
||||
[]string{"admin:ServerInfo"},
|
||||
},
|
||||
want: 9,
|
||||
},
|
||||
{
|
||||
name: "policies endpoint",
|
||||
args: args{
|
||||
[]string{
|
||||
"admin:CreatePolicy",
|
||||
"admin:DeletePolicy",
|
||||
"admin:GetPolicy",
|
||||
"admin:AttachUserOrGroupPolicy",
|
||||
"admin:ListUserPolicies",
|
||||
},
|
||||
},
|
||||
want: 9,
|
||||
},
|
||||
{
|
||||
name: "all admin endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"admin:*",
|
||||
},
|
||||
},
|
||||
want: 34,
|
||||
},
|
||||
{
|
||||
name: "all s3 endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 10,
|
||||
},
|
||||
{
|
||||
name: "all admin and s3 endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"admin:*",
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 37,
|
||||
},
|
||||
{
|
||||
name: "Console User - default endpoints",
|
||||
args: args{
|
||||
[]string{},
|
||||
},
|
||||
want: 7,
|
||||
},
|
||||
}
|
||||
|
||||
validateEndpoints(t, tests)
|
||||
}
|
||||
|
||||
func TestOperatorOnlyEndpoints(t *testing.T) {
|
||||
operatorOnly = true
|
||||
|
||||
tests := []endpoint{
|
||||
{
|
||||
name: "Operator Only - all admin endpoints",
|
||||
args: args{},
|
||||
want: 17,
|
||||
},
|
||||
}
|
||||
|
||||
validateEndpoints(t, tests)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
|
||||
@@ -66,10 +66,7 @@
|
||||
<div id="loader-block">
|
||||
<svg class="loader-svg-container" viewBox="22 22 44 44">
|
||||
<circle
|
||||
class="
|
||||
loader-style
|
||||
MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate
|
||||
"
|
||||
class="loader-style MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate"
|
||||
cx="44"
|
||||
cy="44"
|
||||
fill="none"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// match some of the permissions)
|
||||
export const hasAccessToResource = (
|
||||
userPermissionsOnBucket: string[] | null | undefined,
|
||||
requiredPermissions: string[],
|
||||
requiredPermissions: string[] = [],
|
||||
matchAll?: boolean
|
||||
) => {
|
||||
if (!userPermissionsOnBucket) {
|
||||
@@ -44,10 +44,9 @@ export const hasAccessToResource = (
|
||||
};
|
||||
|
||||
export const IAM_ROLES = {
|
||||
viewer: "VIEWER",
|
||||
editor: "EDITOR",
|
||||
owner: "OWNER",
|
||||
admin: "ADMIN",
|
||||
BUCKET_OWNER: "BUCKET_OWNER", // upload/delete objects from the bucket
|
||||
BUCKET_VIEWER: "BUCKET_VIEWER", // only view objects on the bucket
|
||||
BUCKET_ADMIN: "BUCKET_ADMIN", // administrate the bucket
|
||||
};
|
||||
|
||||
export const IAM_SCOPES = {
|
||||
@@ -84,11 +83,17 @@ export const IAM_SCOPES = {
|
||||
"s3:PutBucketObjectLockConfiguration",
|
||||
ADMIN_GET_POLICY: "admin:GetPolicy",
|
||||
ADMIN_LIST_USERS: "admin:ListUsers",
|
||||
ADMIN_CREATE_USER: "admin:CreateUser",
|
||||
ADMIN_DELETE_USER: "admin:DeleteUser",
|
||||
ADMIN_ENABLE_USER: "admin:EnableUser",
|
||||
ADMIN_DISABLE_USER: "admin:DisableUser",
|
||||
ADMIN_GET_USER: "admin:GetUser",
|
||||
ADMIN_LIST_USER_POLICIES: "admin:ListUserPolicies",
|
||||
ADMIN_SERVER_INFO: "admin:ServerInfo",
|
||||
ADMIN_GET_BUCKET_QUOTA: "admin:GetBucketQuota",
|
||||
ADMIN_SET_BUCKET_QUOTA: "admin:SetBucketQuota",
|
||||
ADMIN_LIST_TIERS: "admin:ListTier",
|
||||
ADMIN_SET_TIER: "admin:SetTier",
|
||||
ADMIN_LIST_GROUPS: "admin:ListGroups",
|
||||
S3_GET_OBJECT_VERSION_FOR_REPLICATION: "s3:GetObjectVersionForReplication",
|
||||
S3_REPLICATE_TAGS: "s3:ReplicateTags",
|
||||
@@ -117,20 +122,87 @@ export const IAM_SCOPES = {
|
||||
ADMIN_GET_GROUP: "admin:GetGroup",
|
||||
ADMIN_ENABLE_GROUP: "admin:EnableGroup",
|
||||
ADMIN_DISABLE_GROUP: "admin:DisableGroup",
|
||||
ADMIN_GET_USER: "admin:GetUser",
|
||||
ADMIN_CREATE_POLICY: "admin:CreatePolicy",
|
||||
ADMIN_DELETE_POLICY: "admin:DeletePolicy",
|
||||
ADMIN_ATTACH_USER_OR_GROUP_POLICY: "admin:AttachUserOrGroupPolicy",
|
||||
ADMIN_HEAL_ACTION: "admin:Heal",
|
||||
ADMIN_HEALTH_ACTION: "admin:OBDInfo",
|
||||
ADMIN_CONSOLE_LOG_ACTION: "admin:ConsoleLog",
|
||||
ADMIN_TRACE_ACTION: "admin:ServerTrace",
|
||||
ADMIN_CREATE_SERVICEACCOUNT: "admin:CreateServiceAccount",
|
||||
ADMIN_UPDATE_SERVICEACCOUNT: "admin:UpdateServiceAccount",
|
||||
ADMIN_REMOVE_SERVICEACCOUNT: "admin:RemoveServiceAccount",
|
||||
ADMIN_LIST_SERVICEACCOUNTS: "admin:ListServiceAccounts",
|
||||
ADMIN_CONFIG_UPDATE: "admin:ConfigUpdate",
|
||||
ADMIN_GET_CONSOLE_LOG: "admin:ConsoleLog",
|
||||
ADMIN_SERVER_TRACE: "admin:ServerTrace",
|
||||
ADMIN_HEALTH_INFO: "admin:OBDInfo",
|
||||
ADMIN_HEAL: "admin:Heal",
|
||||
S3_ALL_ACTIONS: "s3:*",
|
||||
ADMIN_ALL_ACTIONS: "admin:*",
|
||||
};
|
||||
|
||||
export const IAM_PAGES = {
|
||||
POLICIES: "/policies",
|
||||
POLICIES_VIEW: "/policies/*",
|
||||
DASHBOARD: "/dashboard",
|
||||
METRICS: "/metrics",
|
||||
ADD_BUCKETS: "/add-bucket",
|
||||
BUCKETS: "/buckets",
|
||||
BUCKETS_ADMIN_VIEW: "/buckets/:bucketName/admin*",
|
||||
BUCKETS_BROWSE_VIEW: "/buckets/:bucketName/browse*",
|
||||
TOOLS_WATCH: "/tools/watch",
|
||||
TOOLS_SPEEDTEST: "/tools/speedtest",
|
||||
USERS: "/users",
|
||||
USERS_VIEW: "/users/:userName+",
|
||||
GROUPS: "/groups",
|
||||
GROUPS_VIEW: "/groups/:groupName+",
|
||||
TOOLS_HEAL: "/tools/heal",
|
||||
TOOLS_TRACE: "/tools/trace",
|
||||
TOOLS_DIAGNOSTICS: "/tools/diagnostics",
|
||||
TOOLS_LOGS: "/tools/logs",
|
||||
TOOLS_AUDITLOGS: "/tools/audit-logs",
|
||||
TOOLS: "/tools",
|
||||
SETTINGS: "/settings",
|
||||
SETTINGS_VIEW: "/settings/:option",
|
||||
NOTIFICATIONS_ENDPOINTS_ADD: "/notification-endpoints/add",
|
||||
NOTIFICATIONS_ENDPOINTS_ADD_SERVICE: "/notification-endpoints/add/:service",
|
||||
NOTIFICATIONS_ENDPOINTS: "/notification-endpoints",
|
||||
TIERS: "/tiers",
|
||||
TIERS_ADD: "/tiers/add",
|
||||
TIERS_ADD_SERVICE: "/tiers/add/:service",
|
||||
ACCOUNT: "/account",
|
||||
TENANTS: "/tenants",
|
||||
TENANTS_ADD: "/tenants/add",
|
||||
STORAGE: "/storage",
|
||||
STORAGE_VOLUMES: "/storage/volumes",
|
||||
STORAGE_DRIVES: "/storage/drives",
|
||||
NAMESPACE_TENANT: "/namespaces/:tenantNamespace/tenants/:tenantName",
|
||||
NAMESPACE_TENANT_HOP: "/namespaces/:tenantNamespace/tenants/:tenantName/hop",
|
||||
NAMESPACE_TENANT_PODS:
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName",
|
||||
NAMESPACE_TENANT_PODS_LIST:
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/pods",
|
||||
NAMESPACE_TENANT_SUMMARY:
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/summary",
|
||||
NAMESPACE_TENANT_METRICS:
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/metrics",
|
||||
NAMESPACE_TENANT_POOLS:
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/pools",
|
||||
NAMESPACE_TENANT_VOLUMES:
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/volumes",
|
||||
NAMESPACE_TENANT_LICENSE:
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/license",
|
||||
NAMESPACE_TENANT_SECURITY:
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/security",
|
||||
LICENSE: "/license",
|
||||
DOCUMENTATION: "/documentation",
|
||||
};
|
||||
|
||||
// roles
|
||||
export const IAM_PERMISSIONS = {
|
||||
[IAM_ROLES.admin]: [
|
||||
[IAM_ROLES.BUCKET_OWNER]: [
|
||||
IAM_SCOPES.S3_PUT_OBJECT,
|
||||
IAM_SCOPES.S3_DELETE_OBJECT,
|
||||
],
|
||||
[IAM_ROLES.BUCKET_VIEWER]: [IAM_SCOPES.S3_LIST_BUCKET],
|
||||
[IAM_ROLES.BUCKET_ADMIN]: [
|
||||
IAM_SCOPES.S3_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.S3_REPLICATE_OBJECT,
|
||||
@@ -183,9 +255,111 @@ export const IAM_PERMISSIONS = {
|
||||
IAM_SCOPES.ADMIN_GET_POLICY,
|
||||
IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
|
||||
IAM_SCOPES.ADMIN_LIST_USERS,
|
||||
IAM_SCOPES.ADMIN_HEAL_ACTION,
|
||||
IAM_SCOPES.ADMIN_HEAL,
|
||||
],
|
||||
};
|
||||
|
||||
// application pages/routes and required scopes/roles
|
||||
export const IAM_PAGES_PERMISSIONS = {
|
||||
[IAM_PAGES.ADD_BUCKETS]: [
|
||||
IAM_SCOPES.S3_CREATE_BUCKET, // create bucket page
|
||||
],
|
||||
[IAM_PAGES.BUCKETS_ADMIN_VIEW]: [
|
||||
...IAM_PERMISSIONS[IAM_ROLES.BUCKET_ADMIN], // bucket admin page
|
||||
],
|
||||
[IAM_PAGES.BUCKETS_BROWSE_VIEW]: [
|
||||
...IAM_PERMISSIONS[IAM_ROLES.BUCKET_OWNER],
|
||||
...IAM_PERMISSIONS[IAM_ROLES.BUCKET_VIEWER],
|
||||
],
|
||||
[IAM_PAGES.GROUPS]: [
|
||||
IAM_SCOPES.ADMIN_LIST_GROUPS, // displays groups
|
||||
IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP, // displays create group button
|
||||
],
|
||||
[IAM_PAGES.GROUPS_VIEW]: [
|
||||
IAM_SCOPES.ADMIN_GET_GROUP,
|
||||
IAM_SCOPES.ADMIN_DISABLE_GROUP,
|
||||
IAM_SCOPES.ADMIN_ENABLE_GROUP,
|
||||
IAM_SCOPES.ADMIN_REMOVE_USER_FROM_GROUP,
|
||||
IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
|
||||
IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP, // display "edit members" button in groups detail page
|
||||
IAM_SCOPES.ADMIN_ATTACH_USER_OR_GROUP_POLICY, // display "set policy" button in groups details page
|
||||
],
|
||||
[IAM_PAGES.USERS]: [
|
||||
IAM_SCOPES.ADMIN_LIST_USERS, // displays users
|
||||
IAM_SCOPES.ADMIN_CREATE_USER, // displays create user button
|
||||
],
|
||||
[IAM_PAGES.USERS_VIEW]: [
|
||||
IAM_SCOPES.ADMIN_GET_USER, // displays list of users
|
||||
IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP, // displays "add to gorups" button in user details page
|
||||
IAM_SCOPES.ADMIN_ENABLE_USER,
|
||||
IAM_SCOPES.ADMIN_DISABLE_USER,
|
||||
IAM_SCOPES.ADMIN_DELETE_USER,
|
||||
],
|
||||
[IAM_PAGES.DASHBOARD]: [
|
||||
IAM_SCOPES.ADMIN_SERVER_INFO, // displays dashboard information
|
||||
],
|
||||
[IAM_PAGES.POLICIES_VIEW]: [
|
||||
IAM_SCOPES.ADMIN_DELETE_POLICY,
|
||||
IAM_SCOPES.ADMIN_LIST_GROUPS,
|
||||
IAM_SCOPES.ADMIN_GET_GROUP,
|
||||
IAM_SCOPES.ADMIN_GET_POLICY,
|
||||
IAM_SCOPES.ADMIN_CREATE_POLICY,
|
||||
],
|
||||
[IAM_PAGES.POLICIES]: [
|
||||
IAM_SCOPES.ADMIN_LIST_USER_POLICIES, // displays policies
|
||||
IAM_SCOPES.ADMIN_CREATE_POLICY, // displays create policy button
|
||||
],
|
||||
[IAM_PAGES.SETTINGS]: [
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE, // displays configuration list
|
||||
],
|
||||
[IAM_PAGES.SETTINGS_VIEW]: [
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE, // displays configuration list
|
||||
],
|
||||
[IAM_PAGES.NOTIFICATIONS_ENDPOINTS_ADD_SERVICE]: [
|
||||
IAM_SCOPES.ADMIN_SERVER_INFO,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.NOTIFICATIONS_ENDPOINTS_ADD]: [
|
||||
IAM_SCOPES.ADMIN_SERVER_INFO,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.NOTIFICATIONS_ENDPOINTS]: [
|
||||
IAM_SCOPES.ADMIN_SERVER_INFO, // displays notifications endpoints
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE, // displays create notification button
|
||||
],
|
||||
[IAM_PAGES.TIERS]: [
|
||||
IAM_SCOPES.ADMIN_LIST_TIERS, // display tiers list
|
||||
IAM_SCOPES.ADMIN_SET_TIER, // display "add tier" button
|
||||
],
|
||||
[IAM_PAGES.TIERS_ADD]: [
|
||||
IAM_SCOPES.ADMIN_SET_TIER, // display "add tier" button / shows add service tier page
|
||||
],
|
||||
[IAM_PAGES.TIERS_ADD_SERVICE]: [
|
||||
IAM_SCOPES.ADMIN_SET_TIER, // display "add tier" button / shows add service tier page
|
||||
],
|
||||
[IAM_PAGES.TOOLS]: [
|
||||
IAM_SCOPES.S3_LISTEN_NOTIFICATIONS, // displays watch notifications
|
||||
IAM_SCOPES.S3_LISTEN_BUCKET_NOTIFICATIONS, // display watch notifications
|
||||
IAM_SCOPES.ADMIN_GET_CONSOLE_LOG, // display minio console logs
|
||||
IAM_SCOPES.ADMIN_SERVER_TRACE, // display minio trace
|
||||
IAM_SCOPES.ADMIN_HEAL, // display heal
|
||||
IAM_SCOPES.ADMIN_HEALTH_INFO, // display diagnostics / display speedtest / display audit log
|
||||
IAM_SCOPES.ADMIN_SERVER_INFO, // display diagnostics
|
||||
],
|
||||
[IAM_PAGES.TOOLS_LOGS]: [IAM_SCOPES.ADMIN_GET_CONSOLE_LOG],
|
||||
[IAM_PAGES.TOOLS_AUDITLOGS]: [IAM_SCOPES.ADMIN_HEALTH_INFO],
|
||||
[IAM_PAGES.TOOLS_WATCH]: [
|
||||
IAM_SCOPES.S3_LISTEN_NOTIFICATIONS, // displays watch notifications
|
||||
IAM_SCOPES.S3_LISTEN_BUCKET_NOTIFICATIONS, // display watch notifications
|
||||
],
|
||||
[IAM_PAGES.TOOLS_TRACE]: [IAM_SCOPES.ADMIN_SERVER_TRACE],
|
||||
[IAM_PAGES.TOOLS_HEAL]: [IAM_SCOPES.ADMIN_HEAL],
|
||||
[IAM_PAGES.TOOLS_DIAGNOSTICS]: [
|
||||
IAM_SCOPES.ADMIN_HEALTH_INFO,
|
||||
IAM_SCOPES.ADMIN_SERVER_INFO,
|
||||
],
|
||||
[IAM_PAGES.TOOLS_SPEEDTEST]: [IAM_SCOPES.ADMIN_HEALTH_INFO],
|
||||
};
|
||||
|
||||
export const S3_ALL_RESOURCES = "arn:aws:s3:::*";
|
||||
export const CONSOLE_UI_RESOURCE = "console-ui";
|
||||
|
||||
@@ -41,6 +41,11 @@ import HelpBox from "../../../common/HelpBox";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
import SearchBox from "../Common/SearchBox";
|
||||
import withSuspense from "../Common/Components/withSuspense";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_SCOPES,
|
||||
} from "../../../common/SecureComponent/permissions";
|
||||
import SecureComponent from "../../../common/SecureComponent/SecureComponent";
|
||||
|
||||
const AddServiceAccount = withSuspense(
|
||||
React.lazy(() => import("./AddServiceAccount"))
|
||||
@@ -68,14 +73,9 @@ const styles = (theme: Theme) =>
|
||||
interface IServiceAccountsProps {
|
||||
classes: any;
|
||||
displayErrorMessage: typeof setErrorSnackMessage;
|
||||
changePassword: boolean;
|
||||
}
|
||||
|
||||
const Account = ({
|
||||
classes,
|
||||
displayErrorMessage,
|
||||
changePassword,
|
||||
}: IServiceAccountsProps) => {
|
||||
const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => {
|
||||
const [records, setRecords] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
@@ -193,21 +193,24 @@ const Account = ({
|
||||
<PageHeader
|
||||
label="Service Accounts"
|
||||
actions={
|
||||
<React.Fragment>
|
||||
{changePassword && (
|
||||
<Tooltip title="Change Password">
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Change Password"
|
||||
component="span"
|
||||
onClick={() => setChangePasswordModalOpen(true)}
|
||||
size="large"
|
||||
>
|
||||
<LockIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</React.Fragment>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_CREATE_USER]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
matchAll
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Tooltip title="Change Password">
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Change Password"
|
||||
component="span"
|
||||
onClick={() => setChangePasswordModalOpen(true)}
|
||||
size="large"
|
||||
>
|
||||
<LockIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</SecureComponent>
|
||||
}
|
||||
/>
|
||||
<PageLayout>
|
||||
|
||||
@@ -64,12 +64,9 @@ interface IAccessDetailsProps {
|
||||
}
|
||||
|
||||
const AccessDetails = ({
|
||||
classes,
|
||||
match,
|
||||
setErrorSnackMessage,
|
||||
session,
|
||||
loadingBucket,
|
||||
bucketInfo,
|
||||
}: IAccessDetailsProps) => {
|
||||
const [curTab, setCurTab] = useState<number>(0);
|
||||
const [loadingPolicies, setLoadingPolicies] = useState<boolean>(true);
|
||||
@@ -189,6 +186,7 @@ const AccessDetails = ({
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_LIST_USER_POLICIES]}
|
||||
resource={bucketName}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
noBackground={true}
|
||||
@@ -211,6 +209,7 @@ const AccessDetails = ({
|
||||
]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
noBackground={true}
|
||||
|
||||
@@ -222,6 +222,7 @@ const AccessRule = ({
|
||||
]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
||||
@@ -88,8 +88,9 @@ const BrowserHandler = ({
|
||||
}
|
||||
actions={
|
||||
<SecureComponent
|
||||
scopes={IAM_PERMISSIONS[IAM_ROLES.admin]}
|
||||
scopes={IAM_PERMISSIONS[IAM_ROLES.BUCKET_ADMIN]}
|
||||
resource={bucketName}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Tooltip title={"Configure Bucket"}>
|
||||
<IconButton
|
||||
|
||||
@@ -265,6 +265,7 @@ const BucketDetails = ({
|
||||
IAM_SCOPES.S3_FORCE_DELETE_BUCKET,
|
||||
]}
|
||||
resource={bucketName}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<BoxIconButton
|
||||
tooltip={"Delete"}
|
||||
|
||||
@@ -160,6 +160,7 @@ const BucketEventsPanel = ({
|
||||
]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
||||
@@ -211,6 +211,7 @@ const BucketLifecyclePanel = ({
|
||||
]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
||||
@@ -188,6 +188,7 @@ const BucketReplicationPanel = ({
|
||||
scopes={[IAM_SCOPES.S3_PUT_REPLICATION_CONFIGURATION]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
||||
@@ -666,6 +666,7 @@ const BucketSummary = ({
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_PUT_BUCKET_TAGGING]}
|
||||
resource={bucketName}
|
||||
errorProps={{ disabled: true, onClick: null }}
|
||||
>
|
||||
<Chip
|
||||
className={classes.tag}
|
||||
|
||||
@@ -238,7 +238,7 @@ const BucketListItem = ({
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={5} className={classes.bucketActionButtons}>
|
||||
<SecureComponent
|
||||
scopes={IAM_PERMISSIONS[IAM_ROLES.admin]}
|
||||
scopes={IAM_PERMISSIONS[IAM_ROLES.BUCKET_ADMIN]}
|
||||
resource={bucket.name}
|
||||
>
|
||||
<Link
|
||||
|
||||
@@ -242,6 +242,7 @@ const ListBuckets = ({
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_CREATE_BUCKET]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
||||
@@ -1012,7 +1012,6 @@ const ListObjects = ({
|
||||
onClosePreview={closePreviewWindow}
|
||||
/>
|
||||
)}
|
||||
|
||||
<PageLayout>
|
||||
<Grid item xs={12}>
|
||||
<ScreenTitle
|
||||
@@ -1038,6 +1037,7 @@ const ListObjects = ({
|
||||
<SecureComponent
|
||||
resource={bucketName}
|
||||
scopes={[IAM_SCOPES.S3_PUT_OBJECT]}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<BoxIconButton
|
||||
tooltip={"Choose or create a new path"}
|
||||
@@ -1051,6 +1051,12 @@ const ListObjects = ({
|
||||
>
|
||||
<AddFolderIcon />
|
||||
</BoxIconButton>
|
||||
</SecureComponent>
|
||||
<SecureComponent
|
||||
resource={bucketName}
|
||||
scopes={[IAM_SCOPES.S3_PUT_OBJECT]}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<BoxIconButton
|
||||
tooltip={"Upload file"}
|
||||
color="primary"
|
||||
@@ -1065,14 +1071,20 @@ const ListObjects = ({
|
||||
>
|
||||
<UploadIcon />
|
||||
</BoxIconButton>
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
onChange={(e) => uploadObject(e)}
|
||||
id="file-input"
|
||||
style={{ display: "none" }}
|
||||
ref={fileUpload}
|
||||
/>
|
||||
</SecureComponent>
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
onChange={(e) => uploadObject(e)}
|
||||
id="file-input"
|
||||
style={{ display: "none" }}
|
||||
ref={fileUpload}
|
||||
/>
|
||||
<SecureComponent
|
||||
resource={bucketName}
|
||||
scopes={[IAM_SCOPES.S3_PUT_OBJECT]}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<BoxIconButton
|
||||
tooltip={"Upload folder"}
|
||||
color="primary"
|
||||
@@ -1087,48 +1099,60 @@ const ListObjects = ({
|
||||
>
|
||||
<UploadFolderIcon />
|
||||
</BoxIconButton>
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
onChange={(e) => uploadObject(e)}
|
||||
id="file-input"
|
||||
style={{ display: "none" }}
|
||||
ref={folderUpload}
|
||||
/>
|
||||
</SecureComponent>
|
||||
<Badge
|
||||
badgeContent=" "
|
||||
color="secondary"
|
||||
variant="dot"
|
||||
invisible={!rewindEnabled}
|
||||
className={classes.badgeOverlap}
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
onChange={(e) => uploadObject(e)}
|
||||
id="file-input"
|
||||
style={{ display: "none" }}
|
||||
ref={folderUpload}
|
||||
/>
|
||||
<SecureComponent
|
||||
resource={bucketName}
|
||||
scopes={[IAM_SCOPES.S3_PUT_OBJECT]}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Badge
|
||||
badgeContent=" "
|
||||
color="secondary"
|
||||
variant="dot"
|
||||
invisible={!rewindEnabled}
|
||||
className={classes.badgeOverlap}
|
||||
>
|
||||
<BoxIconButton
|
||||
tooltip={"Rewind"}
|
||||
color="primary"
|
||||
aria-label="Rewind"
|
||||
onClick={() => {
|
||||
setRewindSelect(true);
|
||||
}}
|
||||
disabled={!isVersioned}
|
||||
size="large"
|
||||
>
|
||||
<HistoryIcon />
|
||||
</BoxIconButton>
|
||||
</Badge>
|
||||
</SecureComponent>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_LIST_BUCKET]}
|
||||
resource={bucketName}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<BoxIconButton
|
||||
tooltip={"Rewind"}
|
||||
tooltip={"Refresh list"}
|
||||
color="primary"
|
||||
aria-label="Rewind"
|
||||
aria-label="Refresh List"
|
||||
onClick={() => {
|
||||
setRewindSelect(true);
|
||||
setLoading(true);
|
||||
}}
|
||||
disabled={!isVersioned}
|
||||
disabled={rewindEnabled}
|
||||
size="large"
|
||||
variant={"contained"}
|
||||
>
|
||||
<HistoryIcon />
|
||||
<RefreshIcon />
|
||||
</BoxIconButton>
|
||||
</Badge>
|
||||
<BoxIconButton
|
||||
tooltip={"Refresh list"}
|
||||
color="primary"
|
||||
aria-label="Refresh List"
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
}}
|
||||
disabled={rewindEnabled}
|
||||
size="large"
|
||||
variant={"contained"}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</BoxIconButton>
|
||||
</SecureComponent>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
@@ -1137,6 +1161,7 @@ const ListObjects = ({
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_LIST_BUCKET]}
|
||||
resource={bucketName}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<SearchBox
|
||||
onChange={setFilterObjects}
|
||||
@@ -1157,6 +1182,7 @@ const ListObjects = ({
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_DELETE_OBJECT]}
|
||||
resource={bucketName}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
||||
@@ -589,6 +589,7 @@ const ObjectDetails = ({
|
||||
scopes={[IAM_SCOPES.S3_DELETE_OBJECT]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<BoxIconButton
|
||||
tooltip={"Delete Object"}
|
||||
@@ -640,6 +641,10 @@ const ObjectDetails = ({
|
||||
]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
errorProps={{
|
||||
disabled: true,
|
||||
onClick: null,
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
color="primary"
|
||||
@@ -737,6 +742,7 @@ const ObjectDetails = ({
|
||||
scopes={[IAM_SCOPES.S3_PUT_OBJECT_TAGGING]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
errorProps={{ disabled: true, onClick: null }}
|
||||
>
|
||||
<Chip
|
||||
className={classes.tag}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment } from "react";
|
||||
import { Grid } from "@mui/material";
|
||||
import HelpBox from "../../../../common/HelpBox";
|
||||
|
||||
interface IMissingIntegration {
|
||||
iconComponent: any;
|
||||
entity: string;
|
||||
documentationLink: string;
|
||||
}
|
||||
|
||||
const MissingIntegration = ({
|
||||
iconComponent,
|
||||
entity,
|
||||
documentationLink,
|
||||
}: IMissingIntegration) => {
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
justifyContent={"center"}
|
||||
alignContent={"center"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Grid item xs={8}>
|
||||
<HelpBox
|
||||
title={`${entity} not available`}
|
||||
iconComponent={iconComponent}
|
||||
help={
|
||||
<Fragment>
|
||||
This feature is not available.
|
||||
<br />
|
||||
Please configure{" "}
|
||||
<a href={documentationLink} target="_blank" rel="noreferrer">
|
||||
{entity}
|
||||
</a>{" "}
|
||||
first to use this feature.
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default MissingIntegration;
|
||||
@@ -47,6 +47,13 @@ import PageLayout from "../../Common/Layout/PageLayout";
|
||||
import SearchBox from "../../Common/SearchBox";
|
||||
|
||||
import withSuspense from "../../Common/Components/withSuspense";
|
||||
import { AppState } from "../../../../store";
|
||||
import DistributedOnly from "../../Common/DistributedOnly/DistributedOnly";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_SCOPES,
|
||||
} from "../../../../common/SecureComponent/permissions";
|
||||
import SecureComponent from "../../../../common/SecureComponent/SecureComponent";
|
||||
|
||||
const UpdateTierCredentialsModal = withSuspense(
|
||||
React.lazy(() => import("./UpdateTierCredentialsModal"))
|
||||
@@ -56,6 +63,7 @@ interface IListTiersConfig {
|
||||
classes: any;
|
||||
history: any;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
distributedSetup: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -90,6 +98,7 @@ const ListTiersConfiguration = ({
|
||||
classes,
|
||||
history,
|
||||
setErrorSnackMessage,
|
||||
distributedSetup,
|
||||
}: IListTiersConfig) => {
|
||||
const [records, setRecords] = useState<ITierElement[]>([]);
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
@@ -102,21 +111,25 @@ const ListTiersConfiguration = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
const fetchRecords = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/admin/tiers`)
|
||||
.then((res: ITierResponse) => {
|
||||
setRecords(res.items || []);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setErrorSnackMessage(err);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
fetchRecords();
|
||||
if (distributedSetup) {
|
||||
const fetchRecords = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/admin/tiers`)
|
||||
.then((res: ITierResponse) => {
|
||||
setRecords(res.items || []);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setErrorSnackMessage(err);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
fetchRecords();
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
}, [isLoading, setErrorSnackMessage]);
|
||||
}, [isLoading, setErrorSnackMessage, distributedSetup]);
|
||||
|
||||
const filteredRecords = records.filter((b: ITierElement) => {
|
||||
if (filter === "") {
|
||||
@@ -197,150 +210,169 @@ const ListTiersConfiguration = ({
|
||||
)}
|
||||
<PageHeader label="Tiers" />
|
||||
<PageLayout>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<SearchBox
|
||||
placeholder="Filter"
|
||||
onChange={setFilter}
|
||||
overrideClass={classes.searchField}
|
||||
/>
|
||||
|
||||
<div className={classes.rightActionButtons}>
|
||||
<BoxIconButton
|
||||
color="primary"
|
||||
aria-label="Refresh List"
|
||||
onClick={() => {
|
||||
setIsLoading(true);
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
<RefreshIcon />
|
||||
</BoxIconButton>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
endIcon={<AddIcon />}
|
||||
onClick={addTier}
|
||||
>
|
||||
Add Tier
|
||||
</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
{isLoading && <LinearProgress />}
|
||||
{!isLoading && (
|
||||
{!distributedSetup ? (
|
||||
<DistributedOnly entity={"Tiers"} iconComponent={<TiersIcon />} />
|
||||
) : (
|
||||
<Fragment>
|
||||
{records.length > 0 && (
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<SearchBox
|
||||
placeholder="Filter"
|
||||
onChange={setFilter}
|
||||
overrideClass={classes.searchField}
|
||||
/>
|
||||
<div className={classes.rightActionButtons}>
|
||||
<BoxIconButton
|
||||
color="primary"
|
||||
aria-label="Refresh List"
|
||||
onClick={() => {
|
||||
setIsLoading(true);
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
<RefreshIcon />
|
||||
</BoxIconButton>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_SET_TIER]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
endIcon={<AddIcon />}
|
||||
onClick={addTier}
|
||||
>
|
||||
Add Tier
|
||||
</Button>
|
||||
</SecureComponent>
|
||||
</div>
|
||||
</Grid>
|
||||
{isLoading && <LinearProgress />}
|
||||
{!isLoading && (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
<TableWrapper
|
||||
itemActions={[
|
||||
{
|
||||
type: "edit",
|
||||
onClick: (tierData: ITierElement) => {
|
||||
setSelectedTier(tierData);
|
||||
setUpdateCredentialsOpen(true);
|
||||
},
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
label: "Tier Name",
|
||||
elementKey: "type",
|
||||
renderFunction: renderTierName,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Type",
|
||||
elementKey: "type",
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
label: "Endpoint",
|
||||
elementKey: "type",
|
||||
renderFunction: renderTierEndpoint,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Bucket",
|
||||
elementKey: "type",
|
||||
renderFunction: renderTierBucket,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Prefix",
|
||||
elementKey: "type",
|
||||
renderFunction: renderTierPrefix,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Region",
|
||||
elementKey: "type",
|
||||
renderFunction: renderTierRegion,
|
||||
renderFullObject: true,
|
||||
},
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
records={filteredRecords}
|
||||
entityName="Tiers"
|
||||
idField="service_name"
|
||||
customPaperHeight={classes.customConfigurationPage}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<HelpBox
|
||||
title={"Learn more about TIERS"}
|
||||
iconComponent={<TiersIcon />}
|
||||
help={
|
||||
<Fragment>
|
||||
Tiers are used by the MinIO Object Lifecycle Management
|
||||
which allows creating rules for time or date based
|
||||
automatic transition or expiry of objects. For object
|
||||
transition, MinIO automatically moves the object to a
|
||||
configured remote storage tier.
|
||||
<br />
|
||||
<br />
|
||||
You can learn more at our{" "}
|
||||
<a
|
||||
href="https://docs.min.io/minio/baremetal/lifecycle-management/lifecycle-management-overview.html?ref=con"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
documentation
|
||||
</a>
|
||||
.
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
{records.length > 0 && (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_LIST_TIERS]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
itemActions={[
|
||||
{
|
||||
type: "edit",
|
||||
onClick: (tierData: ITierElement) => {
|
||||
setSelectedTier(tierData);
|
||||
setUpdateCredentialsOpen(true);
|
||||
},
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
label: "Tier Name",
|
||||
elementKey: "type",
|
||||
renderFunction: renderTierName,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Type",
|
||||
elementKey: "type",
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
label: "Endpoint",
|
||||
elementKey: "type",
|
||||
renderFunction: renderTierEndpoint,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Bucket",
|
||||
elementKey: "type",
|
||||
renderFunction: renderTierBucket,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Prefix",
|
||||
elementKey: "type",
|
||||
renderFunction: renderTierPrefix,
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: "Region",
|
||||
elementKey: "type",
|
||||
renderFunction: renderTierRegion,
|
||||
renderFullObject: true,
|
||||
},
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
records={filteredRecords}
|
||||
entityName="Tiers"
|
||||
idField="service_name"
|
||||
customPaperHeight={classes.customConfigurationPage}
|
||||
/>
|
||||
</SecureComponent>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<HelpBox
|
||||
title={"Learn more about TIERS"}
|
||||
iconComponent={<TiersIcon />}
|
||||
help={
|
||||
<Fragment>
|
||||
Tiers are used by the MinIO Object Lifecycle
|
||||
Management which allows creating rules for time or
|
||||
date based automatic transition or expiry of
|
||||
objects. For object transition, MinIO automatically
|
||||
moves the object to a configured remote storage
|
||||
tier.
|
||||
<br />
|
||||
<br />
|
||||
You can learn more at our{" "}
|
||||
<a
|
||||
href="https://docs.min.io/minio/baremetal/lifecycle-management/lifecycle-management-overview.html?ref=con"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
documentation
|
||||
</a>
|
||||
.
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
{records.length === 0 && (
|
||||
<Grid
|
||||
container
|
||||
justifyContent={"center"}
|
||||
alignContent={"center"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Grid item xs={8}>
|
||||
<HelpBox
|
||||
title={"Tiers"}
|
||||
iconComponent={<TiersIcon />}
|
||||
help={
|
||||
<Fragment>
|
||||
Tiers are used by the MinIO Object Lifecycle
|
||||
Management which allows creating rules for time or
|
||||
date based automatic transition or expiry of
|
||||
objects. For object transition, MinIO automatically
|
||||
moves the object to a configured remote storage
|
||||
tier.
|
||||
<br />
|
||||
<br />
|
||||
To get started,{" "}
|
||||
<AButton onClick={addTier}>Add A Tier</AButton>.
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
{records.length === 0 && (
|
||||
<Grid
|
||||
container
|
||||
justifyContent={"center"}
|
||||
alignContent={"center"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Grid item xs={8}>
|
||||
<HelpBox
|
||||
title={"Tiers"}
|
||||
iconComponent={<TiersIcon />}
|
||||
help={
|
||||
<Fragment>
|
||||
Tiers are used by the MinIO Object Lifecycle Management
|
||||
which allows creating rules for time or date based
|
||||
automatic transition or expiry of objects. For object
|
||||
transition, MinIO automatically moves the object to a
|
||||
configured remote storage tier.
|
||||
<br />
|
||||
<br />
|
||||
To get started,{" "}
|
||||
<AButton onClick={addTier}>Add A Tier</AButton>.
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</PageLayout>
|
||||
@@ -348,10 +380,14 @@ const ListTiersConfiguration = ({
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
distributedSetup: state.system.distributedSetup,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setErrorSnackMessage,
|
||||
};
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
const connector = connect(mapState, mapDispatchToProps);
|
||||
|
||||
export default withStyles(styles)(connector(ListTiersConfiguration));
|
||||
|
||||
@@ -58,4 +58,5 @@ export interface IElement {
|
||||
configuration_id: string;
|
||||
configuration_label: string;
|
||||
icon?: any;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,15 @@ import Menu from "./Menu/Menu";
|
||||
import api from "../../common/api";
|
||||
|
||||
import MainError from "./Common/MainError/MainError";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES,
|
||||
IAM_PAGES_PERMISSIONS,
|
||||
IAM_SCOPES,
|
||||
S3_ALL_RESOURCES,
|
||||
} from "../../common/SecureComponent/permissions";
|
||||
import { hasPermission } from "../../common/SecureComponent/SecureComponent";
|
||||
import { IRouteRule } from "./Menu/types";
|
||||
|
||||
const Trace = React.lazy(() => import("./Trace/Trace"));
|
||||
const Heal = React.lazy(() => import("./Heal/Heal"));
|
||||
@@ -149,6 +158,9 @@ interface IConsoleProps {
|
||||
loadingProgress: number;
|
||||
snackBarMessage: snackBarMessage;
|
||||
setSnackBarMessage: typeof setSnackBarMessage;
|
||||
operatorMode: boolean;
|
||||
distributedSetup: boolean;
|
||||
features: string[] | null;
|
||||
}
|
||||
|
||||
const Console = ({
|
||||
@@ -162,9 +174,13 @@ const Console = ({
|
||||
loadingProgress,
|
||||
snackBarMessage,
|
||||
setSnackBarMessage,
|
||||
operatorMode,
|
||||
distributedSetup,
|
||||
features,
|
||||
}: IConsoleProps) => {
|
||||
const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
|
||||
|
||||
const ldapIsEnabled = (features && features.includes("ldap-idp")) || false;
|
||||
const restartServer = () => {
|
||||
serverIsLoading(true);
|
||||
api
|
||||
@@ -185,199 +201,259 @@ const Console = ({
|
||||
});
|
||||
};
|
||||
|
||||
const allowedPages = !session
|
||||
? []
|
||||
: session.pages.reduce((result: any, item: any, index: any) => {
|
||||
if (item.startsWith("/tools")) {
|
||||
result["/tools"] = true;
|
||||
}
|
||||
result[item] = true;
|
||||
return result;
|
||||
}, {});
|
||||
const routes = [
|
||||
const consoleAdminRoutes: IRouteRule[] = [
|
||||
{
|
||||
component: Dashboard,
|
||||
path: "/dashboard",
|
||||
path: IAM_PAGES.DASHBOARD,
|
||||
},
|
||||
{
|
||||
component: Metrics,
|
||||
path: "/metrics",
|
||||
path: IAM_PAGES.METRICS,
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/add-bucket",
|
||||
path: IAM_PAGES.ADD_BUCKETS,
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets",
|
||||
path: IAM_PAGES.BUCKETS,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets/*",
|
||||
},
|
||||
{
|
||||
component: Watch,
|
||||
path: "/tools/watch",
|
||||
},
|
||||
{
|
||||
component: Speedtest,
|
||||
path: "/tools/speedtest",
|
||||
},
|
||||
{
|
||||
component: Users,
|
||||
path: "/users/:userName+",
|
||||
},
|
||||
{
|
||||
component: Users,
|
||||
path: "/users",
|
||||
},
|
||||
{
|
||||
component: Groups,
|
||||
path: "/groups",
|
||||
},
|
||||
{
|
||||
component: GroupsDetails,
|
||||
path: "/groups/:groupName+",
|
||||
},
|
||||
{
|
||||
component: Policies,
|
||||
path: "/policies/*",
|
||||
},
|
||||
{
|
||||
component: Policies,
|
||||
path: "/policies",
|
||||
},
|
||||
{
|
||||
component: Heal,
|
||||
path: "/tools/heal",
|
||||
},
|
||||
{
|
||||
component: Trace,
|
||||
path: "/tools/trace",
|
||||
},
|
||||
{
|
||||
component: HealthInfo,
|
||||
path: "/tools/diagnostics",
|
||||
},
|
||||
{
|
||||
component: ErrorLogs,
|
||||
path: "/tools/logs",
|
||||
},
|
||||
{
|
||||
component: LogsSearchMain,
|
||||
path: "/tools/audit-logs",
|
||||
},
|
||||
{
|
||||
component: Tools,
|
||||
path: "/tools",
|
||||
},
|
||||
{
|
||||
component: ConfigurationOptions,
|
||||
path: "/settings",
|
||||
},
|
||||
{
|
||||
component: ConfigurationOptions,
|
||||
path: "/settings/:option",
|
||||
},
|
||||
{
|
||||
component: AddNotificationEndpoint,
|
||||
path: "/notification-endpoints/add/:service",
|
||||
},
|
||||
{
|
||||
component: NotificationTypeSelector,
|
||||
path: "/notification-endpoints/add",
|
||||
},
|
||||
{
|
||||
component: NotificationEndpoints,
|
||||
path: "/notification-endpoints",
|
||||
},
|
||||
{
|
||||
component: AddTierConfiguration,
|
||||
path: "/tiers/add/:service",
|
||||
},
|
||||
{
|
||||
component: TierTypeSelector,
|
||||
path: "/tiers/add",
|
||||
},
|
||||
{
|
||||
component: ListTiersConfiguration,
|
||||
path: "/tiers",
|
||||
},
|
||||
{
|
||||
component: Account,
|
||||
path: "/account",
|
||||
props: {
|
||||
changePassword: (!session ? [] : session.pages).includes(
|
||||
"/account/change-password"
|
||||
),
|
||||
path: IAM_PAGES.BUCKETS_ADMIN_VIEW,
|
||||
customPermissionFnc: () => {
|
||||
const path = window.location.pathname;
|
||||
const resource = path.match(/buckets\/(.*)\/admin*/);
|
||||
return (
|
||||
resource &&
|
||||
resource.length > 0 &&
|
||||
hasPermission(
|
||||
resource[1],
|
||||
IAM_PAGES_PERMISSIONS[IAM_PAGES.BUCKETS_ADMIN_VIEW]
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
component: ListTenants,
|
||||
path: "/tenants",
|
||||
component: Buckets,
|
||||
path: IAM_PAGES.BUCKETS_BROWSE_VIEW,
|
||||
customPermissionFnc: () => {
|
||||
const path = window.location.pathname;
|
||||
const resource = path.match(/buckets\/(.*)\/browse*/);
|
||||
return (
|
||||
resource &&
|
||||
resource.length > 0 &&
|
||||
hasPermission(
|
||||
resource[1],
|
||||
IAM_PAGES_PERMISSIONS[IAM_PAGES.BUCKETS_BROWSE_VIEW]
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
component: AddTenant,
|
||||
path: "/tenants/add",
|
||||
component: Watch,
|
||||
path: IAM_PAGES.TOOLS_WATCH,
|
||||
},
|
||||
{
|
||||
component: Storage,
|
||||
path: "/storage",
|
||||
component: Speedtest,
|
||||
path: IAM_PAGES.TOOLS_SPEEDTEST,
|
||||
},
|
||||
{
|
||||
component: Storage,
|
||||
path: "/storage/volumes",
|
||||
component: Users,
|
||||
path: IAM_PAGES.USERS_VIEW,
|
||||
},
|
||||
{
|
||||
component: Storage,
|
||||
path: "/storage/drives",
|
||||
component: Users,
|
||||
path: IAM_PAGES.USERS,
|
||||
fsHidden: ldapIsEnabled,
|
||||
customPermissionFnc: () =>
|
||||
hasPermission(CONSOLE_UI_RESOURCE, [IAM_SCOPES.ADMIN_LIST_USERS]) ||
|
||||
hasPermission(S3_ALL_RESOURCES, [IAM_SCOPES.ADMIN_CREATE_USER]),
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName",
|
||||
component: Groups,
|
||||
path: IAM_PAGES.GROUPS,
|
||||
fsHidden: ldapIsEnabled,
|
||||
},
|
||||
{
|
||||
component: Hop,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName/hop",
|
||||
component: GroupsDetails,
|
||||
path: IAM_PAGES.GROUPS_VIEW,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName",
|
||||
component: Policies,
|
||||
path: IAM_PAGES.POLICIES_VIEW,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName/summary",
|
||||
component: Policies,
|
||||
path: IAM_PAGES.POLICIES,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName/metrics",
|
||||
component: Heal,
|
||||
path: IAM_PAGES.TOOLS_HEAL,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods",
|
||||
component: Trace,
|
||||
path: IAM_PAGES.TOOLS_TRACE,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName/pools",
|
||||
component: HealthInfo,
|
||||
path: IAM_PAGES.TOOLS_DIAGNOSTICS,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName/volumes",
|
||||
component: ErrorLogs,
|
||||
path: IAM_PAGES.TOOLS_LOGS,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName/license",
|
||||
component: LogsSearchMain,
|
||||
path: IAM_PAGES.TOOLS_AUDITLOGS,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName/security",
|
||||
component: Tools,
|
||||
path: IAM_PAGES.TOOLS,
|
||||
},
|
||||
{
|
||||
component: ConfigurationOptions,
|
||||
path: IAM_PAGES.SETTINGS,
|
||||
},
|
||||
{
|
||||
component: ConfigurationOptions,
|
||||
path: IAM_PAGES.SETTINGS_VIEW,
|
||||
},
|
||||
{
|
||||
component: AddNotificationEndpoint,
|
||||
path: IAM_PAGES.NOTIFICATIONS_ENDPOINTS_ADD_SERVICE,
|
||||
},
|
||||
{
|
||||
component: NotificationTypeSelector,
|
||||
path: IAM_PAGES.NOTIFICATIONS_ENDPOINTS_ADD,
|
||||
},
|
||||
{
|
||||
component: NotificationEndpoints,
|
||||
path: IAM_PAGES.NOTIFICATIONS_ENDPOINTS,
|
||||
},
|
||||
{
|
||||
component: AddTierConfiguration,
|
||||
path: IAM_PAGES.TIERS_ADD_SERVICE,
|
||||
fsHidden: !distributedSetup,
|
||||
},
|
||||
{
|
||||
component: TierTypeSelector,
|
||||
path: IAM_PAGES.TIERS_ADD,
|
||||
fsHidden: !distributedSetup,
|
||||
},
|
||||
{
|
||||
component: ListTiersConfiguration,
|
||||
path: IAM_PAGES.TIERS,
|
||||
},
|
||||
{
|
||||
component: Account,
|
||||
path: IAM_PAGES.ACCOUNT,
|
||||
forceDisplay: true, // user has implicit access to service-accounts
|
||||
},
|
||||
{
|
||||
component: License,
|
||||
path: "/license",
|
||||
path: IAM_PAGES.LICENSE,
|
||||
forceDisplay: true,
|
||||
},
|
||||
];
|
||||
const allowedRoutes = routes.filter((route: any) => allowedPages[route.path]);
|
||||
|
||||
const operatorConsoleRoutes: IRouteRule[] = [
|
||||
{
|
||||
component: ListTenants,
|
||||
path: IAM_PAGES.TENANTS,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: AddTenant,
|
||||
path: IAM_PAGES.TENANTS_ADD,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: Storage,
|
||||
path: IAM_PAGES.STORAGE,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: Storage,
|
||||
path: IAM_PAGES.STORAGE_VOLUMES,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: Storage,
|
||||
path: IAM_PAGES.STORAGE_DRIVES,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: Hop,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_HOP,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_PODS,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_SUMMARY,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_METRICS,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_PODS_LIST,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_POOLS,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_VOLUMES,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_LICENSE,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_SECURITY,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: License,
|
||||
path: IAM_PAGES.LICENSE,
|
||||
forceDisplay: true,
|
||||
},
|
||||
];
|
||||
|
||||
const allowedRoutes = (
|
||||
operatorMode ? operatorConsoleRoutes : consoleAdminRoutes
|
||||
).filter(
|
||||
(route: any) =>
|
||||
(route.forceDisplay ||
|
||||
(route.customPermissionFnc
|
||||
? route.customPermissionFnc()
|
||||
: hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[route.path]
|
||||
))) &&
|
||||
!route.fsHidden
|
||||
);
|
||||
|
||||
const closeSnackBar = () => {
|
||||
setOpenSnackbar(false);
|
||||
@@ -409,7 +485,7 @@ const Console = ({
|
||||
{session && session.status === "ok" ? (
|
||||
<div className={classes.root}>
|
||||
<CssBaseline />
|
||||
{!hideMenu && <Menu pages={session.pages} />}
|
||||
{!hideMenu && <Menu />}
|
||||
|
||||
<main className={classes.content}>
|
||||
{needsRestart && (
|
||||
@@ -505,6 +581,9 @@ const mapState = (state: AppState) => ({
|
||||
session: state.console.session,
|
||||
loadingProgress: state.system.loadingProgress,
|
||||
snackBarMessage: state.system.snackBar,
|
||||
operatorMode: state.system.operatorMode,
|
||||
distributedSetup: state.system.distributedSetup,
|
||||
features: state.console.session.features,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
|
||||
@@ -219,6 +219,7 @@ const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => {
|
||||
IAM_SCOPES.ADMIN_LIST_USERS,
|
||||
]}
|
||||
matchAll
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
||||
@@ -17,7 +17,7 @@ import { connect } from "react-redux";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Button, Grid, Tooltip } from "@mui/material";
|
||||
import ScreenTitle from "../Common/ScreenTitle/ScreenTitle";
|
||||
import { IAMPoliciesIcon, TrashIcon, UsersIcon } from "../../../icons";
|
||||
import { IAMPoliciesIcon, TrashIcon, GroupsIcon } from "../../../icons";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import history from "../../../history";
|
||||
import api from "../../../common/api";
|
||||
@@ -194,7 +194,7 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
endIcon={<UsersIcon />}
|
||||
endIcon={<GroupsIcon />}
|
||||
size="medium"
|
||||
onClick={() => {
|
||||
setUsersOpen(true);
|
||||
@@ -212,6 +212,14 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
itemActions={[
|
||||
{
|
||||
type: "view",
|
||||
onClick: (userName) => {
|
||||
history.push(`/users/${userName}`);
|
||||
},
|
||||
},
|
||||
]}
|
||||
columns={[{ label: "Access Key", elementKey: "" }]}
|
||||
selectedItems={[]}
|
||||
isLoading={false}
|
||||
@@ -269,7 +277,7 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
|
||||
<ScreenTitle
|
||||
icon={
|
||||
<Fragment>
|
||||
<UsersIcon width={40} />
|
||||
<GroupsIcon width={40} />
|
||||
</Fragment>
|
||||
}
|
||||
title={groupName}
|
||||
@@ -305,6 +313,7 @@ const GroupsDetails = ({ classes }: IGroupDetailsProps) => {
|
||||
<SecureComponent
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
scopes={[IAM_SCOPES.ADMIN_REMOVE_USER_FROM_GROUP]}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Tooltip title="Delete Group">
|
||||
<div className={classes.spacerLeft}>
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import React, { useEffect, useState, Fragment } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { HorizontalBar } from "react-chartjs-2";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
@@ -264,9 +263,8 @@ const Heal = ({ classes, distributedSetup }: IHeal) => {
|
||||
<DistributedOnly entity={"Heal"} iconComponent={<HealIcon />} />
|
||||
) : (
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_HEAL_ACTION]}
|
||||
scopes={[IAM_SCOPES.ADMIN_HEAL]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
RenderError={<Redirect to={"/"} />}
|
||||
>
|
||||
<Grid xs={12} item className={classes.formBox}>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
|
||||
@@ -42,6 +42,13 @@ import PageHeader from "../../Common/PageHeader/PageHeader";
|
||||
import BackLink from "../../../../common/BackLink";
|
||||
import PageLayout from "../../Common/Layout/PageLayout";
|
||||
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_SCOPES,
|
||||
} from "../../../../common/SecureComponent/permissions";
|
||||
import SecureComponent from "../../../../common/SecureComponent/SecureComponent";
|
||||
import { SearchIcon } from "../../../../icons";
|
||||
import MissingIntegration from "../../Common/MissingIntegration/MissingIntegration";
|
||||
|
||||
interface ILogSearchProps {
|
||||
classes: any;
|
||||
@@ -199,6 +206,9 @@ const LogsSearchMain = ({
|
||||
setAlreadyFetching(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
setAlreadyFetching(false);
|
||||
}
|
||||
}, [
|
||||
alreadyFetching,
|
||||
@@ -279,192 +289,221 @@ const LogsSearchMain = ({
|
||||
<PageHeader label="Audit Logs" />
|
||||
<BackLink to="/tools" label="Return to Tools" />
|
||||
<PageLayout>
|
||||
<Grid xs={12} className={classes.formBox}>
|
||||
<Grid item xs={12} className={`${classes.searchOptions}`}>
|
||||
<div className={classes.dateRangePicker}>
|
||||
<DateRangeSelector
|
||||
setTimeEnd={setTimeEnd}
|
||||
setTimeStart={setTimeStart}
|
||||
timeEnd={timeEnd}
|
||||
timeStart={timeStart}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Grid item className={classes.advancedButton}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setFilterOpen(!filterOpen);
|
||||
}}
|
||||
className={classes.advancedConfiguration}
|
||||
>
|
||||
{filterOpen ? "Hide" : "Show"} advanced Filters{" "}
|
||||
<span
|
||||
className={
|
||||
filterOpen ? classes.advancedOpen : classes.advancedClosed
|
||||
}
|
||||
>
|
||||
<ArrowForwardIosIcon />
|
||||
</span>
|
||||
</button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={`${classes.blockCollapsed} ${
|
||||
filterOpen ? classes.filterOpen : ""
|
||||
}`}
|
||||
>
|
||||
<div className={classes.innerContainer}>
|
||||
<div className={classes.noticeLabel}>
|
||||
Enable your preferred options to get filtered records.
|
||||
<br />
|
||||
You can use '*' to match any character, '.' to signify a single
|
||||
character or '\' to scape an special character (E.g. mybucket-*)
|
||||
</div>
|
||||
<div className={classes.filtersContainer}>
|
||||
<FilterInputWrapper
|
||||
onChange={setBucket}
|
||||
value={bucket}
|
||||
label={"Bucket"}
|
||||
id="bucket"
|
||||
name="bucket"
|
||||
/>
|
||||
<FilterInputWrapper
|
||||
onChange={setApiName}
|
||||
value={apiName}
|
||||
label={"API Name"}
|
||||
id="api_name"
|
||||
name="api_name"
|
||||
/>
|
||||
<FilterInputWrapper
|
||||
onChange={setUserAgent}
|
||||
value={userAgent}
|
||||
label={"User Agent"}
|
||||
id="user_agent"
|
||||
name="user_agent"
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.filtersContainer}>
|
||||
<FilterInputWrapper
|
||||
onChange={setObject}
|
||||
value={object}
|
||||
label={"Object"}
|
||||
id="object"
|
||||
name="object"
|
||||
/>
|
||||
<FilterInputWrapper
|
||||
onChange={setRequestID}
|
||||
value={requestID}
|
||||
label={"Request ID"}
|
||||
id="request_id"
|
||||
name="request_id"
|
||||
/>
|
||||
<FilterInputWrapper
|
||||
onChange={setResponseStatus}
|
||||
value={responseStatus}
|
||||
label={"Response Status"}
|
||||
id="response_status"
|
||||
name="response_status"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.endLineAction}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={triggerLoad}
|
||||
>
|
||||
Get Information
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
<TableWrapper
|
||||
columns={[
|
||||
{
|
||||
label: LogSearchColumnLabels.time,
|
||||
elementKey: "time",
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.api_name,
|
||||
elementKey: "api_name",
|
||||
},
|
||||
{ label: LogSearchColumnLabels.bucket, elementKey: "bucket" },
|
||||
{ label: LogSearchColumnLabels.object, elementKey: "object" },
|
||||
{
|
||||
label: LogSearchColumnLabels.remote_host,
|
||||
elementKey: "remote_host",
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.request_id,
|
||||
elementKey: "request_id",
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.user_agent,
|
||||
elementKey: "user_agent",
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.response_status,
|
||||
elementKey: "response_status",
|
||||
renderFunction: (element) => (
|
||||
<Fragment>
|
||||
<span>
|
||||
{element.response_status_code} ({element.response_status})
|
||||
</span>
|
||||
</Fragment>
|
||||
),
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.request_content_length,
|
||||
elementKey: "request_content_length",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.response_content_length,
|
||||
elementKey: "response_content_length",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.time_to_response_ns,
|
||||
elementKey: "time_to_response_ns",
|
||||
renderFunction: nsToSeconds,
|
||||
contentTextAlign: "right",
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={records}
|
||||
entityName="Logs"
|
||||
customEmptyMessage={"There is no information with this criteria"}
|
||||
idField="request_id"
|
||||
columnsSelector
|
||||
columnsShown={columnsShown}
|
||||
onColumnChange={selectColumn}
|
||||
customPaperHeight={
|
||||
filterOpen ? classes.tableFOpen : classes.tableFClosed
|
||||
}
|
||||
sortConfig={{
|
||||
currentSort: "time",
|
||||
currentDirection: sortOrder,
|
||||
triggerSort: sortChange,
|
||||
}}
|
||||
infiniteScrollConfig={{
|
||||
recordsCount: 1000000,
|
||||
loadMoreRecords: loadMoreRecords,
|
||||
}}
|
||||
itemActions={[
|
||||
{
|
||||
type: "view",
|
||||
onClick: openExtraInformation,
|
||||
},
|
||||
]}
|
||||
textSelectable
|
||||
{!logSearchEnabled ? (
|
||||
<MissingIntegration
|
||||
entity={"Audit Logs"}
|
||||
iconComponent={<SearchIcon />}
|
||||
documentationLink="https://github.com/minio/operator/tree/master/logsearchapi"
|
||||
/>
|
||||
</Grid>
|
||||
) : (
|
||||
<Fragment>
|
||||
{" "}
|
||||
<Grid xs={12} className={classes.formBox}>
|
||||
<Grid item xs={12} className={`${classes.searchOptions}`}>
|
||||
<div className={classes.dateRangePicker}>
|
||||
<DateRangeSelector
|
||||
setTimeEnd={setTimeEnd}
|
||||
setTimeStart={setTimeStart}
|
||||
timeEnd={timeEnd}
|
||||
timeStart={timeStart}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Grid item className={classes.advancedButton}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setFilterOpen(!filterOpen);
|
||||
}}
|
||||
className={classes.advancedConfiguration}
|
||||
>
|
||||
{filterOpen ? "Hide" : "Show"} advanced Filters{" "}
|
||||
<span
|
||||
className={
|
||||
filterOpen
|
||||
? classes.advancedOpen
|
||||
: classes.advancedClosed
|
||||
}
|
||||
>
|
||||
<ArrowForwardIosIcon />
|
||||
</span>
|
||||
</button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={`${classes.blockCollapsed} ${
|
||||
filterOpen ? classes.filterOpen : ""
|
||||
}`}
|
||||
>
|
||||
<div className={classes.innerContainer}>
|
||||
<div className={classes.noticeLabel}>
|
||||
Enable your preferred options to get filtered records.
|
||||
<br />
|
||||
You can use '*' to match any character, '.' to signify a
|
||||
single character or '\' to scape an special character (E.g.
|
||||
mybucket-*)
|
||||
</div>
|
||||
<div className={classes.filtersContainer}>
|
||||
<FilterInputWrapper
|
||||
onChange={setBucket}
|
||||
value={bucket}
|
||||
label={"Bucket"}
|
||||
id="bucket"
|
||||
name="bucket"
|
||||
/>
|
||||
<FilterInputWrapper
|
||||
onChange={setApiName}
|
||||
value={apiName}
|
||||
label={"API Name"}
|
||||
id="api_name"
|
||||
name="api_name"
|
||||
/>
|
||||
<FilterInputWrapper
|
||||
onChange={setUserAgent}
|
||||
value={userAgent}
|
||||
label={"User Agent"}
|
||||
id="user_agent"
|
||||
name="user_agent"
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.filtersContainer}>
|
||||
<FilterInputWrapper
|
||||
onChange={setObject}
|
||||
value={object}
|
||||
label={"Object"}
|
||||
id="object"
|
||||
name="object"
|
||||
/>
|
||||
<FilterInputWrapper
|
||||
onChange={setRequestID}
|
||||
value={requestID}
|
||||
label={"Request ID"}
|
||||
id="request_id"
|
||||
name="request_id"
|
||||
/>
|
||||
<FilterInputWrapper
|
||||
onChange={setResponseStatus}
|
||||
value={responseStatus}
|
||||
label={"Response Status"}
|
||||
id="response_status"
|
||||
name="response_status"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.endLineAction}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={triggerLoad}
|
||||
>
|
||||
Get Information
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_HEALTH_INFO]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
columns={[
|
||||
{
|
||||
label: LogSearchColumnLabels.time,
|
||||
elementKey: "time",
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.api_name,
|
||||
elementKey: "api_name",
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.bucket,
|
||||
elementKey: "bucket",
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.object,
|
||||
elementKey: "object",
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.remote_host,
|
||||
elementKey: "remote_host",
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.request_id,
|
||||
elementKey: "request_id",
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.user_agent,
|
||||
elementKey: "user_agent",
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.response_status,
|
||||
elementKey: "response_status",
|
||||
renderFunction: (element) => (
|
||||
<Fragment>
|
||||
<span>
|
||||
{element.response_status_code} (
|
||||
{element.response_status})
|
||||
</span>
|
||||
</Fragment>
|
||||
),
|
||||
renderFullObject: true,
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.request_content_length,
|
||||
elementKey: "request_content_length",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.response_content_length,
|
||||
elementKey: "response_content_length",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
{
|
||||
label: LogSearchColumnLabels.time_to_response_ns,
|
||||
elementKey: "time_to_response_ns",
|
||||
renderFunction: nsToSeconds,
|
||||
contentTextAlign: "right",
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={records}
|
||||
entityName="Logs"
|
||||
customEmptyMessage={
|
||||
"There is no information with this criteria"
|
||||
}
|
||||
idField="request_id"
|
||||
columnsSelector
|
||||
columnsShown={columnsShown}
|
||||
onColumnChange={selectColumn}
|
||||
customPaperHeight={
|
||||
filterOpen ? classes.tableFOpen : classes.tableFClosed
|
||||
}
|
||||
sortConfig={{
|
||||
currentSort: "time",
|
||||
currentDirection: sortOrder,
|
||||
triggerSort: sortChange,
|
||||
}}
|
||||
infiniteScrollConfig={{
|
||||
recordsCount: 1000000,
|
||||
loadMoreRecords: loadMoreRecords,
|
||||
}}
|
||||
itemActions={[
|
||||
{
|
||||
type: "view",
|
||||
onClick: openExtraInformation,
|
||||
},
|
||||
]}
|
||||
textSelectable
|
||||
/>
|
||||
</SecureComponent>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</PageLayout>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -60,6 +60,14 @@ import {
|
||||
UsersIcon,
|
||||
VersionIcon,
|
||||
} from "../../../icons";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS,
|
||||
IAM_PAGES,
|
||||
S3_ALL_RESOURCES,
|
||||
IAM_SCOPES,
|
||||
} from "../../../common/SecureComponent/permissions";
|
||||
import { hasPermission } from "../../../common/SecureComponent/SecureComponent";
|
||||
|
||||
const drawerWidth = 245;
|
||||
|
||||
@@ -217,23 +225,23 @@ const styles = (theme: Theme) =>
|
||||
interface IMenuProps {
|
||||
classes: any;
|
||||
userLoggedIn: typeof userLoggedIn;
|
||||
pages: string[];
|
||||
operatorMode: boolean;
|
||||
distributedSetup: boolean;
|
||||
sidebarOpen: boolean;
|
||||
setMenuOpen: typeof setMenuOpen;
|
||||
resetSession: typeof resetSession;
|
||||
features: string[] | null;
|
||||
}
|
||||
|
||||
const Menu = ({
|
||||
userLoggedIn,
|
||||
classes,
|
||||
pages,
|
||||
operatorMode,
|
||||
distributedSetup,
|
||||
sidebarOpen,
|
||||
setMenuOpen,
|
||||
resetSession,
|
||||
features,
|
||||
}: IMenuProps) => {
|
||||
const logout = () => {
|
||||
const deleteSession = () => {
|
||||
@@ -254,12 +262,13 @@ const Menu = ({
|
||||
});
|
||||
};
|
||||
|
||||
let menuItems: IMenuItem[] = [
|
||||
const ldapIsEnabled = (features && features.includes("ldap-idp")) || false;
|
||||
let menuConsoleAdmin: IMenuItem[] = [
|
||||
{
|
||||
group: "common",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/dashboard",
|
||||
to: IAM_PAGES.DASHBOARD,
|
||||
name: "Dashboard",
|
||||
icon: DashboardIcon,
|
||||
},
|
||||
@@ -267,40 +276,46 @@ const Menu = ({
|
||||
group: "common",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/buckets",
|
||||
to: IAM_PAGES.BUCKETS,
|
||||
name: "Buckets",
|
||||
icon: BucketsIcon,
|
||||
forceDisplay: true,
|
||||
},
|
||||
|
||||
{
|
||||
group: "common",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/users",
|
||||
to: IAM_PAGES.USERS,
|
||||
customPermissionFnc: () =>
|
||||
hasPermission(CONSOLE_UI_RESOURCE, [IAM_SCOPES.ADMIN_LIST_USERS]) ||
|
||||
hasPermission(S3_ALL_RESOURCES, [IAM_SCOPES.ADMIN_CREATE_USER]),
|
||||
name: "Users",
|
||||
icon: UsersIcon,
|
||||
fsHidden: ldapIsEnabled,
|
||||
},
|
||||
{
|
||||
group: "common",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/groups",
|
||||
to: IAM_PAGES.GROUPS,
|
||||
name: "Groups",
|
||||
icon: GroupsIcon,
|
||||
fsHidden: ldapIsEnabled,
|
||||
},
|
||||
{
|
||||
group: "common",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/account",
|
||||
to: IAM_PAGES.ACCOUNT,
|
||||
name: "Service Accounts",
|
||||
icon: AccountIcon,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
group: "common",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/policies",
|
||||
to: IAM_PAGES.POLICIES,
|
||||
name: "IAM Policies",
|
||||
icon: IAMPoliciesIcon,
|
||||
},
|
||||
@@ -308,7 +323,7 @@ const Menu = ({
|
||||
group: "common",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/settings",
|
||||
to: IAM_PAGES.SETTINGS,
|
||||
name: "Settings",
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
@@ -316,7 +331,7 @@ const Menu = ({
|
||||
group: "common",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/notification-endpoints",
|
||||
to: IAM_PAGES.NOTIFICATIONS_ENDPOINTS,
|
||||
name: "Notification Endpoints",
|
||||
icon: LambdaIcon,
|
||||
},
|
||||
@@ -324,7 +339,7 @@ const Menu = ({
|
||||
group: "common",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/tiers",
|
||||
to: IAM_PAGES.TIERS,
|
||||
name: "Tiers",
|
||||
icon: TiersIcon,
|
||||
},
|
||||
@@ -332,107 +347,97 @@ const Menu = ({
|
||||
group: "common",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/tools",
|
||||
to: IAM_PAGES.TOOLS,
|
||||
name: "Tools",
|
||||
icon: ToolsIcon,
|
||||
},
|
||||
{
|
||||
group: "License",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: IAM_PAGES.LICENSE,
|
||||
name: "License",
|
||||
icon: LicenseIcon,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
group: "License",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: IAM_PAGES.DOCUMENTATION,
|
||||
name: "Documentation",
|
||||
icon: DocumentationIcon,
|
||||
forceDisplay: true,
|
||||
onClick: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLLIElement>
|
||||
| React.MouseEvent<HTMLAnchorElement>
|
||||
| React.MouseEvent<HTMLDivElement>
|
||||
) => {
|
||||
e.preventDefault();
|
||||
window.open("https://docs.min.io/?ref=con", "_blank");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let menuOperatorConsole: IMenuItem[] = [
|
||||
{
|
||||
group: "Operator",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/tenants",
|
||||
to: IAM_PAGES.TENANTS,
|
||||
name: "Tenants",
|
||||
icon: TenantsOutlineIcon,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
group: "Operator",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/storage",
|
||||
to: IAM_PAGES.STORAGE,
|
||||
name: "Storage",
|
||||
icon: StorageIcon,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
group: "Operator",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: IAM_PAGES.LICENSE,
|
||||
name: "License",
|
||||
icon: LicenseIcon,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
group: "Operator",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: IAM_PAGES.DOCUMENTATION,
|
||||
name: "Documentation",
|
||||
icon: DocumentationIcon,
|
||||
forceDisplay: true,
|
||||
onClick: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLLIElement>
|
||||
| React.MouseEvent<HTMLAnchorElement>
|
||||
| React.MouseEvent<HTMLDivElement>
|
||||
) => {
|
||||
e.preventDefault();
|
||||
window.open("https://docs.min.io/?ref=op", "_blank");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const allowedPages = pages.reduce((result: any, item: any) => {
|
||||
if (item.startsWith("/tools")) {
|
||||
result["/tools"] = true;
|
||||
}
|
||||
result[item] = true;
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
const documentation: IMenuItem = {
|
||||
group: "License",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/documentation",
|
||||
name: "Documentation",
|
||||
icon: DocumentationIcon,
|
||||
forceDisplay: true,
|
||||
};
|
||||
|
||||
// Append the license page according to the allowedPages
|
||||
if (allowedPages.hasOwnProperty("/tenants")) {
|
||||
menuItems.push(
|
||||
{
|
||||
group: "Operator",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/license",
|
||||
name: "License",
|
||||
icon: LicenseIcon,
|
||||
},
|
||||
{
|
||||
...documentation,
|
||||
group: "Operator",
|
||||
onClick: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLLIElement>
|
||||
| React.MouseEvent<HTMLAnchorElement>
|
||||
| React.MouseEvent<HTMLDivElement>
|
||||
) => {
|
||||
e.preventDefault();
|
||||
window.open(
|
||||
`https://docs.min.io/?ref=${operatorMode ? "op" : "con"}`,
|
||||
"_blank"
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
} else {
|
||||
menuItems.push(
|
||||
{
|
||||
group: "License",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/license",
|
||||
name: "License",
|
||||
icon: LicenseIcon,
|
||||
},
|
||||
{
|
||||
...documentation,
|
||||
group: "License",
|
||||
onClick: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLLIElement>
|
||||
| React.MouseEvent<HTMLAnchorElement>
|
||||
| React.MouseEvent<HTMLDivElement>
|
||||
) => {
|
||||
e.preventDefault();
|
||||
window.open(
|
||||
`https://docs.min.io/?ref=${operatorMode ? "op" : "con"}`,
|
||||
"_blank"
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const allowedItems = menuItems.filter(
|
||||
const allowedItems = (
|
||||
operatorMode ? menuOperatorConsole : menuConsoleAdmin
|
||||
).filter(
|
||||
(item: any) =>
|
||||
(allowedPages[item.to] || item.forceDisplay || item.type !== "item") &&
|
||||
item.fsHidden !== false
|
||||
((item.customPermissionFnc
|
||||
? item.customPermissionFnc()
|
||||
: hasPermission(CONSOLE_UI_RESOURCE, IAM_PAGES_PERMISSIONS[item.to])) ||
|
||||
item.forceDisplay ||
|
||||
item.type !== "item") &&
|
||||
!item.fsHidden
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -561,6 +566,7 @@ const mapState = (state: AppState) => ({
|
||||
sidebarOpen: state.system.sidebarOpen,
|
||||
operatorMode: state.system.operatorMode,
|
||||
distributedSetup: state.system.distributedSetup,
|
||||
features: state.console.session.features,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { userLoggedIn } from "../../../actions";
|
||||
|
||||
export interface IMenuItem {
|
||||
group: string;
|
||||
type: string;
|
||||
@@ -27,4 +25,13 @@ export interface IMenuItem {
|
||||
forceDisplay?: boolean;
|
||||
extraMargin?: boolean;
|
||||
fsHidden?: boolean;
|
||||
customPermissionFnc?: any;
|
||||
}
|
||||
|
||||
export interface IRouteRule {
|
||||
component: any;
|
||||
path: string;
|
||||
forceDisplay?: boolean;
|
||||
fsHidden?: boolean;
|
||||
customPermissionFnc?: any;
|
||||
}
|
||||
|
||||
@@ -206,6 +206,7 @@ const ListPolicies = ({ classes, setErrorSnackMessage }: IPoliciesProps) => {
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_CREATE_POLICY]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
||||
@@ -56,6 +56,7 @@ import SecureComponent, {
|
||||
} from "../../../common/SecureComponent/SecureComponent";
|
||||
|
||||
import withSuspense from "../Common/Components/withSuspense";
|
||||
import { AppState } from "../../../store";
|
||||
|
||||
const DeletePolicy = withSuspense(React.lazy(() => import("./DeletePolicy")));
|
||||
|
||||
@@ -64,6 +65,7 @@ interface IPolicyDetailsProps {
|
||||
match: any;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
setSnackBarMessage: typeof setSnackBarMessage;
|
||||
features: string[] | null;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -103,6 +105,7 @@ const PolicyDetails = ({
|
||||
match,
|
||||
setErrorSnackMessage,
|
||||
setSnackBarMessage,
|
||||
features,
|
||||
}: IPolicyDetailsProps) => {
|
||||
const [policy, setPolicy] = useState<Policy | null>(null);
|
||||
const [policyStatements, setPolicyStatements] = useState<IAMStatement[]>([]);
|
||||
@@ -118,6 +121,8 @@ const PolicyDetails = ({
|
||||
const [loadingGroups, setLoadingGroups] = useState<boolean>(true);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
|
||||
const ldapIsEnabled = (features && features.includes("ldap-idp")) || false;
|
||||
|
||||
const displayGroups = hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
[IAM_SCOPES.ADMIN_LIST_GROUPS, IAM_SCOPES.ADMIN_GET_GROUP],
|
||||
@@ -172,7 +177,7 @@ const PolicyDetails = ({
|
||||
useEffect(() => {
|
||||
const loadUsersForPolicy = () => {
|
||||
if (loadingUsers) {
|
||||
if (displayUsers) {
|
||||
if (displayUsers && !ldapIsEnabled) {
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
@@ -194,7 +199,7 @@ const PolicyDetails = ({
|
||||
|
||||
const loadGroupsForPolicy = () => {
|
||||
if (loadingGroups) {
|
||||
if (displayGroups) {
|
||||
if (displayGroups && !ldapIsEnabled) {
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
@@ -264,6 +269,7 @@ const PolicyDetails = ({
|
||||
displayUsers,
|
||||
displayGroups,
|
||||
displayPolicy,
|
||||
ldapIsEnabled,
|
||||
]);
|
||||
|
||||
const resetForm = () => {
|
||||
@@ -347,6 +353,7 @@ const PolicyDetails = ({
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_DELETE_POLICY]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<BoxIconButton
|
||||
tooltip={"Delete Policy"}
|
||||
@@ -456,7 +463,10 @@ const PolicyDetails = ({
|
||||
),
|
||||
}}
|
||||
{{
|
||||
tabConfig: { label: "Users", disabled: !displayUsers },
|
||||
tabConfig: {
|
||||
label: "Users",
|
||||
disabled: !displayUsers || ldapIsEnabled,
|
||||
},
|
||||
content: (
|
||||
<Fragment>
|
||||
<div className={classes.sectionTitle}>Users</div>
|
||||
@@ -497,7 +507,10 @@ const PolicyDetails = ({
|
||||
),
|
||||
}}
|
||||
{{
|
||||
tabConfig: { label: "Groups", disabled: !displayGroups },
|
||||
tabConfig: {
|
||||
label: "Groups",
|
||||
disabled: !displayGroups || ldapIsEnabled,
|
||||
},
|
||||
content: (
|
||||
<Fragment>
|
||||
<div className={classes.sectionTitle}>Groups</div>
|
||||
@@ -576,6 +589,7 @@ const PolicyDetails = ({
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_CREATE_POLICY]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -604,7 +618,11 @@ const PolicyDetails = ({
|
||||
);
|
||||
};
|
||||
|
||||
const connector = connect(null, {
|
||||
const mapState = (state: AppState) => ({
|
||||
features: state.console.session.features,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
setErrorSnackMessage,
|
||||
setSnackBarMessage,
|
||||
});
|
||||
|
||||
@@ -19,7 +19,6 @@ import { connect } from "react-redux";
|
||||
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
|
||||
import { Button, CircularProgress, Grid } from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
||||
@@ -205,9 +204,8 @@ const Speedtest = ({ classes, distributedSetup }: ISpeedtest) => {
|
||||
/>
|
||||
) : (
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_HEAL_ACTION]}
|
||||
scopes={[IAM_SCOPES.ADMIN_HEAL]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
RenderError={<Redirect to={"/"} />}
|
||||
>
|
||||
<Grid item xs={12} className={classes.boxy}>
|
||||
<Grid container>
|
||||
|
||||
@@ -36,16 +36,20 @@ import {
|
||||
SearchIcon,
|
||||
TraceIcon,
|
||||
WatchIcon,
|
||||
SpeedtestIcon,
|
||||
} from "../../../../icons";
|
||||
import { hasPermission } from "../../../../common/SecureComponent/SecureComponent";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_SCOPES,
|
||||
IAM_PAGES,
|
||||
IAM_PAGES_PERMISSIONS,
|
||||
} from "../../../../common/SecureComponent/permissions";
|
||||
import SpeedtestIcon from "../../../../icons/SpeedtestIcon";
|
||||
import { hasPermission } from "../../../../common/SecureComponent/SecureComponent";
|
||||
import { AppState } from "../../../../store";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
interface IConfigurationOptions {
|
||||
classes: any;
|
||||
features: string[];
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -65,57 +69,70 @@ const styles = (theme: Theme) =>
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const ToolsList = ({ classes }: IConfigurationOptions) => {
|
||||
const ToolsList = ({ classes, features }: IConfigurationOptions) => {
|
||||
const configurationElements: IElement[] = [
|
||||
{
|
||||
icon: <LogsIcon />,
|
||||
configuration_id: "logs",
|
||||
configuration_label: "Logs",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_CONSOLE_LOG_ACTION,
|
||||
]),
|
||||
disabled: !hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[IAM_PAGES.TOOLS_LOGS]
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <SearchIcon />,
|
||||
configuration_id: "audit-logs",
|
||||
configuration_label: "Audit Logs",
|
||||
disabled: !hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[IAM_PAGES.TOOLS_AUDITLOGS]
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <WatchIcon />,
|
||||
configuration_id: "watch",
|
||||
configuration_label: "Watch",
|
||||
disabled: !hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[IAM_PAGES.TOOLS_WATCH]
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <TraceIcon />,
|
||||
configuration_id: "trace",
|
||||
configuration_label: "Trace",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_TRACE_ACTION,
|
||||
]),
|
||||
disabled: !hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[IAM_PAGES.TOOLS_TRACE]
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <HealIcon />,
|
||||
configuration_id: "heal",
|
||||
configuration_label: "Heal",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_HEAL_ACTION,
|
||||
]),
|
||||
disabled: !hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[IAM_PAGES.TOOLS_HEAL]
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <DiagnosticsIcon />,
|
||||
configuration_id: "diagnostics",
|
||||
configuration_label: "Diagnostics",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_HEALTH_ACTION,
|
||||
]),
|
||||
disabled: !hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[IAM_PAGES.TOOLS_DIAGNOSTICS]
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <SpeedtestIcon />,
|
||||
configuration_id: "speedtest",
|
||||
configuration_label: "Speedtest",
|
||||
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_HEAL_ACTION,
|
||||
]),
|
||||
disabled: !hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[IAM_PAGES.TOOLS_SPEEDTEST]
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -142,4 +159,10 @@ const ToolsList = ({ classes }: IConfigurationOptions) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ToolsList);
|
||||
const mapState = (state: AppState) => ({
|
||||
features: state.console.session.features,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, null);
|
||||
|
||||
export default connector(withStyles(styles)(ToolsList));
|
||||
|
||||
@@ -42,6 +42,14 @@ import AButton from "../Common/AButton/AButton";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
import SearchBox from "../Common/SearchBox";
|
||||
import withSuspense from "../Common/Components/withSuspense";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_SCOPES,
|
||||
S3_ALL_RESOURCES,
|
||||
} from "../../../common/SecureComponent/permissions";
|
||||
import SecureComponent, {
|
||||
hasPermission,
|
||||
} from "../../../common/SecureComponent/SecureComponent";
|
||||
|
||||
const AddUser = withSuspense(React.lazy(() => import("./AddUser")));
|
||||
const SetPolicy = withSuspense(
|
||||
@@ -81,6 +89,22 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
|
||||
const [checkedUsers, setCheckedUsers] = useState<string[]>([]);
|
||||
const [policyOpen, setPolicyOpen] = useState<boolean>(false);
|
||||
|
||||
const displayListUsers = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_LIST_USERS,
|
||||
]);
|
||||
|
||||
const deleteUser = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_DELETE_USER,
|
||||
]);
|
||||
|
||||
const viewUser = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_GET_USER,
|
||||
]);
|
||||
|
||||
const addUserToGroup = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP,
|
||||
]);
|
||||
|
||||
const closeAddModalAndRefresh = () => {
|
||||
setAddScreenOpen(false);
|
||||
setLoading(true);
|
||||
@@ -102,20 +126,24 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/users`)
|
||||
.then((res: UsersList) => {
|
||||
const users = res.users === null ? [] : res.users;
|
||||
if (displayListUsers) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/users`)
|
||||
.then((res: UsersList) => {
|
||||
const users = res.users === null ? [] : res.users;
|
||||
|
||||
setLoading(false);
|
||||
setRecords(users.sort(usersSort));
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
setLoading(false);
|
||||
setRecords(users.sort(usersSort));
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoading(false);
|
||||
setErrorSnackMessage(err);
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}, [loading, setErrorSnackMessage]);
|
||||
}, [loading, setErrorSnackMessage, displayListUsers]);
|
||||
|
||||
const filteredRecords = records.filter((elementItem) =>
|
||||
elementItem.accessKey.includes(filter)
|
||||
@@ -155,11 +183,16 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
|
||||
);
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: viewAction },
|
||||
{
|
||||
type: "view",
|
||||
onClick: viewAction,
|
||||
disableButtonFunction: () => !viewUser,
|
||||
},
|
||||
{
|
||||
type: "delete",
|
||||
onClick: deleteAction,
|
||||
disableButtonFunction: (topValue: any) => topValue === userLoggedIn,
|
||||
disableButtonFunction: (topValue: any) =>
|
||||
topValue === userLoggedIn || !deleteUser,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -211,32 +244,48 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
|
||||
onChange={setFilter}
|
||||
overrideClass={classes.searchField}
|
||||
/>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
endIcon={<GroupIcon />}
|
||||
disabled={checkedUsers.length <= 0}
|
||||
onClick={() => {
|
||||
if (checkedUsers.length > 0) {
|
||||
setAddGroupOpen(true);
|
||||
}
|
||||
}}
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
Add to Group
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
endIcon={<AddIcon />}
|
||||
onClick={() => {
|
||||
setAddScreenOpen(true);
|
||||
setSelectedUser(null);
|
||||
}}
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
endIcon={<GroupIcon />}
|
||||
disabled={checkedUsers.length <= 0}
|
||||
onClick={() => {
|
||||
if (checkedUsers.length > 0) {
|
||||
setAddGroupOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Add to Group
|
||||
</Button>
|
||||
</SecureComponent>
|
||||
<SecureComponent
|
||||
scopes={[
|
||||
IAM_SCOPES.ADMIN_CREATE_USER,
|
||||
IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
|
||||
IAM_SCOPES.ADMIN_LIST_GROUPS,
|
||||
]}
|
||||
resource={S3_ALL_RESOURCES}
|
||||
matchAll
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
Create User
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
endIcon={<AddIcon />}
|
||||
onClick={() => {
|
||||
setAddScreenOpen(true);
|
||||
setSelectedUser(null);
|
||||
}}
|
||||
>
|
||||
Create User
|
||||
</Button>
|
||||
</SecureComponent>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
@@ -246,16 +295,24 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
|
||||
{records.length > 0 && (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[{ label: "Access Key", elementKey: "accessKey" }]}
|
||||
onSelect={selectionChanged}
|
||||
selectedItems={checkedUsers}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="Users"
|
||||
idField="accessKey"
|
||||
/>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_LIST_USERS]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Access Key", elementKey: "accessKey" },
|
||||
]}
|
||||
onSelect={addUserToGroup ? selectionChanged : undefined}
|
||||
selectedItems={checkedUsers}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="Users"
|
||||
idField="accessKey"
|
||||
/>
|
||||
</SecureComponent>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<HelpBox
|
||||
@@ -315,18 +372,30 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
|
||||
explicitly list the actions and resources to which that
|
||||
user has access. Users can also inherit policies from
|
||||
the groups in which they have membership.
|
||||
<br />
|
||||
<br />
|
||||
To get started,{" "}
|
||||
<AButton
|
||||
onClick={() => {
|
||||
setAddScreenOpen(true);
|
||||
setSelectedUser(null);
|
||||
}}
|
||||
<SecureComponent
|
||||
scopes={[
|
||||
IAM_SCOPES.ADMIN_CREATE_USER,
|
||||
IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
|
||||
IAM_SCOPES.ADMIN_LIST_GROUPS,
|
||||
]}
|
||||
matchAll
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
>
|
||||
Create a User
|
||||
</AButton>
|
||||
.
|
||||
<Fragment>
|
||||
<br />
|
||||
<br />
|
||||
To get started,{" "}
|
||||
<AButton
|
||||
onClick={() => {
|
||||
setAddScreenOpen(true);
|
||||
setSelectedUser(null);
|
||||
}}
|
||||
>
|
||||
Create a User
|
||||
</AButton>
|
||||
.
|
||||
</Fragment>
|
||||
</SecureComponent>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -25,7 +25,6 @@ const initialState: ConsoleState = {
|
||||
session: {
|
||||
operator: false,
|
||||
status: "",
|
||||
pages: [],
|
||||
features: [],
|
||||
distributedMode: false,
|
||||
permissions: {},
|
||||
|
||||
@@ -20,7 +20,6 @@ export interface ISessionPermissions {
|
||||
|
||||
export interface ISessionResponse {
|
||||
status: string;
|
||||
pages: string[];
|
||||
features: string[];
|
||||
operator: boolean;
|
||||
distributedMode: boolean;
|
||||
|
||||
@@ -9612,10 +9612,10 @@ prepend-http@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
||||
|
||||
prettier@2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d"
|
||||
integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==
|
||||
prettier@2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.0.tgz#a6370e2d4594e093270419d9cc47f7670488f893"
|
||||
integrity sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==
|
||||
|
||||
pretty-bytes@^5.3.0:
|
||||
version "5.6.0"
|
||||
|
||||
@@ -5223,12 +5223,6 @@ func init() {
|
||||
"operator": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pages": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@@ -11074,12 +11068,6 @@ func init() {
|
||||
"operator": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pages": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
|
||||
@@ -31,7 +31,8 @@ import (
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/minio/console/models"
|
||||
"github.com/minio/console/pkg/acl"
|
||||
"github.com/minio/console/pkg/auth/idp/oauth2"
|
||||
"github.com/minio/console/pkg/auth/ldap"
|
||||
"github.com/minio/console/restapi/operations"
|
||||
"github.com/minio/console/restapi/operations/user_api"
|
||||
)
|
||||
@@ -111,15 +112,6 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
|
||||
if err != nil {
|
||||
return nil, prepareError(err, errorGenericInvalidSession)
|
||||
}
|
||||
// by default every user starts with an empty array of available val
|
||||
// therefore we would have access only to pages that doesn't require any privilege
|
||||
// ie: service-account page
|
||||
var actions []string
|
||||
// if a policy is assigned to this user we parse the val from there
|
||||
if policy != nil {
|
||||
actions = acl.GetActionsStringFromPolicy(policy)
|
||||
}
|
||||
|
||||
currTime := time.Now().UTC()
|
||||
|
||||
// This actions will be global, meaning has to be attached to all resources
|
||||
@@ -229,7 +221,6 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
sessionResp := &models.SessionResponse{
|
||||
Pages: acl.GetAuthorizedEndpoints(actions),
|
||||
Features: getListOfEnabledFeatures(),
|
||||
Status: models.SessionResponseStatusOk,
|
||||
Operator: false,
|
||||
@@ -241,12 +232,20 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
|
||||
|
||||
// getListOfEnabledFeatures returns a list of features
|
||||
func getListOfEnabledFeatures() []string {
|
||||
var features []string
|
||||
features := []string{}
|
||||
logSearchURL := getLogSearchURL()
|
||||
oidcEnabled := oauth2.IsIDPEnabled()
|
||||
ldapEnabled := ldap.GetLDAPEnabled()
|
||||
|
||||
if logSearchURL != "" {
|
||||
features = append(features, "log-search")
|
||||
}
|
||||
if oidcEnabled {
|
||||
features = append(features, "oidc-idp", "external-idp")
|
||||
}
|
||||
if ldapEnabled {
|
||||
features = append(features, "ldap-idp", "external-idp")
|
||||
}
|
||||
|
||||
return features
|
||||
}
|
||||
|
||||
@@ -3128,10 +3128,6 @@ definitions:
|
||||
sessionResponse:
|
||||
type: object
|
||||
properties:
|
||||
pages:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
features:
|
||||
type: array
|
||||
items:
|
||||
|
||||
@@ -978,14 +978,6 @@ definitions:
|
||||
operatorSessionResponse:
|
||||
type: object
|
||||
properties:
|
||||
pages:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
features:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: [ ok ]
|
||||
|
||||
Reference in New Issue
Block a user