Add feature hide-menu for embedded screens on Operator UI (#1604)
* Add feature hide-menu for embedded screens on Operator UI Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
@@ -29,7 +29,7 @@ WORKDIR /go/src/github.com/minio/console/
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
COPY --from=uilayer /app/build /go/src/github.com/minio/console/portal-ui/build
|
||||
RUN go build -ldflags "-w -s" -a -o console ./cmd/console
|
||||
RUN go build --tags=kqueue,operator -ldflags "-w -s" -a -o console ./cmd/console
|
||||
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.5
|
||||
MAINTAINER MinIO Development "dev@min.io"
|
||||
|
||||
@@ -40,6 +40,9 @@ type LoginRequest struct {
|
||||
// Required: true
|
||||
AccessKey *string `json:"accessKey"`
|
||||
|
||||
// features
|
||||
Features *LoginRequestFeatures `json:"features,omitempty"`
|
||||
|
||||
// secret key
|
||||
// Required: true
|
||||
SecretKey *string `json:"secretKey"`
|
||||
@@ -53,6 +56,10 @@ func (m *LoginRequest) Validate(formats strfmt.Registry) error {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateFeatures(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateSecretKey(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
@@ -72,6 +79,25 @@ func (m *LoginRequest) validateAccessKey(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *LoginRequest) validateFeatures(formats strfmt.Registry) error {
|
||||
if swag.IsZero(m.Features) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.Features != nil {
|
||||
if err := m.Features.Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("features")
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("features")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *LoginRequest) validateSecretKey(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("secretKey", "body", m.SecretKey); err != nil {
|
||||
@@ -81,8 +107,33 @@ func (m *LoginRequest) validateSecretKey(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this login request based on context it is used
|
||||
// ContextValidate validate this login request based on the context it is used
|
||||
func (m *LoginRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.contextValidateFeatures(ctx, formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *LoginRequest) contextValidateFeatures(ctx context.Context, formats strfmt.Registry) error {
|
||||
|
||||
if m.Features != nil {
|
||||
if err := m.Features.ContextValidate(ctx, formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("features")
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("features")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -103,3 +154,40 @@ func (m *LoginRequest) UnmarshalBinary(b []byte) error {
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoginRequestFeatures login request features
|
||||
//
|
||||
// swagger:model LoginRequestFeatures
|
||||
type LoginRequestFeatures struct {
|
||||
|
||||
// hide menu
|
||||
HideMenu bool `json:"hide_menu,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this login request features
|
||||
func (m *LoginRequestFeatures) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this login request features based on context it is used
|
||||
func (m *LoginRequestFeatures) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *LoginRequestFeatures) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *LoginRequestFeatures) UnmarshalBinary(b []byte) error {
|
||||
var res LoginRequestFeatures
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ type Principal struct {
|
||||
|
||||
// account access key
|
||||
AccountAccessKey string `json:"accountAccessKey,omitempty"`
|
||||
|
||||
// Hide Console Menu
|
||||
Hm bool `json:"hm,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this principal
|
||||
|
||||
@@ -2605,6 +2605,14 @@ func init() {
|
||||
"accessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"features": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hide_menu": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secretKey": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -5812,6 +5820,14 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginRequestFeatures": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hide_menu": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NodeSelectorTermMatchExpressionsItems0": {
|
||||
"description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.",
|
||||
"type": "object",
|
||||
@@ -7043,6 +7059,14 @@ func init() {
|
||||
"accessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"features": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hide_menu": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secretKey": {
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func login(credentials restapi.ConsoleCredentialsI) (*string, error) {
|
||||
return nil, err
|
||||
}
|
||||
// if we made it here, the consoleCredentials work, generate a jwt with claims
|
||||
token, err := auth.NewEncryptedTokenForClient(&tokens, credentials.GetAccountAccessKey())
|
||||
token, err := auth.NewEncryptedTokenForClient(&tokens, credentials.GetAccountAccessKey(), nil)
|
||||
if err != nil {
|
||||
LogError("error authenticating user: %v", err)
|
||||
return nil, errInvalidCredentials
|
||||
|
||||
@@ -123,9 +123,12 @@ func serveProxy(responseWriter http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]string{
|
||||
"accessKey": string(tenantConfiguration["accesskey"]),
|
||||
"secretKey": string(tenantConfiguration["secretkey"]),
|
||||
data := map[string]interface{}{
|
||||
"accessKey": tenantConfiguration["accesskey"],
|
||||
"secretKey": tenantConfiguration["secretkey"],
|
||||
"features": map[string]bool{
|
||||
"hide_menu": true,
|
||||
},
|
||||
}
|
||||
payload, _ := json.Marshal(data)
|
||||
|
||||
|
||||
@@ -66,6 +66,12 @@ type TokenClaims struct {
|
||||
STSSecretAccessKey string `json:"stsSecretAccessKey,omitempty"`
|
||||
STSSessionToken string `json:"stsSessionToken,omitempty"`
|
||||
AccountAccessKey string `json:"accountAccessKey,omitempty"`
|
||||
HideMenu bool `json:"hm,omitempty"`
|
||||
}
|
||||
|
||||
// SessionFeatures represents features stored in the session
|
||||
type SessionFeatures struct {
|
||||
HideMenu bool
|
||||
}
|
||||
|
||||
// SessionTokenAuthenticate takes a session token, decode it, extract claims and validate the signature
|
||||
@@ -96,14 +102,18 @@ func SessionTokenAuthenticate(token string) (*TokenClaims, error) {
|
||||
|
||||
// NewEncryptedTokenForClient generates a new session token with claims based on the provided STS credentials, first
|
||||
// encrypts the claims and the sign them
|
||||
func NewEncryptedTokenForClient(credentials *credentials.Value, accountAccessKey string) (string, error) {
|
||||
func NewEncryptedTokenForClient(credentials *credentials.Value, accountAccessKey string, features *SessionFeatures) (string, error) {
|
||||
if credentials != nil {
|
||||
encryptedClaims, err := encryptClaims(&TokenClaims{
|
||||
tokenClaims := &TokenClaims{
|
||||
STSAccessKeyID: credentials.AccessKeyID,
|
||||
STSSecretAccessKey: credentials.SecretAccessKey,
|
||||
STSSessionToken: credentials.SessionToken,
|
||||
AccountAccessKey: accountAccessKey,
|
||||
})
|
||||
}
|
||||
if features != nil {
|
||||
tokenClaims.HideMenu = features.HideMenu
|
||||
}
|
||||
encryptedClaims, err := encryptClaims(tokenClaims)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -36,14 +36,14 @@ func TestNewJWTWithClaimsForClient(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : NewEncryptedTokenForClient() is generated correctly without errors
|
||||
function := "NewEncryptedTokenForClient()"
|
||||
token, err := NewEncryptedTokenForClient(creds, "")
|
||||
token, err := NewEncryptedTokenForClient(creds, "", nil)
|
||||
if err != nil || token == "" {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err)
|
||||
}
|
||||
// saving token for future tests
|
||||
goodToken = token
|
||||
// Test-2 : NewEncryptedTokenForClient() throws error because of empty credentials
|
||||
if _, err = NewEncryptedTokenForClient(nil, ""); err != nil {
|
||||
if _, err = NewEncryptedTokenForClient(nil, "", nil); err != nil {
|
||||
funcAssert.Equal("provided credentials are empty", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +129,7 @@ export const IAM_PAGES = {
|
||||
TOOLS_LOGS: "/tools/logs",
|
||||
TOOLS_AUDITLOGS: "/tools/audit-logs",
|
||||
TOOLS_TRACE: "/tools/trace",
|
||||
METRICS: "/tools/metrics",
|
||||
DASHBOARD: "/tools/dashboard",
|
||||
DASHBOARD: "/tools/metrics",
|
||||
TOOLS_HEAL: "/tools/heal",
|
||||
TOOLS_WATCH: "/tools/watch",
|
||||
/* Health */
|
||||
@@ -178,6 +177,8 @@ export const IAM_PAGES = {
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/summary",
|
||||
NAMESPACE_TENANT_METRICS:
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/metrics",
|
||||
NAMESPACE_TENANT_TRACE:
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/trace",
|
||||
NAMESPACE_TENANT_POOLS:
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/pools",
|
||||
NAMESPACE_TENANT_VOLUMES:
|
||||
@@ -297,9 +298,6 @@ export const IAM_PAGES_PERMISSIONS = {
|
||||
[IAM_PAGES.DASHBOARD]: [
|
||||
IAM_SCOPES.ADMIN_SERVER_INFO, // displays dashboard information
|
||||
],
|
||||
[IAM_PAGES.METRICS]: [
|
||||
IAM_SCOPES.ADMIN_SERVER_INFO, // displays dashboard information
|
||||
],
|
||||
[IAM_PAGES.POLICIES_VIEW]: [
|
||||
IAM_SCOPES.ADMIN_DELETE_POLICY,
|
||||
IAM_SCOPES.ADMIN_LIST_GROUPS,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import React, { Fragment } from "react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import { connect } from "react-redux";
|
||||
import Grid from "@mui/material/Grid";
|
||||
@@ -38,6 +38,7 @@ interface IPageHeader {
|
||||
managerObjects?: IFileItem[];
|
||||
toggleList: typeof toggleList;
|
||||
middleComponent?: React.ReactNode;
|
||||
features: string[];
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -88,7 +89,11 @@ const PageHeader = ({
|
||||
managerObjects,
|
||||
toggleList,
|
||||
middleComponent,
|
||||
features,
|
||||
}: IPageHeader) => {
|
||||
if (features.includes("hide-menu")) {
|
||||
return <Fragment />;
|
||||
}
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
@@ -157,6 +162,7 @@ const mapState = (state: AppState) => ({
|
||||
sidebarOpen: state.system.sidebarOpen,
|
||||
operatorMode: state.system.operatorMode,
|
||||
managerObjects: state.objectBrowser.objectManager.objectsToManage,
|
||||
features: state.console.session.features,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
|
||||
@@ -56,7 +56,6 @@ const Heal = React.lazy(() => import("./Heal/Heal"));
|
||||
const Watch = React.lazy(() => import("./Watch/Watch"));
|
||||
const HealthInfo = React.lazy(() => import("./HealthInfo/HealthInfo"));
|
||||
const Storage = React.lazy(() => import("./Storage/Storage"));
|
||||
const Metrics = React.lazy(() => import("./Dashboard/Metrics"));
|
||||
const Hop = React.lazy(() => import("./Tenants/TenantDetails/hop/Hop"));
|
||||
|
||||
const AddTenant = React.lazy(() => import("./Tenants/AddTenant/AddTenant"));
|
||||
@@ -211,10 +210,6 @@ const Console = ({
|
||||
component: Dashboard,
|
||||
path: IAM_PAGES.DASHBOARD,
|
||||
},
|
||||
{
|
||||
component: Metrics,
|
||||
path: IAM_PAGES.METRICS,
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: IAM_PAGES.ADD_BUCKETS,
|
||||
@@ -433,6 +428,11 @@ const Console = ({
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_METRICS,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_TRACE,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: IAM_PAGES.NAMESPACE_TENANT_PODS_LIST,
|
||||
@@ -513,7 +513,7 @@ const Console = ({
|
||||
const location = useLocation();
|
||||
|
||||
let hideMenu = false;
|
||||
if (location.pathname === IAM_PAGES.METRICS) {
|
||||
if (features?.includes("hide-menu")) {
|
||||
hideMenu = true;
|
||||
} else if (location.pathname.endsWith("/hop")) {
|
||||
hideMenu = true;
|
||||
|
||||
@@ -84,6 +84,10 @@ const ConsoleKBar = ({
|
||||
operatorMode: boolean;
|
||||
features: string[] | null;
|
||||
}) => {
|
||||
if (features?.includes("hide-menu")) {
|
||||
return <Console />;
|
||||
}
|
||||
|
||||
const allowedMenuItems = validRoutes(features, operatorMode);
|
||||
|
||||
const initialActions = [];
|
||||
|
||||
@@ -1,91 +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/>.
|
||||
|
||||
import React, { Fragment, useCallback, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import get from "lodash/get";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { LinearProgress } from "@mui/material";
|
||||
import api from "../../../common/api";
|
||||
import { Usage } from "./types";
|
||||
import { setErrorSnackMessage } from "../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import PrDashboard from "./Prometheus/PrDashboard";
|
||||
import BasicDashboard from "./BasicDashboard/BasicDashboard";
|
||||
|
||||
interface IMetricsSimple {
|
||||
classes: any;
|
||||
displayErrorMessage: typeof setErrorSnackMessage;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const Metrics = ({ classes, displayErrorMessage }: IMetricsSimple) => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [basicResult, setBasicResult] = useState<Usage | null>(null);
|
||||
|
||||
const fetchUsage = useCallback(() => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/admin/info`)
|
||||
.then((res: Usage) => {
|
||||
setBasicResult(res);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
displayErrorMessage(err);
|
||||
setLoading(false);
|
||||
});
|
||||
}, [setBasicResult, setLoading, displayErrorMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
fetchUsage();
|
||||
}
|
||||
}, [loading, fetchUsage]);
|
||||
|
||||
const widgets = get(basicResult, "widgets", null);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Grid container>
|
||||
{loading ? (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
) : (
|
||||
<Fragment>
|
||||
{widgets !== null ? (
|
||||
<Grid container>
|
||||
<PrDashboard />
|
||||
</Grid>
|
||||
) : (
|
||||
<BasicDashboard usage={basicResult} />
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const connector = connect(null, {
|
||||
displayErrorMessage: setErrorSnackMessage,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(Metrics));
|
||||
@@ -50,7 +50,6 @@ import BackLink from "../../../../common/BackLink";
|
||||
import VerticalTabs from "../../Common/VerticalTabs/VerticalTabs";
|
||||
import BoxIconButton from "../../Common/BoxIconButton/BoxIconButton";
|
||||
import withSuspense from "../../Common/Components/withSuspense";
|
||||
import PVCDetails from "./pvcs/PVCDetails";
|
||||
|
||||
const TenantYAML = withSuspense(React.lazy(() => import("./TenantYAML")));
|
||||
const TenantSummary = withSuspense(React.lazy(() => import("./TenantSummary")));
|
||||
@@ -63,6 +62,10 @@ const VolumesSummary = withSuspense(
|
||||
React.lazy(() => import("./VolumesSummary"))
|
||||
);
|
||||
const TenantMetrics = withSuspense(React.lazy(() => import("./TenantMetrics")));
|
||||
const TenantTrace = withSuspense(React.lazy(() => import("./TenantTrace")));
|
||||
const TenantVolumes = withSuspense(
|
||||
React.lazy(() => import("./pvcs/TenantVolumes"))
|
||||
);
|
||||
const TenantSecurity = withSuspense(
|
||||
React.lazy(() => import("./TenantSecurity"))
|
||||
);
|
||||
@@ -424,6 +427,10 @@ const TenantDetails = ({
|
||||
path="/namespaces/:tenantNamespace/tenants/:tenantName/metrics"
|
||||
component={TenantMetrics}
|
||||
/>
|
||||
<Route
|
||||
path="/namespaces/:tenantNamespace/tenants/:tenantName/trace"
|
||||
component={TenantTrace}
|
||||
/>
|
||||
<Route
|
||||
path="/namespaces/:tenantNamespace/tenants/:tenantName/security"
|
||||
component={TenantSecurity}
|
||||
@@ -442,7 +449,7 @@ const TenantDetails = ({
|
||||
/>
|
||||
<Route
|
||||
path="/namespaces/:tenantNamespace/tenants/:tenantName/pvcs/:PVCName"
|
||||
component={PVCDetails}
|
||||
component={TenantVolumes}
|
||||
/>
|
||||
<Route
|
||||
path="/namespaces/:tenantNamespace/tenants/:tenantName/volumes"
|
||||
|
||||
@@ -28,6 +28,7 @@ import { ITenant } from "../ListTenants/types";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { AppState } from "../../../../store";
|
||||
import { LinearProgress } from "@mui/material";
|
||||
import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
|
||||
|
||||
interface ITenantMetrics {
|
||||
classes: any;
|
||||
@@ -66,7 +67,7 @@ const TenantMetrics = ({ classes, match }: ITenantMetrics) => {
|
||||
<iframe
|
||||
className={classes.iframeStyle}
|
||||
title={"metrics"}
|
||||
src={`/api/proxy/${tenantNamespace}/${tenantName}/metrics?cp=y`}
|
||||
src={`/api/proxy/${tenantNamespace}/${tenantName}${IAM_PAGES.DASHBOARD}?cp=y`}
|
||||
onLoad={() => {
|
||||
setLoading(false);
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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, { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import get from "lodash/get";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import {
|
||||
containerForHeader,
|
||||
tenantDetailsStyles,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { ITenant } from "../ListTenants/types";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { AppState } from "../../../../store";
|
||||
import { LinearProgress } from "@mui/material";
|
||||
import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
|
||||
|
||||
interface ITenantTrace {
|
||||
classes: any;
|
||||
match: any;
|
||||
tenant: ITenant | null;
|
||||
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...tenantDetailsStyles,
|
||||
iframeStyle: {
|
||||
border: "0px",
|
||||
flex: "1 1 auto",
|
||||
minHeight: "800px",
|
||||
width: "100%",
|
||||
},
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const TenantTrace = ({ classes, match }: ITenantTrace) => {
|
||||
const tenantName = match.params["tenantName"];
|
||||
const tenantNamespace = match.params["tenantNamespace"];
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1 className={classes.sectionTitle}>Metrics</h1>
|
||||
{loading && (
|
||||
<div style={{ marginTop: "80px" }}>
|
||||
<LinearProgress />
|
||||
</div>
|
||||
)}
|
||||
<iframe
|
||||
className={classes.iframeStyle}
|
||||
title={"metrics"}
|
||||
src={`/api/proxy/${tenantNamespace}/${tenantName}${IAM_PAGES.TOOLS_TRACE}?cp=y`}
|
||||
onLoad={() => {
|
||||
setLoading(false);
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
loadingTenant: state.tenants.tenantDetails.loadingTenant,
|
||||
selectedTenant: state.tenants.tenantDetails.currentTenant,
|
||||
tenant: state.tenants.tenantDetails.tenantInfo,
|
||||
logEnabled: get(state.tenants.tenantDetails.tenantInfo, "logEnabled", false),
|
||||
monitoringEnabled: get(
|
||||
state.tenants.tenantDetails.tenantInfo,
|
||||
"monitoringEnabled",
|
||||
false
|
||||
),
|
||||
encryptionEnabled: get(
|
||||
state.tenants.tenantDetails.tenantInfo,
|
||||
"encryptionEnabled",
|
||||
false
|
||||
),
|
||||
adEnabled: get(state.tenants.tenantDetails.tenantInfo, "idpAdEnabled", false),
|
||||
oidcEnabled: get(
|
||||
state.tenants.tenantDetails.tenantInfo,
|
||||
"idpOidcEnabled",
|
||||
false
|
||||
),
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
setErrorSnackMessage,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(TenantTrace));
|
||||
@@ -43,7 +43,7 @@ const styles = (theme: Theme) =>
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const PVCDetails = ({
|
||||
const TenantVolumes = ({
|
||||
classes,
|
||||
match,
|
||||
setErrorSnackMessage,
|
||||
@@ -98,4 +98,4 @@ const PVCDetails = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(PVCDetails);
|
||||
export default withStyles(styles)(TenantVolumes);
|
||||
@@ -23,6 +23,6 @@ fixture("For user with default permissions").page("http://localhost:9090");
|
||||
test("Login to Operator Web Page", async (t) => {
|
||||
await t
|
||||
.navigateTo("http://localhost:9090/login")
|
||||
.typeText("#jwt","anyrandompasswordwillwork")
|
||||
.click("button.MuiButton-root")
|
||||
});
|
||||
.typeText("#jwt", "anyrandompasswordwillwork")
|
||||
.click("button.MuiButton-root");
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import * as constants from "./constants";
|
||||
import { Selector } from "testcafe";
|
||||
import { IAM_PAGES } from "../../src/common/SecureComponent/permissions";
|
||||
|
||||
//----------------------------------------------------
|
||||
// General sidebar element
|
||||
@@ -32,7 +33,7 @@ export const monitoringElement = Selector(".MuiPaper-root")
|
||||
export const monitoringChildren = Selector("#tools-children");
|
||||
export const dashboardElement = monitoringChildren
|
||||
.find("a")
|
||||
.withAttribute("href", "/tools/dashboard");
|
||||
.withAttribute("href", IAM_PAGES.DASHBOARD);
|
||||
export const logsElement = monitoringChildren
|
||||
.find("a")
|
||||
.withAttribute("href", "/tools/logs");
|
||||
|
||||
@@ -79,6 +79,7 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
|
||||
STSSecretAccessKey: claims.STSSecretAccessKey,
|
||||
STSSessionToken: claims.STSSessionToken,
|
||||
AccountAccessKey: claims.AccountAccessKey,
|
||||
Hm: claims.HideMenu,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5018,6 +5018,14 @@ func init() {
|
||||
"accessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"features": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hide_menu": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secretKey": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -5467,6 +5475,10 @@ func init() {
|
||||
},
|
||||
"accountAccessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"hm": {
|
||||
"description": "Hide Console Menu",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -10422,6 +10434,14 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginRequestFeatures": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hide_menu": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SubnetRegTokenResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11583,6 +11603,14 @@ func init() {
|
||||
"accessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"features": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hide_menu": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secretKey": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -12032,6 +12060,10 @@ func init() {
|
||||
},
|
||||
"accountAccessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"hm": {
|
||||
"description": "Hide Console Menu",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/minio/console/pkg/auth"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/minio/console/models"
|
||||
@@ -80,7 +82,7 @@ func getChangePasswordResponse(session *models.Principal, params user_api.Accoun
|
||||
return nil, prepareError(errInvalidCredentials, nil, err)
|
||||
}
|
||||
// authenticate user and generate new session token
|
||||
sessionID, err := login(credentials)
|
||||
sessionID, err := login(credentials, &auth.SessionFeatures{HideMenu: session.Hm})
|
||||
if err != nil {
|
||||
return nil, prepareError(errInvalidCredentials, nil, err)
|
||||
}
|
||||
|
||||
@@ -74,14 +74,14 @@ func registerLoginHandlers(api *operations.ConsoleAPI) {
|
||||
|
||||
// login performs a check of ConsoleCredentials against MinIO, generates some claims and returns the jwt
|
||||
// for subsequent authentication
|
||||
func login(credentials ConsoleCredentialsI) (*string, error) {
|
||||
func login(credentials ConsoleCredentialsI, sessionFeatures *auth.SessionFeatures) (*string, error) {
|
||||
// try to obtain consoleCredentials,
|
||||
tokens, err := credentials.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// if we made it here, the consoleCredentials work, generate a jwt with claims
|
||||
token, err := auth.NewEncryptedTokenForClient(&tokens, credentials.GetAccountAccessKey())
|
||||
token, err := auth.NewEncryptedTokenForClient(&tokens, credentials.GetAccountAccessKey(), sessionFeatures)
|
||||
if err != nil {
|
||||
LogError("error authenticating user: %v", err)
|
||||
return nil, errInvalidCredentials
|
||||
@@ -115,11 +115,15 @@ func getConsoleCredentials(accessKey, secretKey string) (*ConsoleCredentials, er
|
||||
// getLoginResponse performs login() and serializes it to the handler's output
|
||||
func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, *models.Error) {
|
||||
// prepare console credentials
|
||||
consolCreds, err := getConsoleCredentials(*lr.AccessKey, *lr.SecretKey)
|
||||
consoleCreds, err := getConsoleCredentials(*lr.AccessKey, *lr.SecretKey)
|
||||
if err != nil {
|
||||
return nil, prepareError(err, errInvalidCredentials, err)
|
||||
}
|
||||
sessionID, err := login(consolCreds)
|
||||
sf := &auth.SessionFeatures{}
|
||||
if lr.Features != nil {
|
||||
sf.HideMenu = lr.Features.HideMenu
|
||||
}
|
||||
sessionID, err := login(consoleCreds, sf)
|
||||
if err != nil {
|
||||
return nil, prepareError(err, errInvalidCredentials, err)
|
||||
}
|
||||
@@ -185,7 +189,7 @@ func getLoginOauth2AuthResponse(r *http.Request, lr *models.LoginOauth2AuthReque
|
||||
token, err := login(&ConsoleCredentials{
|
||||
ConsoleCredentials: userCredentials,
|
||||
AccountAccessKey: "",
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func TestLogin(t *testing.T) {
|
||||
SignerType: 0,
|
||||
}, nil
|
||||
}
|
||||
token, err := login(consoleCredentials)
|
||||
token, err := login(consoleCredentials, nil)
|
||||
funcAssert.NotEmpty(token, "Token was returned empty")
|
||||
funcAssert.Nil(err, "error creating a session")
|
||||
|
||||
@@ -73,7 +73,7 @@ func TestLogin(t *testing.T) {
|
||||
consoleCredentialsGetMock = func() (credentials.Value, error) {
|
||||
return credentials.Value{}, errors.New("")
|
||||
}
|
||||
_, err = login(consoleCredentials)
|
||||
_, err = login(consoleCredentials, nil)
|
||||
funcAssert.NotNil(err, "not error returned creating a session")
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
sessionResp := &models.SessionResponse{
|
||||
Features: getListOfEnabledFeatures(),
|
||||
Features: getListOfEnabledFeatures(session),
|
||||
Status: models.SessionResponseStatusOk,
|
||||
Operator: false,
|
||||
DistributedMode: isErasureMode(),
|
||||
@@ -230,7 +230,7 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
|
||||
}
|
||||
|
||||
// getListOfEnabledFeatures returns a list of features
|
||||
func getListOfEnabledFeatures() []string {
|
||||
func getListOfEnabledFeatures(session *models.Principal) []string {
|
||||
features := []string{}
|
||||
logSearchURL := getLogSearchURL()
|
||||
oidcEnabled := oauth2.IsIDPEnabled()
|
||||
@@ -246,5 +246,9 @@ func getListOfEnabledFeatures() []string {
|
||||
features = append(features, "ldap-idp", "external-idp")
|
||||
}
|
||||
|
||||
if session.Hm {
|
||||
features = append(features, "hide-menu")
|
||||
}
|
||||
|
||||
return features
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ securityDefinitions:
|
||||
tokenUrl: http://min.io
|
||||
# Apply the key security definition to all APIs
|
||||
security:
|
||||
- key: []
|
||||
- key: [ ]
|
||||
paths:
|
||||
/login:
|
||||
get:
|
||||
@@ -35,7 +35,7 @@ paths:
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
# Exclude this API from the authentication requirement
|
||||
security: []
|
||||
security: [ ]
|
||||
tags:
|
||||
- UserAPI
|
||||
post:
|
||||
@@ -55,7 +55,7 @@ paths:
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
# Exclude this API from the authentication requirement
|
||||
security: []
|
||||
security: [ ]
|
||||
tags:
|
||||
- UserAPI
|
||||
/login/oauth2/auth:
|
||||
@@ -75,7 +75,7 @@ paths:
|
||||
description: Generic error response.
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
security: []
|
||||
security: [ ]
|
||||
tags:
|
||||
- UserAPI
|
||||
|
||||
@@ -2579,7 +2579,7 @@ paths:
|
||||
- name: order
|
||||
in: query
|
||||
type: string
|
||||
enum: [timeDesc, timeAsc]
|
||||
enum: [ timeDesc, timeAsc ]
|
||||
default: timeDesc
|
||||
- name: timeStart
|
||||
in: query
|
||||
@@ -3383,7 +3383,7 @@ definitions:
|
||||
properties:
|
||||
loginStrategy:
|
||||
type: string
|
||||
enum: [form, redirect, service-account]
|
||||
enum: [ form, redirect, service-account ]
|
||||
redirect:
|
||||
type: string
|
||||
loginOauth2AuthRequest:
|
||||
@@ -3406,6 +3406,11 @@ definitions:
|
||||
type: string
|
||||
secretKey:
|
||||
type: string
|
||||
features:
|
||||
type: object
|
||||
properties:
|
||||
hide_menu:
|
||||
type: boolean
|
||||
loginResponse:
|
||||
type: object
|
||||
properties:
|
||||
@@ -3423,6 +3428,8 @@ definitions:
|
||||
type: string
|
||||
accountAccessKey:
|
||||
type: string
|
||||
hm:
|
||||
type: boolean
|
||||
startProfilingItem:
|
||||
type: object
|
||||
properties:
|
||||
@@ -3469,7 +3476,7 @@ definitions:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: [ok]
|
||||
enum: [ ok ]
|
||||
operator:
|
||||
type: boolean
|
||||
distributedMode:
|
||||
@@ -3490,7 +3497,7 @@ definitions:
|
||||
type: string
|
||||
values:
|
||||
type: array
|
||||
items: {}
|
||||
items: { }
|
||||
resultTarget:
|
||||
type: object
|
||||
properties:
|
||||
@@ -3746,7 +3753,7 @@ definitions:
|
||||
secretKey:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
type: string
|
||||
remoteBucket:
|
||||
type: object
|
||||
required:
|
||||
@@ -3772,7 +3779,7 @@ definitions:
|
||||
type: string
|
||||
service:
|
||||
type: string
|
||||
enum: [replication]
|
||||
enum: [ replication ]
|
||||
syncMode:
|
||||
type: string
|
||||
bandwidth:
|
||||
|
||||
@@ -19,7 +19,7 @@ securityDefinitions:
|
||||
tokenUrl: http://min.io
|
||||
# Apply the key security definition to all APIs
|
||||
security:
|
||||
- key: []
|
||||
- key: [ ]
|
||||
paths:
|
||||
/login:
|
||||
get:
|
||||
@@ -35,7 +35,7 @@ paths:
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
# Exclude this API from the authentication requirement
|
||||
security: []
|
||||
security: [ ]
|
||||
tags:
|
||||
- UserAPI
|
||||
/login/operator:
|
||||
@@ -55,7 +55,7 @@ paths:
|
||||
description: Generic error response.
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
security: []
|
||||
security: [ ]
|
||||
tags:
|
||||
- UserAPI
|
||||
|
||||
@@ -76,7 +76,7 @@ paths:
|
||||
description: Generic error response.
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
security: []
|
||||
security: [ ]
|
||||
tags:
|
||||
- UserAPI
|
||||
|
||||
@@ -781,13 +781,13 @@ paths:
|
||||
200:
|
||||
description: A successful response.
|
||||
schema:
|
||||
$ref: "#/definitions/tenantMonitoringInfo"
|
||||
$ref: "#/definitions/tenantMonitoringInfo"
|
||||
default:
|
||||
description: Generic error response.
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
tags:
|
||||
- OperatorAPI
|
||||
- OperatorAPI
|
||||
put:
|
||||
summary: Set Prometheus monitoring fields for tenant
|
||||
operationId: SetTenantMonitoring
|
||||
@@ -813,7 +813,7 @@ paths:
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
tags:
|
||||
- OperatorAPI
|
||||
- OperatorAPI
|
||||
|
||||
/namespaces/{namespace}/tenants/{tenant}/certificates:
|
||||
put:
|
||||
@@ -1199,7 +1199,7 @@ definitions:
|
||||
properties:
|
||||
loginStrategy:
|
||||
type: string
|
||||
enum: [form, redirect, service-account]
|
||||
enum: [ form, redirect, service-account ]
|
||||
redirect:
|
||||
type: string
|
||||
loginRequest:
|
||||
@@ -1212,6 +1212,11 @@ definitions:
|
||||
type: string
|
||||
secretKey:
|
||||
type: string
|
||||
features:
|
||||
type: object
|
||||
properties:
|
||||
hide_menu:
|
||||
type: boolean
|
||||
loginOauth2AuthRequest:
|
||||
type: object
|
||||
required:
|
||||
@@ -1238,7 +1243,7 @@ definitions:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: [ok]
|
||||
enum: [ ok ]
|
||||
operator:
|
||||
type: boolean
|
||||
permissions:
|
||||
@@ -1895,7 +1900,7 @@ definitions:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
|
||||
|
||||
tenantPod:
|
||||
type: object
|
||||
required:
|
||||
@@ -2551,12 +2556,12 @@ definitions:
|
||||
logCPURequest:
|
||||
type: string
|
||||
logMemRequest:
|
||||
type: string
|
||||
type: string
|
||||
logDBCPURequest:
|
||||
type: string
|
||||
logDBMemRequest:
|
||||
type: string
|
||||
|
||||
type: string
|
||||
|
||||
|
||||
listPVCsResponse:
|
||||
type: object
|
||||
@@ -2622,14 +2627,14 @@ definitions:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
|
||||
|
||||
tenantMonitoringInfo:
|
||||
type: object
|
||||
properties:
|
||||
prometheusEnabled:
|
||||
type: boolean
|
||||
type: boolean
|
||||
toggle:
|
||||
type: boolean
|
||||
type: boolean
|
||||
image:
|
||||
type: string
|
||||
sidecarImage:
|
||||
@@ -2651,14 +2656,14 @@ definitions:
|
||||
items:
|
||||
$ref: "#/definitions/nodeSelector"
|
||||
serviceAccountName:
|
||||
type: string
|
||||
type: string
|
||||
storageClassName:
|
||||
type: string
|
||||
type: string
|
||||
monitoringCPURequest:
|
||||
type: string
|
||||
type: string
|
||||
monitoringMemRequest:
|
||||
type: string
|
||||
|
||||
type: string
|
||||
|
||||
label:
|
||||
type: object
|
||||
properties:
|
||||
@@ -2674,7 +2679,7 @@ definitions:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
|
||||
|
||||
nodeSelector:
|
||||
type: object
|
||||
properties:
|
||||
@@ -2687,7 +2692,7 @@ definitions:
|
||||
type: object
|
||||
properties:
|
||||
prometheusEnabled:
|
||||
type: boolean
|
||||
type: boolean
|
||||
|
||||
formatConfiguration:
|
||||
type: object
|
||||
|
||||
Reference in New Issue
Block a user