diff --git a/locales/en/plugin__nmstate-console-plugin.json b/locales/en/plugin__nmstate-console-plugin.json index 9e2bf5d4..91644ada 100644 --- a/locales/en/plugin__nmstate-console-plugin.json +++ b/locales/en/plugin__nmstate-console-plugin.json @@ -6,6 +6,7 @@ "Actions": "Actions", "Add another interface to the policy": "Add another interface to the policy", "Add Label": "Add Label", + "Add mapping": "Add mapping", "Add Option": "Add Option", "Aggregation mode": "Aggregation mode", "An error occurred": "An error occurred", @@ -96,6 +97,9 @@ "NodeNetworkState": "NodeNetworkState", "nodes": "nodes", "None": "None", + "Open vSwitch bridge mapping": "Open vSwitch bridge mapping", + "OVN localnet name": "OVN localnet name", + "OVS bridge name": "OVS bridge name", "Pending": "Pending", "Please <2>try again.": "Please <2>try again.", "Policy details": "Policy details", @@ -124,6 +128,7 @@ "Server": "Server", "System Description": "System Description", "System Name": "System Name", + "The Open vSwitch bridge mapping is a list of Open vSwitch bridges and the physical interfaces that are connected to them.": "The Open vSwitch bridge mapping is a list of Open vSwitch bridges and the physical interfaces that are connected to them.", "This is the list of ports to copy MAC address from. Select one of the matched ports this policy will apply to": "This is the list of ports to copy MAC address from. Select one of the matched ports this policy will apply to", "This node already has a policy matching it": "This node already has a policy matching it", "This policy must be edited via YAML": "This policy must be edited via YAML", diff --git a/package.json b/package.json index 10a6e426..dab3e42f 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,12 @@ "helm:build": "mkdir -p tmp && helm package -d tmp deployment/nmstate-console-plugin/" }, "devDependencies": { - "@kubevirt-ui/kubevirt-api": "^0.0.47", - "@openshift-console/dynamic-plugin-sdk": "1.1.0", - "@openshift-console/dynamic-plugin-sdk-webpack": "1.0.1", - "@openshift/dynamic-plugin-sdk": "~1.0.0", - "@openshift/dynamic-plugin-sdk-webpack": "~1.0.0", + "@kubevirt-ui/kubevirt-api": "^1.2.2", + "@openshift-console/dynamic-plugin-sdk": "1.4.0", + "@openshift-console/dynamic-plugin-sdk-webpack": "1.1.1", + "@openshift/dynamic-plugin-sdk": "~5.0.1", + "@openshift/dynamic-plugin-sdk-webpack": "~4.1.0", + "@patternfly/react-icons": "^5.1.1", "@patternfly/react-core": "5.1.1", "@patternfly/react-table": "^5.1.1", "@testing-library/jest-dom": "^5.16.5", diff --git a/scripts/start-console.sh b/scripts/start-console.sh index a22a7a46..c17eaf5a 100755 --- a/scripts/start-console.sh +++ b/scripts/start-console.sh @@ -38,5 +38,9 @@ if [ -x "$(command -v podman)" ]; then fi else BRIDGE_PLUGINS="${npm_package_consolePlugin_name}=http://host.docker.internal:9001" - docker run --pull=always --rm -p "$CONSOLE_PORT":9000 --env-file <(set | grep BRIDGE) $CONSOLE_IMAGE + if [ "$(uname)" == "Darwin" ]; then + docker run --platform=linux/x86_64 --pull=always --rm -p "$CONSOLE_PORT":9000 --env-file <(set | grep BRIDGE) $CONSOLE_IMAGE + else + docker run --pull=always --rm -p "$CONSOLE_PORT":9000 --env-file <(set | grep BRIDGE) $CONSOLE_IMAGE + fi fi diff --git a/src/utils/components/PolicyForm/PolicyForm.tsx b/src/utils/components/PolicyForm/PolicyForm.tsx index 6eb114bd..a161a8ce 100644 --- a/src/utils/components/PolicyForm/PolicyForm.tsx +++ b/src/utils/components/PolicyForm/PolicyForm.tsx @@ -14,7 +14,9 @@ import { import NodeSelectorModal from '../NodeSelectorModal/NodeSelectorModal'; import ApplySelectorCheckbox from './ApplySelectorCheckbox'; +import PolicyFormOVSBridgeMapping from './PolicyFormOVSBridgeMapping'; import PolicyInterfacesExpandable from './PolicyInterfaceExpandable'; +import { isOVSBridgeExisting } from './utils'; import './policy-form.scss'; @@ -28,6 +30,7 @@ type PolicyFormProps = { const PolicyForm: FC = ({ policy, setPolicy, createForm = false, formId }) => { const { t } = useNMStateTranslation(); const [modalOpen, setModalOpen] = useState(false); + const isOVSBridge = isOVSBridgeExisting(policy); const onDescriptionChange = (newDescription: string) => { setPolicy(({ metadata }) => { @@ -137,9 +140,9 @@ const PolicyForm: FC = ({ policy, setPolicy, createForm = false {t('Add another interface to the policy')} - + {isOVSBridge && } ); }; diff --git a/src/utils/components/PolicyForm/PolicyFormOVSBridgeMapping.tsx b/src/utils/components/PolicyForm/PolicyFormOVSBridgeMapping.tsx new file mode 100644 index 00000000..66184816 --- /dev/null +++ b/src/utils/components/PolicyForm/PolicyFormOVSBridgeMapping.tsx @@ -0,0 +1,54 @@ +import React, { FC } from 'react'; +import { Updater } from 'use-immer'; + +import { Button, ButtonVariant, Popover, Text } from '@patternfly/react-core'; +import { HelpIcon, PlusCircleIcon } from '@patternfly/react-icons'; +import { V1NodeNetworkConfigurationPolicy } from '@types'; +import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation'; + +import PolicyFormOVSBridgeMappingExpandable from './PolicyFormOVSBridgeMappingExpandable'; + +type PolicyFormOVSBridgeMappingProps = { + policy: V1NodeNetworkConfigurationPolicy; + setPolicy: Updater; +}; + +const PolicyFormOVSBridgeMapping: FC = ({ policy, setPolicy }) => { + const { t } = useNMStateTranslation(); + + return ( +
+ + {t('Open vSwitch bridge mapping')}{' '} + + + + + + + + +
+ ); +}; + +export default PolicyFormOVSBridgeMapping; diff --git a/src/utils/components/PolicyForm/PolicyFormOVSBridgeMappingExpandable.tsx b/src/utils/components/PolicyForm/PolicyFormOVSBridgeMappingExpandable.tsx new file mode 100644 index 00000000..c30c4698 --- /dev/null +++ b/src/utils/components/PolicyForm/PolicyFormOVSBridgeMappingExpandable.tsx @@ -0,0 +1,76 @@ +import React, { FC, FormEvent } from 'react'; +import { Updater } from 'use-immer'; + +import { + Button, + ButtonVariant, + Flex, + FlexItem, + PageSection, + PageSectionVariants, + TextInput, + Title, +} from '@patternfly/react-core'; +import { MinusCircleIcon } from '@patternfly/react-icons'; +import { V1NodeNetworkConfigurationPolicy } from '@types'; +import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation'; + +type PolicyFormOVSBridgeMappingExpandableProps = { + policy: V1NodeNetworkConfigurationPolicy; + setPolicy: Updater; +}; + +const PolicyFormOVSBridgeMappingExpandable: FC = ({ + policy, + setPolicy, +}) => { + const { t } = useNMStateTranslation(); + + const onChange = + (index: number, field: string) => (_: FormEvent, value: string) => { + setPolicy((draftPolicy) => { + draftPolicy.spec.desiredState.ovn['bridge-mapping'][index] = { + ...draftPolicy.spec.desiredState.ovn['bridge-mapping'][index], + [field]: value, + }; + }); + }; + + const onRemove = (index: number) => { + setPolicy((draftPolicy) => { + draftPolicy.spec.desiredState.ovn['bridge-mapping'].splice(index, 1); + }); + }; + + return policy?.spec?.desiredState?.ovn?.['bridge-mapping']?.map((bridgeMapping, index) => { + return ( + + + + + {t('OVN localnet name')} + + + + + + {t('OVS bridge name')} + + + + + + + + + ); + }); +}; + +export default PolicyFormOVSBridgeMappingExpandable; diff --git a/src/utils/components/PolicyForm/PolicyInterface.tsx b/src/utils/components/PolicyForm/PolicyInterface.tsx index c47bbd0d..62f3c7f9 100644 --- a/src/utils/components/PolicyForm/PolicyInterface.tsx +++ b/src/utils/components/PolicyForm/PolicyInterface.tsx @@ -29,14 +29,18 @@ import { AUTO_ROUTES, InterfaceType, NodeNetworkConfigurationInterface, + V1NodeNetworkConfigurationPolicy, } from '@types'; import BondOptions from './BondOptions'; import { DEFAULT_PREFIX_LENGTH, INTERFACE_TYPE_OPTIONS, NETWORK_STATES } from './constants'; import CopyMAC from './CopyMAC'; -import { validateInterfaceName } from './utils'; +import { isOVSBridgeExisting, validateInterfaceName } from './utils'; -export type onInterfaceChangeType = (policyInterface: NodeNetworkConfigurationInterface) => void; +export type onInterfaceChangeType = ( + policyInterface: NodeNetworkConfigurationInterface, + policy: V1NodeNetworkConfigurationPolicy, +) => void; type HandleSelectChange = FormSelectProps['onChange']; @@ -64,14 +68,30 @@ const PolicyInterface: FC = ({ }; const handleTypechange: HandleSelectChange = (event, newType: string) => { - onInterfaceChange((draftInterface) => { + onInterfaceChange((draftInterface, draftPolicy) => { draftInterface.type = newType as InterfaceType; + !isOVSBridgeExisting(draftPolicy) && delete draftPolicy.spec.desiredState.ovn; if (newType === InterfaceType.LINUX_BRIDGE) { delete draftInterface['link-aggregation']; draftInterface.bridge = { port: [], options: {} }; } + if (newType === InterfaceType.OVS_BRIDGE) { + delete draftInterface['link-aggregation']; + draftInterface.bridge = { port: [], options: {} }; + if (!draftPolicy?.spec?.desiredState?.ovn) { + draftPolicy.spec.desiredState.ovn = { + 'bridge-mapping': [], + }; + } + draftPolicy.spec.desiredState.ovn['bridge-mapping'].push({ + bridge: '', + localnet: '', + state: 'present', + }); + } + if (newType === InterfaceType.BOND) { delete draftInterface.bridge; draftInterface['link-aggregation'] = { @@ -155,7 +175,10 @@ const PolicyInterface: FC = ({ : delete draftInterface['link-aggregation'].port; } - if (draftInterface.type === InterfaceType.LINUX_BRIDGE) { + if ( + draftInterface.type === InterfaceType.LINUX_BRIDGE || + draftInterface.type === InterfaceType.OVS_BRIDGE + ) { ensurePath(draftInterface, 'bridge.port'); value ? (draftInterface.bridge.port = [{ name: value }]) @@ -346,7 +369,8 @@ const PolicyInterface: FC = ({ )} )} - {policyInterface.type === InterfaceType.LINUX_BRIDGE && ( + {(policyInterface.type === InterfaceType.LINUX_BRIDGE || + policyInterface.type === InterfaceType.OVS_BRIDGE) && ( = ({ interfaceIndex, 1, ); + !isOVSBridgeExisting(draftPolicy) && delete draftPolicy.spec.desiredState.ovn; }); }; @@ -95,7 +96,7 @@ const PolicyInterfacesExpandable: FC = ({ policyInterface={policyInterface} onInterfaceChange={(updateInterface: onInterfaceChangeType) => setPolicy((draftPolicy) => { - updateInterface(draftPolicy.spec.desiredState.interfaces[index]); + updateInterface(draftPolicy.spec.desiredState.interfaces[index], draftPolicy); }) } /> diff --git a/src/utils/components/PolicyForm/constants.ts b/src/utils/components/PolicyForm/constants.ts index 879ac8d9..74066908 100644 --- a/src/utils/components/PolicyForm/constants.ts +++ b/src/utils/components/PolicyForm/constants.ts @@ -7,7 +7,8 @@ export enum NETWORK_STATES { } export const INTERFACE_TYPE_OPTIONS = { - [InterfaceType.LINUX_BRIDGE]: 'Bridge', + [InterfaceType.LINUX_BRIDGE]: 'Linux bridge', + [InterfaceType.OVS_BRIDGE]: 'Open vSwitch bridge', [InterfaceType.BOND]: 'Bonding', [InterfaceType.ETHERNET]: 'Ethernet', }; diff --git a/src/utils/components/PolicyForm/utils.ts b/src/utils/components/PolicyForm/utils.ts index 219e2be8..34673f51 100644 --- a/src/utils/components/PolicyForm/utils.ts +++ b/src/utils/components/PolicyForm/utils.ts @@ -1,6 +1,10 @@ import { TFunction } from 'react-i18next'; -import { NodeNetworkConfigurationInterface } from '@types'; +import { + InterfaceType, + NodeNetworkConfigurationInterface, + V1NodeNetworkConfigurationPolicy, +} from '@types'; import { t } from '@utils/hooks/useNMStateTranslation'; import { INTERFACE_TYPE_OPTIONS } from './constants'; @@ -17,6 +21,11 @@ export const getExpandableTitle = ( return t('Policy interface'); }; +export const isOVSBridgeExisting = (policy: V1NodeNetworkConfigurationPolicy): boolean => + policy?.spec?.desiredState?.interfaces?.some( + (iface: NodeNetworkConfigurationInterface) => iface?.type === InterfaceType.OVS_BRIDGE, + ); + export const validateInterfaceName = (name: string): string => { if (!name) return ''; diff --git a/yarn.lock b/yarn.lock index f33686cb..41fa3fd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -691,10 +691,10 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@kubevirt-ui/kubevirt-api@^0.0.47": - version "0.0.47" - resolved "https://registry.yarnpkg.com/@kubevirt-ui/kubevirt-api/-/kubevirt-api-0.0.47.tgz#763a5c1da69a64772545465c83e1bb9e386d1523" - integrity sha512-W+gZymVdT8Jb6alP8PRYlp7JMaki4gK8run45gw+2QOp7VNXDwT21CpZU/eANupftuhIH102hLITAjlvtIS/qA== +"@kubevirt-ui/kubevirt-api@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@kubevirt-ui/kubevirt-api/-/kubevirt-api-1.2.2.tgz#2a056159fb503d36bca94e62f9c2899aa2728202" + integrity sha512-8hcafV28OAnnNzQuCc5DOk/NCU8IR+h2/xCYW5gCZJFyziidWZwI3FAIcdalxQ9fgY9sgM2di65WeN5j/l4QNg== "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" @@ -722,10 +722,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@openshift-console/dynamic-plugin-sdk-webpack@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@openshift-console/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-1.0.1.tgz#0dbb0cf2f10526439249888c10c25223571f918a" - integrity sha512-xUv6obNPSp0T1Nc+ywywobQMvfxxsySraIejwnrjxBYelFMuMp3Tn/4c4ziUp/mftBTxPy0T4Nx4sLRB4gAPsw== +"@openshift-console/dynamic-plugin-sdk-webpack@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@openshift-console/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-1.1.1.tgz#349326366f84defa8c81baa8be411257d0b1e954" + integrity sha512-1m1iBpdj0HNWQIEFAhMlI5pmigyN5B/Eqcb2nGAI2n030hOOxAalxySO95dvEFa2hiyXMUt4z/Tyq4fDYKZ2nw== dependencies: "@openshift/dynamic-plugin-sdk-webpack" "^4.0.2" ajv "^6.12.3" @@ -738,10 +738,10 @@ semver "6.x" webpack "5.75.0" -"@openshift-console/dynamic-plugin-sdk@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@openshift-console/dynamic-plugin-sdk/-/dynamic-plugin-sdk-1.1.0.tgz#63b525aba54f01231921af23939cae0059e02eaf" - integrity sha512-gaNqhGq7nne+cC3QaCxuyK+SPvuC/K5Ww5tV2FabIepgbl3qKkUePrejPgvBJ52tMID1sn8prdW/rImYma6NwQ== +"@openshift-console/dynamic-plugin-sdk@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@openshift-console/dynamic-plugin-sdk/-/dynamic-plugin-sdk-1.4.0.tgz#802c1225f22a6d59b16b5b47f0ff81f176f2db72" + integrity sha512-LcwNqG2ZzMKLiSQNpHBwAtzALQghUngILhdijndpIklSH0Dsqi05nhwAJPyuFrpwywZIj6BxE1tmaBzR5gUfcA== dependencies: classnames "2.x" immutable "3.x" @@ -767,22 +767,23 @@ semver "^7.3.7" yup "^0.32.11" -"@openshift/dynamic-plugin-sdk-webpack@~1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@openshift/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-1.0.0.tgz#665fcda7601bee1c0718b7f8b75126c061e18c32" - integrity sha512-yEoqGSDLfaqGYKZseYMY/HvWzQIfUrQpQXjB+aMYv4hxxwF0dw3DAQYmZIauItJObGvMyqlxlCnTRMZCNtIcxg== +"@openshift/dynamic-plugin-sdk-webpack@~4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@openshift/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-4.1.0.tgz#eb62ee18d975431411b78da15c9774d95dbe1fa7" + integrity sha512-Pkq6R+fkoE0llgv9WJBcotViAPywrzDkpWK0HSTmrVyfEuWS5cuZUs8ono6L5w9BqDBRXm3ceEuUAZA/Zrar1w== dependencies: - glob "^7.2.0" lodash "^4.17.21" + semver "^7.3.7" yup "^0.32.11" -"@openshift/dynamic-plugin-sdk@~1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@openshift/dynamic-plugin-sdk/-/dynamic-plugin-sdk-1.0.0.tgz#7f05f7023a3eff7973067541fa0c23dcbcefd765" - integrity sha512-lkb2ZlbFXziPzVWOykfSAzeA1siGxMlzIDzd1wBh0RikjVr7a07U6gjrOcByaeC45AdUc2SNGRTERWoobj4THQ== +"@openshift/dynamic-plugin-sdk@~5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@openshift/dynamic-plugin-sdk/-/dynamic-plugin-sdk-5.0.1.tgz#3487afe57d07a28b9aba393e643d86ca13a3735c" + integrity sha512-+azUBN6FgcDmlcWMzG0bthcRUJC1u12wf9xa2aJGFbC/uTiOXwjrkcQ7LW/PyK5Em7wDhwaUdapaeOgh8I6Kjg== dependencies: - lodash-es "^4.17.21" + lodash "^4.17.21" semver "^7.3.7" + uuid "^8.3.2" yup "^0.32.11" "@patternfly/react-core@5.1.1": @@ -4002,7 +4003,7 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@7.x, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: +glob@7.x, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==