This is an automated email from the ASF dual-hosted git repository. marat pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
commit 19e66247b244e6e717a30c2ba8eacd8f0c6efd93 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Tue Nov 26 20:18:15 2024 -0500 Sensitive fields validator --- .../webui/src/designer/property/DslProperties.css | 9 +++++ .../webui/src/designer/property/DslProperties.tsx | 41 ++++++++++++++++------ .../src/designer/property/PropertiesHeader.tsx | 2 +- .../webui/src/designer/property/PropertyStore.ts | 6 ++++ .../property/property/ComponentPropertyField.tsx | 26 +++++++++++--- .../property/property/DslPropertyField.tsx | 38 ++++++++++++++++++-- .../property/property/KameletPropertyField.tsx | 37 ++++++++++++++----- .../src/designer/property/DslProperties.css | 9 +++++ .../src/designer/property/DslProperties.tsx | 41 ++++++++++++++++------ .../src/designer/property/PropertiesHeader.tsx | 2 +- .../src/designer/property/PropertyStore.ts | 6 ++++ .../property/property/ComponentPropertyField.tsx | 26 +++++++++++--- .../property/property/DslPropertyField.tsx | 38 ++++++++++++++++++-- .../property/property/KameletPropertyField.tsx | 37 ++++++++++++++----- .../src/designer/property/DslProperties.css | 9 +++++ .../src/designer/property/DslProperties.tsx | 41 ++++++++++++++++------ .../src/designer/property/PropertiesHeader.tsx | 2 +- .../src/designer/property/PropertyStore.ts | 6 ++++ .../property/property/ComponentPropertyField.tsx | 26 +++++++++++--- .../property/property/DslPropertyField.tsx | 38 ++++++++++++++++++-- .../property/property/KameletPropertyField.tsx | 37 ++++++++++++++----- 21 files changed, 402 insertions(+), 75 deletions(-) diff --git a/karavan-app/src/main/webui/src/designer/property/DslProperties.css b/karavan-app/src/main/webui/src/designer/property/DslProperties.css index e2401cf3..6e867105 100644 --- a/karavan-app/src/main/webui/src/designer/property/DslProperties.css +++ b/karavan-app/src/main/webui/src/designer/property/DslProperties.css @@ -276,3 +276,12 @@ background-color: yellow; } +.karavan .properties .value-sensitive-invalid { + background-color: red; +} + +.karavan .properties .property-selector .pf-v5-c-toggle-group__button { + padding-left: 6px; + padding-right: 6px; +} + diff --git a/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx b/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx index 9511b1f8..dd2376ee 100644 --- a/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx +++ b/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx @@ -14,15 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import { + Button, + ExpandableSection, Form, Text, - Title, + TextInputGroup, + TextInputGroupMain, + TextInputGroupUtilities, TextVariants, - ExpandableSection, - Button, - Tooltip, ToggleGroupItem, ToggleGroup, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, + Title, + ToggleGroup, + ToggleGroupItem, + Tooltip, } from '@patternfly/react-core'; import '../karavan.css'; import './DslProperties.css'; @@ -63,11 +68,18 @@ export function DslProperties(props: Props) { const [selectedStep, dark] = useDesignerStore((s) => [s.selectedStep, s.dark], shallow) - const [propertyFilter, changedOnly, requiredOnly, setChangedOnly, setPropertyFilter, setRequiredOnly] - = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, s.requiredOnly, s.setChangedOnly, s.setPropertyFilter, s.setRequiredOnly], shallow) + const [propertyFilter, changedOnly, requiredOnly, setChangedOnly, sensitiveOnly, setSensitiveOnly, setPropertyFilter, setRequiredOnly] + = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, s.requiredOnly, s.setChangedOnly, s.sensitiveOnly, s.setSensitiveOnly, s.setPropertyFilter, s.setRequiredOnly], shallow) const [showAdvanced, setShowAdvanced] = useState<boolean>(false); + useEffect(() => { + setRequiredOnly(false); + setChangedOnly(false); + setSensitiveOnly(false); + setPropertyFilter(''); + }, [selectedStep?.uuid]) + function getClonableElementHeader(): React.JSX.Element { const title = selectedStep && CamelDisplayUtil.getTitle(selectedStep); const description = selectedStep?.dslName ? CamelMetadataApi.getCamelModelMetadataByClassName(selectedStep?.dslName)?.description : title; @@ -130,7 +142,10 @@ export function DslProperties(props: Props) { propertyMetas = propertyMetas.filter(p => p.name === 'parameters' || p.required); } if (changedOnly) { - propertyMetas = propertyMetas.filter(p =>p.name === 'parameters' || PropertyUtil.hasDslPropertyValueChanged(p, getPropertyValue(p))); + propertyMetas = propertyMetas.filter(p => p.name === 'parameters' || PropertyUtil.hasDslPropertyValueChanged(p, getPropertyValue(p))); + } + if (sensitiveOnly) { + propertyMetas = propertyMetas.filter(p => p.name === 'parameters' || p.secret); } return propertyMetas } @@ -144,7 +159,7 @@ export function DslProperties(props: Props) { function getPropertySelector() { return ( <div style={{display: 'flex', flexDirection: 'row', alignItems: 'center', gap: '3px', pointerEvents: 'initial', opacity: '1'}}> - <ToggleGroup aria-label="properties selctor"> + <ToggleGroup className='property-selector' aria-label="properties selctor"> <ToggleGroupItem text="Required" buttonId="requiredOnly" @@ -157,6 +172,12 @@ export function DslProperties(props: Props) { isSelected={changedOnly} onChange={(_, selected) => setChangedOnly(selected)} /> + <ToggleGroupItem + text="Sensitive" + buttonId="sensitiveOnly" + isSelected={sensitiveOnly} + onChange={(_, selected) => setSensitiveOnly(selected)} + /> </ToggleGroup> <TextInputGroup> <TextInputGroupMain @@ -179,7 +200,7 @@ export function DslProperties(props: Props) { } function getPropertySelectorChanged(): boolean { - return requiredOnly || changedOnly || propertyFilter?.trim().length > 0; + return requiredOnly || changedOnly || sensitiveOnly || propertyFilter?.trim().length > 0; } function getShowExpanded(): boolean { diff --git a/karavan-app/src/main/webui/src/designer/property/PropertiesHeader.tsx b/karavan-app/src/main/webui/src/designer/property/PropertiesHeader.tsx index 00a3e1ee..ac7a3b3d 100644 --- a/karavan-app/src/main/webui/src/designer/property/PropertiesHeader.tsx +++ b/karavan-app/src/main/webui/src/designer/property/PropertiesHeader.tsx @@ -25,7 +25,7 @@ import { MenuToggle, DropdownList, DropdownItem, Flex, Popover, FlexItem, Badge, ClipboardCopy, - Switch, Radio, Tooltip, + Switch, Tooltip, } from '@patternfly/react-core'; import '../karavan.css'; import './DslProperties.css'; diff --git a/karavan-app/src/main/webui/src/designer/property/PropertyStore.ts b/karavan-app/src/main/webui/src/designer/property/PropertyStore.ts index b9d0e511..3515ec0a 100644 --- a/karavan-app/src/main/webui/src/designer/property/PropertyStore.ts +++ b/karavan-app/src/main/webui/src/designer/property/PropertyStore.ts @@ -24,11 +24,14 @@ interface PropertiesState { setRequiredOnly: (requiredOnly: boolean) => void changedOnly: boolean; setChangedOnly: (changedOnly: boolean) => void + sensitiveOnly: boolean; + setSensitiveOnly: (sensitiveOnly: boolean) => void } export const usePropertiesStore = createWithEqualityFn<PropertiesState>((set, get) => ({ requiredOnly: false, changedOnly: false, + sensitiveOnly: false, propertyFilter: '', setPropertyFilter: (propertyFilter: string) => { set({propertyFilter: propertyFilter}); @@ -39,4 +42,7 @@ export const usePropertiesStore = createWithEqualityFn<PropertiesState>((set, ge setChangedOnly: (changedOnly: boolean) => { set({changedOnly: changedOnly}); }, + setSensitiveOnly: (sensitiveOnly: boolean) => { + set({sensitiveOnly: sensitiveOnly}); + }, }), shallow) diff --git a/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx b/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx index 57ff7d4c..f6227cc1 100644 --- a/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx +++ b/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx @@ -27,7 +27,7 @@ import { InputGroupItem, TextInputGroup, TextVariants, - Text + Text, ValidatedOptions, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; import { Select, @@ -45,8 +45,6 @@ import {ToDefinition} from "karavan-core/lib/model/CamelDefinition"; import {InfrastructureSelector} from "./InfrastructureSelector"; import {InfrastructureAPI} from "../../utils/InfrastructureAPI"; import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon"; -import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; -import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; import {usePropertiesHook} from "../usePropertiesHook"; import {useDesignerStore, useIntegrationStore} from "../../DesignerStore"; @@ -57,6 +55,8 @@ import {ExpressionModalEditor} from "../../../expression/ExpressionModalEditor"; import {PropertyPlaceholderDropdown} from "./PropertyPlaceholderDropdown"; import {INTERNAL_COMPONENTS} from "karavan-core/lib/api/ComponentApi"; import {PropertyUtil} from "./PropertyUtil"; +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; +import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon"; const prefix = "parameters"; const beanPrefix = "#bean:"; @@ -101,7 +101,6 @@ export function ComponentPropertyField(props: Props) { }, [checkChanges, textValue]) function parametersChanged(parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) { - console.log("parametersChange", parameter, value); setCheckChanges(false); onParametersChange(parameter, value, pathParameter, newRoute); setSelectStatus(new Map<string, boolean>([[parameter, false]])) @@ -266,6 +265,7 @@ export function ComponentPropertyField(props: Props) { {(!showEditor || property.secret) && <TextInput className="text-field" isRequired ref={ref} type="text" + validated={validated} autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} @@ -315,6 +315,7 @@ export function ComponentPropertyField(props: Props) { <TextInput className="text-field" isRequired type="text" + validated={validated} autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} @@ -387,6 +388,7 @@ export function ComponentPropertyField(props: Props) { id={property.name + "-placeholder"} name={property.name + "-placeholder"} type="text" + validated={validated} aria-label="placeholder" value={!isValueBoolean ? textValue?.toString() : ''} onBlur={_ => onParametersChange(property.name, textValue)} @@ -417,8 +419,23 @@ export function ComponentPropertyField(props: Props) { ) } + function getValidationHelper() { + return ( + validated !== ValidatedOptions.default + ? <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={validated}> + {'Must be a placeholder {{ }} or secret {{secret:name/key}}'} + </HelperTextItem> + </HelperText> + </FormHelperText> + : <></> + ) + } + const property: ComponentProperty = props.property; const value = props.value; + const validated = (property.secret && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; return ( <FormGroup key={id} @@ -453,6 +470,7 @@ export function ComponentPropertyField(props: Props) { {property.type === 'boolean' && getSwitch(property)} {getInfrastructureSelectorModal()} + {getValidationHelper()} </FormGroup> ) } diff --git a/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx b/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx index 5331f8fb..18090154 100644 --- a/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx +++ b/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx @@ -33,7 +33,15 @@ import { Card, InputGroup, SelectOptionProps, - capitalize, InputGroupItem, TextVariants, ToggleGroup, ToggleGroupItem + capitalize, + InputGroupItem, + TextVariants, + ToggleGroup, + ToggleGroupItem, + ValidatedOptions, + FormHelperText, + HelperText, + HelperTextItem } from '@patternfly/react-core'; import { Select, @@ -83,6 +91,8 @@ import {SelectField} from "./SelectField"; import {PropertyUtil} from "./PropertyUtil"; import {usePropertiesStore} from "../PropertyStore"; import {Property} from "karavan-core/lib/model/KameletModels"; +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; +import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon"; const beanPrefix = "#bean:"; const classPrefix = "#class:"; @@ -103,7 +113,8 @@ export function DslPropertyField(props: Props) { const [integration, setIntegration, addVariable, files] = useIntegrationStore((s) => [s.integration, s.setIntegration, s.addVariable, s.files], shallow) const [dark, setSelectedStep, beans] = useDesignerStore((s) => [s.dark, s.setSelectedStep, s.beans], shallow) - const [propertyFilter, changedOnly, requiredOnly] = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, s.requiredOnly], shallow) + const [propertyFilter, changedOnly, requiredOnly, sensitiveOnly] = usePropertiesStore((s) => + [s.propertyFilter, s.changedOnly, s.requiredOnly, s.sensitiveOnly], shallow) const [isShowAdvanced, setIsShowAdvanced] = useState<string[]>([]); const [arrayValues, setArrayValues] = useState<Map<string, string>>(new Map<string, string>()); @@ -638,6 +649,7 @@ export function DslPropertyField(props: Props) { id={property.name + "-placeholder"} name={property.name + "-placeholder"} type="text" + validated={validated} aria-label="placeholder" value={!isValueBoolean ? textValue?.toString() : ''} onBlur={_ => propertyChanged(property.name, textValue)} @@ -918,6 +930,9 @@ export function DslPropertyField(props: Props) { if (changedOnly) { properties = properties.filter(p => PropertyUtil.hasKameletPropertyValueChanged(p, getKameletPropertyValue(p))); } + if (sensitiveOnly) { + properties = properties.filter(p => p.format == "password"); + } return properties; } @@ -1038,6 +1053,9 @@ export function DslPropertyField(props: Props) { if (changedOnly) { componentProperties = componentProperties.filter(p => PropertyUtil.hasComponentPropertyValueChanged(p, getComponentPropertyValue(p))); } + if (sensitiveOnly) { + componentProperties = componentProperties.filter(p => p.secret); + } return componentProperties } @@ -1082,11 +1100,26 @@ export function DslPropertyField(props: Props) { return false; } + function getValidationHelper() { + return ( + validated !== ValidatedOptions.default + ? <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={validated}> + {'Must be a placeholder {{ }} or secret {{secret:name/key}}'} + </HelperTextItem> + </HelperText> + </FormHelperText> + : <></> + ) + } + const element = props.element; const isKamelet = CamelUtil.isKameletComponent(element); const isRouteTemplate = element?.dslName === 'RouteTemplateDefinition'; const property: PropertyMeta = props.property; const value = props.value; + const validated = (property.secret && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; const isVariable = getIsVariable(); const beanConstructors = element?.dslName === 'BeanFactoryDefinition' && property.name === 'constructors' const beanProperties = element?.dslName === 'BeanFactoryDefinition' && property.name === 'properties' @@ -1143,6 +1176,7 @@ export function DslPropertyField(props: Props) { {!isKamelet && property.name === 'parameters' && getComponentParameters(property)} {beanConstructors && getBeanProperties('constructors')} {beanProperties && getBeanProperties('properties')} + {getValidationHelper()} </FormGroup> {getInfrastructureSelectorModal()} </> diff --git a/karavan-app/src/main/webui/src/designer/property/property/KameletPropertyField.tsx b/karavan-app/src/main/webui/src/designer/property/property/KameletPropertyField.tsx index d70582b5..b91289c1 100644 --- a/karavan-app/src/main/webui/src/designer/property/property/KameletPropertyField.tsx +++ b/karavan-app/src/main/webui/src/designer/property/property/KameletPropertyField.tsx @@ -16,19 +16,22 @@ */ import React, {useEffect, useRef, useState} from 'react'; import { - FormGroup, - TextInput, - Popover, - Switch, InputGroup, Button, Tooltip, capitalize, Text, TextVariants, InputGroupItem + InputGroup, + Button, + Tooltip, + capitalize, + Text, + TextVariants, + InputGroupItem, + ValidatedOptions, + FormHelperText, HelperText, HelperTextItem, TextInput, FormGroup, Popover, Switch } from '@patternfly/react-core'; import '../../karavan.css'; import "@patternfly/patternfly/patternfly.css"; -import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; +import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import {Property} from "karavan-core/lib/model/KameletModels"; import {InfrastructureSelector} from "./InfrastructureSelector"; import {InfrastructureAPI} from "../../utils/InfrastructureAPI"; -import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; -import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon"; import {usePropertiesHook} from "../usePropertiesHook"; import {Select, SelectDirection, SelectOption, SelectVariant} from "@patternfly/react-core/deprecated"; @@ -38,6 +41,8 @@ import EditorIcon from "@patternfly/react-icons/dist/js/icons/code-icon"; import {ExpressionModalEditor} from "../../../expression/ExpressionModalEditor"; import {useDesignerStore} from "../../DesignerStore"; import {shallow} from "zustand/shallow"; +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; +import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; interface Props { property: Property, @@ -171,9 +176,10 @@ export function KameletPropertyField(props: Props) { ref={ref} className="text-field" isRequired type='text' + validated={validated} autoComplete="off" id={id} name={id} - value={textValue} + value={textValue || ''} onBlur={_ => { if (isNumeric((textValue))) { parametersChanged(property.id, Number(textValue)) @@ -240,9 +246,23 @@ export function KameletPropertyField(props: Props) { </div> ) } + function getValidationHelper() { + return ( + validated !== ValidatedOptions.default + ? <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={validated}> + {'Must be a placeholder {{ }} or secret {{secret:name/key}}'} + </HelperTextItem> + </HelperText> + </FormHelperText> + : <></> + ) + } const property = props.property; const value = props.value; + const validated = (property.format === 'password' && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; const prefix = "parameters"; const id = prefix + "-" + property.id; return ( @@ -279,6 +299,7 @@ export function KameletPropertyField(props: Props) { isChecked={Boolean(value) === true} onChange={e => parametersChanged(property.id, !Boolean(value))}/> } + {getValidationHelper()} </FormGroup> {getInfrastructureSelectorModal()} </div> diff --git a/karavan-designer/src/designer/property/DslProperties.css b/karavan-designer/src/designer/property/DslProperties.css index e2401cf3..6e867105 100644 --- a/karavan-designer/src/designer/property/DslProperties.css +++ b/karavan-designer/src/designer/property/DslProperties.css @@ -276,3 +276,12 @@ background-color: yellow; } +.karavan .properties .value-sensitive-invalid { + background-color: red; +} + +.karavan .properties .property-selector .pf-v5-c-toggle-group__button { + padding-left: 6px; + padding-right: 6px; +} + diff --git a/karavan-designer/src/designer/property/DslProperties.tsx b/karavan-designer/src/designer/property/DslProperties.tsx index 9511b1f8..dd2376ee 100644 --- a/karavan-designer/src/designer/property/DslProperties.tsx +++ b/karavan-designer/src/designer/property/DslProperties.tsx @@ -14,15 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import { + Button, + ExpandableSection, Form, Text, - Title, + TextInputGroup, + TextInputGroupMain, + TextInputGroupUtilities, TextVariants, - ExpandableSection, - Button, - Tooltip, ToggleGroupItem, ToggleGroup, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, + Title, + ToggleGroup, + ToggleGroupItem, + Tooltip, } from '@patternfly/react-core'; import '../karavan.css'; import './DslProperties.css'; @@ -63,11 +68,18 @@ export function DslProperties(props: Props) { const [selectedStep, dark] = useDesignerStore((s) => [s.selectedStep, s.dark], shallow) - const [propertyFilter, changedOnly, requiredOnly, setChangedOnly, setPropertyFilter, setRequiredOnly] - = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, s.requiredOnly, s.setChangedOnly, s.setPropertyFilter, s.setRequiredOnly], shallow) + const [propertyFilter, changedOnly, requiredOnly, setChangedOnly, sensitiveOnly, setSensitiveOnly, setPropertyFilter, setRequiredOnly] + = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, s.requiredOnly, s.setChangedOnly, s.sensitiveOnly, s.setSensitiveOnly, s.setPropertyFilter, s.setRequiredOnly], shallow) const [showAdvanced, setShowAdvanced] = useState<boolean>(false); + useEffect(() => { + setRequiredOnly(false); + setChangedOnly(false); + setSensitiveOnly(false); + setPropertyFilter(''); + }, [selectedStep?.uuid]) + function getClonableElementHeader(): React.JSX.Element { const title = selectedStep && CamelDisplayUtil.getTitle(selectedStep); const description = selectedStep?.dslName ? CamelMetadataApi.getCamelModelMetadataByClassName(selectedStep?.dslName)?.description : title; @@ -130,7 +142,10 @@ export function DslProperties(props: Props) { propertyMetas = propertyMetas.filter(p => p.name === 'parameters' || p.required); } if (changedOnly) { - propertyMetas = propertyMetas.filter(p =>p.name === 'parameters' || PropertyUtil.hasDslPropertyValueChanged(p, getPropertyValue(p))); + propertyMetas = propertyMetas.filter(p => p.name === 'parameters' || PropertyUtil.hasDslPropertyValueChanged(p, getPropertyValue(p))); + } + if (sensitiveOnly) { + propertyMetas = propertyMetas.filter(p => p.name === 'parameters' || p.secret); } return propertyMetas } @@ -144,7 +159,7 @@ export function DslProperties(props: Props) { function getPropertySelector() { return ( <div style={{display: 'flex', flexDirection: 'row', alignItems: 'center', gap: '3px', pointerEvents: 'initial', opacity: '1'}}> - <ToggleGroup aria-label="properties selctor"> + <ToggleGroup className='property-selector' aria-label="properties selctor"> <ToggleGroupItem text="Required" buttonId="requiredOnly" @@ -157,6 +172,12 @@ export function DslProperties(props: Props) { isSelected={changedOnly} onChange={(_, selected) => setChangedOnly(selected)} /> + <ToggleGroupItem + text="Sensitive" + buttonId="sensitiveOnly" + isSelected={sensitiveOnly} + onChange={(_, selected) => setSensitiveOnly(selected)} + /> </ToggleGroup> <TextInputGroup> <TextInputGroupMain @@ -179,7 +200,7 @@ export function DslProperties(props: Props) { } function getPropertySelectorChanged(): boolean { - return requiredOnly || changedOnly || propertyFilter?.trim().length > 0; + return requiredOnly || changedOnly || sensitiveOnly || propertyFilter?.trim().length > 0; } function getShowExpanded(): boolean { diff --git a/karavan-designer/src/designer/property/PropertiesHeader.tsx b/karavan-designer/src/designer/property/PropertiesHeader.tsx index 00a3e1ee..ac7a3b3d 100644 --- a/karavan-designer/src/designer/property/PropertiesHeader.tsx +++ b/karavan-designer/src/designer/property/PropertiesHeader.tsx @@ -25,7 +25,7 @@ import { MenuToggle, DropdownList, DropdownItem, Flex, Popover, FlexItem, Badge, ClipboardCopy, - Switch, Radio, Tooltip, + Switch, Tooltip, } from '@patternfly/react-core'; import '../karavan.css'; import './DslProperties.css'; diff --git a/karavan-designer/src/designer/property/PropertyStore.ts b/karavan-designer/src/designer/property/PropertyStore.ts index b9d0e511..3515ec0a 100644 --- a/karavan-designer/src/designer/property/PropertyStore.ts +++ b/karavan-designer/src/designer/property/PropertyStore.ts @@ -24,11 +24,14 @@ interface PropertiesState { setRequiredOnly: (requiredOnly: boolean) => void changedOnly: boolean; setChangedOnly: (changedOnly: boolean) => void + sensitiveOnly: boolean; + setSensitiveOnly: (sensitiveOnly: boolean) => void } export const usePropertiesStore = createWithEqualityFn<PropertiesState>((set, get) => ({ requiredOnly: false, changedOnly: false, + sensitiveOnly: false, propertyFilter: '', setPropertyFilter: (propertyFilter: string) => { set({propertyFilter: propertyFilter}); @@ -39,4 +42,7 @@ export const usePropertiesStore = createWithEqualityFn<PropertiesState>((set, ge setChangedOnly: (changedOnly: boolean) => { set({changedOnly: changedOnly}); }, + setSensitiveOnly: (sensitiveOnly: boolean) => { + set({sensitiveOnly: sensitiveOnly}); + }, }), shallow) diff --git a/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx b/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx index 57ff7d4c..f6227cc1 100644 --- a/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx +++ b/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx @@ -27,7 +27,7 @@ import { InputGroupItem, TextInputGroup, TextVariants, - Text + Text, ValidatedOptions, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; import { Select, @@ -45,8 +45,6 @@ import {ToDefinition} from "karavan-core/lib/model/CamelDefinition"; import {InfrastructureSelector} from "./InfrastructureSelector"; import {InfrastructureAPI} from "../../utils/InfrastructureAPI"; import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon"; -import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; -import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; import {usePropertiesHook} from "../usePropertiesHook"; import {useDesignerStore, useIntegrationStore} from "../../DesignerStore"; @@ -57,6 +55,8 @@ import {ExpressionModalEditor} from "../../../expression/ExpressionModalEditor"; import {PropertyPlaceholderDropdown} from "./PropertyPlaceholderDropdown"; import {INTERNAL_COMPONENTS} from "karavan-core/lib/api/ComponentApi"; import {PropertyUtil} from "./PropertyUtil"; +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; +import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon"; const prefix = "parameters"; const beanPrefix = "#bean:"; @@ -101,7 +101,6 @@ export function ComponentPropertyField(props: Props) { }, [checkChanges, textValue]) function parametersChanged(parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) { - console.log("parametersChange", parameter, value); setCheckChanges(false); onParametersChange(parameter, value, pathParameter, newRoute); setSelectStatus(new Map<string, boolean>([[parameter, false]])) @@ -266,6 +265,7 @@ export function ComponentPropertyField(props: Props) { {(!showEditor || property.secret) && <TextInput className="text-field" isRequired ref={ref} type="text" + validated={validated} autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} @@ -315,6 +315,7 @@ export function ComponentPropertyField(props: Props) { <TextInput className="text-field" isRequired type="text" + validated={validated} autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} @@ -387,6 +388,7 @@ export function ComponentPropertyField(props: Props) { id={property.name + "-placeholder"} name={property.name + "-placeholder"} type="text" + validated={validated} aria-label="placeholder" value={!isValueBoolean ? textValue?.toString() : ''} onBlur={_ => onParametersChange(property.name, textValue)} @@ -417,8 +419,23 @@ export function ComponentPropertyField(props: Props) { ) } + function getValidationHelper() { + return ( + validated !== ValidatedOptions.default + ? <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={validated}> + {'Must be a placeholder {{ }} or secret {{secret:name/key}}'} + </HelperTextItem> + </HelperText> + </FormHelperText> + : <></> + ) + } + const property: ComponentProperty = props.property; const value = props.value; + const validated = (property.secret && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; return ( <FormGroup key={id} @@ -453,6 +470,7 @@ export function ComponentPropertyField(props: Props) { {property.type === 'boolean' && getSwitch(property)} {getInfrastructureSelectorModal()} + {getValidationHelper()} </FormGroup> ) } diff --git a/karavan-designer/src/designer/property/property/DslPropertyField.tsx b/karavan-designer/src/designer/property/property/DslPropertyField.tsx index 5331f8fb..18090154 100644 --- a/karavan-designer/src/designer/property/property/DslPropertyField.tsx +++ b/karavan-designer/src/designer/property/property/DslPropertyField.tsx @@ -33,7 +33,15 @@ import { Card, InputGroup, SelectOptionProps, - capitalize, InputGroupItem, TextVariants, ToggleGroup, ToggleGroupItem + capitalize, + InputGroupItem, + TextVariants, + ToggleGroup, + ToggleGroupItem, + ValidatedOptions, + FormHelperText, + HelperText, + HelperTextItem } from '@patternfly/react-core'; import { Select, @@ -83,6 +91,8 @@ import {SelectField} from "./SelectField"; import {PropertyUtil} from "./PropertyUtil"; import {usePropertiesStore} from "../PropertyStore"; import {Property} from "karavan-core/lib/model/KameletModels"; +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; +import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon"; const beanPrefix = "#bean:"; const classPrefix = "#class:"; @@ -103,7 +113,8 @@ export function DslPropertyField(props: Props) { const [integration, setIntegration, addVariable, files] = useIntegrationStore((s) => [s.integration, s.setIntegration, s.addVariable, s.files], shallow) const [dark, setSelectedStep, beans] = useDesignerStore((s) => [s.dark, s.setSelectedStep, s.beans], shallow) - const [propertyFilter, changedOnly, requiredOnly] = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, s.requiredOnly], shallow) + const [propertyFilter, changedOnly, requiredOnly, sensitiveOnly] = usePropertiesStore((s) => + [s.propertyFilter, s.changedOnly, s.requiredOnly, s.sensitiveOnly], shallow) const [isShowAdvanced, setIsShowAdvanced] = useState<string[]>([]); const [arrayValues, setArrayValues] = useState<Map<string, string>>(new Map<string, string>()); @@ -638,6 +649,7 @@ export function DslPropertyField(props: Props) { id={property.name + "-placeholder"} name={property.name + "-placeholder"} type="text" + validated={validated} aria-label="placeholder" value={!isValueBoolean ? textValue?.toString() : ''} onBlur={_ => propertyChanged(property.name, textValue)} @@ -918,6 +930,9 @@ export function DslPropertyField(props: Props) { if (changedOnly) { properties = properties.filter(p => PropertyUtil.hasKameletPropertyValueChanged(p, getKameletPropertyValue(p))); } + if (sensitiveOnly) { + properties = properties.filter(p => p.format == "password"); + } return properties; } @@ -1038,6 +1053,9 @@ export function DslPropertyField(props: Props) { if (changedOnly) { componentProperties = componentProperties.filter(p => PropertyUtil.hasComponentPropertyValueChanged(p, getComponentPropertyValue(p))); } + if (sensitiveOnly) { + componentProperties = componentProperties.filter(p => p.secret); + } return componentProperties } @@ -1082,11 +1100,26 @@ export function DslPropertyField(props: Props) { return false; } + function getValidationHelper() { + return ( + validated !== ValidatedOptions.default + ? <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={validated}> + {'Must be a placeholder {{ }} or secret {{secret:name/key}}'} + </HelperTextItem> + </HelperText> + </FormHelperText> + : <></> + ) + } + const element = props.element; const isKamelet = CamelUtil.isKameletComponent(element); const isRouteTemplate = element?.dslName === 'RouteTemplateDefinition'; const property: PropertyMeta = props.property; const value = props.value; + const validated = (property.secret && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; const isVariable = getIsVariable(); const beanConstructors = element?.dslName === 'BeanFactoryDefinition' && property.name === 'constructors' const beanProperties = element?.dslName === 'BeanFactoryDefinition' && property.name === 'properties' @@ -1143,6 +1176,7 @@ export function DslPropertyField(props: Props) { {!isKamelet && property.name === 'parameters' && getComponentParameters(property)} {beanConstructors && getBeanProperties('constructors')} {beanProperties && getBeanProperties('properties')} + {getValidationHelper()} </FormGroup> {getInfrastructureSelectorModal()} </> diff --git a/karavan-designer/src/designer/property/property/KameletPropertyField.tsx b/karavan-designer/src/designer/property/property/KameletPropertyField.tsx index d70582b5..b91289c1 100644 --- a/karavan-designer/src/designer/property/property/KameletPropertyField.tsx +++ b/karavan-designer/src/designer/property/property/KameletPropertyField.tsx @@ -16,19 +16,22 @@ */ import React, {useEffect, useRef, useState} from 'react'; import { - FormGroup, - TextInput, - Popover, - Switch, InputGroup, Button, Tooltip, capitalize, Text, TextVariants, InputGroupItem + InputGroup, + Button, + Tooltip, + capitalize, + Text, + TextVariants, + InputGroupItem, + ValidatedOptions, + FormHelperText, HelperText, HelperTextItem, TextInput, FormGroup, Popover, Switch } from '@patternfly/react-core'; import '../../karavan.css'; import "@patternfly/patternfly/patternfly.css"; -import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; +import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import {Property} from "karavan-core/lib/model/KameletModels"; import {InfrastructureSelector} from "./InfrastructureSelector"; import {InfrastructureAPI} from "../../utils/InfrastructureAPI"; -import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; -import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon"; import {usePropertiesHook} from "../usePropertiesHook"; import {Select, SelectDirection, SelectOption, SelectVariant} from "@patternfly/react-core/deprecated"; @@ -38,6 +41,8 @@ import EditorIcon from "@patternfly/react-icons/dist/js/icons/code-icon"; import {ExpressionModalEditor} from "../../../expression/ExpressionModalEditor"; import {useDesignerStore} from "../../DesignerStore"; import {shallow} from "zustand/shallow"; +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; +import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; interface Props { property: Property, @@ -171,9 +176,10 @@ export function KameletPropertyField(props: Props) { ref={ref} className="text-field" isRequired type='text' + validated={validated} autoComplete="off" id={id} name={id} - value={textValue} + value={textValue || ''} onBlur={_ => { if (isNumeric((textValue))) { parametersChanged(property.id, Number(textValue)) @@ -240,9 +246,23 @@ export function KameletPropertyField(props: Props) { </div> ) } + function getValidationHelper() { + return ( + validated !== ValidatedOptions.default + ? <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={validated}> + {'Must be a placeholder {{ }} or secret {{secret:name/key}}'} + </HelperTextItem> + </HelperText> + </FormHelperText> + : <></> + ) + } const property = props.property; const value = props.value; + const validated = (property.format === 'password' && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; const prefix = "parameters"; const id = prefix + "-" + property.id; return ( @@ -279,6 +299,7 @@ export function KameletPropertyField(props: Props) { isChecked={Boolean(value) === true} onChange={e => parametersChanged(property.id, !Boolean(value))}/> } + {getValidationHelper()} </FormGroup> {getInfrastructureSelectorModal()} </div> diff --git a/karavan-space/src/designer/property/DslProperties.css b/karavan-space/src/designer/property/DslProperties.css index e2401cf3..6e867105 100644 --- a/karavan-space/src/designer/property/DslProperties.css +++ b/karavan-space/src/designer/property/DslProperties.css @@ -276,3 +276,12 @@ background-color: yellow; } +.karavan .properties .value-sensitive-invalid { + background-color: red; +} + +.karavan .properties .property-selector .pf-v5-c-toggle-group__button { + padding-left: 6px; + padding-right: 6px; +} + diff --git a/karavan-space/src/designer/property/DslProperties.tsx b/karavan-space/src/designer/property/DslProperties.tsx index 9511b1f8..dd2376ee 100644 --- a/karavan-space/src/designer/property/DslProperties.tsx +++ b/karavan-space/src/designer/property/DslProperties.tsx @@ -14,15 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import { + Button, + ExpandableSection, Form, Text, - Title, + TextInputGroup, + TextInputGroupMain, + TextInputGroupUtilities, TextVariants, - ExpandableSection, - Button, - Tooltip, ToggleGroupItem, ToggleGroup, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, + Title, + ToggleGroup, + ToggleGroupItem, + Tooltip, } from '@patternfly/react-core'; import '../karavan.css'; import './DslProperties.css'; @@ -63,11 +68,18 @@ export function DslProperties(props: Props) { const [selectedStep, dark] = useDesignerStore((s) => [s.selectedStep, s.dark], shallow) - const [propertyFilter, changedOnly, requiredOnly, setChangedOnly, setPropertyFilter, setRequiredOnly] - = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, s.requiredOnly, s.setChangedOnly, s.setPropertyFilter, s.setRequiredOnly], shallow) + const [propertyFilter, changedOnly, requiredOnly, setChangedOnly, sensitiveOnly, setSensitiveOnly, setPropertyFilter, setRequiredOnly] + = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, s.requiredOnly, s.setChangedOnly, s.sensitiveOnly, s.setSensitiveOnly, s.setPropertyFilter, s.setRequiredOnly], shallow) const [showAdvanced, setShowAdvanced] = useState<boolean>(false); + useEffect(() => { + setRequiredOnly(false); + setChangedOnly(false); + setSensitiveOnly(false); + setPropertyFilter(''); + }, [selectedStep?.uuid]) + function getClonableElementHeader(): React.JSX.Element { const title = selectedStep && CamelDisplayUtil.getTitle(selectedStep); const description = selectedStep?.dslName ? CamelMetadataApi.getCamelModelMetadataByClassName(selectedStep?.dslName)?.description : title; @@ -130,7 +142,10 @@ export function DslProperties(props: Props) { propertyMetas = propertyMetas.filter(p => p.name === 'parameters' || p.required); } if (changedOnly) { - propertyMetas = propertyMetas.filter(p =>p.name === 'parameters' || PropertyUtil.hasDslPropertyValueChanged(p, getPropertyValue(p))); + propertyMetas = propertyMetas.filter(p => p.name === 'parameters' || PropertyUtil.hasDslPropertyValueChanged(p, getPropertyValue(p))); + } + if (sensitiveOnly) { + propertyMetas = propertyMetas.filter(p => p.name === 'parameters' || p.secret); } return propertyMetas } @@ -144,7 +159,7 @@ export function DslProperties(props: Props) { function getPropertySelector() { return ( <div style={{display: 'flex', flexDirection: 'row', alignItems: 'center', gap: '3px', pointerEvents: 'initial', opacity: '1'}}> - <ToggleGroup aria-label="properties selctor"> + <ToggleGroup className='property-selector' aria-label="properties selctor"> <ToggleGroupItem text="Required" buttonId="requiredOnly" @@ -157,6 +172,12 @@ export function DslProperties(props: Props) { isSelected={changedOnly} onChange={(_, selected) => setChangedOnly(selected)} /> + <ToggleGroupItem + text="Sensitive" + buttonId="sensitiveOnly" + isSelected={sensitiveOnly} + onChange={(_, selected) => setSensitiveOnly(selected)} + /> </ToggleGroup> <TextInputGroup> <TextInputGroupMain @@ -179,7 +200,7 @@ export function DslProperties(props: Props) { } function getPropertySelectorChanged(): boolean { - return requiredOnly || changedOnly || propertyFilter?.trim().length > 0; + return requiredOnly || changedOnly || sensitiveOnly || propertyFilter?.trim().length > 0; } function getShowExpanded(): boolean { diff --git a/karavan-space/src/designer/property/PropertiesHeader.tsx b/karavan-space/src/designer/property/PropertiesHeader.tsx index 00a3e1ee..ac7a3b3d 100644 --- a/karavan-space/src/designer/property/PropertiesHeader.tsx +++ b/karavan-space/src/designer/property/PropertiesHeader.tsx @@ -25,7 +25,7 @@ import { MenuToggle, DropdownList, DropdownItem, Flex, Popover, FlexItem, Badge, ClipboardCopy, - Switch, Radio, Tooltip, + Switch, Tooltip, } from '@patternfly/react-core'; import '../karavan.css'; import './DslProperties.css'; diff --git a/karavan-space/src/designer/property/PropertyStore.ts b/karavan-space/src/designer/property/PropertyStore.ts index b9d0e511..3515ec0a 100644 --- a/karavan-space/src/designer/property/PropertyStore.ts +++ b/karavan-space/src/designer/property/PropertyStore.ts @@ -24,11 +24,14 @@ interface PropertiesState { setRequiredOnly: (requiredOnly: boolean) => void changedOnly: boolean; setChangedOnly: (changedOnly: boolean) => void + sensitiveOnly: boolean; + setSensitiveOnly: (sensitiveOnly: boolean) => void } export const usePropertiesStore = createWithEqualityFn<PropertiesState>((set, get) => ({ requiredOnly: false, changedOnly: false, + sensitiveOnly: false, propertyFilter: '', setPropertyFilter: (propertyFilter: string) => { set({propertyFilter: propertyFilter}); @@ -39,4 +42,7 @@ export const usePropertiesStore = createWithEqualityFn<PropertiesState>((set, ge setChangedOnly: (changedOnly: boolean) => { set({changedOnly: changedOnly}); }, + setSensitiveOnly: (sensitiveOnly: boolean) => { + set({sensitiveOnly: sensitiveOnly}); + }, }), shallow) diff --git a/karavan-space/src/designer/property/property/ComponentPropertyField.tsx b/karavan-space/src/designer/property/property/ComponentPropertyField.tsx index 57ff7d4c..f6227cc1 100644 --- a/karavan-space/src/designer/property/property/ComponentPropertyField.tsx +++ b/karavan-space/src/designer/property/property/ComponentPropertyField.tsx @@ -27,7 +27,7 @@ import { InputGroupItem, TextInputGroup, TextVariants, - Text + Text, ValidatedOptions, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; import { Select, @@ -45,8 +45,6 @@ import {ToDefinition} from "karavan-core/lib/model/CamelDefinition"; import {InfrastructureSelector} from "./InfrastructureSelector"; import {InfrastructureAPI} from "../../utils/InfrastructureAPI"; import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon"; -import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; -import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; import {usePropertiesHook} from "../usePropertiesHook"; import {useDesignerStore, useIntegrationStore} from "../../DesignerStore"; @@ -57,6 +55,8 @@ import {ExpressionModalEditor} from "../../../expression/ExpressionModalEditor"; import {PropertyPlaceholderDropdown} from "./PropertyPlaceholderDropdown"; import {INTERNAL_COMPONENTS} from "karavan-core/lib/api/ComponentApi"; import {PropertyUtil} from "./PropertyUtil"; +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; +import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon"; const prefix = "parameters"; const beanPrefix = "#bean:"; @@ -101,7 +101,6 @@ export function ComponentPropertyField(props: Props) { }, [checkChanges, textValue]) function parametersChanged(parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) { - console.log("parametersChange", parameter, value); setCheckChanges(false); onParametersChange(parameter, value, pathParameter, newRoute); setSelectStatus(new Map<string, boolean>([[parameter, false]])) @@ -266,6 +265,7 @@ export function ComponentPropertyField(props: Props) { {(!showEditor || property.secret) && <TextInput className="text-field" isRequired ref={ref} type="text" + validated={validated} autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} @@ -315,6 +315,7 @@ export function ComponentPropertyField(props: Props) { <TextInput className="text-field" isRequired type="text" + validated={validated} autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} @@ -387,6 +388,7 @@ export function ComponentPropertyField(props: Props) { id={property.name + "-placeholder"} name={property.name + "-placeholder"} type="text" + validated={validated} aria-label="placeholder" value={!isValueBoolean ? textValue?.toString() : ''} onBlur={_ => onParametersChange(property.name, textValue)} @@ -417,8 +419,23 @@ export function ComponentPropertyField(props: Props) { ) } + function getValidationHelper() { + return ( + validated !== ValidatedOptions.default + ? <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={validated}> + {'Must be a placeholder {{ }} or secret {{secret:name/key}}'} + </HelperTextItem> + </HelperText> + </FormHelperText> + : <></> + ) + } + const property: ComponentProperty = props.property; const value = props.value; + const validated = (property.secret && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; return ( <FormGroup key={id} @@ -453,6 +470,7 @@ export function ComponentPropertyField(props: Props) { {property.type === 'boolean' && getSwitch(property)} {getInfrastructureSelectorModal()} + {getValidationHelper()} </FormGroup> ) } diff --git a/karavan-space/src/designer/property/property/DslPropertyField.tsx b/karavan-space/src/designer/property/property/DslPropertyField.tsx index 5331f8fb..18090154 100644 --- a/karavan-space/src/designer/property/property/DslPropertyField.tsx +++ b/karavan-space/src/designer/property/property/DslPropertyField.tsx @@ -33,7 +33,15 @@ import { Card, InputGroup, SelectOptionProps, - capitalize, InputGroupItem, TextVariants, ToggleGroup, ToggleGroupItem + capitalize, + InputGroupItem, + TextVariants, + ToggleGroup, + ToggleGroupItem, + ValidatedOptions, + FormHelperText, + HelperText, + HelperTextItem } from '@patternfly/react-core'; import { Select, @@ -83,6 +91,8 @@ import {SelectField} from "./SelectField"; import {PropertyUtil} from "./PropertyUtil"; import {usePropertiesStore} from "../PropertyStore"; import {Property} from "karavan-core/lib/model/KameletModels"; +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; +import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon"; const beanPrefix = "#bean:"; const classPrefix = "#class:"; @@ -103,7 +113,8 @@ export function DslPropertyField(props: Props) { const [integration, setIntegration, addVariable, files] = useIntegrationStore((s) => [s.integration, s.setIntegration, s.addVariable, s.files], shallow) const [dark, setSelectedStep, beans] = useDesignerStore((s) => [s.dark, s.setSelectedStep, s.beans], shallow) - const [propertyFilter, changedOnly, requiredOnly] = usePropertiesStore((s) => [s.propertyFilter, s.changedOnly, s.requiredOnly], shallow) + const [propertyFilter, changedOnly, requiredOnly, sensitiveOnly] = usePropertiesStore((s) => + [s.propertyFilter, s.changedOnly, s.requiredOnly, s.sensitiveOnly], shallow) const [isShowAdvanced, setIsShowAdvanced] = useState<string[]>([]); const [arrayValues, setArrayValues] = useState<Map<string, string>>(new Map<string, string>()); @@ -638,6 +649,7 @@ export function DslPropertyField(props: Props) { id={property.name + "-placeholder"} name={property.name + "-placeholder"} type="text" + validated={validated} aria-label="placeholder" value={!isValueBoolean ? textValue?.toString() : ''} onBlur={_ => propertyChanged(property.name, textValue)} @@ -918,6 +930,9 @@ export function DslPropertyField(props: Props) { if (changedOnly) { properties = properties.filter(p => PropertyUtil.hasKameletPropertyValueChanged(p, getKameletPropertyValue(p))); } + if (sensitiveOnly) { + properties = properties.filter(p => p.format == "password"); + } return properties; } @@ -1038,6 +1053,9 @@ export function DslPropertyField(props: Props) { if (changedOnly) { componentProperties = componentProperties.filter(p => PropertyUtil.hasComponentPropertyValueChanged(p, getComponentPropertyValue(p))); } + if (sensitiveOnly) { + componentProperties = componentProperties.filter(p => p.secret); + } return componentProperties } @@ -1082,11 +1100,26 @@ export function DslPropertyField(props: Props) { return false; } + function getValidationHelper() { + return ( + validated !== ValidatedOptions.default + ? <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={validated}> + {'Must be a placeholder {{ }} or secret {{secret:name/key}}'} + </HelperTextItem> + </HelperText> + </FormHelperText> + : <></> + ) + } + const element = props.element; const isKamelet = CamelUtil.isKameletComponent(element); const isRouteTemplate = element?.dslName === 'RouteTemplateDefinition'; const property: PropertyMeta = props.property; const value = props.value; + const validated = (property.secret && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; const isVariable = getIsVariable(); const beanConstructors = element?.dslName === 'BeanFactoryDefinition' && property.name === 'constructors' const beanProperties = element?.dslName === 'BeanFactoryDefinition' && property.name === 'properties' @@ -1143,6 +1176,7 @@ export function DslPropertyField(props: Props) { {!isKamelet && property.name === 'parameters' && getComponentParameters(property)} {beanConstructors && getBeanProperties('constructors')} {beanProperties && getBeanProperties('properties')} + {getValidationHelper()} </FormGroup> {getInfrastructureSelectorModal()} </> diff --git a/karavan-space/src/designer/property/property/KameletPropertyField.tsx b/karavan-space/src/designer/property/property/KameletPropertyField.tsx index d70582b5..b91289c1 100644 --- a/karavan-space/src/designer/property/property/KameletPropertyField.tsx +++ b/karavan-space/src/designer/property/property/KameletPropertyField.tsx @@ -16,19 +16,22 @@ */ import React, {useEffect, useRef, useState} from 'react'; import { - FormGroup, - TextInput, - Popover, - Switch, InputGroup, Button, Tooltip, capitalize, Text, TextVariants, InputGroupItem + InputGroup, + Button, + Tooltip, + capitalize, + Text, + TextVariants, + InputGroupItem, + ValidatedOptions, + FormHelperText, HelperText, HelperTextItem, TextInput, FormGroup, Popover, Switch } from '@patternfly/react-core'; import '../../karavan.css'; import "@patternfly/patternfly/patternfly.css"; -import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; +import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import {Property} from "karavan-core/lib/model/KameletModels"; import {InfrastructureSelector} from "./InfrastructureSelector"; import {InfrastructureAPI} from "../../utils/InfrastructureAPI"; -import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; -import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon"; import {usePropertiesHook} from "../usePropertiesHook"; import {Select, SelectDirection, SelectOption, SelectVariant} from "@patternfly/react-core/deprecated"; @@ -38,6 +41,8 @@ import EditorIcon from "@patternfly/react-icons/dist/js/icons/code-icon"; import {ExpressionModalEditor} from "../../../expression/ExpressionModalEditor"; import {useDesignerStore} from "../../DesignerStore"; import {shallow} from "zustand/shallow"; +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; +import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; interface Props { property: Property, @@ -171,9 +176,10 @@ export function KameletPropertyField(props: Props) { ref={ref} className="text-field" isRequired type='text' + validated={validated} autoComplete="off" id={id} name={id} - value={textValue} + value={textValue || ''} onBlur={_ => { if (isNumeric((textValue))) { parametersChanged(property.id, Number(textValue)) @@ -240,9 +246,23 @@ export function KameletPropertyField(props: Props) { </div> ) } + function getValidationHelper() { + return ( + validated !== ValidatedOptions.default + ? <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={validated}> + {'Must be a placeholder {{ }} or secret {{secret:name/key}}'} + </HelperTextItem> + </HelperText> + </FormHelperText> + : <></> + ) + } const property = props.property; const value = props.value; + const validated = (property.format === 'password' && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; const prefix = "parameters"; const id = prefix + "-" + property.id; return ( @@ -279,6 +299,7 @@ export function KameletPropertyField(props: Props) { isChecked={Boolean(value) === true} onChange={e => parametersChanged(property.id, !Boolean(value))}/> } + {getValidationHelper()} </FormGroup> {getInfrastructureSelectorModal()} </div>