diff --git a/src/floatPanels/TopDisplayOptionsPanel.tsx b/src/floatPanels/TopDisplayOptionsPanel.tsx
new file mode 100644
index 0000000..9ad96e0
--- /dev/null
+++ b/src/floatPanels/TopDisplayOptionsPanel.tsx
@@ -0,0 +1,24 @@
+import { DisplayPanelOptionEnv } from "../model";
+
+interface TopDisplayOptionsPanelProps {
+ options: DisplayPanelOptionEnv[];
+ setShowOptionsPanel: (v: boolean) => void;
+}
+
+export default function TopDisplayOptionsPanel({ options, setShowOptionsPanel }: TopDisplayOptionsPanelProps) {
+ return (
+
setShowOptionsPanel(false)}>
+ {options.map((opt, index) => (
+
opt.onClick()}>
+ {}}
+ className="top-display-options-panel-item"
+ />
+ {opt.label}
+
+ ))}
+
+ );
+}
diff --git a/src/floatPanels/TopDisplayPanel.tsx b/src/floatPanels/TopDisplayPanel.tsx
new file mode 100644
index 0000000..f56d301
--- /dev/null
+++ b/src/floatPanels/TopDisplayPanel.tsx
@@ -0,0 +1,23 @@
+import { useState } from 'react';
+import TopDisplayOptionsPanel from "./TopDisplayOptionsPanel";
+import { DisplayPanelOptionEnv } from "../model";
+
+interface TopDisplayPanelProps {
+ options: DisplayPanelOptionEnv[];
+}
+
+export default function TopDisplayPanel({ options }: TopDisplayPanelProps) {
+ const [showOptionsPanel, setShowOptionsPanel] = useState(false);
+
+ return (
+ <>
+ setShowOptionsPanel(!showOptionsPanel)}
+ >
+
Display
+
+ {showOptionsPanel && }
+ >
+ );
+}
diff --git a/src/model.ts b/src/model.ts
index 5bafc56..257f293 100644
--- a/src/model.ts
+++ b/src/model.ts
@@ -23,6 +23,7 @@ export type Edge = {
export enum NodeType {
Cluster = "cluster",
Interface = "interface",
+ LCInterface = "loopConIfNT",
Client = "client",
Forwarder = "forwarder",
Manager = "manager",
@@ -34,19 +35,20 @@ export enum NodeType {
export enum EdgeType {
InterfaceConnection = "interfaceConnection",
InterfaceCrossConnection = "interfaceCrossConnection",
+ InterfaceLoopedConnection = "interfaceLoopedConnection",
ServiceRequest = "serviceRequest",
RegistryRequest = "registryRequest"
}
export const AllowedNodeTypes = {
- Dataplane: [NodeType.Cluster, NodeType.Interface, NodeType.Forwarder, NodeType.Client, NodeType.Endpoint],
+ Dataplane: [NodeType.Cluster, NodeType.Interface, NodeType.LCInterface, NodeType.Forwarder, NodeType.Client, NodeType.Endpoint],
NetworkServices: [NodeType.Client, NodeType.Service],
NetworkServiceRequests: [NodeType.Forwarder, NodeType.Client, NodeType.Endpoint, NodeType.Manager],
RegistryRequests: [NodeType.Endpoint, NodeType.Forwarder, NodeType.Manager, NodeType.Registry],
}
export const AllowedEdgeTypes = {
- Dataplane: [EdgeType.InterfaceConnection, EdgeType.InterfaceCrossConnection],
+ Dataplane: [EdgeType.InterfaceConnection, EdgeType.InterfaceCrossConnection, EdgeType.InterfaceLoopedConnection],
NetworkServices: [EdgeType.ServiceRequest],
NetworkServiceRequests: [EdgeType.ServiceRequest],
RegistryRequests: [EdgeType.RegistryRequest],
@@ -80,3 +82,22 @@ export enum LineStyle {
Dashed = "dashed",
Dotted = "dotted"
}
+
+export enum Page {
+ Dataplane = "dataplane"
+}
+
+export enum Option {
+ ShowLoopedConnections = "showLoopedConnections"
+}
+
+export type DisplayPanelOption = {
+ page: Page,
+ option: Option
+}
+
+export type DisplayPanelOptionEnv = {
+ label: string,
+ onClick: () => void,
+ checked: boolean,
+}
diff --git a/src/pages/Dataplane.tsx b/src/pages/Dataplane.tsx
index edff26c..bdd9c28 100644
--- a/src/pages/Dataplane.tsx
+++ b/src/pages/Dataplane.tsx
@@ -1,7 +1,11 @@
import * as React from "react";
+import { useDispatch, useSelector } from 'react-redux';
+import { switchDisplayPanelOption } from '../store/actions';
+import { AppDispatch, RootState } from '../store/store';
import { Stylesheet } from "cytoscape";
import cloneDeep from 'lodash/cloneDeep';
import CytoscapeCanvas from "./CytoscapeCanvas";
+import TopDisplayPanel from "../floatPanels/TopDisplayPanel";
import {
NodeType,
InterfaceSize,
@@ -13,7 +17,10 @@ import {
AllowedNodeTypes,
AllowedEdgeTypes,
Node,
- Edge
+ Edge,
+ Page,
+ Option,
+ DisplayPanelOptionEnv
} from "../model";
interface DataplaneProps {
@@ -51,6 +58,20 @@ function getDataplaneStylesheet() {
"text-border-opacity": 1
}
},
+ {
+ selector: `node[type = '${NodeType.LCInterface}']`,
+ style: {
+ width: InterfaceSize,
+ height: InterfaceSize,
+ "font-size": "8px",
+ 'text-background-color': Color.Gray,
+ 'text-background-opacity': 1,
+ "text-background-shape": "roundrectangle",
+ 'text-border-color': Color.Gray,
+ "text-border-width": "0.2em",
+ "text-border-opacity": 1
+ }
+ },
{
selector: `node[type = '${NodeType.Interface}'][label]`,
style: {
@@ -95,24 +116,51 @@ function getDataplaneStylesheet() {
"line-color": (edge: { data: (arg0: string) => boolean }) =>
edge.data("healthy") === false ? Color.Red : Color.Green
}
+ },
+ {
+ selector: `edge[type = '${EdgeType.InterfaceLoopedConnection}']`,
+ style: {
+ "line-color": Color.Gray
+ }
}
];
}
export default function Dataplane({ nodes, edges }: DataplaneProps) {
+ const dispatch = useDispatch();
+ const showLoopedConnections = useSelector((state: RootState) => state.app.pages.dataplane.topDisplayPanelOptions.showLoopedConnections);
+
+ const displayPanelOptions: DisplayPanelOptionEnv[] = [
+ {
+ label: 'Looped Connections',
+ onClick: () => dispatch(switchDisplayPanelOption({
+ page: Page.Dataplane,
+ option: Option.ShowLoopedConnections
+ })),
+ checked: showLoopedConnections,
+ },
+ ];
+
const [stylesheet] = React.useState(getDataplaneStylesheet() as Stylesheet[]);
const dataplaneNodes = nodes
.filter(n => AllowedNodeTypes.Dataplane.includes(n.data.type))
+ .filter(n => showLoopedConnections ? n : n.data.type !== NodeType.LCInterface)
.map(n => ({ ...n, data: cloneDeep(n.data) }));
const dataplaneEdges = edges
.filter(e => AllowedEdgeTypes.Dataplane.includes(e.data.type))
+ .filter(e => showLoopedConnections ? e : e.data.type !== EdgeType.InterfaceLoopedConnection)
.map(e => ({ ...e, data: cloneDeep(e.data) }));
return (
-
+ <>
+
+
+ >
);
}
diff --git a/src/store/actions.ts b/src/store/actions.ts
index 1d1e077..2f9f2af 100644
--- a/src/store/actions.ts
+++ b/src/store/actions.ts
@@ -1,5 +1,10 @@
-import { SET_NODES, SET_EDGES, SET_SELECTED_MENU_ITEM } from './types';
-import { Node, Edge } from "../model";
+import {
+ SET_NODES,
+ SET_EDGES,
+ SET_SELECTED_MENU_ITEM,
+ SWITCH_DISPLAY_PANEL_OPTION
+} from './types';
+import { Node, Edge, DisplayPanelOption } from "../model";
export const setNodes = (nodes: Node[]) => ({
type: SET_NODES,
@@ -15,3 +20,8 @@ export const setSelectedMenuItem = (selectedMenuItem: number) => ({
type: SET_SELECTED_MENU_ITEM,
payload: selectedMenuItem,
});
+
+export const switchDisplayPanelOption = (displayPanelOption: DisplayPanelOption) => ({
+ type: SWITCH_DISPLAY_PANEL_OPTION,
+ payload: displayPanelOption,
+});
diff --git a/src/store/reducer.ts b/src/store/reducer.ts
index fd40f86..ecc82ea 100644
--- a/src/store/reducer.ts
+++ b/src/store/reducer.ts
@@ -1,5 +1,10 @@
-import { SET_NODES, SET_EDGES, SET_SELECTED_MENU_ITEM } from './types';
-import { Node, Edge } from "../model";
+import {
+ SET_NODES,
+ SET_EDGES,
+ SET_SELECTED_MENU_ITEM,
+ SWITCH_DISPLAY_PANEL_OPTION
+} from './types';
+import { Node, Edge, Page, Option } from "../model";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const initialState: { nodes: Node[]; edges: Edge[]; app: any } = {
@@ -7,6 +12,13 @@ const initialState: { nodes: Node[]; edges: Edge[]; app: any } = {
edges: [],
app: {
selectedMenuItem: 2,
+ pages: {
+ [Page.Dataplane]: {
+ topDisplayPanelOptions: {
+ [Option.ShowLoopedConnections]: false
+ }
+ }
+ }
}
};
@@ -18,7 +30,24 @@ const rootReducer = (state = initialState, action: any) => {
case SET_EDGES:
return { ...state, edges: action.payload };
case SET_SELECTED_MENU_ITEM:
- return { ...state, app: { selectedMenuItem: action.payload} };
+ return { ...state, app: { ...state.app, selectedMenuItem: action.payload} };
+ case SWITCH_DISPLAY_PANEL_OPTION:
+ return {
+ ...state,
+ app: {
+ ...state.app,
+ pages: {
+ ...state.app.pages,
+ [action.payload.page]: {
+ ...state.app.pages[action.payload.page],
+ topDisplayPanelOptions: {
+ ...state.app.pages[action.payload.page].topDisplayPanelOptions,
+ [action.payload.option]: !state.app.pages[action.payload.page].topDisplayPanelOptions[action.payload.option]
+ }
+ }
+ }
+ }
+ };
default:
return state;
}
diff --git a/src/store/types.ts b/src/store/types.ts
index c30a7e3..2e3dd3b 100644
--- a/src/store/types.ts
+++ b/src/store/types.ts
@@ -1,3 +1,4 @@
export const SET_NODES = 'SET_NODES';
export const SET_EDGES = 'SET_EDGES';
export const SET_SELECTED_MENU_ITEM ='SET_SELECTED_MENU_ITEM';
+export const SWITCH_DISPLAY_PANEL_OPTION ='SWITCH_DISPLAY_PANEL_OPTION';
diff --git a/src/styles.css b/src/styles.css
index e3ce22e..9ee47e9 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1,3 +1,9 @@
+:root {
+ --theme-color: #202020;
+ --theme-text-color: white;
+ --floating-panel-background-color: #D9D9D9;
+}
+
body {
margin: 0;
padding: 0;
@@ -5,7 +11,7 @@ body {
}
.header {
- background-color: #202020;
+ background-color: var(--theme-color);
height: 39px;
display: flex;
justify-content: center;
@@ -19,12 +25,12 @@ body {
}
.header-title {
- color: white;
+ color: var(--theme-text-color);
font-size: 16px;
}
.footer {
- background-color: #202020;
+ background-color: var(--theme-color);
height: 16px;
display: flex;
justify-content: center;
@@ -33,17 +39,43 @@ body {
}
.footer-text {
- color: white;
+ color: var(--theme-text-color);
font-size: 9px;
}
.left-panel {
- background-color: #202020;
+ background-color: var(--theme-color);
width: 200px;
height: calc(100vh - 55px);
padding-left: 10px;
}
+.top-display-panel {
+ position: fixed;
+ left: 220px;
+ top: 49px;
+ font-size: 14px;
+ background-color: var(--floating-panel-background-color);
+ padding: 5px;
+ border-radius: 5px;
+ user-select: none;
+ cursor: pointer;
+}
+
+.top-display-options-panel {
+ position: fixed;
+ left: 220px;
+ top: 79px;
+ padding: 10px;
+ background-color: var(--floating-panel-background-color);
+ user-select: none;
+}
+
+.top-display-options-panel-item {
+ cursor: pointer;
+ margin-bottom: 5px;
+}
+
.menu {
list-style: none;
padding: 0;
@@ -51,7 +83,7 @@ body {
}
.menu-item {
- color: white;
+ color: var(--theme-text-color);
font-size: 16px;
cursor: pointer;
padding: 5px 0;