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
The following commit(s) were added to refs/heads/main by this push: new a86356a8 Change for the issue - #979, to configure allowed components and kamelets (#1113) a86356a8 is described below commit a86356a84b6c003927778297483180bcd82ea429 Author: Vidhya Sagar <36588343+vidhyasag...@users.noreply.github.com> AuthorDate: Tue Feb 20 05:32:50 2024 +0800 Change for the issue - #979, to configure allowed components and kamelets (#1113) * fix#979 - implementation of blocked components and kamelets * fix#979 -removed debugger * Block list save in file implemented * removed space changes by format. * space changes reverted * infinispan spaces reverted * Vs code changes to save blocked lists in file . * review comments fixed in designer and space * block lists moved to settings folder --------- Co-authored-by: induja <induja.b...@gmail.com> --- karavan-core/src/core/api/ComponentApi.ts | 25 ++++++++++++- karavan-core/src/core/api/KameletApi.ts | 22 +++++++++++- karavan-designer/src/App.tsx | 21 +++++++---- karavan-designer/src/KnowledgebaseHome.tsx | 23 ++++++++++++ karavan-designer/src/designer/karavan.css | 9 +++-- .../src/designer/route/DslSelector.tsx | 9 ++++- .../src/knowledgebase/KnowledgebasePage.tsx | 5 +-- .../src/knowledgebase/components/ComponentCard.tsx | 28 +++++++++++---- .../src/knowledgebase/components/ComponentsTab.tsx | 3 +- .../src/knowledgebase/kamelets/KameletCard.tsx | 26 ++++++++++---- .../src/knowledgebase/kamelets/KameletsTab.tsx | 8 ++--- karavan-space/src/App.tsx | 22 ++++++++++-- karavan-space/src/designer/karavan.css | 11 +++++- karavan-space/src/designer/route/DslSelector.tsx | 9 ++++- .../src/knowledgebase/KnowledgebasePage.tsx | 5 +-- .../src/knowledgebase/components/ComponentCard.tsx | 28 +++++++++++---- .../src/knowledgebase/components/ComponentsTab.tsx | 3 +- .../src/knowledgebase/kamelets/KameletCard.tsx | 26 ++++++++++---- .../src/knowledgebase/kamelets/KameletsTab.tsx | 8 ++--- karavan-vscode/package.json | 13 +++++-- karavan-vscode/settings/components-blocklist.txt | 0 karavan-vscode/settings/kamelets-blocklist.txt | 0 karavan-vscode/src/designerView.ts | 4 +++ karavan-vscode/src/helpView.ts | 9 ++++- karavan-vscode/src/utils.ts | 25 ++++++++++++- karavan-vscode/webview/App.tsx | 27 +++++++++++++- .../camel/karavan/api/ComponentResources.java | 2 +- .../org/apache/camel/karavan/code/CodeService.java | 2 ++ .../resources/snippets/components-blocklist.txt | 0 .../main/resources/snippets/kamelets-blocklist.txt | 0 .../src/main/webui/src/api/ProjectService.ts | 17 +++++++-- .../src/main/webui/src/designer/karavan.css | 11 +++++- .../main/webui/src/designer/route/DslSelector.tsx | 9 ++++- .../webui/src/knowledgebase/KnowledgebaseHome.tsx | 41 ++++++++++++++++++++++ .../webui/src/knowledgebase/KnowledgebasePage.tsx | 5 +-- .../src/knowledgebase/components/ComponentCard.tsx | 28 +++++++++++---- .../src/knowledgebase/components/ComponentsTab.tsx | 3 +- .../src/knowledgebase/kamelets/KameletCard.tsx | 26 ++++++++++---- .../src/knowledgebase/kamelets/KameletsTab.tsx | 8 ++--- .../src/main/webui/src/main/MainRoutes.tsx | 4 +-- .../src/main/webui/src/main/useMainHook.tsx | 4 ++- 41 files changed, 434 insertions(+), 95 deletions(-) diff --git a/karavan-core/src/core/api/ComponentApi.ts b/karavan-core/src/core/api/ComponentApi.ts index da9b9be1..3a390c6c 100644 --- a/karavan-core/src/core/api/ComponentApi.ts +++ b/karavan-core/src/core/api/ComponentApi.ts @@ -20,7 +20,7 @@ import { CamelElement } from '../model/IntegrationDefinition'; const Components: Component[] = []; const SupportedComponents: SupportedComponent[] = []; let SupportedOnly: boolean = false; - +const BlockedComponents: string[] = []; export class ComponentApi { private constructor() {} @@ -339,4 +339,27 @@ export class ComponentApi { } return Array.from(new Map(properties.map(item => [item.name, item])).values()); }; + + + static saveBlockedComponentNames = (componentNames: string[]) => { + BlockedComponents.length = 0; + BlockedComponents.push(...componentNames); + } + + + static saveBlockedComponentName = (componentName: string, checked :boolean) => { + const index = BlockedComponents.indexOf(componentName); + if (!checked && index === -1) { + BlockedComponents.push(componentName); + } + else if (checked && index > -1) { + BlockedComponents.splice(index, 1); + } + return BlockedComponents; + } + + + static getBlockedComponentNames = () => { + return BlockedComponents; + } } diff --git a/karavan-core/src/core/api/KameletApi.ts b/karavan-core/src/core/api/KameletApi.ts index 62ecb4f2..acccea33 100644 --- a/karavan-core/src/core/api/KameletApi.ts +++ b/karavan-core/src/core/api/KameletApi.ts @@ -19,7 +19,7 @@ import * as yaml from 'js-yaml'; const Kamelets: KameletModel[] = []; const CustomNames: string[] = []; - +const BlockedKamelets: string[] = []; export class KameletApi { private constructor() {} @@ -99,4 +99,24 @@ export class KameletApi { Kamelets.push(kamelet); } }; + + static saveBlockedKameletNames = (names: string[]): void => { + BlockedKamelets.length = 0; + BlockedKamelets.push(...names); + } + + static saveBlockedKameletName = (name: string, checked: boolean) => { + const index = BlockedKamelets.indexOf(name); + if ( !checked && index === -1) { + BlockedKamelets.push(name); + } + else if ( checked && index > -1) { + BlockedKamelets.splice(index, 1); + } + return BlockedKamelets; + } + + static getBlockedKameletNames = () => { + return BlockedKamelets; + } } diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx index 46894d94..e2d9803c 100644 --- a/karavan-designer/src/App.tsx +++ b/karavan-designer/src/App.tsx @@ -37,10 +37,10 @@ import {KaravanIcon} from "./designer/icons/KaravanIcons"; import './designer/karavan.css'; import {DesignerPage} from "./DesignerPage"; import {TemplateApi} from "karavan-core/lib/api/TemplateApi"; -import {KnowledgebasePage} from "./knowledgebase/KnowledgebasePage"; import {Notification} from "./designer/utils/Notification"; import {EventBus} from "./designer/utils/EventBus"; import {TopologyTab} from "./topology/TopologyTab"; +import {KnowledgebaseHome} from "./KnowledgebaseHome"; import {useEffect, useState} from "react"; import {IntegrationFile} from "karavan-core/lib/model/IntegrationDefinition"; @@ -70,10 +70,13 @@ export function App() { fetch("components/components.json"), fetch("snippets/org.apache.camel.AggregationStrategy"), fetch("snippets/org.apache.camel.Processor"), - fetch("example/demo.camel.yaml") + fetch("example/demo.camel.yaml"), + fetch("components/blocked-components.properties"), + fetch("kamelets/blocked-kamelets.properties") // fetch("example/aws-cloudwatch-sink.kamelet.yaml") // fetch("example/aws-s3-cdc-source.kamelet.yaml") - // fetch("components/supported-components.json"), + //fetch("components/supported-components.json"), + ]).then(responses => Promise.all(responses.map(response => response.text())) ).then(data => { @@ -94,11 +97,17 @@ export function App() { setYaml(data[4]); setName("demo.camel.yaml"); } - if (data[5]) { - ComponentApi.saveSupportedComponents(data[4]); + ComponentApi.saveBlockedComponentNames(data[5].split('\r\n')); + } + if (data[6]) { + KameletApi.saveBlockedKameletNames(data[6].split('\n')); + } + if (data[7]) { + ComponentApi.saveSupportedComponents(data[7]); ComponentApi.setSupportedOnly(true); } + }).catch(err => EventBus.sendAlert("Error", err.text, 'danger') ); @@ -159,7 +168,7 @@ export function App() { ) case "knowledgebase": return ( - <KnowledgebasePage dark={dark}/> + <KnowledgebaseHome dark={dark}/> ) case "topology": return ( diff --git a/karavan-designer/src/KnowledgebaseHome.tsx b/karavan-designer/src/KnowledgebaseHome.tsx new file mode 100644 index 00000000..7c8719ca --- /dev/null +++ b/karavan-designer/src/KnowledgebaseHome.tsx @@ -0,0 +1,23 @@ +import { useEffect, useState } from "react"; +import { KnowledgebasePage } from "./knowledgebase/KnowledgebasePage" +import { ComponentApi } from "karavan-core/lib/api/ComponentApi"; +import { KameletApi } from "karavan-core/lib/api/KameletApi"; +interface Props { + dark: boolean, +} +export const KnowledgebaseHome = (props: Props) => { + + + const onchangeBlockedList = (type: string, name: string, checked: boolean) => { + if (type === 'kamelet') { + + const blockedKamelet = KameletApi.saveBlockedKameletName(name, checked); + } + else if (type === 'component') { + const blockedComponent = ComponentApi.saveBlockedComponentName(name, checked); + } + } + return ( + <KnowledgebasePage dark={props.dark} changeBlockList={(type: string, name: string, checked: boolean) => onchangeBlockedList(type, name, checked)} /> + ); +} \ No newline at end of file diff --git a/karavan-designer/src/designer/karavan.css b/karavan-designer/src/designer/karavan.css index 4d9a7162..78b2a135 100644 --- a/karavan-designer/src/designer/karavan.css +++ b/karavan-designer/src/designer/karavan.css @@ -189,7 +189,12 @@ padding: 5px; display: flex; flex-direction: row; - justify-content: flex-end; + justify-content:flex-end; +} +.kamelets-page .kamelet-card .header-labels .pf-v5-c-card__header-main{ + display: flex; + flex-direction: row; + justify-content:space-between; } .kamelets-page .kamelet-card .footer-labels { @@ -813,4 +818,4 @@ } .karavan .knowledbase-eip-section .pf-v5-c-toggle-group{ margin:16px; -} \ No newline at end of file +} diff --git a/karavan-designer/src/designer/route/DslSelector.tsx b/karavan-designer/src/designer/route/DslSelector.tsx index a80525ba..27093f25 100644 --- a/karavan-designer/src/designer/route/DslSelector.tsx +++ b/karavan-designer/src/designer/route/DslSelector.tsx @@ -27,6 +27,8 @@ import {DslMetaModel} from "../utils/DslMetaModel"; import {useDesignerStore, useIntegrationStore, useSelectorStore} from "../DesignerStore"; import {shallow} from "zustand/shallow"; import {useRouteDesignerHook} from "./useRouteDesignerHook"; +import { ComponentApi } from 'karavan-core/lib/api/ComponentApi'; +import { KameletApi } from 'karavan-core/lib/api/KameletApi'; interface Props { tabIndex?: string | number @@ -119,9 +121,14 @@ export function DslSelector (props: Props) { const isEip = selectorTabIndex === 'eip'; const title = parentDsl === undefined ? "Select source" : "Select step"; const navigation: string = selectorTabIndex ? selectorTabIndex.toString() : ''; + const blockedComponents = ComponentApi.getBlockedComponentNames(); + const blockedKamelets = KameletApi.getBlockedKameletNames(); const elements = CamelUi.getSelectorModelsForParentFiltered(parentDsl, navigation, showSteps); + const allowedElements = selectorTabIndex === 'component' ? + elements.filter(dsl => (!blockedComponents.includes(dsl.uri || dsl.name))) : + (selectorTabIndex === 'kamelet' ? elements.filter(dsl => (!blockedKamelets.includes(dsl.name))) : elements); const eipLabels = [...new Set(elements.map(e => e.labels).join(",").split(",").filter(e => e !== 'eip'))]; - const filteredElement = elements + const filteredElement = allowedElements .filter((dsl: DslMetaModel) => CamelUi.checkFilter(dsl, filter)) .filter((dsl: DslMetaModel) => { if (!isEip || selectedLabels.length === 0) { diff --git a/karavan-designer/src/knowledgebase/KnowledgebasePage.tsx b/karavan-designer/src/knowledgebase/KnowledgebasePage.tsx index 02c6b641..66eae532 100644 --- a/karavan-designer/src/knowledgebase/KnowledgebasePage.tsx +++ b/karavan-designer/src/knowledgebase/KnowledgebasePage.tsx @@ -24,6 +24,7 @@ import {ComponentsTab} from "./components/ComponentsTab"; interface Props { dark: boolean, + changeBlockList: (type: string, name: string, checked: boolean) => void, } export const KnowledgebasePage = (props: Props) => { @@ -76,9 +77,9 @@ export const KnowledgebasePage = (props: Props) => { </Flex> </PageSection> <> - {tab === 'kamelets' && <KameletsTab dark={props.dark} filter={filter} customOnly={customOnly}/>} + {tab === 'kamelets' && <KameletsTab dark={props.dark} filter={filter} customOnly={customOnly} onChange={(name: string, checked: boolean) => props.changeBlockList('kamelet', name, checked)} />} {tab === 'eip' && <EipTab dark={props.dark} filter={filter}/>} - {tab === 'components' && <ComponentsTab dark={props.dark} filter={filter}/>} + {tab === 'components' && <ComponentsTab dark={props.dark} filter={filter} onChange={(name: string, checked: boolean) => props.changeBlockList('component', name, checked)} />} </> </PageSection> ) diff --git a/karavan-designer/src/knowledgebase/components/ComponentCard.tsx b/karavan-designer/src/knowledgebase/components/ComponentCard.tsx index 338dc3b9..f99d8136 100644 --- a/karavan-designer/src/knowledgebase/components/ComponentCard.tsx +++ b/karavan-designer/src/knowledgebase/components/ComponentCard.tsx @@ -14,32 +14,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { - CardHeader, Card, CardTitle, CardBody, CardFooter, Badge + CardHeader, Card, CardTitle, CardBody, CardFooter, Badge, Checkbox } from '@patternfly/react-core'; import '../../designer/karavan.css'; import {CamelUi} from "../../designer/utils/CamelUi"; import {Component} from "karavan-core/lib/model/ComponentModels"; import {useKnowledgebaseStore} from "../KnowledgebaseStore"; import {shallow} from "zustand/shallow"; +import { ComponentApi } from 'karavan-core/lib/api/ComponentApi'; interface Props { component: Component, + onChange: (name: string, checked: boolean) => void } export function ComponentCard(props: Props) { const [setComponent, setModalOpen] = useKnowledgebaseStore((s) => [s.setComponent, s.setModalOpen], shallow) - const component = props.component; + const [blockedComponents, setBlockedComponents] = useState<string[]>(); + useEffect(() => { + setBlockedComponents(ComponentApi.getBlockedComponentNames()); + }, []); + - function click (event: React.MouseEvent) { - setComponent(component) - setModalOpen(true); + function click(event: React.MouseEvent) { + const { target } = event; + if (!(target as HTMLElement).parentElement?.className.includes("block-checkbox")) { + setComponent(component) + setModalOpen(true); + } } - + function selectComponent(event: React.FormEvent, checked: boolean) { + props.onChange(component.component.name, checked); + setBlockedComponents([...ComponentApi.getBlockedComponentNames()]); + } + const isBlockedComponent = blockedComponents ? blockedComponents.findIndex(r => r === component.component.name) > -1 : false; return ( <Card isCompact key={component.component.name} className="kamelet-card" onClick={event => click(event)} @@ -47,6 +60,7 @@ export function ComponentCard(props: Props) { <CardHeader className="header-labels"> {component.component.supportType === 'Supported' && <Badge isRead className="support-type labels">{component.component.supportType}</Badge>} <Badge isRead className="support-level labels">{component.component.supportLevel}</Badge> + <Checkbox id={component.component.name} className="block-checkbox labels" isChecked={!isBlockedComponent} onChange={(_, checked) => selectComponent(_, checked)} /> </CardHeader> <CardHeader> {CamelUi.getIconForComponent(component.component.title, component.component.label)} diff --git a/karavan-designer/src/knowledgebase/components/ComponentsTab.tsx b/karavan-designer/src/knowledgebase/components/ComponentsTab.tsx index 708b04bd..9e9fb470 100644 --- a/karavan-designer/src/knowledgebase/components/ComponentsTab.tsx +++ b/karavan-designer/src/knowledgebase/components/ComponentsTab.tsx @@ -29,6 +29,7 @@ import {useKnowledgebaseStore} from "../KnowledgebaseStore"; interface Props { dark: boolean, filter: string, + onChange: (name: string, checked: boolean) => void, } export function ComponentsTab(props: Props) { @@ -49,7 +50,7 @@ export function ComponentsTab(props: Props) { <PageSection isFilled className="kamelets-page" variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light}> <Gallery hasGutter> {components.map(c => ( - <ComponentCard key={c.component.name} component={c}/> + <ComponentCard key={c.component.name} component={c} onChange={props.onChange} /> ))} </Gallery> </PageSection> diff --git a/karavan-designer/src/knowledgebase/kamelets/KameletCard.tsx b/karavan-designer/src/knowledgebase/kamelets/KameletCard.tsx index 045846e9..1dff9f2c 100644 --- a/karavan-designer/src/knowledgebase/kamelets/KameletCard.tsx +++ b/karavan-designer/src/knowledgebase/kamelets/KameletCard.tsx @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { - CardHeader, Card, CardTitle, CardBody, CardFooter,Badge + CardHeader, Card, CardTitle, CardBody, CardFooter, Badge, Checkbox } from '@patternfly/react-core'; import '../../designer/karavan.css'; import {KameletModel} from "karavan-core/lib/model/KameletModels"; @@ -27,21 +27,34 @@ import {shallow} from "zustand/shallow"; interface Props { kamelet: KameletModel, + onChange: (name: string, checked: boolean) => void } export function KameletCard(props: Props) { const [setKamelet, setModalOpen] = useKnowledgebaseStore((s) => [s.setKamelet, s.setModalOpen], shallow) - + const [blockedKamelets, setBlockedKamelets] = useState<string[]>(); + useEffect(() => { + setBlockedKamelets(KameletApi.getBlockedKameletNames()); + }, []); + const kamelet = props.kamelet; const isCustom = KameletApi.getCustomKameletNames().includes(kamelet.metadata.name); - function click (event: React.MouseEvent) { - setKamelet(kamelet) - setModalOpen(true); + function click(event: React.MouseEvent) { + const { target } = event; + if (!(target as HTMLElement).parentElement?.className.includes("block-checkbox")) { + setKamelet(kamelet) + setModalOpen(true); + } } + function selectKamelet(event: React.FormEvent, checked: boolean) { + props.onChange(kamelet.metadata.name, checked ); + setBlockedKamelets([...KameletApi.getBlockedKameletNames()]); + } + const isblockedKamelet = blockedKamelets ? blockedKamelets.findIndex(r => r === kamelet.metadata.name) > -1 : false; return ( <Card isCompact key={kamelet.metadata.name} className="kamelet-card" onClick={event => click(event)} @@ -49,6 +62,7 @@ export function KameletCard(props: Props) { <CardHeader className="header-labels"> {isCustom && <Badge className="custom">custom</Badge>} <Badge isRead className="support-level labels">{kamelet.metadata.annotations["camel.apache.org/kamelet.support.level"]}</Badge> + <Checkbox id={kamelet.metadata.name} className="block-checkbox labels" isChecked={!isblockedKamelet} onChange={(_, checked) => selectKamelet(_, checked)} /> </CardHeader> <CardHeader> {CamelUi.getIconFromSource(kamelet.icon())} diff --git a/karavan-designer/src/knowledgebase/kamelets/KameletsTab.tsx b/karavan-designer/src/knowledgebase/kamelets/KameletsTab.tsx index fb8df6df..353819e4 100644 --- a/karavan-designer/src/knowledgebase/kamelets/KameletsTab.tsx +++ b/karavan-designer/src/knowledgebase/kamelets/KameletsTab.tsx @@ -31,11 +31,7 @@ interface Props { dark: boolean, filter: string, customOnly: boolean, -} - -interface Props { - dark: boolean, - filter: string, + onChange: (name: string, checked: boolean) => void } export function KameletsTab(props: Props) { @@ -55,7 +51,7 @@ export function KameletsTab(props: Props) { variant={dark ? PageSectionVariants.darker : PageSectionVariants.light}> <Gallery hasGutter> {kameletList.map(k => ( - <KameletCard key={k.metadata.name} kamelet={k}/> + <KameletCard key={k.metadata.name} kamelet={k} onChange={props.onChange} /> ))} </Gallery> </PageSection> diff --git a/karavan-space/src/App.tsx b/karavan-space/src/App.tsx index 2adec90f..5a723404 100644 --- a/karavan-space/src/App.tsx +++ b/karavan-space/src/App.tsx @@ -74,7 +74,9 @@ class App extends React.Component<Props, State> { fetch("kamelets/kamelets.yaml"), fetch("components/components.json"), fetch("snippets/org.apache.camel.AggregationStrategy"), - fetch("snippets/org.apache.camel.Processor") + fetch("snippets/org.apache.camel.Processor"), + fetch("components/blocked-components.properties"), + fetch("kamelets/blocked-kamelets.properties") ]).then(responses => Promise.all(responses.map(response => response.text())) ).then(data => { @@ -91,6 +93,12 @@ class App extends React.Component<Props, State> { TemplateApi.saveTemplate("org.apache.camel.AggregationStrategy", data[2]); TemplateApi.saveTemplate("org.apache.camel.Processor", data[3]); + if (data[4]) { + ComponentApi.saveBlockedComponentNames(data[4].split('\r\n')); + } + if (data[5]) { + KameletApi.saveBlockedKameletNames(data[5].split('\n')); + } }).catch(err => EventBus.sendAlert("Error", err.text, 'danger') ); @@ -100,6 +108,16 @@ class App extends React.Component<Props, State> { this.setState({name: filename, yaml: yaml}); // console.log(yaml); } + + onchangeBlockedList(type: string, name: string, checked: boolean){ + if (type === 'kamelet') { + + const blockedKamelet = KameletApi.saveBlockedKameletName(name, checked); + } + else if (type === 'component') { + const blockedComponent = ComponentApi.saveBlockedComponentName(name, checked); + } + } closeGithubModal() { this.setState({githubModalIsOpen: false}) @@ -167,7 +185,7 @@ class App extends React.Component<Props, State> { ) case "knowledgebase": return ( - <KnowledgebasePage dark={dark}/> + <KnowledgebasePage dark={dark} changeBlockList={(type: string, name: string, checked: boolean) => this.onchangeBlockedList(type, name, checked)}/> ) case "topology": return ( diff --git a/karavan-space/src/designer/karavan.css b/karavan-space/src/designer/karavan.css index 4d9a7162..82f75a51 100644 --- a/karavan-space/src/designer/karavan.css +++ b/karavan-space/src/designer/karavan.css @@ -189,7 +189,12 @@ padding: 5px; display: flex; flex-direction: row; - justify-content: flex-end; + justify-content:flex-end; +} +.kamelets-page .kamelet-card .header-labels .pf-v5-c-card__header-main{ + display: flex; + flex-direction: row; + justify-content:space-between; } .kamelets-page .kamelet-card .footer-labels { @@ -813,4 +818,8 @@ } .karavan .knowledbase-eip-section .pf-v5-c-toggle-group{ margin:16px; +} +.karavan .kamelet-section .kamelet-card .block-checkbox input{ + width:18px; + height:18px } \ No newline at end of file diff --git a/karavan-space/src/designer/route/DslSelector.tsx b/karavan-space/src/designer/route/DslSelector.tsx index a80525ba..27093f25 100644 --- a/karavan-space/src/designer/route/DslSelector.tsx +++ b/karavan-space/src/designer/route/DslSelector.tsx @@ -27,6 +27,8 @@ import {DslMetaModel} from "../utils/DslMetaModel"; import {useDesignerStore, useIntegrationStore, useSelectorStore} from "../DesignerStore"; import {shallow} from "zustand/shallow"; import {useRouteDesignerHook} from "./useRouteDesignerHook"; +import { ComponentApi } from 'karavan-core/lib/api/ComponentApi'; +import { KameletApi } from 'karavan-core/lib/api/KameletApi'; interface Props { tabIndex?: string | number @@ -119,9 +121,14 @@ export function DslSelector (props: Props) { const isEip = selectorTabIndex === 'eip'; const title = parentDsl === undefined ? "Select source" : "Select step"; const navigation: string = selectorTabIndex ? selectorTabIndex.toString() : ''; + const blockedComponents = ComponentApi.getBlockedComponentNames(); + const blockedKamelets = KameletApi.getBlockedKameletNames(); const elements = CamelUi.getSelectorModelsForParentFiltered(parentDsl, navigation, showSteps); + const allowedElements = selectorTabIndex === 'component' ? + elements.filter(dsl => (!blockedComponents.includes(dsl.uri || dsl.name))) : + (selectorTabIndex === 'kamelet' ? elements.filter(dsl => (!blockedKamelets.includes(dsl.name))) : elements); const eipLabels = [...new Set(elements.map(e => e.labels).join(",").split(",").filter(e => e !== 'eip'))]; - const filteredElement = elements + const filteredElement = allowedElements .filter((dsl: DslMetaModel) => CamelUi.checkFilter(dsl, filter)) .filter((dsl: DslMetaModel) => { if (!isEip || selectedLabels.length === 0) { diff --git a/karavan-space/src/knowledgebase/KnowledgebasePage.tsx b/karavan-space/src/knowledgebase/KnowledgebasePage.tsx index 02c6b641..66eae532 100644 --- a/karavan-space/src/knowledgebase/KnowledgebasePage.tsx +++ b/karavan-space/src/knowledgebase/KnowledgebasePage.tsx @@ -24,6 +24,7 @@ import {ComponentsTab} from "./components/ComponentsTab"; interface Props { dark: boolean, + changeBlockList: (type: string, name: string, checked: boolean) => void, } export const KnowledgebasePage = (props: Props) => { @@ -76,9 +77,9 @@ export const KnowledgebasePage = (props: Props) => { </Flex> </PageSection> <> - {tab === 'kamelets' && <KameletsTab dark={props.dark} filter={filter} customOnly={customOnly}/>} + {tab === 'kamelets' && <KameletsTab dark={props.dark} filter={filter} customOnly={customOnly} onChange={(name: string, checked: boolean) => props.changeBlockList('kamelet', name, checked)} />} {tab === 'eip' && <EipTab dark={props.dark} filter={filter}/>} - {tab === 'components' && <ComponentsTab dark={props.dark} filter={filter}/>} + {tab === 'components' && <ComponentsTab dark={props.dark} filter={filter} onChange={(name: string, checked: boolean) => props.changeBlockList('component', name, checked)} />} </> </PageSection> ) diff --git a/karavan-space/src/knowledgebase/components/ComponentCard.tsx b/karavan-space/src/knowledgebase/components/ComponentCard.tsx index 338dc3b9..f99d8136 100644 --- a/karavan-space/src/knowledgebase/components/ComponentCard.tsx +++ b/karavan-space/src/knowledgebase/components/ComponentCard.tsx @@ -14,32 +14,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { - CardHeader, Card, CardTitle, CardBody, CardFooter, Badge + CardHeader, Card, CardTitle, CardBody, CardFooter, Badge, Checkbox } from '@patternfly/react-core'; import '../../designer/karavan.css'; import {CamelUi} from "../../designer/utils/CamelUi"; import {Component} from "karavan-core/lib/model/ComponentModels"; import {useKnowledgebaseStore} from "../KnowledgebaseStore"; import {shallow} from "zustand/shallow"; +import { ComponentApi } from 'karavan-core/lib/api/ComponentApi'; interface Props { component: Component, + onChange: (name: string, checked: boolean) => void } export function ComponentCard(props: Props) { const [setComponent, setModalOpen] = useKnowledgebaseStore((s) => [s.setComponent, s.setModalOpen], shallow) - const component = props.component; + const [blockedComponents, setBlockedComponents] = useState<string[]>(); + useEffect(() => { + setBlockedComponents(ComponentApi.getBlockedComponentNames()); + }, []); + - function click (event: React.MouseEvent) { - setComponent(component) - setModalOpen(true); + function click(event: React.MouseEvent) { + const { target } = event; + if (!(target as HTMLElement).parentElement?.className.includes("block-checkbox")) { + setComponent(component) + setModalOpen(true); + } } - + function selectComponent(event: React.FormEvent, checked: boolean) { + props.onChange(component.component.name, checked); + setBlockedComponents([...ComponentApi.getBlockedComponentNames()]); + } + const isBlockedComponent = blockedComponents ? blockedComponents.findIndex(r => r === component.component.name) > -1 : false; return ( <Card isCompact key={component.component.name} className="kamelet-card" onClick={event => click(event)} @@ -47,6 +60,7 @@ export function ComponentCard(props: Props) { <CardHeader className="header-labels"> {component.component.supportType === 'Supported' && <Badge isRead className="support-type labels">{component.component.supportType}</Badge>} <Badge isRead className="support-level labels">{component.component.supportLevel}</Badge> + <Checkbox id={component.component.name} className="block-checkbox labels" isChecked={!isBlockedComponent} onChange={(_, checked) => selectComponent(_, checked)} /> </CardHeader> <CardHeader> {CamelUi.getIconForComponent(component.component.title, component.component.label)} diff --git a/karavan-space/src/knowledgebase/components/ComponentsTab.tsx b/karavan-space/src/knowledgebase/components/ComponentsTab.tsx index 708b04bd..9e9fb470 100644 --- a/karavan-space/src/knowledgebase/components/ComponentsTab.tsx +++ b/karavan-space/src/knowledgebase/components/ComponentsTab.tsx @@ -29,6 +29,7 @@ import {useKnowledgebaseStore} from "../KnowledgebaseStore"; interface Props { dark: boolean, filter: string, + onChange: (name: string, checked: boolean) => void, } export function ComponentsTab(props: Props) { @@ -49,7 +50,7 @@ export function ComponentsTab(props: Props) { <PageSection isFilled className="kamelets-page" variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light}> <Gallery hasGutter> {components.map(c => ( - <ComponentCard key={c.component.name} component={c}/> + <ComponentCard key={c.component.name} component={c} onChange={props.onChange} /> ))} </Gallery> </PageSection> diff --git a/karavan-space/src/knowledgebase/kamelets/KameletCard.tsx b/karavan-space/src/knowledgebase/kamelets/KameletCard.tsx index 045846e9..1dff9f2c 100644 --- a/karavan-space/src/knowledgebase/kamelets/KameletCard.tsx +++ b/karavan-space/src/knowledgebase/kamelets/KameletCard.tsx @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { - CardHeader, Card, CardTitle, CardBody, CardFooter,Badge + CardHeader, Card, CardTitle, CardBody, CardFooter, Badge, Checkbox } from '@patternfly/react-core'; import '../../designer/karavan.css'; import {KameletModel} from "karavan-core/lib/model/KameletModels"; @@ -27,21 +27,34 @@ import {shallow} from "zustand/shallow"; interface Props { kamelet: KameletModel, + onChange: (name: string, checked: boolean) => void } export function KameletCard(props: Props) { const [setKamelet, setModalOpen] = useKnowledgebaseStore((s) => [s.setKamelet, s.setModalOpen], shallow) - + const [blockedKamelets, setBlockedKamelets] = useState<string[]>(); + useEffect(() => { + setBlockedKamelets(KameletApi.getBlockedKameletNames()); + }, []); + const kamelet = props.kamelet; const isCustom = KameletApi.getCustomKameletNames().includes(kamelet.metadata.name); - function click (event: React.MouseEvent) { - setKamelet(kamelet) - setModalOpen(true); + function click(event: React.MouseEvent) { + const { target } = event; + if (!(target as HTMLElement).parentElement?.className.includes("block-checkbox")) { + setKamelet(kamelet) + setModalOpen(true); + } } + function selectKamelet(event: React.FormEvent, checked: boolean) { + props.onChange(kamelet.metadata.name, checked ); + setBlockedKamelets([...KameletApi.getBlockedKameletNames()]); + } + const isblockedKamelet = blockedKamelets ? blockedKamelets.findIndex(r => r === kamelet.metadata.name) > -1 : false; return ( <Card isCompact key={kamelet.metadata.name} className="kamelet-card" onClick={event => click(event)} @@ -49,6 +62,7 @@ export function KameletCard(props: Props) { <CardHeader className="header-labels"> {isCustom && <Badge className="custom">custom</Badge>} <Badge isRead className="support-level labels">{kamelet.metadata.annotations["camel.apache.org/kamelet.support.level"]}</Badge> + <Checkbox id={kamelet.metadata.name} className="block-checkbox labels" isChecked={!isblockedKamelet} onChange={(_, checked) => selectKamelet(_, checked)} /> </CardHeader> <CardHeader> {CamelUi.getIconFromSource(kamelet.icon())} diff --git a/karavan-space/src/knowledgebase/kamelets/KameletsTab.tsx b/karavan-space/src/knowledgebase/kamelets/KameletsTab.tsx index fb8df6df..353819e4 100644 --- a/karavan-space/src/knowledgebase/kamelets/KameletsTab.tsx +++ b/karavan-space/src/knowledgebase/kamelets/KameletsTab.tsx @@ -31,11 +31,7 @@ interface Props { dark: boolean, filter: string, customOnly: boolean, -} - -interface Props { - dark: boolean, - filter: string, + onChange: (name: string, checked: boolean) => void } export function KameletsTab(props: Props) { @@ -55,7 +51,7 @@ export function KameletsTab(props: Props) { variant={dark ? PageSectionVariants.darker : PageSectionVariants.light}> <Gallery hasGutter> {kameletList.map(k => ( - <KameletCard key={k.metadata.name} kamelet={k}/> + <KameletCard key={k.metadata.name} kamelet={k} onChange={props.onChange} /> ))} </Gallery> </PageSection> diff --git a/karavan-vscode/package.json b/karavan-vscode/package.json index 2ac85771..c9e24eb3 100644 --- a/karavan-vscode/package.json +++ b/karavan-vscode/package.json @@ -457,6 +457,13 @@ "description": "application.properties template for camel-main on Kubernetes", "scope": "machine", "order": 95 + }, + "Karavan.settingsPath": { + "type": "string", + "default": "settings", + "description": "Blocked kamelets/components Path", + "scope": "machine", + "order": 96 } } }, @@ -719,7 +726,7 @@ "replace-import": "run-script-os", "replace-import:darwin": "find webview -type f -name '*.ts*' -exec sed -i '' 's!karavan-core/lib!core!g' {} +", "replace-import:linux": "find webview -type f -name '*.ts*' -exec sed -i 's!karavan-core/lib!core!g' {} +", - "copy-karavan": "npm run copy-core && npm run copy-designer && npm run replace-import", + "copy-karavan": "npm run copy-core && npm run copy-designer && npm run replace-import", "vscode:prepublish": "npm run copy-karavan && npm run package", "compile": "npm run copy-karavan && cross-env NODE_ENV=development webpack --progress --stats-error-details", "watch": "npm run copy-karavan && cross-env NODE_ENV=development webpack --progress --watch", @@ -742,9 +749,9 @@ "@types/js-yaml": "4.0.9", "@types/node": "20.11.16", "@types/uuid": "9.0.8", - "html-to-image": "1.11.11", + "html-to-image": "1.11.11", "js-yaml": "^4.1.0", - "path-browserify": "^1.0.1", + "path-browserify": "^1.0.1", "react": "18.2.0", "react-dom": "18.2.0", "rxjs": "7.8.1", diff --git a/karavan-vscode/settings/components-blocklist.txt b/karavan-vscode/settings/components-blocklist.txt new file mode 100644 index 00000000..e69de29b diff --git a/karavan-vscode/settings/kamelets-blocklist.txt b/karavan-vscode/settings/kamelets-blocklist.txt new file mode 100644 index 00000000..e69de29b diff --git a/karavan-vscode/src/designerView.ts b/karavan-vscode/src/designerView.ts index 822ef7dc..612a093c 100644 --- a/karavan-vscode/src/designerView.ts +++ b/karavan-vscode/src/designerView.ts @@ -189,6 +189,8 @@ export class DesignerView { utils.readPropertyPlaceholders(this.context), // Read beans utils.readBeans(fullPath), + //Read BlockList + utils.readBlockTemplates(this.context), // Read integration utils.readCamelYamlFiles(path.dirname(fullPath)) ]).then(results => { @@ -206,6 +208,8 @@ export class DesignerView { // Send integration panel.webview.postMessage({ command: 'files', files: results[8] }); this.sendIntegrationData(panel, filename, relativePath, fullPath, reread, yaml, tab, results[6], results[7]); + // Send block list + panel.webview.postMessage({ command: 'blockList', blockList: Object.fromEntries(results[8]) }); }).catch(err => console.log(err)); } diff --git a/karavan-vscode/src/helpView.ts b/karavan-vscode/src/helpView.ts index c1d11236..435281d5 100644 --- a/karavan-vscode/src/helpView.ts +++ b/karavan-vscode/src/helpView.ts @@ -71,6 +71,10 @@ export class HelpView implements vscode.TreeDataProvider<HelpItem> { case 'getData': this.sendData(panel, page); break; + + case 'saveBlockedList': + utils.saveBlockList(message.key,message.value); + break; } }, undefined, @@ -102,7 +106,10 @@ export class HelpView implements vscode.TreeDataProvider<HelpItem> { utils.readComponents(this.context).then(components => { // Read and send Components panel.webview.postMessage({ command: 'components', components: components }); - }).finally(() => { + }).finally(() => {utils.readBlockTemplates(this.context).then(list => { + // Read and send block lists + panel.webview.postMessage({ command: 'blockList', blockList: Object.fromEntries(list) }); + }) }).finally(() => { // Send integration panel.webview.postMessage({ command: 'open', page: page }); }) diff --git a/karavan-vscode/src/utils.ts b/karavan-vscode/src/utils.ts index 17e999a0..985544b0 100644 --- a/karavan-vscode/src/utils.ts +++ b/karavan-vscode/src/utils.ts @@ -49,6 +49,16 @@ export async function savePropertyPlaceholder(key: string, value: string) { } } +export function saveBlockList(key: string, value: string) { + if (workspace.workspaceFolders) { + const uriFolder: Uri = workspace.workspaceFolders[0].uri; + const settingsPath: string | undefined = workspace.getConfiguration().get("Karavan.settingsPath"); + const name = key+"s-blocklist.txt"; + write(path.join(uriFolder.path, settingsPath+"/"+name), value); + } +} + + export function deleteFile(fullPath: string) { if (workspace.workspaceFolders) { const uriFile: Uri = Uri.file(path.resolve(fullPath)); @@ -64,7 +74,7 @@ export function getRalativePath(fullPath: string): string { } export async function readKamelets(context: ExtensionContext) { - const yamls: string[] = await readBuildInKamelets(context); + const yamls: string[] = await readBuildInKamelets(context); const kameletsPath: string | undefined = workspace.getConfiguration().get("Karavan.kameletsPath"); if (kameletsPath && kameletsPath.trim().length > 0) { const kameletsDir = path.isAbsolute(kameletsPath) ? kameletsPath : path.resolve(kameletsPath); @@ -179,6 +189,19 @@ export async function readTemplates(context: ExtensionContext) { }) return result; } +export async function readBlockTemplates(context: ExtensionContext) { + const result = new Map<string, string>(); + + const blockedListDir: string | undefined = workspace.getConfiguration().get("Karavan.settingsPath"); + if (blockedListDir && blockedListDir.trim().length > 0) { + const files = await readFilesInDirByExtension(path.join(context.extensionPath, blockedListDir), "txt"); + files.forEach((v, k) => { + result.set(k,v); + }) + } + return result; +} + export async function readJavaCode(fullPath: string) { const result = new Map<string, string>(); diff --git a/karavan-vscode/webview/App.tsx b/karavan-vscode/webview/App.tsx index dec9683b..f46b71d0 100644 --- a/karavan-vscode/webview/App.tsx +++ b/karavan-vscode/webview/App.tsx @@ -173,6 +173,21 @@ class App extends React.Component<Props, State> { case 'downloadImage': EventBus.sendCommand("downloadImage"); break; + case 'blockList': + const blockList = message.blockList; + const blockListMap = new Map(Object.keys(blockList).map(key => [key, blockList[key]])).forEach((list,key) => { + if (key === 'components-blocklist.txt') { + ComponentApi.saveBlockedComponentNames(list.split(/\r?\n/)); + } + else if (key === 'kamelets-blocklist.txt') { + KameletApi.saveBlockedKameletNames(list.split(/\r?\n/)); + } + }); + this.setState((prevState: State) => { + prevState.loadingMessages.push("block lists loaded"); + return { loadingMessages: prevState.loadingMessages } + }); + break; } }; @@ -202,6 +217,16 @@ class App extends React.Component<Props, State> { } + onchangeBlockedList(type: string, name: string, checked: boolean) { + let fileContent = ''; + if (type === "component") { + fileContent = ComponentApi.saveBlockedComponentName(name, checked).join('\n'); + } else { + fileContent =KameletApi.saveBlockedKameletName(name, checked).join('\n'); + } + vscode.postMessage({ command: 'saveBlockedList', key: type, value: fileContent }); + } + public render() { const { loadingMessages, filename, key, yaml, page, loaded, tab } = this.state; const { dark } = this.props; @@ -238,7 +263,7 @@ class App extends React.Component<Props, State> { files={this.state.files.map(f => new IntegrationFile(f.name, f.code))} /> } - {loaded && page === "knowledgebase" && <KnowledgebasePage dark={dark} />} + {loaded && page === "knowledgebase" && <KnowledgebasePage dark={dark} changeBlockList={(type: string, name: string, checked: boolean) => this.onchangeBlockedList(type, name, checked)}/>} {loaded && page === "topology" && <TopologyTab hideToolbar={true} diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ComponentResources.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ComponentResources.java index 4388b4e4..d763fefb 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ComponentResources.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ComponentResources.java @@ -25,7 +25,7 @@ import org.apache.camel.karavan.code.CodeService; @Path("/api/component") public class ComponentResources { - + @Inject CodeService codeService; diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java index de039b83..eece054f 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java @@ -87,6 +87,7 @@ public class CodeService { @Inject Vertx vertx; + List<String> blockList = List.of("components-blocklist.txt", "kamelets-blocklist.txt"); List<String> beansTemplates = List.of("database", "messaging"); List<String> targets = List.of("openshift", "kubernetes", "docker"); List<String> interfaces = List.of("org.apache.camel.AggregationStrategy.java", "org.apache.camel.Processor.java"); @@ -197,6 +198,7 @@ public class CodeService { List<String> files = new ArrayList<>(interfaces); files.addAll(targets.stream().map(target -> target + "-" + APPLICATION_PROPERTIES_FILENAME).toList()); files.addAll(targets.stream().map(target -> target + "-" + BUILD_SCRIPT_FILENAME).toList()); + files.addAll(blockList); files.addAll(getBeanTemplateNames()); diff --git a/karavan-web/karavan-app/src/main/resources/snippets/components-blocklist.txt b/karavan-web/karavan-app/src/main/resources/snippets/components-blocklist.txt new file mode 100644 index 00000000..e69de29b diff --git a/karavan-web/karavan-app/src/main/resources/snippets/kamelets-blocklist.txt b/karavan-web/karavan-app/src/main/resources/snippets/kamelets-blocklist.txt new file mode 100644 index 00000000..e69de29b diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts index 9a1b5f21..df52aa72 100644 --- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts +++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts @@ -30,6 +30,7 @@ import { import {ProjectEventBus} from './ProjectEventBus'; import {EventBus} from "../designer/utils/EventBus"; import {KameletApi} from "karavan-core/lib/api/KameletApi"; +import { ComponentApi } from 'karavan-core/lib/api/ComponentApi'; export class ProjectService { @@ -129,14 +130,13 @@ export class ProjectService { public static reloadKamelets() { KaravanApi.getKamelets(yamls => { const kamelets: string[] = []; - yamls.split("\n---\n").map(c => c.trim()).forEach(z => kamelets.push(z)); + yamls.split(/\n?---\n?/).map(c => c.trim()).forEach(z => kamelets.push(z)); KameletApi.saveKamelets(kamelets, true); }) KaravanApi.getCustomKameletNames(names => { KameletApi.saveCustomKameletNames(names); }) } - public static updateFile(file: ProjectFile, active: boolean) { KaravanApi.putProjectFile(file, res => { if (res.status === 200) { @@ -297,4 +297,17 @@ export class ProjectService { useProjectStore.setState({images: images}) }); } + + public static reloadBlockedTemplates() { + KaravanApi.getTemplatesFiles((files: ProjectFile[]) => { + files.filter(f => f.name.endsWith('blocklist.txt')).forEach(file => { + if (file.name === 'components-blocklist.txt') { + ComponentApi.saveBlockedComponentNames(file.code.split(/\r?\n/)); + } + else if (file.name === "kamelets-blocklist.txt") { + KameletApi.saveBlockedKameletNames(file.code.split(/\r?\n/)); + } + }); + }); + } } \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/karavan.css b/karavan-web/karavan-app/src/main/webui/src/designer/karavan.css index 4d9a7162..82f75a51 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/karavan.css +++ b/karavan-web/karavan-app/src/main/webui/src/designer/karavan.css @@ -189,7 +189,12 @@ padding: 5px; display: flex; flex-direction: row; - justify-content: flex-end; + justify-content:flex-end; +} +.kamelets-page .kamelet-card .header-labels .pf-v5-c-card__header-main{ + display: flex; + flex-direction: row; + justify-content:space-between; } .kamelets-page .kamelet-card .footer-labels { @@ -813,4 +818,8 @@ } .karavan .knowledbase-eip-section .pf-v5-c-toggle-group{ margin:16px; +} +.karavan .kamelet-section .kamelet-card .block-checkbox input{ + width:18px; + height:18px } \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslSelector.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslSelector.tsx index a80525ba..27093f25 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslSelector.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslSelector.tsx @@ -27,6 +27,8 @@ import {DslMetaModel} from "../utils/DslMetaModel"; import {useDesignerStore, useIntegrationStore, useSelectorStore} from "../DesignerStore"; import {shallow} from "zustand/shallow"; import {useRouteDesignerHook} from "./useRouteDesignerHook"; +import { ComponentApi } from 'karavan-core/lib/api/ComponentApi'; +import { KameletApi } from 'karavan-core/lib/api/KameletApi'; interface Props { tabIndex?: string | number @@ -119,9 +121,14 @@ export function DslSelector (props: Props) { const isEip = selectorTabIndex === 'eip'; const title = parentDsl === undefined ? "Select source" : "Select step"; const navigation: string = selectorTabIndex ? selectorTabIndex.toString() : ''; + const blockedComponents = ComponentApi.getBlockedComponentNames(); + const blockedKamelets = KameletApi.getBlockedKameletNames(); const elements = CamelUi.getSelectorModelsForParentFiltered(parentDsl, navigation, showSteps); + const allowedElements = selectorTabIndex === 'component' ? + elements.filter(dsl => (!blockedComponents.includes(dsl.uri || dsl.name))) : + (selectorTabIndex === 'kamelet' ? elements.filter(dsl => (!blockedKamelets.includes(dsl.name))) : elements); const eipLabels = [...new Set(elements.map(e => e.labels).join(",").split(",").filter(e => e !== 'eip'))]; - const filteredElement = elements + const filteredElement = allowedElements .filter((dsl: DslMetaModel) => CamelUi.checkFilter(dsl, filter)) .filter((dsl: DslMetaModel) => { if (!isEip || selectedLabels.length === 0) { diff --git a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/KnowledgebaseHome.tsx b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/KnowledgebaseHome.tsx new file mode 100644 index 00000000..d9a50a9a --- /dev/null +++ b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/KnowledgebaseHome.tsx @@ -0,0 +1,41 @@ +import { KameletApi } from "karavan-core/lib/api/KameletApi"; +import { KnowledgebasePage } from "./KnowledgebasePage" +import { ComponentApi } from "karavan-core/lib/api/ComponentApi"; +import { KaravanApi } from "../api/KaravanApi"; +import { useState, useEffect } from "react"; +import { ProjectFile } from "../api/ProjectModels"; +import { ProjectService } from "../api/ProjectService"; +interface Props { + dark: boolean, +} +export const KnowledgebaseHome = (props: Props) => { + + const [blockList, setBlockList] = useState<ProjectFile[]>(); + + useEffect(() => { + KaravanApi.getTemplatesFiles((files:ProjectFile[]) => { + setBlockList([...(files.filter(f => f.name.endsWith('blocklist.txt')))]); + }); + }, []); + + const onChangeBlockedList = async (type: string, name: string, checked: boolean) => { + + let file: ProjectFile | undefined; + let fileContent = ''; + if (type === "component") { + file = blockList?.find(obj => obj.name === 'components-blocklist.txt'); + fileContent = ComponentApi.saveBlockedComponentName(name, checked).join('\n'); + } else { + file = blockList?.find(obj => obj.name === 'kamelets-blocklist.txt'); + fileContent = KameletApi.saveBlockedKameletName(name, checked).join('\n'); + } + if (file) { + file.code = fileContent; + ProjectService.updateFile(file, false); + } + } + + return ( + <KnowledgebasePage dark={props.dark} changeBlockList={(type: string, name: string, checked: boolean) => onChangeBlockedList(type, name, checked)} /> + ); +} \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/KnowledgebasePage.tsx b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/KnowledgebasePage.tsx index 02c6b641..66eae532 100644 --- a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/KnowledgebasePage.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/KnowledgebasePage.tsx @@ -24,6 +24,7 @@ import {ComponentsTab} from "./components/ComponentsTab"; interface Props { dark: boolean, + changeBlockList: (type: string, name: string, checked: boolean) => void, } export const KnowledgebasePage = (props: Props) => { @@ -76,9 +77,9 @@ export const KnowledgebasePage = (props: Props) => { </Flex> </PageSection> <> - {tab === 'kamelets' && <KameletsTab dark={props.dark} filter={filter} customOnly={customOnly}/>} + {tab === 'kamelets' && <KameletsTab dark={props.dark} filter={filter} customOnly={customOnly} onChange={(name: string, checked: boolean) => props.changeBlockList('kamelet', name, checked)} />} {tab === 'eip' && <EipTab dark={props.dark} filter={filter}/>} - {tab === 'components' && <ComponentsTab dark={props.dark} filter={filter}/>} + {tab === 'components' && <ComponentsTab dark={props.dark} filter={filter} onChange={(name: string, checked: boolean) => props.changeBlockList('component', name, checked)} />} </> </PageSection> ) diff --git a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentCard.tsx b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentCard.tsx index 338dc3b9..f99d8136 100644 --- a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentCard.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentCard.tsx @@ -14,32 +14,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { - CardHeader, Card, CardTitle, CardBody, CardFooter, Badge + CardHeader, Card, CardTitle, CardBody, CardFooter, Badge, Checkbox } from '@patternfly/react-core'; import '../../designer/karavan.css'; import {CamelUi} from "../../designer/utils/CamelUi"; import {Component} from "karavan-core/lib/model/ComponentModels"; import {useKnowledgebaseStore} from "../KnowledgebaseStore"; import {shallow} from "zustand/shallow"; +import { ComponentApi } from 'karavan-core/lib/api/ComponentApi'; interface Props { component: Component, + onChange: (name: string, checked: boolean) => void } export function ComponentCard(props: Props) { const [setComponent, setModalOpen] = useKnowledgebaseStore((s) => [s.setComponent, s.setModalOpen], shallow) - const component = props.component; + const [blockedComponents, setBlockedComponents] = useState<string[]>(); + useEffect(() => { + setBlockedComponents(ComponentApi.getBlockedComponentNames()); + }, []); + - function click (event: React.MouseEvent) { - setComponent(component) - setModalOpen(true); + function click(event: React.MouseEvent) { + const { target } = event; + if (!(target as HTMLElement).parentElement?.className.includes("block-checkbox")) { + setComponent(component) + setModalOpen(true); + } } - + function selectComponent(event: React.FormEvent, checked: boolean) { + props.onChange(component.component.name, checked); + setBlockedComponents([...ComponentApi.getBlockedComponentNames()]); + } + const isBlockedComponent = blockedComponents ? blockedComponents.findIndex(r => r === component.component.name) > -1 : false; return ( <Card isCompact key={component.component.name} className="kamelet-card" onClick={event => click(event)} @@ -47,6 +60,7 @@ export function ComponentCard(props: Props) { <CardHeader className="header-labels"> {component.component.supportType === 'Supported' && <Badge isRead className="support-type labels">{component.component.supportType}</Badge>} <Badge isRead className="support-level labels">{component.component.supportLevel}</Badge> + <Checkbox id={component.component.name} className="block-checkbox labels" isChecked={!isBlockedComponent} onChange={(_, checked) => selectComponent(_, checked)} /> </CardHeader> <CardHeader> {CamelUi.getIconForComponent(component.component.title, component.component.label)} diff --git a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentsTab.tsx b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentsTab.tsx index 708b04bd..9e9fb470 100644 --- a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentsTab.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentsTab.tsx @@ -29,6 +29,7 @@ import {useKnowledgebaseStore} from "../KnowledgebaseStore"; interface Props { dark: boolean, filter: string, + onChange: (name: string, checked: boolean) => void, } export function ComponentsTab(props: Props) { @@ -49,7 +50,7 @@ export function ComponentsTab(props: Props) { <PageSection isFilled className="kamelets-page" variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light}> <Gallery hasGutter> {components.map(c => ( - <ComponentCard key={c.component.name} component={c}/> + <ComponentCard key={c.component.name} component={c} onChange={props.onChange} /> ))} </Gallery> </PageSection> diff --git a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/kamelets/KameletCard.tsx b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/kamelets/KameletCard.tsx index 045846e9..1dff9f2c 100644 --- a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/kamelets/KameletCard.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/kamelets/KameletCard.tsx @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { - CardHeader, Card, CardTitle, CardBody, CardFooter,Badge + CardHeader, Card, CardTitle, CardBody, CardFooter, Badge, Checkbox } from '@patternfly/react-core'; import '../../designer/karavan.css'; import {KameletModel} from "karavan-core/lib/model/KameletModels"; @@ -27,21 +27,34 @@ import {shallow} from "zustand/shallow"; interface Props { kamelet: KameletModel, + onChange: (name: string, checked: boolean) => void } export function KameletCard(props: Props) { const [setKamelet, setModalOpen] = useKnowledgebaseStore((s) => [s.setKamelet, s.setModalOpen], shallow) - + const [blockedKamelets, setBlockedKamelets] = useState<string[]>(); + useEffect(() => { + setBlockedKamelets(KameletApi.getBlockedKameletNames()); + }, []); + const kamelet = props.kamelet; const isCustom = KameletApi.getCustomKameletNames().includes(kamelet.metadata.name); - function click (event: React.MouseEvent) { - setKamelet(kamelet) - setModalOpen(true); + function click(event: React.MouseEvent) { + const { target } = event; + if (!(target as HTMLElement).parentElement?.className.includes("block-checkbox")) { + setKamelet(kamelet) + setModalOpen(true); + } } + function selectKamelet(event: React.FormEvent, checked: boolean) { + props.onChange(kamelet.metadata.name, checked ); + setBlockedKamelets([...KameletApi.getBlockedKameletNames()]); + } + const isblockedKamelet = blockedKamelets ? blockedKamelets.findIndex(r => r === kamelet.metadata.name) > -1 : false; return ( <Card isCompact key={kamelet.metadata.name} className="kamelet-card" onClick={event => click(event)} @@ -49,6 +62,7 @@ export function KameletCard(props: Props) { <CardHeader className="header-labels"> {isCustom && <Badge className="custom">custom</Badge>} <Badge isRead className="support-level labels">{kamelet.metadata.annotations["camel.apache.org/kamelet.support.level"]}</Badge> + <Checkbox id={kamelet.metadata.name} className="block-checkbox labels" isChecked={!isblockedKamelet} onChange={(_, checked) => selectKamelet(_, checked)} /> </CardHeader> <CardHeader> {CamelUi.getIconFromSource(kamelet.icon())} diff --git a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/kamelets/KameletsTab.tsx b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/kamelets/KameletsTab.tsx index fb8df6df..353819e4 100644 --- a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/kamelets/KameletsTab.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/kamelets/KameletsTab.tsx @@ -31,11 +31,7 @@ interface Props { dark: boolean, filter: string, customOnly: boolean, -} - -interface Props { - dark: boolean, - filter: string, + onChange: (name: string, checked: boolean) => void } export function KameletsTab(props: Props) { @@ -55,7 +51,7 @@ export function KameletsTab(props: Props) { variant={dark ? PageSectionVariants.darker : PageSectionVariants.light}> <Gallery hasGutter> {kameletList.map(k => ( - <KameletCard key={k.metadata.name} kamelet={k}/> + <KameletCard key={k.metadata.name} kamelet={k} onChange={props.onChange} /> ))} </Gallery> </PageSection> diff --git a/karavan-web/karavan-app/src/main/webui/src/main/MainRoutes.tsx b/karavan-web/karavan-app/src/main/webui/src/main/MainRoutes.tsx index 0434dcef..91956b2c 100644 --- a/karavan-web/karavan-app/src/main/webui/src/main/MainRoutes.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/main/MainRoutes.tsx @@ -21,9 +21,9 @@ import {ProjectsPage} from "../projects/ProjectsPage"; import {ProjectPage} from "../project/ProjectPage"; import {ServicesPage} from "../services/ServicesPage"; import {ContainersPage} from "../containers/ContainersPage"; -import {KnowledgebasePage} from "../knowledgebase/KnowledgebasePage"; import {TemplatesPage} from "../templates/TemplatesPage"; import {ConfigurationPage} from "../config/ConfigurationPage"; +import { KnowledgebaseHome } from '../knowledgebase/KnowledgebaseHome'; export function MainRoutes() { @@ -34,7 +34,7 @@ export function MainRoutes() { <Route path="/templates" element={<TemplatesPage key={'templates'}/>}/> <Route path="/services" element={<ServicesPage key="services"/>}/> <Route path="/containers" element={<ContainersPage key="services"/>}/> - <Route path="/knowledgebase" element={<KnowledgebasePage dark={false}/>}/> + <Route path="/knowledgebase" element={<KnowledgebaseHome dark={false}/>}/> <Route path="/configuration" element={<ConfigurationPage dark={false}/>}/> <Route path="*" element={<Navigate to="/projects" replace/>}/> </Routes> diff --git a/karavan-web/karavan-app/src/main/webui/src/main/useMainHook.tsx b/karavan-web/karavan-app/src/main/webui/src/main/useMainHook.tsx index ed8b5fce..5508002f 100644 --- a/karavan-web/karavan-app/src/main/webui/src/main/useMainHook.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/main/useMainHook.tsx @@ -50,6 +50,7 @@ export function useMainHook () { }); updateKamelets(); updateComponents(); + ProjectService.reloadBlockedTemplates(); // updateSupportedComponents(); // not implemented yet } } @@ -59,7 +60,8 @@ export function useMainHook () { ProjectService.reloadKamelets(); }); } - + + async function updateComponents(): Promise<void> { await new Promise(resolve => { KaravanApi.getComponents(code => {