From a8bc58a4200a2714d0c50994a017d5b74f3ee41e Mon Sep 17 00:00:00 2001 From: jinapurapu <65002498+jinapurapu@users.noreply.github.com> Date: Thu, 30 Jun 2022 14:28:08 -0700 Subject: [PATCH] Add Audit Log and Log DB security context selector and split screen into tabs (#2156) * Added security context selector for logging and log DB to Audit Log screen, split Audit Log screen into tabs * Cleaned up tab titles, disabled Save button while loading --- models/tenant_logs.go | 92 +++ operatorapi/embedded_spec.go | 16 + operatorapi/tenants.go | 276 ++++---- .../FormComponents/common/styleLibrary.ts | 10 + .../Console/Tenants/ListTenants/types.ts | 9 + .../TenantDetails/LoggingDBDetails.tsx | 346 ++++++++++ .../Tenants/TenantDetails/LoggingDetails.tsx | 379 ++++++++++ .../TenantDetails/TenantAuditLogsScreen.tsx | 649 ++++-------------- .../TenantDetails/tenantAuditLogSlice.ts | 55 ++ .../Tenants/securityContextSelector.tsx | 110 +++ .../src/screens/Console/Tenants/types.ts | 1 + portal-ui/src/store.ts | 1 + swagger-operator.yml | 6 + 13 files changed, 1335 insertions(+), 615 deletions(-) create mode 100644 portal-ui/src/screens/Console/Tenants/TenantDetails/LoggingDBDetails.tsx create mode 100644 portal-ui/src/screens/Console/Tenants/TenantDetails/LoggingDetails.tsx create mode 100644 portal-ui/src/screens/Console/Tenants/securityContextSelector.tsx diff --git a/models/tenant_logs.go b/models/tenant_logs.go index 02e6072e2..388791b0e 100644 --- a/models/tenant_logs.go +++ b/models/tenant_logs.go @@ -54,6 +54,9 @@ type TenantLogs struct { // db node selector DbNodeSelector []*NodeSelector `json:"dbNodeSelector"` + // db security context + DbSecurityContext *SecurityContext `json:"dbSecurityContext,omitempty"` + // db service account name DbServiceAccountName string `json:"dbServiceAccountName,omitempty"` @@ -84,6 +87,9 @@ type TenantLogs struct { // node selector NodeSelector []*NodeSelector `json:"nodeSelector"` + // security context + SecurityContext *SecurityContext `json:"securityContext,omitempty"` + // service account name ServiceAccountName string `json:"serviceAccountName,omitempty"` } @@ -108,6 +114,10 @@ func (m *TenantLogs) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateDbSecurityContext(formats); err != nil { + res = append(res, err) + } + if err := m.validateLabels(formats); err != nil { res = append(res, err) } @@ -116,6 +126,10 @@ func (m *TenantLogs) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateSecurityContext(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -226,6 +240,25 @@ func (m *TenantLogs) validateDbNodeSelector(formats strfmt.Registry) error { return nil } +func (m *TenantLogs) validateDbSecurityContext(formats strfmt.Registry) error { + if swag.IsZero(m.DbSecurityContext) { // not required + return nil + } + + if m.DbSecurityContext != nil { + if err := m.DbSecurityContext.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("dbSecurityContext") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("dbSecurityContext") + } + return err + } + } + + return nil +} + func (m *TenantLogs) validateLabels(formats strfmt.Registry) error { if swag.IsZero(m.Labels) { // not required return nil @@ -278,6 +311,25 @@ func (m *TenantLogs) validateNodeSelector(formats strfmt.Registry) error { return nil } +func (m *TenantLogs) validateSecurityContext(formats strfmt.Registry) error { + if swag.IsZero(m.SecurityContext) { // not required + return nil + } + + if m.SecurityContext != nil { + if err := m.SecurityContext.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("securityContext") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("securityContext") + } + return err + } + } + + return nil +} + // ContextValidate validate this tenant logs based on the context it is used func (m *TenantLogs) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -298,6 +350,10 @@ func (m *TenantLogs) ContextValidate(ctx context.Context, formats strfmt.Registr res = append(res, err) } + if err := m.contextValidateDbSecurityContext(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateLabels(ctx, formats); err != nil { res = append(res, err) } @@ -306,6 +362,10 @@ func (m *TenantLogs) ContextValidate(ctx context.Context, formats strfmt.Registr res = append(res, err) } + if err := m.contextValidateSecurityContext(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -392,6 +452,22 @@ func (m *TenantLogs) contextValidateDbNodeSelector(ctx context.Context, formats return nil } +func (m *TenantLogs) contextValidateDbSecurityContext(ctx context.Context, formats strfmt.Registry) error { + + if m.DbSecurityContext != nil { + if err := m.DbSecurityContext.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("dbSecurityContext") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("dbSecurityContext") + } + return err + } + } + + return nil +} + func (m *TenantLogs) contextValidateLabels(ctx context.Context, formats strfmt.Registry) error { for i := 0; i < len(m.Labels); i++ { @@ -432,6 +508,22 @@ func (m *TenantLogs) contextValidateNodeSelector(ctx context.Context, formats st return nil } +func (m *TenantLogs) contextValidateSecurityContext(ctx context.Context, formats strfmt.Registry) error { + + if m.SecurityContext != nil { + if err := m.SecurityContext.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("securityContext") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("securityContext") + } + return err + } + } + + return nil +} + // MarshalBinary interface implementation func (m *TenantLogs) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/operatorapi/embedded_spec.go b/operatorapi/embedded_spec.go index 7c6f0fa1d..2d59eca9e 100644 --- a/operatorapi/embedded_spec.go +++ b/operatorapi/embedded_spec.go @@ -4206,6 +4206,10 @@ func init() { "$ref": "#/definitions/nodeSelector" } }, + "dbSecurityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, "dbServiceAccountName": { "type": "string" }, @@ -4242,6 +4246,10 @@ func init() { "$ref": "#/definitions/nodeSelector" } }, + "securityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, "serviceAccountName": { "type": "string" } @@ -9568,6 +9576,10 @@ func init() { "$ref": "#/definitions/nodeSelector" } }, + "dbSecurityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, "dbServiceAccountName": { "type": "string" }, @@ -9604,6 +9616,10 @@ func init() { "$ref": "#/definitions/nodeSelector" } }, + "securityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, "serviceAccountName": { "type": "string" } diff --git a/operatorapi/tenants.go b/operatorapi/tenants.go index eee3d2f02..0971acb79 100644 --- a/operatorapi/tenants.go +++ b/operatorapi/tenants.go @@ -1357,7 +1357,7 @@ func getTenantUsageResponse(session *models.Principal, params operator_api.GetTe return info, nil } -// getTenantLogsResponse returns the logs of a tenant +// getTenantLogsResponse returns the Audit Log and Log DB configuration of a tenant func getTenantLogsResponse(session *models.Principal, params operator_api.GetTenantLogsParams) (*models.TenantLogs, *models.Error) { ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) defer cancel() @@ -1392,11 +1392,9 @@ func getTenantLogsResponse(session *models.Principal, params operator_api.GetTen for k, v := range minTenant.Spec.Log.NodeSelector { nodeSelector = append(nodeSelector, &models.NodeSelector{Key: k, Value: v}) } - if minTenant.Spec.Log.Db == nil { minTenant.Spec.Log.Db = &miniov2.LogDbConfig{} } - dbAnnotations := []*models.Annotation{} for k, v := range minTenant.Spec.Log.Db.Annotations { dbAnnotations = append(dbAnnotations, &models.Annotation{Key: k, Value: v}) @@ -1409,6 +1407,15 @@ func getTenantLogsResponse(session *models.Principal, params operator_api.GetTen for k, v := range minTenant.Spec.Log.Db.NodeSelector { dbNodeSelector = append(dbNodeSelector, &models.NodeSelector{Key: k, Value: v}) } + var logSecurityContext *models.SecurityContext + var logDBSecurityContext *models.SecurityContext + + if minTenant.Spec.Log.SecurityContext != nil { + logSecurityContext = convertK8sSCToModelSC(minTenant.Spec.Log.SecurityContext) + } + if minTenant.Spec.Log.Db.SecurityContext != nil { + logDBSecurityContext = convertK8sSCToModelSC(minTenant.Spec.Log.Db.SecurityContext) + } if minTenant.Spec.Log.Audit == nil || minTenant.Spec.Log.Audit.DiskCapacityGB == nil { minTenant.Spec.Log.Audit = &miniov2.AuditConfig{DiskCapacityGB: swag.Int(0)} @@ -1421,12 +1428,14 @@ func getTenantLogsResponse(session *models.Principal, params operator_api.GetTen Labels: labels, NodeSelector: nodeSelector, ServiceAccountName: minTenant.Spec.Log.ServiceAccountName, + SecurityContext: logSecurityContext, DbImage: minTenant.Spec.Log.Db.Image, DbInitImage: minTenant.Spec.Log.Db.InitImage, DbAnnotations: dbAnnotations, DbLabels: dbLabels, DbNodeSelector: dbNodeSelector, DbServiceAccountName: minTenant.Spec.Log.Db.ServiceAccountName, + DbSecurityContext: logDBSecurityContext, Disabled: false, } @@ -1454,7 +1463,7 @@ func getTenantLogsResponse(session *models.Principal, params operator_api.GetTen return tenantLoggingConfiguration, nil } -// setTenantLogsResponse returns the logs of a tenant +// setTenantLogsResponse updates the Audit Log and Log DB configuration for the tenant func setTenantLogsResponse(session *models.Principal, params operator_api.SetTenantLogsParams) (bool, *models.Error) { ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) defer cancel() @@ -1474,44 +1483,54 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen } labels := make(map[string]string) - for i := 0; i < len(params.Data.Labels); i++ { - if params.Data.Labels[i] != nil { - labels[params.Data.Labels[i].Key] = params.Data.Labels[i].Value + if params.Data.Labels != nil { + for i := 0; i < len(params.Data.Labels); i++ { + if params.Data.Labels[i] != nil { + labels[params.Data.Labels[i].Key] = params.Data.Labels[i].Value + } } + minTenant.Spec.Log.Labels = labels } - minTenant.Spec.Log.Labels = labels - annotations := make(map[string]string) - for i := 0; i < len(params.Data.Annotations); i++ { - if params.Data.Annotations[i] != nil { - annotations[params.Data.Annotations[i].Key] = params.Data.Annotations[i].Value + + if params.Data.Annotations != nil { + annotations := make(map[string]string) + for i := 0; i < len(params.Data.Annotations); i++ { + if params.Data.Annotations[i] != nil { + annotations[params.Data.Annotations[i].Key] = params.Data.Annotations[i].Value + } } + minTenant.Spec.Log.Annotations = annotations } - minTenant.Spec.Log.Annotations = annotations - nodeSelector := make(map[string]string) - for i := 0; i < len(params.Data.NodeSelector); i++ { - if params.Data.NodeSelector[i] != nil { - nodeSelector[params.Data.NodeSelector[i].Key] = params.Data.NodeSelector[i].Value + if params.Data.NodeSelector != nil { + nodeSelector := make(map[string]string) + for i := 0; i < len(params.Data.NodeSelector); i++ { + if params.Data.NodeSelector[i] != nil { + nodeSelector[params.Data.NodeSelector[i].Key] = params.Data.NodeSelector[i].Value + } } + minTenant.Spec.Log.NodeSelector = nodeSelector } - minTenant.Spec.Log.NodeSelector = nodeSelector logResourceRequest := make(corev1.ResourceList) - - if reflect.TypeOf(params.Data.LogCPURequest).Kind() == reflect.String && params.Data.LogCPURequest != "0Gi" && params.Data.LogCPURequest != "" { - cpuQuantity, err := resource.ParseQuantity(params.Data.LogCPURequest) - if err != nil { - return false, restapi.ErrorWithContext(ctx, err) + if len(params.Data.LogCPURequest) > 0 { + if reflect.TypeOf(params.Data.LogCPURequest).Kind() == reflect.String && params.Data.LogCPURequest != "0Gi" && params.Data.LogCPURequest != "" { + cpuQuantity, err := resource.ParseQuantity(params.Data.LogCPURequest) + if err != nil { + return false, restapi.ErrorWithContext(ctx, err) + } + logResourceRequest["cpu"] = cpuQuantity + minTenant.Spec.Log.Resources.Requests = logResourceRequest } - logResourceRequest["cpu"] = cpuQuantity - minTenant.Spec.Log.Resources.Requests = logResourceRequest } - if reflect.TypeOf(params.Data.LogMemRequest).Kind() == reflect.String { - memQuantity, err := resource.ParseQuantity(params.Data.LogMemRequest) - if err != nil { - return false, restapi.ErrorWithContext(ctx, err) - } + if len(params.Data.LogMemRequest) > 0 { + if reflect.TypeOf(params.Data.LogMemRequest).Kind() == reflect.String && params.Data.LogMemRequest != "" { + memQuantity, err := resource.ParseQuantity(params.Data.LogMemRequest) + if err != nil { + return false, restapi.ErrorWithContext(ctx, err) + } - logResourceRequest["memory"] = memQuantity - minTenant.Spec.Log.Resources.Requests = logResourceRequest + logResourceRequest["memory"] = memQuantity + minTenant.Spec.Log.Resources.Requests = logResourceRequest + } } modified := false @@ -1519,97 +1538,122 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen modified = true } dbLabels := make(map[string]string) - for i := 0; i < len(params.Data.DbLabels); i++ { - if params.Data.DbLabels[i] != nil { - dbLabels[params.Data.DbLabels[i].Key] = params.Data.DbLabels[i].Value + if params.Data.DbLabels != nil { + for i := 0; i < len(params.Data.DbLabels); i++ { + if params.Data.DbLabels[i] != nil { + dbLabels[params.Data.DbLabels[i].Key] = params.Data.DbLabels[i].Value + } + modified = true } - modified = true } dbAnnotations := make(map[string]string) - for i := 0; i < len(params.Data.DbAnnotations); i++ { - if params.Data.DbAnnotations[i] != nil { - dbAnnotations[params.Data.DbAnnotations[i].Key] = params.Data.DbAnnotations[i].Value + if params.Data.DbAnnotations != nil { + for i := 0; i < len(params.Data.DbAnnotations); i++ { + if params.Data.DbAnnotations[i] != nil { + dbAnnotations[params.Data.DbAnnotations[i].Key] = params.Data.DbAnnotations[i].Value + } + modified = true } - modified = true } dbNodeSelector := make(map[string]string) - for i := 0; i < len(params.Data.DbNodeSelector); i++ { - if params.Data.DbNodeSelector[i] != nil { - dbNodeSelector[params.Data.DbNodeSelector[i].Key] = params.Data.DbNodeSelector[i].Value - } - modified = true - } - - logDBResourceRequest := make(corev1.ResourceList) - if reflect.TypeOf(params.Data.LogDBCPURequest).Kind() == reflect.String && params.Data.LogDBCPURequest != "0Gi" && params.Data.LogDBCPURequest != "" { - dbCPUQuantity, err := resource.ParseQuantity(params.Data.LogDBCPURequest) - if err != nil { - return false, restapi.ErrorWithContext(ctx, err) - } - logDBResourceRequest["cpu"] = dbCPUQuantity - minTenant.Spec.Log.Db.Resources.Requests = logDBResourceRequest - } - if reflect.TypeOf(params.Data.LogDBMemRequest).Kind() == reflect.String { - dbMemQuantity, err := resource.ParseQuantity(params.Data.LogDBMemRequest) - if err != nil { - return false, restapi.ErrorWithContext(ctx, err) - } - logDBResourceRequest["memory"] = dbMemQuantity - minTenant.Spec.Log.Db.Resources.Requests = logDBResourceRequest - } - minTenant.Spec.Log.Image = params.Data.Image - diskCapacityGB, err := strconv.Atoi(params.Data.DiskCapacityGB) - if err == nil { - if minTenant.Spec.Log.Audit != nil && minTenant.Spec.Log.Audit.DiskCapacityGB != nil { - *minTenant.Spec.Log.Audit.DiskCapacityGB = diskCapacityGB - } else { - minTenant.Spec.Log.Audit = &miniov2.AuditConfig{DiskCapacityGB: swag.Int(diskCapacityGB)} - } - } - minTenant.Spec.Log.ServiceAccountName = params.Data.ServiceAccountName - if params.Data.DbImage != "" || params.Data.DbServiceAccountName != "" { - modified = true - } - if modified { - if minTenant.Spec.Log.Db == nil { - // Default class name for Log search - diskSpaceFromAPI := int64(5) * humanize.GiByte // Default is 5Gi - logSearchStorageClass := "standard" - - logSearchDiskSpace := resource.NewQuantity(diskSpaceFromAPI, resource.DecimalExponent) - - minTenant.Spec.Log.Db = &miniov2.LogDbConfig{ - VolumeClaimTemplate: &corev1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: params.Tenant + "-log", - }, - Spec: corev1.PersistentVolumeClaimSpec{ - AccessModes: []corev1.PersistentVolumeAccessMode{ - corev1.ReadWriteOnce, - }, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceStorage: *logSearchDiskSpace, - }, - }, - StorageClassName: &logSearchStorageClass, - }, - }, - Labels: dbLabels, - Annotations: dbAnnotations, - NodeSelector: dbNodeSelector, - Image: params.Data.DbImage, - ServiceAccountName: params.Data.DbServiceAccountName, - Resources: corev1.ResourceRequirements{ - Requests: minTenant.Spec.Log.Db.Resources.Requests, - }, + if params.Data.DbNodeSelector != nil { + for i := 0; i < len(params.Data.DbNodeSelector); i++ { + if params.Data.DbNodeSelector[i] != nil { + dbNodeSelector[params.Data.DbNodeSelector[i].Key] = params.Data.DbNodeSelector[i].Value + } + modified = true + } + } + logDBResourceRequest := make(corev1.ResourceList) + if len(params.Data.LogDBCPURequest) > 0 { + if reflect.TypeOf(params.Data.LogDBCPURequest).Kind() == reflect.String && params.Data.LogDBCPURequest != "0Gi" && params.Data.LogDBCPURequest != "" { + dbCPUQuantity, err := resource.ParseQuantity(params.Data.LogDBCPURequest) + if err != nil { + return false, restapi.ErrorWithContext(ctx, err) + } + logDBResourceRequest["cpu"] = dbCPUQuantity + minTenant.Spec.Log.Db.Resources.Requests = logDBResourceRequest + } + } + if len(params.Data.LogDBMemRequest) > 0 { + if reflect.TypeOf(params.Data.LogDBMemRequest).Kind() == reflect.String && params.Data.LogDBMemRequest != "" { + dbMemQuantity, err := resource.ParseQuantity(params.Data.LogDBMemRequest) + if err != nil { + return false, restapi.ErrorWithContext(ctx, err) + } + logDBResourceRequest["memory"] = dbMemQuantity + minTenant.Spec.Log.Db.Resources.Requests = logDBResourceRequest + } + } + if len(params.Data.Image) > 0 { + minTenant.Spec.Log.Image = params.Data.Image + } + if params.Data.SecurityContext != nil { + minTenant.Spec.Log.SecurityContext, err = convertModelSCToK8sSC(params.Data.SecurityContext) + if err != nil { + return false, restapi.ErrorWithContext(ctx, err) + } + } + if len(params.Data.DiskCapacityGB) > 0 { + diskCapacityGB, err := strconv.Atoi(params.Data.DiskCapacityGB) + if err == nil { + if minTenant.Spec.Log.Audit != nil && minTenant.Spec.Log.Audit.DiskCapacityGB != nil { + *minTenant.Spec.Log.Audit.DiskCapacityGB = diskCapacityGB + } else { + minTenant.Spec.Log.Audit = &miniov2.AuditConfig{DiskCapacityGB: swag.Int(diskCapacityGB)} + } + } + } + if params.Data.DbLabels != nil { + minTenant.Spec.Log.ServiceAccountName = params.Data.ServiceAccountName + if params.Data.DbImage != "" || params.Data.DbServiceAccountName != "" { + modified = true + } + if modified { + if minTenant.Spec.Log.Db == nil { + // Default class name for Log search + diskSpaceFromAPI := int64(5) * humanize.GiByte // Default is 5Gi + logSearchStorageClass := "standard" + + logSearchDiskSpace := resource.NewQuantity(diskSpaceFromAPI, resource.DecimalExponent) + + minTenant.Spec.Log.Db = &miniov2.LogDbConfig{ + VolumeClaimTemplate: &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: params.Tenant + "-log", + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: *logSearchDiskSpace, + }, + }, + StorageClassName: &logSearchStorageClass, + }, + }, + Labels: dbLabels, + Annotations: dbAnnotations, + NodeSelector: dbNodeSelector, + Image: params.Data.DbImage, + ServiceAccountName: params.Data.DbServiceAccountName, + Resources: corev1.ResourceRequirements{ + Requests: minTenant.Spec.Log.Db.Resources.Requests, + }, + } + } else { + minTenant.Spec.Log.Db.Labels = dbLabels + minTenant.Spec.Log.Db.Annotations = dbAnnotations + minTenant.Spec.Log.Db.NodeSelector = dbNodeSelector + minTenant.Spec.Log.Db.Image = params.Data.DbImage + minTenant.Spec.Log.Db.ServiceAccountName = params.Data.DbServiceAccountName + minTenant.Spec.Log.Db.SecurityContext, err = convertModelSCToK8sSC(params.Data.DbSecurityContext) + if err != nil { + return false, restapi.ErrorWithContext(ctx, err) + } } - } else { - minTenant.Spec.Log.Db.Labels = dbLabels - minTenant.Spec.Log.Db.Annotations = dbAnnotations - minTenant.Spec.Log.Db.NodeSelector = dbNodeSelector - minTenant.Spec.Log.Db.Image = params.Data.DbImage - minTenant.Spec.Log.Db.ServiceAccountName = params.Data.DbServiceAccountName } } diff --git a/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts b/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts index 9f0445b7e..bdde80a78 100644 --- a/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts +++ b/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts @@ -795,6 +795,16 @@ export const wizardCommon = { alignItems: "center" as const, justifyContent: "flex-start" as const, }, + multiContainerStackNarrow: { + display: "flex" , + alignItems: "center" , + justifyContent: "flex-start", + gap: "8px", + "@media (max-width: 750px)": { + flexFlow: "column", + flexDirection: "column", + }, + }, sizeFactorContainer: { marginLeft: 8, alignSelf: "flex-start" as const, diff --git a/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts b/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts index 19e0c7f8d..1a83a125a 100644 --- a/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts +++ b/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts @@ -238,6 +238,8 @@ export interface ITenantLogsStruct { logMemRequest: string; logDBCPURequest: string; logDBMemRequest: string; + securityContext: ISecurityContext; + dbSecurityContext: ISecurityContext; } export interface ValueUnit { @@ -273,3 +275,10 @@ export interface IEditPoolRequest { export interface IPlotBarValues { [key: string]: CapacityValue; } + +export interface ITenantAuditLogs { + classes: any; + labels: IKeyValue[]; + annotations: IKeyValue[]; + nodeSelector: IKeyValue[]; +} \ No newline at end of file diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/LoggingDBDetails.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/LoggingDBDetails.tsx new file mode 100644 index 000000000..318bd1651 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/LoggingDBDetails.tsx @@ -0,0 +1,346 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//import { ISecurityContext} from "../types"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import { + containerForHeader, + createTenantCommon, + formFieldStyles, + modalBasic, + spacingUtils, + tenantDetailsStyles, + wizardCommon, +} from "../../Common/FormComponents/common/styleLibrary"; +import React, { Fragment, useState } from "react"; +import { useSelector } from "react-redux"; +import { AppState, useAppDispatch } from "../../../../store"; +import api from "../../../../common/api"; +import { ErrorResponseHandler } from "../../../../common/types"; +import { useParams } from "react-router-dom"; + +import Grid from "@mui/material/Grid"; +import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import { Button } from "@mui/material"; +import { + setErrorSnackMessage, + setSnackBarMessage, +} from "../../../../systemSlice"; +import { IKeyValue, ITenantAuditLogs } from "../ListTenants/types"; +import KeyPairEdit from "./KeyPairEdit"; +import InputUnitMenu from "../../Common/FormComponents/InputUnitMenu/InputUnitMenu"; +import { + setDBImage, + setDBInitImage, + setDBCPURequest, + setDBMemRequest, + setDBRunAsUser, + setDBFSGroup, + setDBRunAsGroup, + setDBRunAsNonRoot, + setRefreshLoggingInfo, + } from "../TenantDetails/tenantAuditLogSlice"; + + +import SecurityContextSelector from "../securityContextSelector"; + +import { clearValidationError } from "../utils"; + +const styles = (theme: Theme) => + createStyles({ + ...tenantDetailsStyles, + ...spacingUtils, + bold: { fontWeight: "bold" }, + italic: { fontStyle: "italic" }, + fileItem: { + marginRight: 10, + display: "flex", + "& div label": { + minWidth: 50, + }, + + "@media (max-width: 900px)": { + flexFlow: "column", + }, + }, + ...containerForHeader(theme.spacing(4)), + ...createTenantCommon, + ...formFieldStyles, + ...modalBasic, + ...wizardCommon, + }); + +const LoggingDBDetails = ({ classes, labels, annotations, nodeSelector }: ITenantAuditLogs) => { + const dispatch = useAppDispatch(); + const { tenantName, tenantNamespace } = useParams(); + const dbImage = useSelector( + (state: AppState) => state.editTenantLogging.dbImage + ); + const dbInitImage = useSelector( + (state: AppState) => state.editTenantLogging.dbInitImage + ); + const dbCpuRequest = useSelector( + (state: AppState) => state.editTenantLogging.dbCPURequest + ); + const dbMemRequest = useSelector( + (state: AppState) => state.editTenantLogging.dbMemRequest + ); + const dbServiceAccountName = useSelector( + (state: AppState) => state.editTenantLogging.dbServiceAccountName + ); + + const dbRunAsGroup = useSelector( + (state: AppState) => state.editTenantLogging.dbSecurityContext.runAsGroup + ); + const dbRunAsUser = useSelector( + (state: AppState) => state.editTenantLogging.dbSecurityContext.runAsUser + ) + const dbFSGroup = useSelector( + (state: AppState) => state.editTenantLogging.dbSecurityContext.fsGroup + ) + const dbRunAsNonRoot = useSelector( + (state: AppState) => state.editTenantLogging.dbSecurityContext.runAsNonRoot + ) + const [validationErrors, setValidationErrors] = useState({}); + + const [dbLabels, setDBLabels] = useState((labels != null && labels.length > 0) ? labels : [{ key: "", value: "" }]); + const [dbAnnotations, setDBAnnotations] = useState((annotations != null && annotations.length > 0)? annotations : [{ key: "", value: "" }]); + const [dbNodeSelector, setDBNodeSelector] = useState((nodeSelector != null && nodeSelector.length > 0)? nodeSelector :[{ key: "", value: "" }]); + + const [dbLabelsError, setDBLabelsError] = useState({}); + const [dbAnnotationsError, setDBAnnotationsError] = useState({}); + const [dbNodeSelectorError, setDBNodeSelectorError] = useState({}); + + const cleanValidation = (fieldName: string) => { + setValidationErrors(clearValidationError(validationErrors, fieldName)); + }; + + + + const trim = (x: IKeyValue[]): IKeyValue[] => { + let retval: IKeyValue[] = []; + for (let i = 0; i < x.length; i++) { + if (x[i].key !== "") { + retval.push(x[i]); + } + } + return retval; + }; + + const checkValid = (): boolean => { + if ( + Object.keys(validationErrors).length !== 0 || + Object.keys(dbNodeSelectorError).length !== 0 || + Object.keys(dbAnnotationsError).length !== 0 || + Object.keys(dbLabelsError).length !== 0 + ) { + let err: ErrorResponseHandler = { + errorMessage: "Invalid entry", + detailedError: "", + }; + dispatch(setErrorSnackMessage(err)); + return false; + } else { + return true; + } + }; + + + + const submitLoggingInfo = () => { + if (checkValid()) { + const dbSecurityContext = { + runAsGroup: dbRunAsGroup != null ? dbRunAsGroup : "", + runAsUser: dbRunAsUser != null ? dbRunAsUser : "", + fsGroup: dbFSGroup != null ? dbFSGroup : "", + runAsNonRoot: dbRunAsNonRoot != null ? dbRunAsNonRoot : true, + } + api + .invoke( + "PUT", + `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/log`, + { + dbLabels: trim(dbLabels), + dbAnnotations: trim(dbAnnotations), + dbNodeSelector: trim(dbNodeSelector), + dbImage: dbImage, + dbInitImage: dbInitImage, + dbServiceAccountName: dbServiceAccountName, + logDBCPURequest: dbCpuRequest, + logDBMemRequest: dbMemRequest, + dbSecurityContext: dbSecurityContext, + } + ) + .then(() => { + setRefreshLoggingInfo(true); + dispatch(setSnackBarMessage(`Audit Log DB configuration updated.`)); + }) + .catch((err: ErrorResponseHandler) => { + setErrorSnackMessage(err); + }); + } + }; + + + return ( + + + + + ) => { + if (event.target.validity.valid) { + dispatch(setDBImage(event.target.value)); + } + cleanValidation(`dbImage`); + }} + key={`dbImage`} + pattern={"^[a-zA-Z0-9-./:]{1,253}$"} + error={validationErrors[`dbImage`] || ""} + /> + + + ) => { + if (event.target.validity.valid) { + dispatch(setDBInitImage(event.target.value)); + } + cleanValidation(`dbInitImage`); + }} + key={`dbInitImage`} + pattern={"^[a-zA-Z0-9-./:]{1,253}$"} + error={validationErrors[`dbInitImage`] || ""} + /> + + + ) => { + if (event.target.validity.valid) { + dispatch(setDBCPURequest(event.target.value)); + } + cleanValidation(`dbCPURequest`); + }} + key={`dbCPURequest`} + error={validationErrors[`dbCPURequest`] || ""} + /> + + + ) => { + if (event.target.validity.valid) { + dispatch(setDBMemRequest(event.target.value)); + } + cleanValidation(`dbMemRequest`); + }} + pattern={"[0-9]*"} + key={`dbMemRequest`} + error={validationErrors[`dbMemRequest`] || ""} + overlayObject={ + {}} + unitSelected={"Gi"} + unitsList={[{ label: "Gi", value: "Gi" }]} + disabled={true} + /> + } + /> + + + + DB Labels + + + + DB Annotations + + + + + DB Node Selector + + + + + dispatch(setDBFSGroup(value))} + setRunAsUser={(value : string)=>dispatch(setDBRunAsUser(value))} + setRunAsGroup={(value : string)=>dispatch(setDBRunAsGroup(value))} + setRunAsNonRoot={(value : boolean)=>dispatch(setDBRunAsNonRoot(value))} + /> + + + + + + + ); +}; + +export default withStyles(styles)(LoggingDBDetails); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/LoggingDetails.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/LoggingDetails.tsx new file mode 100644 index 000000000..24d5db8a9 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/LoggingDetails.tsx @@ -0,0 +1,379 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//import { ISecurityContext} from "../types"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import { + containerForHeader, + createTenantCommon, + formFieldStyles, + modalBasic, + spacingUtils, + tenantDetailsStyles, + wizardCommon, +} from "../../Common/FormComponents/common/styleLibrary"; +import React, { Fragment, useState } from "react"; +import { useSelector } from "react-redux"; +import { AppState, useAppDispatch } from "../../../../store"; +import api from "../../../../common/api"; +import { ErrorResponseHandler } from "../../../../common/types"; +import { useParams } from "react-router-dom"; +import Grid from "@mui/material/Grid"; +import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import { Button } from "@mui/material"; +import { + setErrorSnackMessage, + setSnackBarMessage, +} from "../../../../systemSlice"; +import { IKeyValue, ITenantAuditLogs } from "../ListTenants/types"; +import KeyPairEdit from "./KeyPairEdit"; +import InputUnitMenu from "../../Common/FormComponents/InputUnitMenu/InputUnitMenu"; +import SecurityContextSelector from "../securityContextSelector"; +import { clearValidationError } from "../utils"; +import { + setImage, + setDiskCapacityGB, + setServiceAccountName, + setCPURequest, + setMemRequest, + setRunAsUser, + setFSGroup, + setRunAsGroup, + setRunAsNonRoot, + setRefreshLoggingInfo, + } from "../TenantDetails/tenantAuditLogSlice"; + +const styles = (theme: Theme) => + createStyles({ + ...tenantDetailsStyles, + ...spacingUtils, + bold: { fontWeight: "bold" }, + italic: { fontStyle: "italic" }, + fileItem: { + marginRight: 10, + display: "flex", + "& div label": { + minWidth: 50, + }, + + "@media (max-width: 900px)": { + flexFlow: "column", + }, + }, + ...containerForHeader(theme.spacing(4)), + ...createTenantCommon, + ...formFieldStyles, + ...modalBasic, + ...wizardCommon, + }); + +const TenantAuditLogging = ({ classes, labels, annotations, nodeSelector }: ITenantAuditLogs) => { + const dispatch = useAppDispatch(); + const { tenantName, tenantNamespace } = useParams(); + const auditLoggingEnabled = useSelector( + (state: AppState) => state.editTenantLogging.auditLoggingEnabled + ); + const image = useSelector( + (state: AppState) => state.editTenantLogging.image + ); + const diskCapacityGB = useSelector( + (state: AppState) => state.editTenantLogging.diskCapacityGB + ); + const cpuRequest = useSelector( + (state: AppState) => state.editTenantLogging.cpuRequest + ); + const memRequest = useSelector( + (state: AppState) => state.editTenantLogging.memRequest + ); + const serviceAccountName = useSelector( + (state: AppState) => state.editTenantLogging.serviceAccountName + ); + const runAsGroup = useSelector( + (state: AppState) => state.editTenantLogging.securityContext.runAsGroup + ); + const runAsUser = useSelector( + (state: AppState) => state.editTenantLogging.securityContext.runAsUser + ) + const fsGroup = useSelector( + (state: AppState) => state.editTenantLogging.securityContext.fsGroup + ) + const runAsNonRoot = useSelector( + (state: AppState) => state.editTenantLogging.securityContext.runAsNonRoot + ) + + const [validationErrors, setValidationErrors] = useState({}); + const [loading, setLoading] = useState(false); + + const [logLabels, setLabels] = useState((labels != null && labels.length > 0) ? labels : [{ key: "", value: "" }]); + const [logAnnotations, setAnnotations] = useState((annotations != null && annotations.length > 0)? annotations : [{ key: "", value: "" }]); + const [logNodeSelector, setNodeSelector] = useState((nodeSelector != null && nodeSelector.length > 0)? nodeSelector :[{ key: "", value: "" }]); + + const [labelsError, setLabelsError] = useState({}); + const [annotationsError, setAnnotationsError] = useState({}); + const [nodeSelectorError, setNodeSelectorError] = useState({}); + + const cleanValidation = (fieldName: string) => { + setValidationErrors(clearValidationError(validationErrors, fieldName)); + }; + + const trim = (x: IKeyValue[]): IKeyValue[] => { + let retval: IKeyValue[] = []; + for (let i = 0; i < x.length; i++) { + if (x[i].key !== "") { + retval.push(x[i]); + } + } + return retval; + }; + + const checkValid = (): boolean => { + if ( + Object.keys(validationErrors).length !== 0 || + Object.keys(labelsError).length !== 0 || + Object.keys(annotationsError).length !== 0 || + Object.keys(nodeSelectorError).length !== 0 + ) { + let err: ErrorResponseHandler = { + errorMessage: "Invalid entry", + detailedError: "", + }; + dispatch(setErrorSnackMessage(err)); + return false; + } else { + return true; + } + }; + + const submitLoggingInfo = () => { + + if (checkValid()) { + setLoading(true); + const securityContext = { + runAsGroup: runAsGroup != null ? runAsGroup : "", + runAsUser: runAsUser != null ? runAsUser : "", + fsGroup: fsGroup != null ? fsGroup : "", + runAsNonRoot: runAsNonRoot != null ? runAsNonRoot : true, + } + + api + .invoke( + "PUT", + `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/log`, + { + labels: trim(logLabels), + annotations: trim(logAnnotations), + nodeSelector: trim(logNodeSelector), + image: image, + diskCapacityGB: diskCapacityGB.toString(), + serviceAccountName: serviceAccountName, + logCPURequest: cpuRequest, + logMemRequest: memRequest, + securityContext: securityContext, + } + ) + .then(() => { + setRefreshLoggingInfo(true); + dispatch(setSnackBarMessage(`Audit Log configuration updated.`)); + setLoading(false); + }) + .catch((err: ErrorResponseHandler) => { + setErrorSnackMessage(err); + setLoading(false); + }); + } + }; + + return ( + + {auditLoggingEnabled && ( + + + ) => { + if (event.target.validity.valid) { + dispatch(setImage(event.target.value)); + } + cleanValidation(`image`); + }} + key={`image`} + pattern={"^[a-zA-Z0-9-./:]{1,253}$"} + error={validationErrors[`image`] || ""} + /> + + + + ) => { + if (event.target.validity.valid) { + dispatch(setDiskCapacityGB(parseInt(event.target.value))); + } + cleanValidation(`diskCapacityGB`); + }} + key={`diskCapacityGB`} + pattern={"[0-9]*"} + error={validationErrors[`diskCapacityGB`] || ""} + overlayObject={ + {}} + unitSelected={"Gi"} + unitsList={[{ label: "Gi", value: "Gi" }]} + disabled={true} + /> + } + /> + + + ) => { + if (event.target.validity.valid) { + dispatch(setCPURequest(event.target.value)); + } + cleanValidation(`cpuRequest`); + }} + key={`cpuRequest`} + error={validationErrors[`cpuRequest`] || ""} + /> + + + + ) => { + if (event.target.validity.valid) { + dispatch(setMemRequest(event.target.value)); + } + cleanValidation(`memRequest`); + }} + pattern={"[0-9]*"} + key={`memRequest`} + error={validationErrors[`memRequest`] || ""} + overlayObject={ + {}} + unitSelected={"Gi"} + unitsList={[{ label: "Gi", value: "Gi" }]} + disabled={true} + /> + } + /> + + + + ) => { + if (event.target.validity.valid) { + dispatch(setServiceAccountName(event.target.value)); + } + cleanValidation(`serviceAccountName`); + }} + key={`serviceAccountName`} + pattern={"^[a-zA-Z0-9-.]{1,253}$"} + error={validationErrors[`serviceAccountName`] || ""} + /> + + + dispatch(setFSGroup(value))} + setRunAsUser={(value : string)=>dispatch(setRunAsUser(value))} + setRunAsGroup={(value : string)=>dispatch(setRunAsGroup(value))} + setRunAsNonRoot={(value : boolean)=>dispatch(setRunAsNonRoot(value))} + /> + + + + Labels + + + + + Annotations + + + + + Node Selector + + + + + + + + )} + + ); +}; + +export default withStyles(styles)(TenantAuditLogging); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantAuditLogsScreen.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantAuditLogsScreen.tsx index 1602431d6..1900cebe9 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantAuditLogsScreen.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantAuditLogsScreen.tsx @@ -1,5 +1,6 @@ + // This file is part of MinIO Console Server -// Copyright (c) 2022 MinIO, Inc. +// 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 @@ -14,220 +15,129 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//import { ISecurityContext} from "../types"; +import React, { Fragment, useEffect, useState } from "react"; import { Theme } from "@mui/material/styles"; +import { useParams } from "react-router-dom"; import createStyles from "@mui/styles/createStyles"; import withStyles from "@mui/styles/withStyles"; -import { - containerForHeader, - createTenantCommon, - formFieldStyles, - modalBasic, - spacingUtils, - tenantDetailsStyles, - wizardCommon, -} from "../../Common/FormComponents/common/styleLibrary"; -import React, { Fragment, useEffect, useState } from "react"; +import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary"; +import Grid from "@mui/material/Grid"; +import Tabs from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; +import { DialogContentText } from "@mui/material"; +import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog"; +import api from "../../../../common/api"; import { useSelector } from "react-redux"; import { AppState, useAppDispatch } from "../../../../store"; -import api from "../../../../common/api"; import { ErrorResponseHandler } from "../../../../common/types"; -import { useParams } from "react-router-dom"; +import { setErrorSnackMessage} from "../../../../systemSlice"; import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; -import Grid from "@mui/material/Grid"; -import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; -import { Button, DialogContentText } from "@mui/material"; -import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog"; -import { - setErrorSnackMessage, - setSnackBarMessage, -} from "../../../../systemSlice"; -import { IKeyValue } from "../ListTenants/types"; -import KeyPairEdit from "./KeyPairEdit"; -import InputUnitMenu from "../../Common/FormComponents/InputUnitMenu/InputUnitMenu"; import { ITenantLogsStruct } from "../ListTenants/types"; +import { IKeyValue } from "../ListTenants/types"; + +import LoggingDetails from "./LoggingDetails"; +import LoggingDBDetails from "./LoggingDBDetails"; import { - setAuditLoggingEnabled, + setAuditLoggingEnabled, + setDBImage, + setDBInitImage, + setDBServiceAccountName, + setDBCPURequest, + setDBMemRequest, + setDBRunAsUser, + setDBFSGroup, + setDBRunAsGroup, + setDBRunAsNonRoot, setImage, - setDBImage, - setDBInitImage, setDiskCapacityGB, setServiceAccountName, - setDBServiceAccountName, setCPURequest, setMemRequest, - setDBCPURequest, - setDBMemRequest, + setRunAsUser, + setFSGroup, + setRunAsGroup, + setRunAsNonRoot, + resetAuditLogForm, } from "../TenantDetails/tenantAuditLogSlice"; -import { clearValidationError } from "../utils"; - -interface ITenantAuditLogs { +interface ILoggingScreenProps { classes: any; } const styles = (theme: Theme) => createStyles({ - ...tenantDetailsStyles, - ...spacingUtils, - bold: { fontWeight: "bold" }, - italic: { fontStyle: "italic" }, - fileItem: { - marginRight: 10, - display: "flex", - "& div label": { - minWidth: 50, - }, - - "@media (max-width: 900px)": { - flexFlow: "column", - }, + breadcrumLink: { + textDecoration: "none", + color: "black", }, ...containerForHeader(theme.spacing(4)), - ...createTenantCommon, - ...formFieldStyles, - ...modalBasic, - ...wizardCommon, }); -const TenantAuditLogging = ({ classes }: ITenantAuditLogs) => { +const LoggingScreen = ({ classes }: ILoggingScreenProps) => { + const { tenantNamespace, tenantName } = useParams(); + const [curTab, setCurTab] = useState(0); + const [loading, setLoading] = useState(true); + const [toggleConfirmOpen, setToggleConfirmOpen] = useState(false); + const [refreshLoggingInfo, setRefreshLoggingInfo] = useState(true); + const [dbLabels, setDBLabels] = useState([{ key: "", value: "" }]); + const [dbAnnotations, setDBAnnotations] = useState([{ key: "", value: "" }]); + const [dbNodeSelector, setDBNodeSelector] = useState([{ key: "", value: "" }]); + const [labels, setLabels] = useState([{ key: "", value: "" }]); + const [annotations, setAnnotations] = useState([{ key: "", value: "" }]); + const [nodeSelector, setNodeSelector] = useState([{ key: "", value: "" }]); const dispatch = useAppDispatch(); - const { tenantName, tenantNamespace } = useParams(); const auditLoggingEnabled = useSelector( (state: AppState) => state.editTenantLogging.auditLoggingEnabled ); - const image = useSelector( - (state: AppState) => state.editTenantLogging.image - ); - const dbImage = useSelector( - (state: AppState) => state.editTenantLogging.dbImage - ); - const dbInitImage = useSelector( - (state: AppState) => state.editTenantLogging.dbInitImage - ); - const diskCapacityGB = useSelector( - (state: AppState) => state.editTenantLogging.diskCapacityGB - ); - const cpuRequest = useSelector( - (state: AppState) => state.editTenantLogging.cpuRequest - ); - const memRequest = useSelector( - (state: AppState) => state.editTenantLogging.memRequest - ); - - const dbCpuRequest = useSelector( - (state: AppState) => state.editTenantLogging.dbCPURequest - ); - const dbMemRequest = useSelector( - (state: AppState) => state.editTenantLogging.dbMemRequest - ); - const serviceAccountName = useSelector( - (state: AppState) => state.editTenantLogging.serviceAccountName - ); - const dbServiceAccountName = useSelector( - (state: AppState) => state.editTenantLogging.dbServiceAccountName - ); - const [validationErrors, setValidationErrors] = useState({}); - const [toggleConfirmOpen, setToggleConfirmOpen] = useState(false); - - const [labels, setLabels] = useState([{ key: "", value: "" }]); - const [annotations, setAnnotations] = useState([ - { key: "", value: "" }, - ]); - const [nodeSelector, setNodeSelector] = useState([ - { key: "", value: "" }, - ]); - const [dbLabels, setDBLabels] = useState([{ key: "", value: "" }]); - const [dbAnnotations, setDBAnnotations] = useState([ - { key: "", value: "" }, - ]); - const [dbNodeSelector, setDBNodeSelector] = useState([ - { key: "", value: "" }, - ]); - - const [refreshLoggingInfo, setRefreshLoggingInfo] = - useState(true); - const [labelsError, setLabelsError] = useState({}); - const [annotationsError, setAnnotationsError] = useState({}); - const [nodeSelectorError, setNodeSelectorError] = useState({}); - - const [dbLabelsError, setDBLabelsError] = useState({}); - const [dbAnnotationsError, setDBAnnotationsError] = useState({}); - const [dbNodeSelectorError, setDBNodeSelectorError] = useState({}); - - const cleanValidation = (fieldName: string) => { - setValidationErrors(clearValidationError(validationErrors, fieldName)); - }; + function a11yProps(index: any) { + return { + id: `simple-tab-${index}`, + "aria-controls": `simple-tabpanel-${index}`, + }; + } const setLoggingInfo = (res: ITenantLogsStruct) => { - dispatch(setAuditLoggingEnabled(!res.disabled)) - dispatch(setImage(res.image)); - dispatch(setServiceAccountName(res.serviceAccountName)); - dispatch(setDBServiceAccountName(res.dbServiceAccountName)); - dispatch(setDBImage(res.dbImage)); - dispatch(setDBInitImage(res.dbInitImage)); - dispatch(setCPURequest(res.logCPURequest)); - dispatch(setDBCPURequest(res.logDBCPURequest)); - if (res.logMemRequest) { - dispatch( - setMemRequest( - Math.floor( - parseInt(res.logMemRequest, 10) / 1000000000 - ).toString() - ) - ); - } else { - dispatch(setMemRequest("0")); - } + if (res !== null) { + dispatch(setAuditLoggingEnabled(res !== null && !res.disabled)); + res.dbServiceAccountName != null && dispatch(setDBServiceAccountName(res.dbServiceAccountName)); + res.dbImage != null && dispatch(setDBImage(res.dbImage)); + res.dbInitImage != null && dispatch(setDBInitImage(res.dbInitImage)); + res.logDBCPURequest != null && dispatch(setDBCPURequest(res.logDBCPURequest)); if (res.logDBMemRequest) { - dispatch( - setDBMemRequest( - Math.floor( - parseInt(res.logDBMemRequest, 10) / 1000000000 - ).toString() - ) - ); + dispatch(setDBMemRequest(Math.floor(parseInt(res.logDBMemRequest, 10)).toString())); } else { dispatch(setDBMemRequest("0")); } - dispatch(setDiskCapacityGB(res.diskCapacityGB)); - res.labels.length > 0 ? setLabels(res.labels) : setLabels([{ key: "test", value: "test" }]); - res.annotations.length > 0 ? setAnnotations(res.annotations) : setAnnotations([{ key: "", value: "" }]); - res.nodeSelector.length > 0 ? setNodeSelector(res.nodeSelector) : setNodeSelector([{ key: "", value: "" }]); - res.dbLabels.length > 0 ? setDBLabels(res.dbLabels) : setDBLabels([{ key: "", value: "" }]); - res.dbAnnotations.length > 0 ? setDBAnnotations(res.dbAnnotations) : setDBAnnotations([{ key: "", value: "" }]); - res.dbNodeSelector.length > 0 ? setDBNodeSelector(res.dbNodeSelector) : setDBNodeSelector([{ key: "", value: "" }]); - }; - - const trim = (x: IKeyValue[]): IKeyValue[] => { - let retval: IKeyValue[] = []; - for (let i = 0; i < x.length; i++) { - if (x[i].key !== "") { - retval.push(x[i]); - } - } - return retval; - }; - - const checkValid = (): boolean => { - if ( - Object.keys(validationErrors).length !== 0 || - Object.keys(labelsError).length !== 0 || - Object.keys(annotationsError).length !== 0 || - Object.keys(nodeSelectorError).length !== 0 || - Object.keys(dbNodeSelectorError).length !== 0 || - Object.keys(dbAnnotationsError).length !== 0 || - Object.keys(dbLabelsError).length !== 0 - ) { - let err: ErrorResponseHandler = { - errorMessage: "Invalid entry", - detailedError: "", - }; - dispatch(setErrorSnackMessage(err)); - return false; - } else { - return true; - } + if (res.dbSecurityContext) { + dispatch(setDBRunAsGroup(res.dbSecurityContext.runAsGroup)); + dispatch(setDBRunAsUser(res.dbSecurityContext.runAsUser)); + dispatch(setDBFSGroup(res.dbSecurityContext.fsGroup)); + dispatch(setDBRunAsNonRoot(res.dbSecurityContext.runAsNonRoot)); + } + res.image != null && dispatch(setImage(res.image)); + res.serviceAccountName != null && dispatch(setServiceAccountName(res.serviceAccountName)); + res.logCPURequest != null && dispatch(setCPURequest(res.logCPURequest)); + if (res.logMemRequest) { + dispatch(setMemRequest(Math.floor(parseInt(res.logMemRequest, 10)).toString())); + } else { + dispatch(setMemRequest("0")); + } + if (res.securityContext) { + dispatch(setRunAsGroup(res.securityContext.runAsGroup)); + dispatch(setRunAsUser(res.securityContext.runAsUser)); + dispatch(setFSGroup(res.securityContext.fsGroup)); + dispatch(setRunAsNonRoot(res.securityContext.runAsNonRoot)); + } + + res.diskCapacityGB != null && dispatch(setDiskCapacityGB(res.diskCapacityGB)); + res.labels != null ? setLabels(res.labels) : setLabels([{ key: "", value: "" }]); + res.annotations != null ? setAnnotations(res.annotations) : setAnnotations([{ key: "", value: "" }]); + res.nodeSelector != null ? setNodeSelector(res.nodeSelector) : setNodeSelector([{ key: "", value: "" }]); + res.dbLabels != null ? setDBLabels(res.dbLabels) : setDBLabels([{ key: "", value: "" }]); + res.dbAnnotations != null ? setDBAnnotations(res.dbAnnotations) : setDBAnnotations([{ key: "", value: "" }]); + res.dbNodeSelector != null ? setDBNodeSelector(res.dbNodeSelector) : setDBNodeSelector([{ key: "", value: "" }]); + setRefreshLoggingInfo(false); + } }; useEffect(() => { @@ -238,9 +148,11 @@ const TenantAuditLogging = ({ classes }: ITenantAuditLogs) => { `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/log` ) .then((res: ITenantLogsStruct) => { + if (res !== null) { dispatch(setAuditLoggingEnabled(res.auditLoggingEnabled)); setLoggingInfo(res); setRefreshLoggingInfo(false); + } }) .catch((err: ErrorResponseHandler) => { dispatch(setErrorSnackMessage(err)); @@ -250,42 +162,14 @@ const TenantAuditLogging = ({ classes }: ITenantAuditLogs) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [refreshLoggingInfo]); - const submitLoggingInfo = () => { - if (checkValid()) { - api - .invoke( - "PUT", - `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/log`, - { - labels: trim(labels), - annotations: trim(annotations), - nodeSelector: trim(nodeSelector), - image: image, - diskCapacityGB: diskCapacityGB.toString(), - serviceAccountName: serviceAccountName, - dbLabels: trim(dbLabels), - dbAnnotations: trim(dbAnnotations), - dbNodeSelector: trim(dbNodeSelector), - dbImage: dbImage, - dbInitImage: dbInitImage, - dbServiceAccountName: dbServiceAccountName, - logCPURequest: cpuRequest, - logMemRequest: memRequest + "Gi", - logDBCPURequest: dbCpuRequest, - logDBMemRequest: dbMemRequest + "Gi", - } - ) - .then(() => { - setRefreshLoggingInfo(true); - dispatch(setSnackBarMessage(`Audit Log configuration updated.`)); - }) - .catch((err: ErrorResponseHandler) => { - setErrorSnackMessage(err); - }); + useEffect(() => { + if (loading) { + setLoading(false); } - }; + }, [loading, refreshLoggingInfo]); const toggleLogging = () => { + dispatch(resetAuditLogForm()); if(!auditLoggingEnabled) { api .invoke( @@ -295,6 +179,7 @@ const TenantAuditLogging = ({ classes }: ITenantAuditLogs) => { .then(() => { setRefreshLoggingInfo(true); setToggleConfirmOpen(false); + setAuditLoggingEnabled(true); }) .catch((err: ErrorResponseHandler) => { dispatch( @@ -311,8 +196,10 @@ const TenantAuditLogging = ({ classes }: ITenantAuditLogs) => { `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/disable-logging` ) .then(() => { + setAuditLoggingEnabled(false); setRefreshLoggingInfo(true); setToggleConfirmOpen(false); + dispatch(resetAuditLogForm()); }) .catch((err: ErrorResponseHandler) => { dispatch( @@ -324,9 +211,13 @@ const TenantAuditLogging = ({ classes }: ITenantAuditLogs) => { }); }; }; + return ( - - {toggleConfirmOpen && ( + + + + {toggleConfirmOpen && ( + { } /> )} - - + + +

Audit Logs

- + { description="" /> - -
+ +
+ + {auditLoggingEnabled && ( + + + , newValue: number) => { + setCurTab(newValue); + }} + indicatorColor="primary" + textColor="primary" + aria-label="cluster-tabs" + variant="scrollable" + scrollButtons="auto" + > + + + - - - {auditLoggingEnabled && ( - - - ) => { - if (event.target.validity.valid) { - dispatch(setImage(event.target.value)); - } - cleanValidation(`image`); - }} - key={`image`} - pattern={"^[a-zA-Z0-9-./:]{1,253}$"} - error={validationErrors[`image`] || ""} + +
+
+ {curTab === 0 && ( + -
- - ) => { - if (event.target.validity.valid) { - dispatch(setDBImage(event.target.value)); - } - cleanValidation(`dbImage`); - }} - key={`dbImage`} - pattern={"^[a-zA-Z0-9-./:]{1,253}$"} - error={validationErrors[`dbImage`] || ""} - /> - - - ) => { - if (event.target.validity.valid) { - dispatch(setDBInitImage(event.target.value)); - } - cleanValidation(`dbInitImage`); - }} - key={`dbInitImage`} - pattern={"^[a-zA-Z0-9-./:]{1,253}$"} - error={validationErrors[`dbInitImage`] || ""} - /> - - - ) => { - if (event.target.validity.valid) { - dispatch(setDiskCapacityGB(parseInt(event.target.value))); - } - cleanValidation(`diskCapacityGB`); - }} - key={`diskCapacityGB`} - pattern={"[0-9]*"} - error={validationErrors[`diskCapacityGB`] || ""} - overlayObject={ - {}} - unitSelected={"Gi"} - unitsList={[{ label: "Gi", value: "Gi" }]} - disabled={true} - /> - } - /> - - - ) => { - if (event.target.validity.valid) { - dispatch(setCPURequest(event.target.value)); - } - cleanValidation(`cpuRequest`); - }} - key={`cpuRequest`} - error={validationErrors[`cpuRequest`] || ""} - /> - - - ) => { - if (event.target.validity.valid) { - dispatch(setDBCPURequest(event.target.value)); - } - cleanValidation(`dbCPURequest`); - }} - key={`dbCPURequest`} - error={validationErrors[`dbCPURequest`] || ""} - /> - - - ) => { - if (event.target.validity.valid) { - dispatch(setMemRequest(event.target.value)); - } - cleanValidation(`memRequest`); - }} - pattern={"[0-9]*"} - key={`memRequest`} - error={validationErrors[`memRequest`] || ""} - overlayObject={ - {}} - unitSelected={"Gi"} - unitsList={[{ label: "Gi", value: "Gi" }]} - disabled={true} - /> - } - /> - - - ) => { - if (event.target.validity.valid) { - dispatch(setDBMemRequest(event.target.value)); - } - cleanValidation(`dbMemRequest`); - }} - pattern={"[0-9]*"} - key={`dbMemRequest`} - error={validationErrors[`dbMemRequest`] || ""} - overlayObject={ - {}} - unitSelected={"Gi"} - unitsList={[{ label: "Gi", value: "Gi" }]} - disabled={true} - /> - } - /> - - - ) => { - if (event.target.validity.valid) { - dispatch(setServiceAccountName(event.target.value)); - } - cleanValidation(`serviceAccountName`); - }} - key={`serviceAccountName`} - pattern={"^[a-zA-Z0-9-.]{1,253}$"} - error={validationErrors[`serviceAccountName`] || ""} - /> - - {labels !== null && ( - - Labels - - - )} - - {annotations !== null && ( - - Annotations - - - )} - {nodeSelector !== null && ( - - Node Selector - - - )} - {dbLabels !== null && ( - - DB Labels - - - )} - - {dbAnnotations !== null && ( - - DB Annotations - - - )} - {dbNodeSelector !== null && ( - - DB Node Selector - - - )} - - - + )} + {curTab === 1 && ( + + )}
- )} + )} +
); }; -export default withStyles(styles)(TenantAuditLogging); +export default withStyles(styles)(LoggingScreen); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/tenantAuditLogSlice.ts b/portal-ui/src/screens/Console/Tenants/TenantDetails/tenantAuditLogSlice.ts index 7f8fcc230..deac83b5f 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/tenantAuditLogSlice.ts +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/tenantAuditLogSlice.ts @@ -15,6 +15,7 @@ // along with this program. If not, see . import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { IKeyValue } from "../ListTenants/types"; +import { ISecurityContext } from "../types"; export interface IEditTenantAuditLogging { auditLoggingEnabled: boolean; @@ -34,6 +35,9 @@ export interface IEditTenantAuditLogging { memRequest: string; dbCPURequest: string; dbMemRequest: string; + securityContext: ISecurityContext; + dbSecurityContext: ISecurityContext; + refreshLoggingInfo: boolean; } const initialState: IEditTenantAuditLogging = { @@ -54,6 +58,19 @@ const initialState: IEditTenantAuditLogging = { dbServiceAccountName: "", cpuRequest: "", memRequest: "", + securityContext: { + runAsUser: "1000", + runAsGroup: "1000", + fsGroup: "1000", + runAsNonRoot: false, + }, + dbSecurityContext: { + runAsUser: "1000", + runAsGroup: "1000", + fsGroup: "1000", + runAsNonRoot: false, + }, + refreshLoggingInfo: true }; export const editTenantAuditLoggingSlice = createSlice({ @@ -111,6 +128,34 @@ export const editTenantAuditLoggingSlice = createSlice({ setDBMemRequest: (state, action: PayloadAction) => { state.dbMemRequest = action.payload; }, + setRunAsUser: (state, action: PayloadAction) => { + state.securityContext.runAsUser = action.payload; + }, + setRunAsGroup: (state, action: PayloadAction) => { + state.securityContext.runAsGroup = action.payload; + }, + setFSGroup: (state, action: PayloadAction) => { + state.securityContext.fsGroup = action.payload; + }, + setRunAsNonRoot: (state, action: PayloadAction) => { + state.securityContext.runAsNonRoot = action.payload; + }, + setDBRunAsUser: (state, action: PayloadAction) => { + state.dbSecurityContext.runAsUser = action.payload; + }, + setDBRunAsGroup: (state, action: PayloadAction) => { + state.dbSecurityContext.runAsGroup = action.payload; + }, + setDBFSGroup: (state, action: PayloadAction) => { + state.dbSecurityContext.fsGroup = action.payload; + }, + setDBRunAsNonRoot: (state, action: PayloadAction) => { + state.dbSecurityContext.runAsNonRoot = action.payload; + }, + setRefreshLoggingInfo: (state, action: PayloadAction) => { + state.refreshLoggingInfo = action.payload; + }, + resetAuditLogForm: () => initialState, }, }); @@ -132,6 +177,16 @@ export const { setMemRequest, setDBCPURequest, setDBMemRequest, + setRunAsUser, + setFSGroup, + setRunAsGroup, + setRunAsNonRoot, + setDBRunAsUser, + setDBFSGroup, + setDBRunAsGroup, + setDBRunAsNonRoot, + setRefreshLoggingInfo, + resetAuditLogForm, } = editTenantAuditLoggingSlice.actions; export default editTenantAuditLoggingSlice.reducer; diff --git a/portal-ui/src/screens/Console/Tenants/securityContextSelector.tsx b/portal-ui/src/screens/Console/Tenants/securityContextSelector.tsx new file mode 100644 index 000000000..2b5b04b41 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/securityContextSelector.tsx @@ -0,0 +1,110 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { Fragment } from "react"; +import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; +import { Grid } from "@mui/material"; +import { useDispatch } from "react-redux"; + +interface IEditSecurityContextProps { + classes: any; + runAsUser: string; + runAsGroup: string; + fsGroup: string; + runAsNonRoot: boolean; + setRunAsUser: any; + setRunAsGroup: any; + setFSGroup: any; + setRunAsNonRoot: any; +} +const SecurityContextSelector = ({ classes, runAsGroup, runAsUser, fsGroup, runAsNonRoot, setRunAsUser, setRunAsGroup, setFSGroup, setRunAsNonRoot }: IEditSecurityContextProps ) => { + const dispatch = useDispatch(); + return ( + +
+ + Security Context + + + +
+
+ ) => { + dispatch(setRunAsUser(e.target.value)); + }} + label="Run As User" + value={runAsUser} + required + min="0" + /> +
+
+ ) => { dispatch(setRunAsGroup(e.target.value)); + }} + label="Run As Group" + value={runAsGroup} + required + min="0" + /> +
+
+ ) => {dispatch(setFSGroup(e.target.value)); }} + label="FsGroup" + value={fsGroup} + required + min="0" + /> +
+
+
+
+ +
+ { + dispatch(setRunAsNonRoot(!runAsNonRoot)); + } + } + label={"Do not run as Root"} + /> +
+
+
+
+ ); +}; +export default SecurityContextSelector; \ No newline at end of file diff --git a/portal-ui/src/screens/Console/Tenants/types.ts b/portal-ui/src/screens/Console/Tenants/types.ts index 5c4549701..111655936 100644 --- a/portal-ui/src/screens/Console/Tenants/types.ts +++ b/portal-ui/src/screens/Console/Tenants/types.ts @@ -338,3 +338,4 @@ export interface ITenantIdentityProviderResponse { user_dn_search_filter: string; }; } + diff --git a/portal-ui/src/store.ts b/portal-ui/src/store.ts index f2c82b81f..dbc8c417f 100644 --- a/portal-ui/src/store.ts +++ b/portal-ui/src/store.ts @@ -34,6 +34,7 @@ import editPoolReducer from "./screens/Console/Tenants/TenantDetails/Pools/EditP import editTenantMonitoringReducer from "./screens/Console/Tenants/TenantDetails/tenantMonitoringSlice"; import editTenantAuditLoggingReducer from "./screens/Console/Tenants/TenantDetails/tenantAuditLogSlice"; + const rootReducer = combineReducers({ system: systemReducer, trace: traceReducer, diff --git a/swagger-operator.yml b/swagger-operator.yml index bdc6f369c..7f14f4838 100644 --- a/swagger-operator.yml +++ b/swagger-operator.yml @@ -2908,6 +2908,12 @@ definitions: type: string logDBMemRequest: type: string + securityContext: + type: object + $ref: "#/definitions/securityContext" + dbSecurityContext: + type: object + $ref: "#/definitions/securityContext" listPVCsResponse: