Skip to content

Commit

Permalink
Merge pull request #75 from kamilmadejek/nephio-network-editor
Browse files Browse the repository at this point in the history
Nephio network editor. Configurable editor fixes and performance enhancement.
  • Loading branch information
nephio-prow[bot] authored Dec 3, 2024
2 parents ba9dd8a + 1311602 commit 5651b39
Show file tree
Hide file tree
Showing 46 changed files with 1,843 additions and 404 deletions.
4 changes: 4 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const { TextEncoder, TextDecoder } = require('util');

global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@
"prettier --write"
]
},
"jest": {
"setupFilesAfterEnv": [
"<rootDir>/../../../jest.setup.js",
"@testing-library/jest-dom"
],
"moduleNameMapper": {
"monaco-editor": "<rootDir>/../../../node_modules/monaco-editor/monaco.d.ts"
}
},
"dependencies": {
"@types/react": "^17",
"@types/react-dom": "^17"
Expand Down
4 changes: 2 additions & 2 deletions plugins/cad/src/components/Controls/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ type SelectProps = {
helperText?: string;
};

export const Select = ({ label, selected, items, onChange, className, helperText }: SelectProps) => {
export const Select = ({ label, selected, items, onChange, className, helperText, ...otherProps }: SelectProps) => {
const handleChange = (event: ChangeEvent<{ name?: string | undefined; value: unknown }>): void => {
onChange(event.target.value as string);
};

return (
<FormControl fullWidth variant="outlined" className={className}>
<FormControl {...otherProps} fullWidth variant="outlined" className={className}>
<InputLabel id="select-label">{label}</InputLabel>
<MaterialSelect labelId="select-label" value={selected} label={label} onChange={handleChange}>
{items.map(item => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { Button, Dialog, DialogActions, DialogContent, DialogTitle, makeStyles } from '@material-ui/core';
import React, { Fragment, useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { KubernetesResource } from '../../types/KubernetesResource';
import { getGroupVersionKind } from '../../utils/kubernetesResource';
import { PackageResource } from '../../utils/packageRevisionResources';
Expand Down Expand Up @@ -91,7 +91,7 @@ export const ResourceEditorDialog = ({ open, onClose, yaml, onSaveYaml, packageR
<Dialog open={open} onClose={onDialogClose} maxWidth="lg">
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<Fragment>
<>
<div className={classes.container} style={{ height: `${latestYamlHeight}px` }}>
{!showYamlView && isNamedEditor ? (
<FirstClassEditorSelector
Expand All @@ -110,7 +110,7 @@ export const ResourceEditorDialog = ({ open, onClose, yaml, onSaveYaml, packageR
Show {showYamlView ? 'Formatted View' : 'YAML View'}
</Button>
)}
</Fragment>
</>
</DialogContent>
<DialogActions>
<Button color="primary" onClick={onDialogClose}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { PackageVariantSetEditor } from './FirstClassEditors/PackageVariantSetEd
import { NephioCapacityParametricEditor } from './ParametricFirstClassEditors/NephioCapacityParametricEditor';
import { NephioTokenParametricEditor } from './ParametricFirstClassEditors/NephioTokenParametricEditor';
import { NephioWorkloadClusterParametricEditor } from './ParametricFirstClassEditors/NephioWorkloadClusterParametricEditor';
import { NephioNetworkParametricEditor } from './ParametricFirstClassEditors/NephioNetworkParametricEditor';

type OnUpdatedYamlFn = (yaml: string) => void;
type OnNoNamedEditorFn = () => void;
Expand Down Expand Up @@ -83,6 +84,9 @@ export const FirstClassEditorSelector = ({
case 'infra.nephio.org/v1alpha1/Token':
return <NephioTokenParametricEditor yamlText={yaml} onResourceChange={onUpdatedYaml} />;

case 'infra.nephio.org/v1alpha1/Network':
return <NephioNetworkParametricEditor yamlText={yaml} onResourceChange={onUpdatedYaml} />;

case 'infra.nephio.org/v1alpha1/WorkloadCluster':
return <NephioWorkloadClusterParametricEditor yamlText={yaml} onResourceChange={onUpdatedYaml} />;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const useStyles = makeStyles(theme => ({
},
}));

export const EditorAccordion = ({ id, title, description, state, children }: EditorAccordionProps) => {
export const EditorAccordion = ({ id, title, description, state, children, ...otherProps }: EditorAccordionProps) => {
const classes = useStyles();
const detailsRef = useRef<HTMLDivElement>(null);

Expand All @@ -70,7 +70,7 @@ export const EditorAccordion = ({ id, title, description, state, children }: Edi
}, [expanded]);

return (
<Accordion expanded={expanded} onChange={onChange}>
<Accordion {...otherProps} expanded={expanded} onChange={onChange}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography className={classes.title}>{title}</Typography>
<Typography className={classes.description}>{description}</Typography>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Copyright 2024 The Nephio Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { createEditorFromConfiguration } from '../PxeParametricEditor/createEditorFromConfiguration';
import { PxeConfigurationFactory } from '../PxeParametricEditor/configuration';
import { metadataEditorSection } from './partial/metadataEditorSection';
import { PxeValueType } from '../PxeParametricEditor/types/PxeConfiguration.types';
import { selectorRosters } from './partial/selectorRosters';

const { section, rowLayout, arrayTypeRoster, objectTypeRoster, selectValue, singleLineText } = PxeConfigurationFactory;

const INTERFACE_KIND_OPTIONS = [
{ value: 'interface', label: 'Interface' },
{ value: 'bridgedomain', label: 'Bridge domain' },
];

const INTERFACE_ATTACHMENT_TYPE_OPTIONS = [
{ value: undefined, label: 'None' },
{ value: 'vlan', label: 'VLAN' },
];

export const NephioNetworkParametricEditor = createEditorFromConfiguration({
topLevelProperties: ['metadata', 'spec'],
entries: [
metadataEditorSection({ isNamespacedResource: true }),
section({ name: 'Topology' }, singleLineText({ path: 'spec.topology', isRequired: true })),
arrayTypeRoster(
{
name: 'Routing tables',
path: 'spec.routingTables',
isRequired: false,
item: { type: PxeValueType.Object, isRequired: true },
},
routingTableItemConfiguration(),
),
arrayTypeRoster(
{
name: 'Bridge domains',
path: 'spec.bridgeDomains',
isRequired: false,
item: { type: PxeValueType.Object, isRequired: true },
},
bridgeDomainItemConfiguration(),
),
],
});

function routingTableItemConfiguration() {
return section(
{ name: 'Routing table' },
singleLineText({ path: '$value.name', isRequired: true }),
arrayTypeRoster(
{
name: 'Prefixes',
path: '$value.prefixes',
isRequired: true,
item: { type: PxeValueType.Object, isRequired: true },
},
prefixItemConfiguration(),
),
arrayTypeRoster(
{
name: 'Interfaces',
path: '$value.interfaces',
item: { type: PxeValueType.Object, isRequired: true },
},
interfaceItemConfiguration(),
),
);
}

function bridgeDomainItemConfiguration() {
return section(
{ name: 'Bridge domain' },
singleLineText({ path: '$value.name', isRequired: true }),
arrayTypeRoster(
{
name: 'Interfaces',
path: '$value.interfaces',
item: { type: PxeValueType.Object, isRequired: true },
},
interfaceItemConfiguration(),
),
);
}

function interfaceItemConfiguration() {
return section(
{ name: 'Interface' },
// TODO Needs different options depending on where interface is used.
selectValue({ path: '$value.kind', isRequired: true, options: INTERFACE_KIND_OPTIONS }),
singleLineText({ path: '$value.interfaceName' }),
singleLineText({ path: '$value.bridgeDomainName' }),
singleLineText({ path: '$value.nodeName' }),
selectValue({ path: '$value.attachmentType', options: INTERFACE_ATTACHMENT_TYPE_OPTIONS }),
...selectorRosters('$value.selector'),
);
}

function prefixItemConfiguration() {
return section(
{ name: 'Prefix' },
singleLineText({ path: '$value.prefix', isRequired: true }),
objectTypeRoster(
{ name: 'Labels', path: '$value.labels' },
rowLayout(singleLineText({ path: '$key' }), singleLineText({ path: '$value', isRequired: true })),
),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ export const NephioWorkloadClusterParametricEditor = createEditorFromConfigurati
{ name: 'Configuration' },
singleLineText({ path: 'spec.clusterName', isRequired: true }),
singleLineText({ path: 'spec.masterInterface' }),
section(
{ name: 'CNIs' },
arrayTypeRoster({ path: 'spec.cnis', isRequired: false, name: 'CNIs' }, singleLineText({ path: 'value' })),
arrayTypeRoster(
{ name: 'CNIs', path: 'spec.cnis', isRequired: false },
singleLineText({ path: '$value', isRequired: true }),
),
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,12 @@ export const metadataEditorSection = ({ isNamespacedResource }: { isNamespacedRe
{ name: 'Resource Metadata' },
singleLineText({ path: 'metadata.name', isRequired: true }),
...(isNamespacedResource ? [singleLineText({ path: 'metadata.namespace' })] : []),
section(
{ name: 'Labels' },
objectTypeRoster(
{ path: 'metadata.labels', isRequired: false },
rowLayout(singleLineText({ path: 'key', isRequired: true }), singleLineText({ path: 'value' })),
),
objectTypeRoster(
{ name: 'Labels', path: 'metadata.labels', isRequired: false },
rowLayout(singleLineText({ path: '$key' }), singleLineText({ path: '$value', isRequired: true })),
),
section(
{ name: 'Annotations' },
objectTypeRoster(
{ path: 'metadata.annotations', isRequired: false },
rowLayout(singleLineText({ path: 'key', isRequired: true }), singleLineText({ path: 'value' })),
),
objectTypeRoster(
{ name: 'Annotations', path: 'metadata.annotations', isRequired: false },
rowLayout(singleLineText({ path: '$key' }), singleLineText({ path: '$value', isRequired: true })),
),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright 2024 The Nephio Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { PxeConfigurationFactory } from '../../PxeParametricEditor/configuration';
import { PxeValueType } from '../../PxeParametricEditor/types/PxeConfiguration.types';

const SELECTOR_OPERATOR_OPTIONS = [
{ value: 'In', label: 'In' },
{ value: 'NotIn', label: 'Not In' },
{ value: 'Exists', label: 'Exists' },
{ value: 'DoesNotExist', label: 'Does Not Exist' },
];

const { section, rowLayout, arrayTypeRoster, objectTypeRoster, selectValue, singleLineText } = PxeConfigurationFactory;

export const selectorRosters = (pathPrefix: string) => [
objectTypeRoster(
{ name: 'Match labels', path: `${pathPrefix}.matchLabels` },
rowLayout(singleLineText({ path: '$key' }), singleLineText({ path: '$value' })),
),
arrayTypeRoster(
{
name: 'Match expressions',
path: `${pathPrefix}.matchExpressions`,
item: { type: PxeValueType.Object, isRequired: true },
},
section(
{ name: 'Match expression' },
rowLayout(
singleLineText({ path: '$value.key', isRequired: true }),
selectValue({ path: '$value.operator', isRequired: true, options: SELECTOR_OPERATOR_OPTIONS }),
),
// TODO Needs different value requirements for different operators.
arrayTypeRoster({ name: 'Values', path: '$value.values' }, singleLineText({ path: '$value', isRequired: true })),
),
),
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Copyright 2024 The Nephio Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { createContext, useContext } from 'react';
import { PxeDiagnosticsReporter } from './types/PxeDiagnostics.types';
import { PxeConfigurationEntry } from './types/PxeConfiguration.types';

export const PxeDiagnosticsContext = createContext<PxeDiagnosticsReporter | null>(null);

export const useDiagnostics = (configurationEntry: PxeConfigurationEntry) => {
const diagnosticsReporter = useContext(PxeDiagnosticsContext);
if (diagnosticsReporter) {
diagnosticsReporter.reportRender(configurationEntry);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright 2024 The Nephio Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { noop } from 'lodash';
import { createContext, useContext, useMemo, useState } from 'react';
import { PxeExpandedSection, PxeExpandedSectionStateTuple } from './types/PxeParametricEditor.types';

export const PxeExpandedSectionContext = createContext<PxeExpandedSectionStateTuple>([undefined, noop]);

export const useNewExpandedSectionState = (): PxeExpandedSectionStateTuple => {
const [expandedSection, setExpandedSection] = useState<PxeExpandedSection>(undefined);
return useMemo<PxeExpandedSectionStateTuple>(() => [expandedSection, setExpandedSection], [expandedSection]);
};

export const useAncestorExpandedSectionState = (): PxeExpandedSectionStateTuple =>
useContext(PxeExpandedSectionContext);
Loading

0 comments on commit 5651b39

Please sign in to comment.