From c9cd785b278d3de31c5774b27f9e783b644f0af3 Mon Sep 17 00:00:00 2001 From: Aviv Turgeman Date: Thu, 8 Aug 2024 16:08:44 +0300 Subject: [PATCH] [WIP] NNS topology Signed-off-by: Aviv Turgeman --- .github/workflows/on_pull_request.yaml | 3 +- cypress.config.ts | 2 +- cypress/e2e/NewPolicy.spec.cy.ts | 4 +- .../en/plugin__nmstate-console-plugin.json | 10 + package.json | 6 +- src/console-models/NodeNetworkStateModel.ts | 4 +- .../NodeNetworkConfigurationInterface.ts | 4 + .../components/DetailItem/DetailItem.scss | 17 + .../components/DetailItem/DetailItem.tsx | 117 +++ .../DetailItem/DetailItemHeader.tsx | 74 ++ .../components/DetailItem/EditButton.tsx | 30 + .../DetailItem/EditButtonWithTooltip.tsx | 37 + .../components/DetailItem/OwnerDetailItem.tsx | 60 ++ .../MetadataLabels/MetadataLabels.scss | 8 + .../MetadataLabels/MetadataLabels.tsx | 38 + .../OwnerReferences/OwnerReferences.tsx | 36 + src/utils/components/Toggles/SelectToggle.tsx | 4 +- src/views/states/details/StateDetailsPage.tsx | 77 ++ src/views/states/list/StatesList.tsx | 37 +- .../InterfaceDrawer/InterfaceDrawer.tsx | 18 +- src/views/states/list/constants.ts | 3 +- src/views/states/manifest.ts | 24 +- src/views/states/topology/Topology.tsx | 108 +++ .../topology/components/CustomGroup.tsx | 23 + .../states/topology/components/CustomNode.tsx | 42 ++ .../TopologySidebar/TopologySidebar.scss | 6 + .../TopologySidebar/TopologySidebar.tsx | 47 ++ .../TopologyToolbar/TopologyToolbar.scss | 31 + .../TopologyToolbar/TopologyToolbar.tsx | 44 ++ .../TopologyToolbar/TopologyToolbarFilter.tsx | 84 +++ src/views/states/topology/utils/constants.ts | 4 + src/views/states/topology/utils/factory.ts | 79 ++ src/views/states/topology/utils/utils.ts | 158 ++++ tsconfig.json | 3 +- yarn.lock | 683 +++++++++++++++++- 35 files changed, 1872 insertions(+), 53 deletions(-) create mode 100644 src/utils/components/DetailItem/DetailItem.scss create mode 100644 src/utils/components/DetailItem/DetailItem.tsx create mode 100644 src/utils/components/DetailItem/DetailItemHeader.tsx create mode 100644 src/utils/components/DetailItem/EditButton.tsx create mode 100644 src/utils/components/DetailItem/EditButtonWithTooltip.tsx create mode 100644 src/utils/components/DetailItem/OwnerDetailItem.tsx create mode 100644 src/utils/components/MetadataLabels/MetadataLabels.scss create mode 100644 src/utils/components/MetadataLabels/MetadataLabels.tsx create mode 100644 src/utils/components/OwnerReferences/OwnerReferences.tsx create mode 100644 src/views/states/details/StateDetailsPage.tsx create mode 100644 src/views/states/topology/Topology.tsx create mode 100644 src/views/states/topology/components/CustomGroup.tsx create mode 100644 src/views/states/topology/components/CustomNode.tsx create mode 100644 src/views/states/topology/components/TopologySidebar/TopologySidebar.scss create mode 100644 src/views/states/topology/components/TopologySidebar/TopologySidebar.tsx create mode 100644 src/views/states/topology/components/TopologyToolbar/TopologyToolbar.scss create mode 100644 src/views/states/topology/components/TopologyToolbar/TopologyToolbar.tsx create mode 100644 src/views/states/topology/components/TopologyToolbar/TopologyToolbarFilter.tsx create mode 100644 src/views/states/topology/utils/constants.ts create mode 100644 src/views/states/topology/utils/factory.ts create mode 100644 src/views/states/topology/utils/utils.ts diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml index 76c52880..920b097e 100644 --- a/.github/workflows/on_pull_request.yaml +++ b/.github/workflows/on_pull_request.yaml @@ -73,7 +73,8 @@ jobs: uses: cypress-io/github-action@v5.8.1 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 diff --git a/cypress.config.ts b/cypress.config.ts index 2d1ad52f..30f17560 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -10,5 +10,5 @@ export default defineConfig({ }, viewportWidth: 1920, viewportHeight: 1080, - defaultCommandTimeout: 40_000, + defaultCommandTimeout: 60_000, }); diff --git a/cypress/e2e/NewPolicy.spec.cy.ts b/cypress/e2e/NewPolicy.spec.cy.ts index fd27f5c6..14ed36c5 100644 --- a/cypress/e2e/NewPolicy.spec.cy.ts +++ b/cypress/e2e/NewPolicy.spec.cy.ts @@ -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(); @@ -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(); diff --git a/locales/en/plugin__nmstate-console-plugin.json b/locales/en/plugin__nmstate-console-plugin.json index d906f2d5..b86c685b 100644 --- a/locales/en/plugin__nmstate-console-plugin.json +++ b/locales/en/plugin__nmstate-console-plugin.json @@ -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.<1><0>{{kind}}<1>metadata<2>ownerReferences": "<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.<1><0>{{kind}}<1>metadata<2>ownerReferences", "Aborted": "Aborted", "Actions": "Actions", "Add another interface to the policy": "Add another interface to the policy", @@ -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}}?": "Are you sure you want to remove {{interfaceType}} interface <5>{{name}}?", "Auto-DNS": "Auto-DNS", @@ -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?", @@ -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", @@ -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", @@ -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", @@ -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.": "Please <2>try again.", "Policy details": "Policy details", diff --git a/package.json b/package.json index dab3e42f..37e43ffb 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", @@ -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", diff --git a/src/console-models/NodeNetworkStateModel.ts b/src/console-models/NodeNetworkStateModel.ts index da362d14..a68c1042 100644 --- a/src/console-models/NodeNetworkStateModel.ts +++ b/src/console-models/NodeNetworkStateModel.ts @@ -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', @@ -17,5 +17,3 @@ const NodeNetworkStateModel: K8sModel = { export const NodeNetworkStateModelGroupVersionKind = modelToGroupVersionKind(NodeNetworkStateModel); export const NodeNetworkStateModelRef = modelToRef(NodeNetworkStateModel); - -export default NodeNetworkStateModel; diff --git a/src/nmstate-types/custom-models/NodeNetworkConfigurationInterface.ts b/src/nmstate-types/custom-models/NodeNetworkConfigurationInterface.ts index 45682f7c..ea0f8d5a 100644 --- a/src/nmstate-types/custom-models/NodeNetworkConfigurationInterface.ts +++ b/src/nmstate-types/custom-models/NodeNetworkConfigurationInterface.ts @@ -42,4 +42,8 @@ export interface NodeNetworkConfigurationInterface { 'base-iface': string; protocol?: string; }; + + patch?: { + peer?: string; + }; } diff --git a/src/utils/components/DetailItem/DetailItem.scss b/src/utils/components/DetailItem/DetailItem.scss new file mode 100644 index 00000000..5bb3d0bd --- /dev/null +++ b/src/utils/components/DetailItem/DetailItem.scss @@ -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; + } + } +} diff --git a/src/utils/components/DetailItem/DetailItem.tsx b/src/utils/components/DetailItem/DetailItem.tsx new file mode 100644 index 00000000..70855513 --- /dev/null +++ b/src/utils/components/DetailItem/DetailItem.tsx @@ -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 = ({ + popoverBodyContent, + breadcrumb, + className, + testId, + description, + header, + editOnTitleJustify = false, + isDisabled, + isEdit, + isPopover, + headerBadge, + messageOnDisabled, + moreInfoURL, + onEditClick, + showEditOnTitle, +}) => { + const { t } = useNMStateTranslation(); + const NotAvailable = {t('Not available')}; + + return ( + + + + {(popoverBodyContent || breadcrumb || header || headerBadge || moreInfoURL) && ( + + + + )} + {isEdit && showEditOnTitle && ( + + + + )} + + + + + {isEdit && !showEditOnTitle ? ( + + {description ?? NotAvailable} + + ) : ( + description + )} + + + ); +}; + +export default DetailItem; diff --git a/src/utils/components/DetailItem/DetailItemHeader.tsx b/src/utils/components/DetailItem/DetailItemHeader.tsx new file mode 100644 index 00000000..6ad253d4 --- /dev/null +++ b/src/utils/components/DetailItem/DetailItemHeader.tsx @@ -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 = ({ + bodyContent, + breadcrumb, + descriptionHeader, + isPopover, + label, + maxWidth, + moreInfoURL, +}) => { + const { t } = useNMStateTranslation(); + + if (isPopover && bodyContent) { + return ( + + {bodyContent} + {moreInfoURL && ( + <> + {t('More info: ')} + {moreInfoURL} + + )} + {breadcrumb && ( +
+ + {breadcrumb.split('.').map((item) => ( + {item} + ))} + +
+ )} + + } + hasAutoWidth + headerContent={descriptionHeader} + maxWidth={maxWidth || '30rem'} + > + + {descriptionHeader} + +
+ ); + } + + return ( + + {descriptionHeader} {label} + + ); +}; diff --git a/src/utils/components/DetailItem/EditButton.tsx b/src/utils/components/DetailItem/EditButton.tsx new file mode 100644 index 00000000..d48829fc --- /dev/null +++ b/src/utils/components/DetailItem/EditButton.tsx @@ -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 = ({ children, onEditClick, isEditable, testId }) => ( + +); + +export default EditButton; diff --git a/src/utils/components/DetailItem/EditButtonWithTooltip.tsx b/src/utils/components/DetailItem/EditButtonWithTooltip.tsx new file mode 100644 index 00000000..55f64d43 --- /dev/null +++ b/src/utils/components/DetailItem/EditButtonWithTooltip.tsx @@ -0,0 +1,37 @@ +import React, { FC, PropsWithChildren, ReactNode } from 'react'; + +import { Tooltip } from '@patternfly/react-core'; + +import EditButton from './EditButton'; + +type EditButtonWithTooltipProps = PropsWithChildren<{ + isEditable: boolean; + onEditClick: () => void; + testId: string; + tooltipContent?: ReactNode; +}>; + +const EditButtonWithTooltip: FC = ({ + children, + isEditable, + tooltipContent, + ...rest +}) => { + if (!isEditable && tooltipContent) { + return ( + + + {children} + + + ); + } + + return ( + + {children} + + ); +}; + +export default EditButtonWithTooltip; diff --git a/src/utils/components/DetailItem/OwnerDetailItem.tsx b/src/utils/components/DetailItem/OwnerDetailItem.tsx new file mode 100644 index 00000000..1a248795 --- /dev/null +++ b/src/utils/components/DetailItem/OwnerDetailItem.tsx @@ -0,0 +1,60 @@ +import React, { FC } from 'react'; +import { Trans } from 'react-i18next'; + +import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk'; +import { + Breadcrumb, + BreadcrumbItem, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTermHelpText, + DescriptionListTermHelpTextButton, + Popover, +} from '@patternfly/react-core'; +import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation'; + +import OwnerReferences from '../OwnerReferences/OwnerReferences'; + +type OwnerDetailsItemProps = { + obj: K8sResourceCommon; +}; + +const OwnerDetailsItem: FC = ({ obj }) => { + const { t } = useNMStateTranslation(); + + return ( + + + +
+ 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. +
+ + {{ kind: obj?.kind }} + metadata + ownerReferences + + + } + hasAutoWidth + headerContent={t('Owner')} + maxWidth="30rem" + > + + {t('Owner')} + +
+
+ + + +
+ ); +}; + +export default OwnerDetailsItem; diff --git a/src/utils/components/MetadataLabels/MetadataLabels.scss b/src/utils/components/MetadataLabels/MetadataLabels.scss new file mode 100644 index 00000000..f7289556 --- /dev/null +++ b/src/utils/components/MetadataLabels/MetadataLabels.scss @@ -0,0 +1,8 @@ +.metadata-labels-group { + padding-top: var(--pf-c-label-group--m-category--PaddingTop); + padding-right: var(--pf-c-label-group--m-category--PaddingRight); + padding-bottom: var(--pf-c-label-group--m-category--PaddingBottom); + padding-left: var(--pf-c-label-group--m-category--PaddingLeft); + border: var(--pf-c-label-group--m-category--BorderWidth) solid + var(--pf-c-label-group--m-category--BorderColor); +} diff --git a/src/utils/components/MetadataLabels/MetadataLabels.tsx b/src/utils/components/MetadataLabels/MetadataLabels.tsx new file mode 100644 index 00000000..c5e1f244 --- /dev/null +++ b/src/utils/components/MetadataLabels/MetadataLabels.tsx @@ -0,0 +1,38 @@ +import React, { FC } from 'react'; +import { Link } from 'react-router-dom-v5-compat'; +import { modelToRef } from 'src/console-models/modelUtils'; + +import { K8sModel } from '@openshift-console/dynamic-plugin-sdk'; +import { Label, LabelGroup } from '@patternfly/react-core'; + +import './MetadataLabels.scss'; + +type MetadataLabelsProps = { + labels?: { [key: string]: string }; + model: K8sModel; +}; + +const MetadataLabels: FC = ({ labels, model }) => { + const modelRef = modelToRef(model); + + return ( + + {Object.keys(labels || {})?.map((key) => { + const labelText = labels?.[key] ? `${key}=${labels?.[key]}` : key; + + return ( + + ); + })} + + ); +}; + +export default MetadataLabels; diff --git a/src/utils/components/OwnerReferences/OwnerReferences.tsx b/src/utils/components/OwnerReferences/OwnerReferences.tsx new file mode 100644 index 00000000..56a14683 --- /dev/null +++ b/src/utils/components/OwnerReferences/OwnerReferences.tsx @@ -0,0 +1,36 @@ +import React, { FC } from 'react'; + +import { + getGroupVersionKindForResource, + K8sResourceCommon, + OwnerReference, + ResourceLink, +} from '@openshift-console/dynamic-plugin-sdk'; +import { isEmpty } from '@utils/helpers'; +import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation'; + +type OwnerReferencesProps = { + obj: K8sResourceCommon; +}; + +const OwnerReferences: FC = ({ obj }) => { + const { t } = useNMStateTranslation(); + const ownerReferences = (obj?.metadata?.ownerReferences || [])?.map( + (ownerRef: OwnerReference) => ( + + ), + ); + + return !isEmpty(ownerReferences) ? ( +
{ownerReferences}
+ ) : ( + {t('No owner')} + ); +}; + +export default OwnerReferences; diff --git a/src/utils/components/Toggles/SelectToggle.tsx b/src/utils/components/Toggles/SelectToggle.tsx index ef267b8f..d9f57be7 100644 --- a/src/utils/components/Toggles/SelectToggle.tsx +++ b/src/utils/components/Toggles/SelectToggle.tsx @@ -1,9 +1,9 @@ -import React, { Ref } from 'react'; +import React, { ReactNode, Ref } from 'react'; import { MenuToggle, MenuToggleElement, MenuToggleProps } from '@patternfly/react-core'; type SelectToggleProps = MenuToggleProps & { - selected: any; + selected: ReactNode | string; }; const SelectToggle = ({ selected, ...menuProps }: SelectToggleProps) => diff --git a/src/views/states/details/StateDetailsPage.tsx b/src/views/states/details/StateDetailsPage.tsx new file mode 100644 index 00000000..a0538ae1 --- /dev/null +++ b/src/views/states/details/StateDetailsPage.tsx @@ -0,0 +1,77 @@ +import React, { FC } from 'react'; + +import { NodeNetworkStateModel, NodeNetworkStateModelGroupVersionKind } from '@models'; +import { + ResourceIcon, + Timestamp, + useAnnotationsModal, + useLabelsModal, +} from '@openshift-console/dynamic-plugin-sdk'; +import { + DescriptionList, + Divider, + PageSection, + PageSectionVariants, + Title, +} from '@patternfly/react-core'; +import { V1beta1NodeNetworkState } from '@types'; +import DetailItem from '@utils/components/DetailItem/DetailItem'; +import OwnerDetailsItem from '@utils/components/DetailItem/OwnerDetailItem'; +import MetadataLabels from '@utils/components/MetadataLabels/MetadataLabels'; +import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation'; + +type StateDetailsPageProps = { + nns: V1beta1NodeNetworkState; +}; + +const StateDetailsPage: FC = ({ nns }) => { + const { t } = useNMStateTranslation(); + const launchLabelsModal = useLabelsModal(nns); + const launchAnnotationsModal = useAnnotationsModal(nns); + + if (!nns) return null; + + const annotationsCount = Object.keys(nns?.metadata?.annotations || {}).length; + const annotationsText = t('{{annotationsCount}} Annotations', { + annotationsCount, + }); + + return ( + <> + + <span className="co-resource-item__resource-name"> + <ResourceIcon groupVersionKind={NodeNetworkStateModelGroupVersionKind} /> + <span className="co-resource-item__resource-name">{nns?.metadata?.name} </span> + </span> + + + + + + + } + header={t('Labels')} + isEdit + onEditClick={launchLabelsModal} + showEditOnTitle + /> + + } + header={t('Created at')} + /> + + + + + ); +}; + +export default StateDetailsPage; diff --git a/src/views/states/list/StatesList.tsx b/src/views/states/list/StatesList.tsx index 7f1ba26f..a92e9288 100644 --- a/src/views/states/list/StatesList.tsx +++ b/src/views/states/list/StatesList.tsx @@ -1,11 +1,12 @@ -import React, { FC, useState } from 'react'; +import React, { FC, useCallback, useState } from 'react'; +import { useNavigate } from 'react-router-dom-v5-compat'; +import { useNMStateTranslation } from 'src/utils/hooks/useNMStateTranslation'; + import { + NodeNetworkStateModel, NodeNetworkStateModelGroupVersionKind, NodeNetworkStateModelRef, -} from 'src/console-models'; -import NodeNetworkStateModel from 'src/console-models/NodeNetworkStateModel'; -import { useNMStateTranslation } from 'src/utils/hooks/useNMStateTranslation'; - +} from '@models'; import { ListPageBody, ListPageFilter, @@ -13,7 +14,8 @@ import { useK8sWatchResource, useListPageFilter, } from '@openshift-console/dynamic-plugin-sdk'; -import { Button, Flex, Pagination } from '@patternfly/react-core'; +import { Button, Flex, Icon, Pagination } from '@patternfly/react-core'; +import { TopologyIcon } from '@patternfly/react-icons'; import { Table, TableGridBreakpoint, Th, Thead, Tr } from '@patternfly/react-table'; import { V1beta1NodeNetworkState } from '@types'; import usePagination from '@utils/hooks/usePagination/usePagination'; @@ -33,7 +35,17 @@ import './states-list.scss'; const StatesList: FC = () => { const { t } = useNMStateTranslation(); - const { selectedInterfaceName, selectedStateName, selectedInterfaceType } = useDrawerInterface(); + const navigate = useNavigate(); + const { + selectedInterfaceName, + selectedStateName, + selectedInterfaceType, + setSelectedInterfaceName, + } = useDrawerInterface(); + + const onClose = useCallback(() => { + setSelectedInterfaceName(); + }, []); const [expandAll, setExpandAll] = useState(false); @@ -62,10 +74,15 @@ const StatesList: FC = () => { const { sortedStates, nameSortParams } = useSortStates(filteredData); const paginatedData = sortedStates.slice(pagination?.startIndex, pagination?.endIndex + 1); - return ( <> - + + +
@@ -144,7 +161,7 @@ const StatesList: FC = () => { )} - + ); }; diff --git a/src/views/states/list/components/InterfaceDrawer/InterfaceDrawer.tsx b/src/views/states/list/components/InterfaceDrawer/InterfaceDrawer.tsx index fcc56901..706a0830 100644 --- a/src/views/states/list/components/InterfaceDrawer/InterfaceDrawer.tsx +++ b/src/views/states/list/components/InterfaceDrawer/InterfaceDrawer.tsx @@ -1,25 +1,21 @@ -import React, { FC, useCallback } from 'react'; +import React, { FC } from 'react'; import { Modal, Title } from '@patternfly/react-core'; import { NodeNetworkConfigurationInterface } from '@types'; import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation'; -import useDrawerInterface from '../../hooks/useDrawerInterface'; - import { InterfaceDrawerTabId, InterfaceDrawerTabProps } from './constants'; import InterfaceDrawerDetailsTab from './InterfaceDrawerDetailsTab'; import InterfaceDrawerYAMLFooter from './InterfaceDrawerFooter'; import InterfaceDrawerYAMLTab from './InterfaceDrawerYAMLTab'; -const InterfaceDrawer: FC<{ selectedInterface: NodeNetworkConfigurationInterface }> = ({ - selectedInterface, -}) => { - const { t } = useNMStateTranslation(); - const { setSelectedInterfaceName } = useDrawerInterface(); +type InterfaceDrawerProps = { + selectedInterface: NodeNetworkConfigurationInterface; + onClose: () => void; +}; - const onClose = useCallback(() => { - setSelectedInterfaceName(); - }, []); +const InterfaceDrawer: FC = ({ selectedInterface, onClose }) => { + const { t } = useNMStateTranslation(); const Tabs: InterfaceDrawerTabProps[] = [ { diff --git a/src/views/states/list/constants.ts b/src/views/states/list/constants.ts index 6733a216..a494f377 100644 --- a/src/views/states/list/constants.ts +++ b/src/views/states/list/constants.ts @@ -1,5 +1,4 @@ -import NodeNetworkStateModel from 'src/console-models/NodeNetworkStateModel'; - +import { NodeNetworkStateModel } from '@models'; import { getResourceUrl } from '@utils/helpers'; export const baseListUrl = getResourceUrl({ model: NodeNetworkStateModel }); diff --git a/src/views/states/manifest.ts b/src/views/states/manifest.ts index 8d6a2f43..3507c66e 100644 --- a/src/views/states/manifest.ts +++ b/src/views/states/manifest.ts @@ -1,20 +1,15 @@ import type { EncodedExtension } from '@openshift/dynamic-plugin-sdk'; import type { - ExtensionK8sModel, ResourceClusterNavItem, ResourceListPage, + RoutePage, } from '@openshift-console/dynamic-plugin-sdk'; -import NodeNetworkStateModel from '../../console-models/NodeNetworkStateModel'; - -const StateExtensionModel: ExtensionK8sModel = { - group: NodeNetworkStateModel.apiGroup as string, - kind: NodeNetworkStateModel.kind, - version: NodeNetworkStateModel.apiVersion, -}; +import { NodeNetworkStateModelGroupVersionKind } from '../../console-models'; export const StateExposedModules = { StatesList: './views/states/list/StatesList', + Topology: './views/states/topology/Topology', }; export const StateExtensions: EncodedExtension[] = [ @@ -25,7 +20,7 @@ export const StateExtensions: EncodedExtension[] = [ perspective: 'admin', name: '%plugin__nmstate-console-plugin~NodeNetworkState%', section: 'networking', - model: StateExtensionModel, + model: NodeNetworkStateModelGroupVersionKind, dataAttributes: { 'data-quickstart-id': 'qs-nav-state-list', 'data-test-id': 'state-nav-list', @@ -36,8 +31,17 @@ export const StateExtensions: EncodedExtension[] = [ type: 'console.page/resource/list', properties: { perspective: 'admin', - model: StateExtensionModel, + model: NodeNetworkStateModelGroupVersionKind, component: { $codeRef: 'StatesList' }, }, } as EncodedExtension, + { + type: 'console.page/route', + properties: { + path: ['nmstate-topology'], + component: { + $codeRef: 'Topology', + }, + }, + } as EncodedExtension, ]; diff --git a/src/views/states/topology/Topology.tsx b/src/views/states/topology/Topology.tsx new file mode 100644 index 00000000..ff92fbc8 --- /dev/null +++ b/src/views/states/topology/Topology.tsx @@ -0,0 +1,108 @@ +import React, { FC, useEffect, useMemo, useState } from 'react'; + +import { NodeNetworkStateModelGroupVersionKind } from '@models'; +import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; +import { + action, + createTopologyControlButtons, + defaultControlButtonsOptions, + SELECTION_EVENT, + TopologyControlBar, + TopologyView, + Visualization, + VisualizationProvider, + VisualizationSurface, +} from '@patternfly/react-topology'; +import { V1beta1NodeNetworkState } from '@types'; + +import TopologySidebar from './components/TopologySidebar/TopologySidebar'; +import TopologyToolbar from './components/TopologyToolbar/TopologyToolbar'; +import { componentFactory, layoutFactory } from './utils/factory'; +import { transformDataToTopologyModel } from './utils/utils'; + +const Topology: FC = () => { + const [selectedIds, setSelectedIds] = useState([]); + const [visualization, setVisualization] = useState(null); + + const [selectedNodeFilters, setSelectedNodeFilters] = useState([]); + + const [states, loaded, error] = useK8sWatchResource({ + groupVersionKind: NodeNetworkStateModelGroupVersionKind, + isList: true, + namespaced: false, + }); + + const nodeNames: string[] = useMemo( + () => states?.map((state) => state.metadata.name) || [], + [states], + ); + + useEffect(() => { + if (loaded && !error) { + const topologyModel = + selectedNodeFilters.length > 0 + ? transformDataToTopologyModel( + states.filter((state) => selectedNodeFilters.includes(state.metadata.name)), + ) + : transformDataToTopologyModel(states); + + if (!visualization) { + const newVisualization = new Visualization(); + newVisualization.registerLayoutFactory(layoutFactory); + newVisualization.registerComponentFactory(componentFactory); + newVisualization.addEventListener(SELECTION_EVENT, setSelectedIds); + newVisualization.setFitToScreenOnLayout(true); + newVisualization.fromModel(topologyModel); + setVisualization(newVisualization); + } else { + visualization.fromModel(topologyModel); + } + } + }, [states, loaded, error, selectedNodeFilters]); + + return ( + + } + viewToolbar={ + + } + controlBar={ + { + visualization.getGraph().scaleBy(4 / 3); + }), + zoomOutCallback: action(() => { + visualization.getGraph().scaleBy(0.75); + }), + fitToScreenCallback: action(() => { + visualization.getGraph().fit(40); + }), + resetViewCallback: action(() => { + visualization.getGraph().reset(); + visualization.getGraph().layout(); + }), + legend: false, + })} + /> + } + > + + + + + ); +}; + +export default Topology; diff --git a/src/views/states/topology/components/CustomGroup.tsx b/src/views/states/topology/components/CustomGroup.tsx new file mode 100644 index 00000000..35540949 --- /dev/null +++ b/src/views/states/topology/components/CustomGroup.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; + +import { + DefaultGroup, + Node, + WithDndDropProps, + WithDragNodeProps, + WithSelectionProps, +} from '@patternfly/react-topology'; + +type CustomGroupProps = { + element: Node; +} & WithSelectionProps & + WithDragNodeProps & + WithDndDropProps; + +const CustomGroup: FC = ({ element, ...rest }) => { + const data = element.getData(); + + return ; +}; + +export default CustomGroup; diff --git a/src/views/states/topology/components/CustomNode.tsx b/src/views/states/topology/components/CustomNode.tsx new file mode 100644 index 00000000..54d3922e --- /dev/null +++ b/src/views/states/topology/components/CustomNode.tsx @@ -0,0 +1,42 @@ +import React, { FC } from 'react'; + +import { + DefaultNode, + Node, + WithDndDropProps, + WithDragNodeProps, + WithSelectionProps, +} from '@patternfly/react-topology'; + +import { ICON_SIZE } from '../utils/constants'; + +type CustomNodeProps = { + element: Node; +} & WithSelectionProps & + WithDragNodeProps & + WithDndDropProps; + +const CustomNode: FC = ({ element, onSelect, selected }) => { + const data = element.getData(); + const Icon = data.icon; + const { width, height } = element.getBounds(); + + const xCenter = (width - ICON_SIZE) / 2; + const yCenter = (height - ICON_SIZE) / 2; + + return ( + + + + + + ); +}; + +export default CustomNode; diff --git a/src/views/states/topology/components/TopologySidebar/TopologySidebar.scss b/src/views/states/topology/components/TopologySidebar/TopologySidebar.scss new file mode 100644 index 00000000..86945bf6 --- /dev/null +++ b/src/views/states/topology/components/TopologySidebar/TopologySidebar.scss @@ -0,0 +1,6 @@ +.topology-sidebar { + &__content { + margin-top: 24px; + margin-left: 20px; + } +} diff --git a/src/views/states/topology/components/TopologySidebar/TopologySidebar.tsx b/src/views/states/topology/components/TopologySidebar/TopologySidebar.tsx new file mode 100644 index 00000000..9415c6d3 --- /dev/null +++ b/src/views/states/topology/components/TopologySidebar/TopologySidebar.tsx @@ -0,0 +1,47 @@ +import React, { Dispatch, FC, SetStateAction, useMemo } from 'react'; + +import { Divider, Title } from '@patternfly/react-core'; +import { TopologySideBar } from '@patternfly/react-topology'; +import { V1beta1NodeNetworkState } from '@types'; + +import StateDetailsPage from '../../../details/StateDetailsPage'; +import InterfaceDrawerDetailsTab from '../../../list/components/InterfaceDrawer/InterfaceDrawerDetailsTab'; + +import './TopologySidebar.scss'; + +type TopologySidebarProps = { + states: V1beta1NodeNetworkState[]; + selectedIds: string[]; + setSelectedIds: Dispatch>; +}; +const TopologySidebar: FC = ({ states, selectedIds, setSelectedIds }) => { + const { selectedState, selectedInterface } = useMemo(() => { + if (selectedIds.length === 0) return { selectedState: null, selectedInterface: null }; + + const [selectedNNSName, selectedInterfaceName] = selectedIds[0].split('~'); + const selectedState = states?.find((state) => state.metadata.name === selectedNNSName); + const selectedInterface = selectedState?.status?.currentState?.interfaces?.find( + (iface) => iface.name === selectedInterfaceName, + ); + + return { selectedState, selectedInterface }; + }, [selectedIds, states]); + + return ( + 0} onClose={() => setSelectedIds([])}> +
+ {!selectedInterface ? ( + + ) : ( + <> + {selectedInterface?.name} + + + + )} +
+
+ ); +}; + +export default TopologySidebar; diff --git a/src/views/states/topology/components/TopologyToolbar/TopologyToolbar.scss b/src/views/states/topology/components/TopologyToolbar/TopologyToolbar.scss new file mode 100644 index 00000000..60e2ec19 --- /dev/null +++ b/src/views/states/topology/components/TopologyToolbar/TopologyToolbar.scss @@ -0,0 +1,31 @@ +.pf-v5-c-toolbar__group.pf-topology-view__view-toolbar { + width: 100%; + .pf-v5-c-toolbar { + width: 100%; + } +} + +div[id^='pf-topology-view-'] { + padding-bottom: 0; +} + +.topology-toolbar { + padding: var(--pf-global--spacer--sm) 0; + &__content { + padding: 0 var(--pf-global--spacer--sm); + } +} + +.list-view-btn { + position: absolute; + right: 0; +} + +.topology-filter { + &__toggle { + width: 200px; + } + &__icon { + margin-right: var(--pf-global--spacer--sm); + } +} diff --git a/src/views/states/topology/components/TopologyToolbar/TopologyToolbar.tsx b/src/views/states/topology/components/TopologyToolbar/TopologyToolbar.tsx new file mode 100644 index 00000000..094752e1 --- /dev/null +++ b/src/views/states/topology/components/TopologyToolbar/TopologyToolbar.tsx @@ -0,0 +1,44 @@ +import React, { Dispatch, FC, SetStateAction } from 'react'; +import { useNavigate } from 'react-router-dom-v5-compat'; + +import { NodeNetworkStateModelRef } from '@models'; +import { Button, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from '@patternfly/react-core'; +import { ListIcon } from '@patternfly/react-icons'; + +import TopologyToolbarFilter from './TopologyToolbarFilter'; + +import './TopologyToolbar.scss'; + +type TopologyToolbarProps = { + selectedNodeFilters: string[]; + setSelectedNodeFilters: Dispatch>; + nodeNames: string[]; +}; + +const TopologyButton: FC = (props) => { + const navigate = useNavigate(); + const setSelectedNodeFilters = props.setSelectedNodeFilters; + + return ( + setSelectedNodeFilters([])}> + + + + + + + + + + + + ); +}; + +export default TopologyButton; diff --git a/src/views/states/topology/components/TopologyToolbar/TopologyToolbarFilter.tsx b/src/views/states/topology/components/TopologyToolbar/TopologyToolbarFilter.tsx new file mode 100644 index 00000000..2b898431 --- /dev/null +++ b/src/views/states/topology/components/TopologyToolbar/TopologyToolbarFilter.tsx @@ -0,0 +1,84 @@ +import React, { ChangeEvent, Dispatch, FC, MouseEvent, SetStateAction, useState } from 'react'; + +import { + MenuToggle, + MenuToggleElement, + Select, + SelectList, + SelectOption, + ToolbarFilter, +} from '@patternfly/react-core'; +import { FilterIcon } from '@patternfly/react-icons'; +import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation'; + +import './TopologyToolbar.scss'; + +type TopologyToolbarFilterProps = { + selectedNodeFilters: string[]; + setSelectedNodeFilters: Dispatch>; + nodeNames: string[]; +}; +const TopologyToolbarFilter: FC = ({ + nodeNames, + selectedNodeFilters, + setSelectedNodeFilters, +}) => { + const { t } = useNMStateTranslation(); + const [isNodeFilterExpanded, setIsNodeFilterExpanded] = useState(false); + + const onClearAllFilters = () => setSelectedNodeFilters([]); + const onNodeFilterSelect = (event: MouseEvent | ChangeEvent, selection: string) => { + const checked = (event.target as HTMLInputElement).checked; + setSelectedNodeFilters((prevFilters) => { + if (checked) { + return [...prevFilters, selection]; + } + + return prevFilters.filter((value) => value !== selection); + }); + }; + return ( + + setSelectedNodeFilters(selectedNodeFilters.filter((filter) => filter !== chip)) + } + deleteChipGroup={onClearAllFilters} + categoryName="nodes" + > + + + ); +}; + +export default TopologyToolbarFilter; diff --git a/src/views/states/topology/utils/constants.ts b/src/views/states/topology/utils/constants.ts new file mode 100644 index 00000000..b3175e2c --- /dev/null +++ b/src/views/states/topology/utils/constants.ts @@ -0,0 +1,4 @@ +export const NODE_DIAMETER = 35; +export const CONNECTOR_TARGET_DROP = 'connector-target-drop'; +export const GROUP = 'group'; +export const ICON_SIZE = 15; diff --git a/src/views/states/topology/utils/factory.ts b/src/views/states/topology/utils/factory.ts new file mode 100644 index 00000000..9bf1bdd2 --- /dev/null +++ b/src/views/states/topology/utils/factory.ts @@ -0,0 +1,79 @@ +import { + ColaLayout, + ComponentFactory, + DefaultEdge, + DragObjectWithType, + Edge, + Graph, + GraphComponent, + GraphElement, + groupDropTargetSpec, + Layout, + LayoutFactory, + ModelKind, + Node, + nodeDragSourceSpec, + nodeDropTargetSpec, + withDndDrop, + withDragNode, + withPanZoom, + withSelection, + withTargetDrag, +} from '@patternfly/react-topology'; + +import CustomGroup from '../components/CustomGroup'; +import CustomNode from '../components/CustomNode'; + +import { CONNECTOR_TARGET_DROP, GROUP } from './constants'; + +export const layoutFactory: LayoutFactory = (type: string, graph: Graph): Layout | undefined => + new ColaLayout(graph, { layoutOnDrag: false }); + +export const componentFactory: ComponentFactory = (kind: ModelKind, type: string) => { + switch (type) { + case GROUP: + return withDndDrop(groupDropTargetSpec)( + withDragNode(nodeDragSourceSpec(GROUP))(withSelection()(CustomGroup)), + ); + default: + switch (kind) { + case ModelKind.graph: + return withPanZoom()(GraphComponent); + case ModelKind.node: + return withDndDrop(nodeDropTargetSpec([CONNECTOR_TARGET_DROP]))( + withDragNode(nodeDragSourceSpec(ModelKind.node, true, true))( + withSelection()(CustomNode), + ), + ); + case ModelKind.edge: + return withTargetDrag< + DragObjectWithType, + Node, + { dragging?: boolean }, + { + element: GraphElement; + } + >({ + item: { type: CONNECTOR_TARGET_DROP }, + begin: (monitor, props) => { + props.element.raise(); + return props.element; + }, + drag: (event, monitor, props) => { + (props.element as Edge).setEndPoint(event.x, event.y); + }, + end: (dropResult, monitor, props) => { + if (monitor.didDrop() && dropResult && props) { + (props.element as Edge).setTarget(dropResult); + } + (props.element as Edge).setEndPoint(); + }, + collect: (monitor) => ({ + dragging: monitor.isDragging(), + }), + })(DefaultEdge); + default: + return undefined; + } + } +}; diff --git a/src/views/states/topology/utils/utils.ts b/src/views/states/topology/utils/utils.ts new file mode 100644 index 00000000..8be6e096 --- /dev/null +++ b/src/views/states/topology/utils/utils.ts @@ -0,0 +1,158 @@ +import { NetworkIcon } from '@patternfly/react-icons'; +import { + EdgeModel, + Model, + ModelKind, + NodeModel, + NodeShape, + NodeStatus, +} from '@patternfly/react-topology'; +import { NodeNetworkConfigurationInterface, V1beta1NodeNetworkState } from '@types'; + +import { GROUP, NODE_DIAMETER } from './constants'; + +const statusMap: { [key: string]: NodeStatus } = { + up: NodeStatus.success, + down: NodeStatus.danger, + absent: NodeStatus.warning, +}; + +export const getStatus = (iface: NodeNetworkConfigurationInterface): NodeStatus => { + return statusMap[iface.state.toLowerCase()] || NodeStatus.default; +}; + +const createNodes = ( + nnsName: string, + interfaces: NodeNetworkConfigurationInterface[], +): NodeModel[] => { + return interfaces.map((iface) => ({ + id: `${nnsName}~${iface.name}`, + type: ModelKind.node, + label: iface.name, + width: NODE_DIAMETER, + height: NODE_DIAMETER, + visible: !iface.patch, + shape: NodeShape.ellipse, + status: getStatus(iface), + data: { + badge: 'I', + icon: NetworkIcon, + }, + parent: nnsName, + })); +}; + +const createEdges = ( + nnsName: string, + interfaces: NodeNetworkConfigurationInterface[], +): EdgeModel[] => { + const edges: EdgeModel[] = []; + const patchConnections: { [key: string]: string } = {}; // Track patch connections + + interfaces.forEach((iface: NodeNetworkConfigurationInterface) => { + if (iface.patch?.peer) { + patchConnections[iface.name] = iface.patch.peer; + } + }); + + interfaces.forEach((iface: NodeNetworkConfigurationInterface) => { + const nodeId = `${nnsName}~${iface.name}`; + + if (iface.bridge?.port) { + iface.bridge.port.forEach((prt) => { + if (patchConnections[prt.name]) { + const peerPatch = patchConnections[prt.name]; + const peerBridge = interfaces.find((intf) => + intf.bridge?.port.some((p) => p.name === peerPatch), + ); + + if (peerBridge) { + const peerBridgeId = `${nnsName}~${peerBridge.name}`; + edges.push({ + id: `${nodeId}~${peerBridgeId}-edge`, + type: ModelKind.edge, + source: nodeId, + target: peerBridgeId, + }); + } + } else if (prt.name && iface.name !== prt.name) { + edges.push({ + id: `${nodeId}~${prt.name}-edge`, + type: ModelKind.edge, + source: nodeId, + target: `${nnsName}~${prt.name}`, + }); + } + }); + } + + if (iface.vlan?.['base-iface'] && iface.name !== iface.vlan?.['base-iface']) { + edges.push({ + id: `${nodeId}~${iface.vlan['base-iface']}-edge`, + type: ModelKind.edge, + source: nodeId, + target: `${nnsName}~${iface.vlan['base-iface']}`, + }); + } + + if (iface['link-aggregation']?.port) { + iface['link-aggregation'].port.forEach((prt: string) => { + if (iface.name !== prt) { + edges.push({ + id: `${nodeId}~${prt}-edge`, + type: ModelKind.edge, + source: nodeId, + target: `${nnsName}~${prt}`, + }); + } + }); + } + }); + + return edges; +}; + +const createGroupNode = (nnsName: string, childNodeIds: string[]): NodeModel => { + return { + id: nnsName, + type: GROUP, + label: nnsName, + group: true, + children: childNodeIds, + style: { + padding: 40, + }, + data: { + badge: 'NNS', + }, + }; +}; + +export const transformDataToTopologyModel = (data: V1beta1NodeNetworkState[]): Model => { + const nodes: NodeModel[] = []; + const edges: EdgeModel[] = []; + + data.forEach((nodeState) => { + const nnsName = nodeState.metadata.name; + + const childNodes = createNodes(nnsName, nodeState.status.currentState.interfaces); + const groupNode = createGroupNode( + nnsName, + childNodes.map((child) => child.id), + ); + const nodeEdges = createEdges(nnsName, nodeState.status.currentState.interfaces); + + nodes.push(groupNode, ...childNodes); + edges.push(...nodeEdges); + }); + + return { + nodes, + edges, + graph: { + id: 'nns-topology', + type: ModelKind.graph, + layout: 'Cola', + }, + }; +}; diff --git a/tsconfig.json b/tsconfig.json index 2d0f1312..62ce955d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,8 @@ "paths": { "@images/*": ["images/*"], "@types": ["src/nmstate-types/index.ts"], - "@utils/*": ["src/utils/*"] + "@utils/*": ["src/utils/*"], + "@models": ["src/console-models/index.ts"] } }, "include": ["src", "pkg", "src/custom.d.ts"], diff --git a/yarn.lock b/yarn.lock index 41fa3fd8..f3a3d23e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -269,6 +269,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.2.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" + integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.24.0", "@babel/template@^7.3.3": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" @@ -798,6 +805,18 @@ react-dropzone "^14.2.3" tslib "^2.5.0" +"@patternfly/react-core@^5.1.1": + version "5.3.4" + resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-5.3.4.tgz#84f85d3528655134cf0bcdb096f82777f0dd69b6" + integrity sha512-zr2yeilIoFp8MFOo0vNgI8XuM+P2466zHvy4smyRNRH2/but2WObqx7Wu4ftd/eBMYdNqmTeuXe6JeqqRqnPMQ== + dependencies: + "@patternfly/react-icons" "^5.3.2" + "@patternfly/react-styles" "^5.3.1" + "@patternfly/react-tokens" "^5.3.1" + focus-trap "7.5.2" + react-dropzone "^14.2.3" + tslib "^2.5.0" + "@patternfly/react-core@^5.2.3": version "5.2.3" resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-5.2.3.tgz#21d8168f1cccc2315162b7b1aca14a436cfa0101" @@ -815,11 +834,21 @@ resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-5.2.1.tgz#c29e9fbecd13c33e772abe6089e31bb86b1ab2a8" integrity sha512-aeJ0X+U2NDe8UmI5eQiT0iuR/wmUq97UkDtx3HoZcpRb9T6eUBfysllxjRqHS8rOOspdU8OWq+CUhQ/E2ZDibg== +"@patternfly/react-icons@^5.3.2": + version "5.3.2" + resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-5.3.2.tgz#f594ed67b0d39f486ea0f0367de058d4bd056605" + integrity sha512-GEygYbl0H4zD8nZuTQy2dayKIrV2bMMeWKSOEZ16Y3EYNgYVUOUnN+J0naAEuEGH39Xb1DE9n+XUbE1PC4CxPA== + "@patternfly/react-styles@^5.1.1", "@patternfly/react-styles@^5.2.1": version "5.2.1" resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-5.2.1.tgz#a6f8c750bd65572702ea94c0a6b58f6c0d4361fb" integrity sha512-GT96hzI1QenBhq6Pfc51kxnj9aVLjL1zSLukKZXcYVe0HPOy0BFm90bT1Fo4e/z7V9cDYw4SqSX1XLc3O4jsTw== +"@patternfly/react-styles@^5.3.1": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-5.3.1.tgz#4bc42f98c48e117df5d956ee3f551d0f57ef1b35" + integrity sha512-H6uBoFH3bJjD6PP75qZ4k+2TtF59vxf9sIVerPpwrGJcRgBZbvbMZCniSC3+S2LQ8DgXLnDvieq78jJzHz0hiA== + "@patternfly/react-table@^5.1.1": version "5.2.4" resolved "https://registry.yarnpkg.com/@patternfly/react-table/-/react-table-5.2.4.tgz#39d7435d40bfe8b2b2d3ee33c94d4137cea0da9c" @@ -837,6 +866,34 @@ resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-5.2.1.tgz#fca7decaa7039dcd93fd215f3f533eff9d070b22" integrity sha512-8GYz/jnJTGAWUJt5eRAW5dtyiHPKETeFJBPGHaUQnvi/t1ZAkoy8i4Kd/RlHsDC7ktiu813SKCmlzwBwldAHKg== +"@patternfly/react-tokens@^5.3.1": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-5.3.1.tgz#b0f840ee3ee3bcf72b5fbf35dc3fd5559666744d" + integrity sha512-VYK0uVP2/2RJ7ZshJCCLeq0Boih5I1bv+9Z/Bg6h12dCkLs85XsxAX9Ve+BGIo5DF54/mzcRHE1RKYap4ISXuw== + +"@patternfly/react-topology@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@patternfly/react-topology/-/react-topology-5.2.0.tgz#09df2807af75af172ef5291513f1f27235c7d41e" + integrity sha512-+kZJSbD6Pb1bTriNzLRiddfSbEBxyiNGSreiV6zOyPwfRizqbFPsOYyRuEocduzLqj0/wT3PM5Ml6JSp8Rw2TQ== + dependencies: + "@patternfly/react-core" "^5.1.1" + "@patternfly/react-icons" "^5.1.1" + "@patternfly/react-styles" "^5.1.1" + "@types/d3" "^7.4.0" + "@types/d3-force" "^1.2.1" + "@types/dagre" "0.7.42" + "@types/react-measure" "^2.0.6" + d3 "^7.8.0" + dagre "0.8.2" + lodash "^4.17.19" + mobx "^6.9.0" + mobx-react "^7.6.0" + point-in-svg-path "^1.0.1" + popper.js "^1.16.1" + react-measure "^2.3.0" + tslib "^2.0.0" + webcola "3.4.0" + "@remix-run/router@1.15.3": version "1.15.3" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.15.3.tgz#d2509048d69dbb72d5389a14945339f1430b2d3c" @@ -1010,6 +1067,226 @@ dependencies: "@types/node" "*" +"@types/d3-array@*": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" + integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== + +"@types/d3-axis@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.6.tgz#e760e5765b8188b1defa32bc8bb6062f81e4c795" + integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-brush@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.6.tgz#c2f4362b045d472e1b186cdbec329ba52bdaee6c" + integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-chord@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.6.tgz#1706ca40cf7ea59a0add8f4456efff8f8775793d" + integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-contour@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.6.tgz#9ada3fa9c4d00e3a5093fed0356c7ab929604231" + integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg== + dependencies: + "@types/d3-array" "*" + "@types/geojson" "*" + +"@types/d3-delaunay@*": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1" + integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw== + +"@types/d3-dispatch@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz#096efdf55eb97480e3f5621ff9a8da552f0961e7" + integrity sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ== + +"@types/d3-drag@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02" + integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-dsv@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz#0a351f996dc99b37f4fa58b492c2d1c04e3dac17" + integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g== + +"@types/d3-ease@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-fetch@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz#c04a2b4f23181aa376f30af0283dbc7b3b569980" + integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA== + dependencies: + "@types/d3-dsv" "*" + +"@types/d3-force@*": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.10.tgz#6dc8fc6e1f35704f3b057090beeeb7ac674bff1a" + integrity sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw== + +"@types/d3-force@^1.2.1": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-1.2.7.tgz#b066d91ac3b8f19c35a60b49e89e99f60187460a" + integrity sha512-zySqZfnxn67RVEGWzpD9dQA0AbNIp4Rj0qGvAuUdUNfGLrwuGCbEGAGze5hEdNaHJKQT2gTqr6j+qAzncm11ew== + +"@types/d3-format@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.4.tgz#b1e4465644ddb3fdf3a263febb240a6cd616de90" + integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g== + +"@types/d3-geo@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.1.0.tgz#b9e56a079449174f0a2c8684a9a4df3f60522440" + integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ== + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@*": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz#6023fb3b2d463229f2d680f9ac4b47466f71f17b" + integrity sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg== + +"@types/d3-interpolate@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a" + integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ== + +"@types/d3-polygon@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz#dfae54a6d35d19e76ac9565bcb32a8e54693189c" + integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA== + +"@types/d3-quadtree@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz#d4740b0fe35b1c58b66e1488f4e7ed02952f570f" + integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg== + +"@types/d3-random@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.3.tgz#ed995c71ecb15e0cd31e22d9d5d23942e3300cfb" + integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ== + +"@types/d3-scale-chromatic@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz#fc0db9c10e789c351f4c42d96f31f2e4df8f5644" + integrity sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw== + +"@types/d3-scale@*": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb" + integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ== + dependencies: + "@types/d3-time" "*" + +"@types/d3-selection@*": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.10.tgz#98cdcf986d0986de6912b5892e7c015a95ca27fe" + integrity sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg== + +"@types/d3-shape@*": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72" + integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time-format@*": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz#d6bc1e6b6a7db69cccfbbdd4c34b70632d9e9db2" + integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg== + +"@types/d3-time@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be" + integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw== + +"@types/d3-timer@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + +"@types/d3-transition@*": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.8.tgz#677707f5eed5b24c66a1918cde05963021351a8f" + integrity sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-zoom@*": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b" + integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw== + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/d3@^7.4.0": + version "7.4.3" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.3.tgz#d4550a85d08f4978faf0a4c36b848c61eaac07e2" + integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww== + dependencies: + "@types/d3-array" "*" + "@types/d3-axis" "*" + "@types/d3-brush" "*" + "@types/d3-chord" "*" + "@types/d3-color" "*" + "@types/d3-contour" "*" + "@types/d3-delaunay" "*" + "@types/d3-dispatch" "*" + "@types/d3-drag" "*" + "@types/d3-dsv" "*" + "@types/d3-ease" "*" + "@types/d3-fetch" "*" + "@types/d3-force" "*" + "@types/d3-format" "*" + "@types/d3-geo" "*" + "@types/d3-hierarchy" "*" + "@types/d3-interpolate" "*" + "@types/d3-path" "*" + "@types/d3-polygon" "*" + "@types/d3-quadtree" "*" + "@types/d3-random" "*" + "@types/d3-scale" "*" + "@types/d3-scale-chromatic" "*" + "@types/d3-selection" "*" + "@types/d3-shape" "*" + "@types/d3-time" "*" + "@types/d3-time-format" "*" + "@types/d3-timer" "*" + "@types/d3-transition" "*" + "@types/d3-zoom" "*" + +"@types/dagre@0.7.42": + version "0.7.42" + resolved "https://registry.yarnpkg.com/@types/dagre/-/dagre-0.7.42.tgz#2b0cd7678d5fc273df7816a88b8b34016e3a5d85" + integrity sha512-knVdi1Ul8xYgJ0wdhQ+/2YGJFKJFa/5srcPII9zvOs4KhsHfpnFrSTQXATYmjslglxRMif3Lg+wEZ0beag+94A== + "@types/eslint-scope@^3.7.3": version "3.7.7" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" @@ -1051,6 +1328,11 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/geojson@*": + version "7946.0.14" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613" + integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg== + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -1212,6 +1494,13 @@ dependencies: "@types/react" "^17" +"@types/react-measure@^2.0.6": + version "2.0.12" + resolved "https://registry.yarnpkg.com/@types/react-measure/-/react-measure-2.0.12.tgz#e8ba05057357b9529aa4115064fe7ea77549f54c" + integrity sha512-Y6V11CH6bU7RhqrIdENPwEUZlPXhfXNGylMNnGwq5TAEs2wDoBA3kSVVM/EQ8u72sz5r9ja+7W8M8PIVcS841Q== + dependencies: + "@types/react" "*" + "@types/react-router-dom@^5.3.2": version "5.3.3" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" @@ -2327,7 +2616,7 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== -classnames@2.x, classnames@^2.3.1: +classnames@2.x, classnames@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== @@ -2454,6 +2743,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +commander@7, commander@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -2464,11 +2758,6 @@ commander@^5.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - commander@~9.4.1: version "9.4.1" resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" @@ -2713,6 +3002,293 @@ cypress@11: untildify "^4.0.0" yauzl "^2.10.0" +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +d3-axis@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" + integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== + +d3-brush@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + +d3-chord@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 3" + +"d3-color@1 - 3", d3-color@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-contour@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc" + integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== + dependencies: + d3-array "^3.2.0" + +d3-delaunay@6: + version "6.0.4" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" + integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== + dependencies: + delaunator "5" + +d3-dispatch@1, d3-dispatch@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58" + integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA== + +"d3-dispatch@1 - 3", d3-dispatch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +d3-drag@^1.0.4: + version "1.2.5" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.5.tgz#2537f451acd39d31406677b7dc77c82f7d988f70" + integrity sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w== + dependencies: + d3-dispatch "1" + d3-selection "1" + +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + +"d3-ease@1 - 3", d3-ease@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +d3-fetch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + +d3-force@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + +"d3-format@1 - 3", d3-format@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +d3-geo@3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d" + integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-path@1: + version "1.0.9" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" + integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== + +"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-polygon@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-random@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + +d3-scale-chromatic@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" + integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + +d3-scale@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +d3-selection@1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.4.2.tgz#dcaa49522c0dbf32d6c1858afc26b6094555bc5c" + integrity sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg== + +"d3-selection@2 - 3", d3-selection@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +d3-shape@3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +d3-shape@^1.3.5: + version "1.3.7" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" + integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== + dependencies: + d3-path "1" + +"d3-time-format@2 - 4", d3-time-format@4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +"d3-timer@1 - 3", d3-timer@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +d3-timer@^1.0.5: + version "1.0.10" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" + integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== + +"d3-transition@2 - 3", d3-transition@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d3@^7.8.0: + version "7.9.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" + integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + +dagre@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.2.tgz#755b79f4d5499d63cf74c3368fb08add93eceafe" + integrity sha512-TEOOGZOkCOgCG7AoUIq64sJ3d21SMv8tyoqteLpX+UsUsS9Qw8iap4hhogXY4oB3r0bbZuAjO0atAilgCmsE0Q== + dependencies: + graphlib "^2.1.5" + lodash "^4.17.4" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2861,6 +3437,13 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delaunator@5: + version "5.0.1" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" + integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== + dependencies: + robust-predicates "^3.0.2" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -3920,6 +4503,11 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-node-dimensions@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-node-dimensions/-/get-node-dimensions-1.2.1.tgz#fb7b4bb57060fb4247dd51c9d690dfbec56b0823" + integrity sha512-2MSPMu7S1iOTL+BOa6K1S62hB2zUAYNF/lV0gSVlOaacd087lc6nR1H1r0e3B1CerTo+RceOmi1iJW+vp21xcQ== + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -4081,6 +4669,13 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +graphlib@^2.1.5: + version "2.1.8" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" + integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== + dependencies: + lodash "^4.17.15" + gulp-sort@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/gulp-sort/-/gulp-sort-2.0.0.tgz#c6762a2f1f0de0a3fc595a21599d3fac8dba1aca" @@ -4383,7 +4978,7 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@0.6.3: +iconv-lite@0.6, iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -4478,6 +5073,11 @@ internal-slot@^1.0.4, internal-slot@^1.0.7: hasown "^2.0.0" side-channel "^1.0.4" +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + interpret@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" @@ -5581,7 +6181,7 @@ lodash.once@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5758,6 +6358,23 @@ mktemp@~0.4.0: resolved "https://registry.yarnpkg.com/mktemp/-/mktemp-0.4.0.tgz#6d0515611c8a8c84e484aa2000129b98e981ff0b" integrity sha512-IXnMcJ6ZyTuhRmJSjzvHSRhlVPiN9Jwc6e59V0bEJ0ba6OBeX2L0E+mRN1QseeOF4mM+F1Rit6Nh7o+rl2Yn/A== +mobx-react-lite@^3.4.0: + version "3.4.3" + resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.4.3.tgz#3a4c22c30bfaa8b1b2aa48d12b2ba811c0947ab7" + integrity sha512-NkJREyFTSUXR772Qaai51BnE1voWx56LOL80xG7qkZr6vo8vEaLF3sz1JNUVh+rxmUzxYaqOhfuxTfqUh0FXUg== + +mobx-react@^7.6.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-7.6.0.tgz#ebf0456728a9bd2e5c24fdcf9b36e285a222a7d6" + integrity sha512-+HQUNuh7AoQ9ZnU6c4rvbiVVl+wEkb9WqYsVDzGLng+Dqj1XntHu79PvEWKtSMoMj67vFp/ZPXcElosuJO8ckA== + dependencies: + mobx-react-lite "^3.4.0" + +mobx@^6.9.0: + version "6.13.1" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.13.1.tgz#76c41aa675199f75b84a257e4bec8ff839e33259" + integrity sha512-ekLRxgjWJr8hVxj9ZKuClPwM/iHckx3euIJ3Np7zLVNtqJvfbbq7l370W/98C8EabdQ1pB5Jd3BbDWxJPNnaOg== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -6186,6 +6803,16 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +point-in-svg-path@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/point-in-svg-path/-/point-in-svg-path-1.0.2.tgz#e7b25370e96f58a858c34882ffb57d41b7a574bf" + integrity sha512-+Smsf7B9e7eRFHIwpN+4rE8inF2APbFWeywPfUgbeh02xdJSkbTz6Pqdt7A36wVCR+CnLbaNkRnBjgOpF5RMVQ== + +popper.js@^1.16.1: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== + portfinder@^1.0.28: version "1.0.32" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81" @@ -6495,6 +7122,16 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-measure@^2.3.0: + version "2.5.2" + resolved "https://registry.yarnpkg.com/react-measure/-/react-measure-2.5.2.tgz#4ffc410e8b9cb836d9455a9ff18fc1f0fca67f89" + integrity sha512-M+rpbTLWJ3FD6FXvYV6YEGvQ5tMayQ3fGrZhRPHrE9bVlBYfDCLuDcgNttYfk8IqfOI03jz6cbpqMRTUclQnaA== + dependencies: + "@babel/runtime" "^7.2.0" + get-node-dimensions "^1.2.1" + prop-types "^15.6.2" + resize-observer-polyfill "^1.5.0" + react-redux@7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736" @@ -6716,6 +7353,11 @@ reselect@4.x: resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== +resize-observer-polyfill@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -6805,6 +7447,11 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +robust-predicates@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" + integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== + rsvp@^4.8.2: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -6822,6 +7469,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== + rxjs@^7.5.1: version "7.8.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" @@ -7665,6 +8317,11 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.0: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + tslib@^2.1.0, tslib@^2.4.0, tslib@^2.5.0: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" @@ -8063,6 +8720,16 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" +webcola@3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/webcola/-/webcola-3.4.0.tgz#490d26ae98e5b5109478b94a846a62ff6831a99d" + integrity sha512-4BiLXjXw3SJHo3Xd+rF+7fyClT6n7I+AR6TkBqyQ4kTsePSAMDLRCXY1f3B/kXJeP9tYn4G1TblxTO+jAt0gaw== + dependencies: + d3-dispatch "^1.0.3" + d3-drag "^1.0.4" + d3-shape "^1.3.5" + d3-timer "^1.0.5" + webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"