Display override settings with env variables (#2636)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2023-02-10 13:28:54 -06:00
committed by GitHub
parent 7bf2b9601f
commit d4031ee7b5
11 changed files with 496 additions and 97 deletions

View File

@@ -25,6 +25,7 @@ package models
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
@@ -34,6 +35,9 @@ import (
// swagger:model configurationKV
type ConfigurationKV struct {
// env override
EnvOverride *EnvOverride `json:"env_override,omitempty"`
// key
Key string `json:"key,omitempty"`
@@ -43,11 +47,64 @@ type ConfigurationKV struct {
// Validate validates this configuration k v
func (m *ConfigurationKV) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateEnvOverride(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validates this configuration k v based on context it is used
func (m *ConfigurationKV) validateEnvOverride(formats strfmt.Registry) error {
if swag.IsZero(m.EnvOverride) { // not required
return nil
}
if m.EnvOverride != nil {
if err := m.EnvOverride.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("env_override")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("env_override")
}
return err
}
}
return nil
}
// ContextValidate validate this configuration k v based on the context it is used
func (m *ConfigurationKV) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateEnvOverride(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ConfigurationKV) contextValidateEnvOverride(ctx context.Context, formats strfmt.Registry) error {
if m.EnvOverride != nil {
if err := m.EnvOverride.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("env_override")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("env_override")
}
return err
}
}
return nil
}

70
models/env_override.go Normal file
View File

@@ -0,0 +1,70 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// EnvOverride env override
//
// swagger:model envOverride
type EnvOverride struct {
// name
Name string `json:"name,omitempty"`
// value
Value string `json:"value,omitempty"`
}
// Validate validates this env override
func (m *EnvOverride) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this env override based on context it is used
func (m *EnvOverride) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *EnvOverride) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *EnvOverride) UnmarshalBinary(b []byte) error {
var res EnvOverride
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -49,9 +49,15 @@ export interface IConfigurationElement {
url?: string;
}
export interface IEnvOverride {
name: string;
value: string;
}
export interface IElementValue {
key: string;
value: string;
env_override?: IEnvOverride;
}
export interface IConfigurationSys {
@@ -65,3 +71,12 @@ export interface IElement {
icon?: any;
disabled?: boolean;
}
export interface OverrideValue {
value: string;
overrideEnv: string;
}
export interface IOverrideEnv {
[key: string]: OverrideValue;
}

View File

@@ -22,7 +22,7 @@ import FindReplaceIcon from "@mui/icons-material/FindReplace";
import VpnKeyIcon from "@mui/icons-material/VpnKey";
import PendingActionsIcon from "@mui/icons-material/PendingActions";
import CallToActionIcon from "@mui/icons-material/CallToAction";
import { IElement, IElementValue } from "./types";
import { IElement, IElementValue, IOverrideEnv, OverrideValue } from "./types";
export const configurationElements: IElement[] = [
{
@@ -309,3 +309,21 @@ export const selectSAs = (
setSelectedSAs(elements);
return elements;
};
export const overrideFields = (formFields: IElementValue[]): IOverrideEnv => {
let overrideReturn: IOverrideEnv = {};
formFields.forEach((envItem) => {
// it has override values, we construct the value
if (envItem.env_override) {
const value: OverrideValue = {
value: envItem.env_override.value,
overrideEnv: envItem.env_override.name,
};
overrideReturn = { ...overrideReturn, [envItem.key]: value };
}
});
return overrideReturn;
};

View File

@@ -19,7 +19,7 @@ import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import Grid from "@mui/material/Grid";
import { IElementValue, KVField } from "../Configurations/types";
import { IElementValue, IOverrideEnv, KVField } from "../Configurations/types";
import {
formFieldStyles,
modalBasic,
@@ -28,11 +28,14 @@ import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWr
import CSVMultiSelector from "../Common/FormComponents/CSVMultiSelector/CSVMultiSelector";
import CommentBoxWrapper from "../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import PredefinedList from "../Common/FormComponents/PredefinedList/PredefinedList";
import { ConsoleIcon, Tooltip } from "mds";
interface IConfGenericProps {
onChange: (newValue: IElementValue[]) => void;
fields: KVField[];
defaultVals?: IElementValue[];
overrideEnv?: IOverrideEnv;
classes: any;
}
@@ -69,6 +72,7 @@ const ConfTargetGeneric = ({
onChange,
fields,
defaultVals,
overrideEnv,
classes,
}: IConfGenericProps) => {
const [valueHolder, setValueHolder] = useState<IElementValue[]>([]);
@@ -102,9 +106,42 @@ const ConfTargetGeneric = ({
};
const fieldDefinition = (field: KVField, item: number) => {
const holderItem = valueHolder[item];
if (holderItem) {
// Override Value with env var, we display generic string component
const override = overrideEnv?.[`${holderItem.key}`];
if (override) {
return (
<PredefinedList
label={field.label}
content={override.value}
actionButton={
<Grid
item
sx={{
display: "flex",
justifyContent: "flex-end",
paddingRight: "10px",
}}
>
<Tooltip
tooltip={`This value is set from the ${override.overrideEnv} environment variable`}
placement={"left"}
>
<ConsoleIcon style={{ width: 20 }} />
</Tooltip>
</Grid>
}
/>
);
}
}
switch (field.type) {
case "on|off":
const value = valueHolder[item] ? valueHolder[item].value : "off";
const value = holderItem ? holderItem.value : "off";
return (
<FormSwitchWrapper
@@ -123,7 +160,7 @@ const ConfTargetGeneric = ({
case "csv":
return (
<CSVMultiSelector
elements={valueHolder[item] ? valueHolder[item].value : ""}
elements={holderItem ? holderItem.value : ""}
label={field.label}
name={field.name}
onChange={(value: string) => {
@@ -141,7 +178,7 @@ const ConfTargetGeneric = ({
name={field.name}
label={field.label}
tooltip={field.tooltip}
value={valueHolder[item] ? valueHolder[item].value : ""}
value={holderItem ? holderItem.value : ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValueElement(field.name, e.target.value, item)
}
@@ -155,7 +192,7 @@ const ConfTargetGeneric = ({
name={field.name}
label={field.label}
tooltip={field.tooltip}
value={valueHolder[item] ? valueHolder[item].value : ""}
value={holderItem ? holderItem.value : ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValueElement(field.name, e.target.value, item)
}

View File

@@ -32,12 +32,14 @@ import {
} from "../../Common/FormComponents/common/styleLibrary";
import {
fieldsConfigurations,
overrideFields,
removeEmptyFields,
} from "../../Configurations/utils";
import {
IConfigurationElement,
IConfigurationSys,
IElementValue,
IOverrideEnv,
} from "../../Configurations/types";
import { ErrorResponseHandler } from "../../../../common/types";
import ResetConfigurationModal from "./ResetConfigurationModal";
@@ -89,6 +91,7 @@ const EditConfiguration = ({
);
const [resetConfigurationOpen, setResetConfigurationOpen] =
useState<boolean>(false);
const [overrideEnvs, setOverrideEnvs] = useState<IOverrideEnv>({});
const loadingConfig = useSelector(
(state: AppState) => state.system.loadingConfigurations
@@ -109,6 +112,7 @@ const EditConfiguration = ({
setConfigSubsysList(res);
const keyVals = get(res[0], "key_values", []);
setConfigValues(keyVals);
setOverrideEnvs(overrideFields(keyVals));
dispatch(configurationIsLoading(false));
})
.catch((err: ErrorResponseHandler) => {
@@ -221,6 +225,7 @@ const EditConfiguration = ({
}
onChange={onValueChange}
defaultVals={configValues}
overrideEnv={overrideEnvs}
/>
</Grid>
<Grid

View File

@@ -15,7 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import { Button, Grid } from "mds";
import { Button, ConsoleIcon, Grid, Tooltip } from "mds";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import { Webhook } from "@mui/icons-material";
@@ -34,6 +34,8 @@ import { useAppDispatch } from "../../../../store";
import { LinearProgress } from "@mui/material";
import { IConfigurationSys } from "../../Configurations/types";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import PredefinedList from "../../Common/FormComponents/PredefinedList/PredefinedList";
import { overrideFields } from "../../Configurations/utils";
interface IEndpointModal {
open: boolean;
@@ -166,6 +168,11 @@ const EditEndpointModal = ({
};
const defaultWH = !name.includes(":");
const hasOverride = endpointInfo.key_values.filter(
(itm) => !!itm.env_override
);
const overrideValues = overrideFields(hasOverride);
let title = "Edit Webhook";
let icon = <Webhook />;
@@ -181,6 +188,10 @@ const EditEndpointModal = ({
break;
}
if (hasOverride.length > 0) {
title = "View env variable Webhook";
}
return (
<Fragment>
<ModalWrapper
@@ -189,89 +200,179 @@ const EditEndpointModal = ({
onClose={onCloseEndpoint}
titleIcon={icon}
>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<FormSwitchWrapper
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.checked ? "on" : "off";
setEndpointState(value);
}}
id={"endpoint_enabled"}
name={"endpoint_enabled"}
label={"Enabled"}
value={"switch_on"}
checked={endpointState === "on"}
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
id="endpoint"
name="endpoint"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setEndpoint(event.target.value);
validateInput("endpoint", event.target.validity.valid);
}}
error={
invalidInputs.includes("endpoint") ? "Invalid Endpoint set" : ""
}
label="Endpoint"
value={endpoint}
pattern={
"^(https?):\\/\\/([a-zA-Z0-9\\-.]+)(:[0-9]+)?(\\/[a-zA-Z0-9\\-.\\/]*)?$"
}
required
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
id="auth-token"
name="auth-token"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setAuthToken(event.target.value);
}}
label="Auth Token"
value={authToken}
/>
</Grid>
{saving && (
<Grid
item
xs={12}
sx={{
marginBottom: 10,
}}
>
<LinearProgress />
</Grid>
{hasOverride.length > 0 ? (
<Fragment>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<PredefinedList
label={"Enabled"}
content={overrideValues.enable?.value || "-"}
actionButton={
<Grid
item
sx={{
display: "flex",
justifyContent: "flex-end",
paddingRight: "10px",
}}
>
<Tooltip
tooltip={
overrideValues.enable
? `This value is set from the ${overrideValues.enable.overrideEnv} environment variable`
: ""
}
placement={"left"}
>
<ConsoleIcon style={{ width: 20 }} />
</Tooltip>
</Grid>
}
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<PredefinedList
label={"Endpoint"}
content={overrideValues.endpoint?.value || "-"}
actionButton={
<Grid
item
sx={{
display: "flex",
justifyContent: "flex-end",
paddingRight: "10px",
}}
>
<Tooltip
tooltip={
overrideValues.enable
? `This value is set from the ${overrideValues.endpoint.overrideEnv} environment variable`
: ""
}
placement={"left"}
>
<ConsoleIcon style={{ width: 20 }} />
</Tooltip>
</Grid>
}
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<PredefinedList
label={"Auth Token"}
content={overrideValues.auth_token?.value || "-"}
actionButton={
<Grid
item
sx={{
display: "flex",
justifyContent: "flex-end",
paddingRight: "10px",
}}
>
<Tooltip
tooltip={
overrideValues.enable
? `This value is set from the ${overrideValues.auth_token.overrideEnv} environment variable`
: ""
}
placement={"left"}
>
<ConsoleIcon style={{ width: 20 }} />
</Tooltip>
</Grid>
}
/>
</Grid>
</Fragment>
) : (
<Fragment>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<FormSwitchWrapper
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.checked ? "on" : "off";
setEndpointState(value);
}}
id={"endpoint_enabled"}
name={"endpoint_enabled"}
label={"Enabled"}
value={"switch_on"}
checked={endpointState === "on"}
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
id="endpoint"
name="endpoint"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setEndpoint(event.target.value);
validateInput("endpoint", event.target.validity.valid);
}}
error={
invalidInputs.includes("endpoint")
? "Invalid Endpoint set"
: ""
}
label="Endpoint"
value={endpoint}
pattern={
"^(https?):\\/\\/([a-zA-Z0-9\\-.]+)(:[0-9]+)?(\\/[a-zA-Z0-9\\-.\\/]*)?$"
}
required
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
id="auth-token"
name="auth-token"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setAuthToken(event.target.value);
}}
label="Auth Token"
value={authToken}
/>
</Grid>
{saving && (
<Grid
item
xs={12}
sx={{
marginBottom: 10,
}}
>
<LinearProgress />
</Grid>
)}
<Grid
item
xs={12}
sx={{
display: "flex",
justifyContent: "flex-end",
}}
>
<Button
id={"reset"}
type="button"
variant="regular"
disabled={saving}
onClick={onCloseEndpoint}
label={"Cancel"}
sx={{
marginRight: 10,
}}
/>
<Button
id={"save-lifecycle"}
type="submit"
variant="callAction"
color="primary"
disabled={saving || invalidInputs.length !== 0}
label={"Update"}
onClick={updateWebhook}
/>
</Grid>
</Fragment>
)}
<Grid
item
xs={12}
sx={{
display: "flex",
justifyContent: "flex-end",
}}
>
<Button
id={"reset"}
type="button"
variant="regular"
disabled={saving}
onClick={onCloseEndpoint}
label={"Cancel"}
sx={{
marginRight: 10,
}}
/>
<Button
id={"save-lifecycle"}
type="submit"
variant="callAction"
color="primary"
disabled={saving || invalidInputs.length !== 0}
label={"Update"}
onClick={updateWebhook}
/>
</Grid>
</ModalWrapper>
</Fragment>
);

View File

@@ -16,7 +16,14 @@
import React, { Fragment, useState } from "react";
import { IConfigurationSys, IElementValue } from "../../Configurations/types";
import { Button, DataTable, Grid, TierOfflineIcon, TierOnlineIcon } from "mds";
import {
Button,
ConsoleIcon,
DataTable,
Grid,
TierOfflineIcon,
TierOnlineIcon,
} from "mds";
import AddEndpointModal from "./AddEndpointModal";
import DeleteWebhookEndpoint from "./DeleteWebhookEndpoint";
import EditWebhookEndpoint from "./EditWebhookEndpoint";
@@ -43,6 +50,10 @@ const WebhookSettings = ({
const endpointFilter = item.find((itm) => itm.key === "endpoint");
if (endpointFilter) {
if (endpointFilter.env_override) {
return endpointFilter.env_override.value;
}
return endpointFilter.value;
}
@@ -52,8 +63,32 @@ const WebhookSettings = ({
const renderWebhookStatus = (item: IElementValue[]) => {
const EnableFilter = item.find((itm) => itm.key === "enable");
if (EnableFilter?.env_override) {
const overrideEnabled =
!EnableFilter?.env_override.value ||
EnableFilter?.env_override.value === "on" ||
!EnableFilter?.env_override.value
? "Enabled"
: "Disabled";
return (
<Grid
container
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyItems: "start",
fontSize: "8px",
}}
>
<ConsoleIcon style={{ fill: "#052F51", width: "14px" }} />
{overrideEnabled ? "Enabled" : "Disabled"}
</Grid>
);
}
// If enable is not set, then enabled by default
if (!EnableFilter || EnableFilter.value === "on") {
if (!EnableFilter || EnableFilter.value === "on" || !EnableFilter.value) {
return (
<Grid
container
@@ -116,9 +151,27 @@ const WebhookSettings = ({
setSelectedARN(item.name);
}
},
disableButtonFunction: (item: string) => {
const wHook = WebhookSettingslist.find(
(element) => element.name === item
);
if (wHook) {
const hasOverride = wHook.key_values.filter(
(itm) => !!itm.env_override
);
// Has override values, we cannot delete.
if (hasOverride.length > 0) {
return true;
}
return false;
}
return false;
},
},
];
return (
<Grid container>
{newEndpointOpen && (
@@ -187,5 +240,4 @@ const WebhookSettings = ({
</Grid>
);
};
export default WebhookSettings;

View File

@@ -129,9 +129,16 @@ func getConfig(ctx context.Context, client MinioAdmin, name string) ([]*models.C
}
var confkv []*models.ConfigurationKV
for _, kv := range scfg.KV {
// FIXME: Ignoring env-overrides for now as support for this
// needs to be added for presentation.
confkv = append(confkv, &models.ConfigurationKV{Key: kv.Key, Value: kv.Value})
var envOverride *models.EnvOverride
if kv.EnvOverride != nil {
envOverride = &models.EnvOverride{
Name: kv.EnvOverride.Name,
Value: kv.EnvOverride.Value,
}
}
confkv = append(confkv, &models.ConfigurationKV{Key: kv.Key, Value: kv.Value, EnvOverride: envOverride})
}
if len(confkv) == 0 {
continue

View File

@@ -5870,6 +5870,9 @@ func init() {
"configurationKV": {
"type": "object",
"properties": {
"env_override": {
"$ref": "#/definitions/envOverride"
},
"key": {
"type": "string"
},
@@ -5939,6 +5942,17 @@ func init() {
}
}
},
"envOverride": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"environmentConstants": {
"type": "object",
"properties": {
@@ -14554,6 +14568,9 @@ func init() {
"configurationKV": {
"type": "object",
"properties": {
"env_override": {
"$ref": "#/definitions/envOverride"
},
"key": {
"type": "string"
},
@@ -14623,6 +14640,17 @@ func init() {
}
}
},
"envOverride": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"environmentConstants": {
"type": "object",
"properties": {

View File

@@ -3894,6 +3894,15 @@ definitions:
type: string
value:
type: string
env_override:
$ref: "#/definitions/envOverride"
envOverride:
type: object
properties:
name:
type: string
value:
type: string
configuration:
type: object
properties: