Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NNS topology #108

Merged
merged 1 commit into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/on_pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ jobs:
uses: cypress-io/[email protected]
with:
start: yarn serve-build, yarn start-console
wait-on: "http://localhost:9001"
wait-on: 'http://localhost:9001'
wait-on-timeout: 120
browser: chrome
headed: false

Expand Down
2 changes: 1 addition & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ export default defineConfig({
},
viewportWidth: 1920,
viewportHeight: 1080,
defaultCommandTimeout: 40_000,
defaultCommandTimeout: 60_000,
});
4 changes: 2 additions & 2 deletions cypress/e2e/NewPolicy.spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('Create new policy with form', () => {
it('with bridge interface', () => {
const testPolicyName = 'test-bridge-policy-name';
cy.visit('/k8s/cluster/nmstate.io~v1~NodeNetworkConfigurationPolicy');
cy.contains('button[type="button"]', 'Create').click();
cy.byTestID('item-create').click();

cy.contains('button', 'From Form').click();

Expand All @@ -42,7 +42,7 @@ describe('Create new policy with form', () => {
it('with bridge and bond interface', () => {
const testPolicyName = 'test-bridge-bond-policy-name';
cy.visit('/k8s/cluster/nmstate.io~v1~NodeNetworkConfigurationPolicy');
cy.contains('button[type="button"]', 'Create').click();
cy.byTestID('item-create').click();

cy.contains('button', 'From Form').click();

Expand Down
10 changes: 10 additions & 0 deletions locales/en/plugin__nmstate-console-plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"{{annotationsCount}} Annotations": "{{annotationsCount}} Annotations",
"{{matchingNodeText}} matching": "{{matchingNodeText}} matching",
"{{modelName}} details": "{{modelName}} details",
"{{qualifiedNodesCount}} matching Nodes found": "{{qualifiedNodesCount}} matching Nodes found",
"<0>List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.</0><1><0>{{kind}}</0><1>metadata</1><2>ownerReferences</2></1>": "<0>List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.</0><1><0>{{kind}}</0><1>metadata</1><2>ownerReferences</2></1>",
"Aborted": "Aborted",
"Actions": "Actions",
"Add another interface to the policy": "Add another interface to the policy",
Expand All @@ -10,6 +12,7 @@
"Add Option": "Add Option",
"Aggregation mode": "Aggregation mode",
"An error occurred": "An error occurred",
"Annotations": "Annotations",
"Apply this NodeNetworkConfigurationPolicy only to specific subsets of nodes using the node selector": "Apply this NodeNetworkConfigurationPolicy only to specific subsets of nodes using the node selector",
"Are you sure you want to remove {{interfaceType}} interface <5>{{name}}</5>?": "Are you sure you want to remove {{interfaceType}} interface <5>{{name}}</5>?",
"Auto-DNS": "Auto-DNS",
Expand All @@ -30,6 +33,7 @@
"Create": "Create",
"Create NodeNetworkConfigurationPolicy": "Create NodeNetworkConfigurationPolicy",
"Create NodeNetworkConfigurationPolicy error": "Create NodeNetworkConfigurationPolicy error",
"Created at": "Created at",
"Created interfaces cannot be removed": "Created interfaces cannot be removed",
"Delete": "Delete",
"Delete NodeNetworkConfigurationPolicy?": "Delete NodeNetworkConfigurationPolicy?",
Expand Down Expand Up @@ -58,6 +62,7 @@
"Explore {{kind}} list": "Explore {{kind}} list",
"Failing": "Failing",
"Features": "Features",
"Filter": "Filter",
"From Form": "From Form",
"Id": "Id",
"Interface name": "Interface name",
Expand All @@ -73,6 +78,7 @@
"IPV4 address": "IPV4 address",
"IPv6": "IPv6",
"Key": "Key",
"Labels": "Labels",
"Learn more": "Learn more",
"List of network interfaces that should be created, modified, or removed, as a part of this policy.": "List of network interfaces that should be created, modified, or removed, as a part of this policy.",
"LLDP": "LLDP",
Expand All @@ -82,6 +88,7 @@
"MAC Address": "MAC Address",
"Matched nodes": "Matched nodes",
"Matched nodes summary": "Matched nodes summary",
"More info: ": "More info: ",
"MTU": "MTU",
"Name": "Name",
"Neighbors": "Neighbors",
Expand All @@ -91,15 +98,18 @@
"No matching Nodes found for the labels": "No matching Nodes found for the labels",
"No node network configuration policies defined yet": "No node network configuration policies defined yet",
"No NodeNetworkStates found": "No NodeNetworkStates found",
"No owner": "No owner",
"Node network is configured and managed by NM state. Create a node network configuration policy to describe the requested network configuration on your nodes in the cluster. The node network configuration enactment reports the netwrok policies enacted upon each node.": "Node network is configured and managed by NM state. Create a node netowrk configuration policy to describe the requested network configuration on your nodes in the cluster. The node network configuration enactment reports the netwrok policies enacted upon each node.",
"Node Selector": "Node Selector",
"NodeNetworkConfigurationPolicy": "NodeNetworkConfigurationPolicy",
"NodeNetworkState": "NodeNetworkState",
"nodes": "nodes",
"None": "None",
"Not available": "Not available",
"Open vSwitch bridge mapping": "Open vSwitch bridge mapping",
"OVN localnet name": "OVN localnet name",
"OVS bridge name": "OVS bridge name",
"Owner": "Owner",
"Pending": "Pending",
"Please <2>try again</2>.": "Please <2>try again</2>.",
"Policy details": "Policy details",
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"destroy-cluster": "./scripts/clean-cluster.sh",
"login-cluster": "oc login https://127.0.0.1:6443 --token abcdef.0123456789abcdef",
"cypress": "cypress run --browser chrome",
"cypress:open": "cypress open",
"i18n": "i18next \"src/**/*.{js,jsx,ts,tsx}\" [-oc] -c i18next-parser.config.mjs",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
Expand All @@ -31,9 +32,10 @@
"@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-icons": "^5.1.1",
"@patternfly/react-table": "^5.1.1",
"@patternfly/react-topology": "5.2.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.0.0",
"@testing-library/react-hooks": "^8.0.1",
Expand All @@ -46,7 +48,7 @@
"@types/react-router-dom": "^5.3.2",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"classnames": "^2.3.1",
"classnames": "^2.5.1",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1",
"cypress": "11",
Expand Down
4 changes: 1 addition & 3 deletions src/console-models/NodeNetworkStateModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { K8sModel } from '@openshift-console/dynamic-plugin-sdk/lib/api/common-t

import { modelToGroupVersionKind, modelToRef } from './modelUtils';

const NodeNetworkStateModel: K8sModel = {
export const NodeNetworkStateModel: K8sModel = {
label: 'NodeNetworkState',
labelPlural: 'NodeNetworkStates',
apiVersion: 'v1beta1',
Expand All @@ -17,5 +17,3 @@ const NodeNetworkStateModel: K8sModel = {

export const NodeNetworkStateModelGroupVersionKind = modelToGroupVersionKind(NodeNetworkStateModel);
export const NodeNetworkStateModelRef = modelToRef(NodeNetworkStateModel);

export default NodeNetworkStateModel;
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@ export interface NodeNetworkConfigurationInterface {
'base-iface': string;
protocol?: string;
};

patch?: {
peer?: string;
};
}
17 changes: 17 additions & 0 deletions src/utils/components/DetailItem/DetailItem.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.description-item__title {
justify-content: space-between;
flex: 1;
}

.margin-top {
margin-top: var(--pf-v5-global--spacer--md);
}

.DescriptionItemHeader {
&--list-term {
.pf-c-description-list__text {
align-items: center;
display: inline-flex;
}
}
}
117 changes: 117 additions & 0 deletions src/utils/components/DetailItem/DetailItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import React, { FC, ReactNode } from 'react';
import classnames from 'classnames';

import {
Button,
ButtonVariant,
DescriptionListDescription,
DescriptionListGroup,
DescriptionListTermHelpText,
Flex,
FlexItem,
} from '@patternfly/react-core';
import { PencilAltIcon } from '@patternfly/react-icons';
import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation';

import { DetailItemHeader } from './DetailItemHeader';
import EditButtonWithTooltip from './EditButtonWithTooltip';

import './DetailItem.scss';

type DetailItemProps = {
popoverBodyContent?: ReactNode;
breadcrumb?: string;
className?: string;
testId?: string;
description: ReactNode | string;
header?: ReactNode;
editOnTitleJustify?: boolean;
isDisabled?: boolean;
isEdit?: boolean;
isPopover?: boolean;
headerBadge?: ReactNode;
messageOnDisabled?: string;
moreInfoURL?: string;
onEditClick?: () => void;
showEditOnTitle?: boolean;
};

const DetailItem: FC<DetailItemProps> = ({
popoverBodyContent,
breadcrumb,
className,
testId,
description,
header,
editOnTitleJustify = false,
isDisabled,
isEdit,
isPopover,
headerBadge,
messageOnDisabled,
moreInfoURL,
onEditClick,
showEditOnTitle,
}) => {
const { t } = useNMStateTranslation();
const NotAvailable = <span className="text-muted">{t('Not available')}</span>;

return (
<DescriptionListGroup className={classnames('pf-c-description-list__group', className)}>
<DescriptionListTermHelpText className="pf-c-description-list__term">
<Flex
justifyContent={{
default: editOnTitleJustify ? 'justifyContentSpaceBetween' : 'justifyContentFlexStart',
}}
>
{(popoverBodyContent || breadcrumb || header || headerBadge || moreInfoURL) && (
<FlexItem>
<DetailItemHeader
bodyContent={popoverBodyContent}
breadcrumb={breadcrumb}
descriptionHeader={header}
isPopover={isPopover}
label={headerBadge}
moreInfoURL={moreInfoURL}
/>
</FlexItem>
)}
{isEdit && showEditOnTitle && (
<FlexItem>
<Button
data-test-id={`${testId}-edit`}
isDisabled={isDisabled}
isInline
onClick={onEditClick}
variant={ButtonVariant.link}
>
{t('Edit')}
<PencilAltIcon className="co-icon-space-l pf-v5-c-button-icon--plain" />
</Button>
</FlexItem>
)}
</Flex>
</DescriptionListTermHelpText>

<DescriptionListDescription
className="pf-c-description-list__description"
data-test-id={testId}
>
{isEdit && !showEditOnTitle ? (
<EditButtonWithTooltip
isEditable={!isDisabled}
onEditClick={onEditClick}
testId={testId}
tooltipContent={messageOnDisabled}
>
{description ?? NotAvailable}
</EditButtonWithTooltip>
) : (
description
)}
</DescriptionListDescription>
</DescriptionListGroup>
);
};

export default DetailItem;
74 changes: 74 additions & 0 deletions src/utils/components/DetailItem/DetailItemHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { FC, ReactNode } from 'react';

import {
Breadcrumb,
BreadcrumbItem,
DescriptionListTerm,
DescriptionListTermHelpTextButton,
Popover,
} from '@patternfly/react-core';
import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation';

import './DetailItem.scss';

type DetailItemHeaderProps = {
bodyContent: ReactNode;
breadcrumb?: string;
descriptionHeader: ReactNode;
isPopover: boolean;
label?: ReactNode;
maxWidth?: string;
moreInfoURL?: string;
};

export const DetailItemHeader: FC<DetailItemHeaderProps> = ({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why making such a genric component? cant split for 2 ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also copied from kubevirt

bodyContent,
breadcrumb,
descriptionHeader,
isPopover,
label,
maxWidth,
moreInfoURL,
}) => {
const { t } = useNMStateTranslation();

if (isPopover && bodyContent) {
return (
<Popover
bodyContent={
<>
{bodyContent}
{moreInfoURL && (
<>
{t('More info: ')}
<a href={moreInfoURL}>{moreInfoURL}</a>
</>
)}
{breadcrumb && (
<div className="margin-top">
<Breadcrumb>
{breadcrumb.split('.').map((item) => (
<BreadcrumbItem key={item}>{item}</BreadcrumbItem>
))}
</Breadcrumb>
</div>
)}
</>
}
hasAutoWidth
headerContent={descriptionHeader}
maxWidth={maxWidth || '30rem'}
>
<DescriptionListTermHelpTextButton className="pf-c-description-list__text">
{descriptionHeader}
</DescriptionListTermHelpTextButton>
</Popover>
);
}

return (
<DescriptionListTerm className="DescriptionItemHeader--list-term">
{descriptionHeader} {label}
</DescriptionListTerm>
);
};
30 changes: 30 additions & 0 deletions src/utils/components/DetailItem/EditButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, { FC, PropsWithChildren, SyntheticEvent } from 'react';

import { Button, ButtonVariant, Flex } from '@patternfly/react-core';
import { PencilAltIcon } from '@patternfly/react-icons';

type EditButtonProps = PropsWithChildren<{
isEditable: boolean;
onEditClick?: () => void;
testId: string;
}>;

const EditButton: FC<EditButtonProps> = ({ children, onEditClick, isEditable, testId }) => (
<Button
onClick={(e: SyntheticEvent<HTMLButtonElement>) => {
e.stopPropagation();
onEditClick?.();
}}
data-test-id={testId}
isDisabled={!isEditable}
isInline
variant={ButtonVariant.link}
>
<Flex>
{children}
<PencilAltIcon className="co-icon-space-l" />
</Flex>
</Button>
);

export default EditButton;
Loading
Loading