diff --git a/ui-common/package.json b/ui-common/package.json index 012d8a959..d255267fd 100644 --- a/ui-common/package.json +++ b/ui-common/package.json @@ -49,6 +49,7 @@ "node-fetch": "^3.1.1", "numeral": "^2.0.6", "protobuf-decoder": "^0.1.0", + "react-resizable": "^3.0.4", "react-graph-vis": "^1.0.7", "react-lowlight": "^3.0.0", "react-router-dom": "^6.2.1", @@ -90,4 +91,4 @@ "files": [ "dist" ] -} +} \ No newline at end of file diff --git a/ui-common/src/components/ServiceMapModal/ServiceMapModal.module.sass b/ui-common/src/components/ServiceMapModal/ServiceMapModal.module.sass new file mode 100644 index 000000000..9c74e4b35 --- /dev/null +++ b/ui-common/src/components/ServiceMapModal/ServiceMapModal.module.sass @@ -0,0 +1,60 @@ +@import "../../variables.module" + +.modalContainer + display: flex + flex-wrap: nowrap + width: 100% + height: 100% + +.graphSection + flex: 85% + +.filterSection + flex: 15% + border-right: 1px solid $blue-color + margin-right: 2% + height: 100% + + .filters table + margin-top: 0px + + tr + border-style: none + + td + color: #8f9bb2 + font-size: 11px + font-weight: 600 + padding-top: 2px + padding-bottom: 2px + +.colorBlock + display: inline-block + height: 15px + width: 50px + +.filterWrapper + height: 100% + display: flex + flex-direction: column + margin-right: 10px + +.servicesFilterSearch + width: calc(100% - 10px) + max-width: 300px + box-shadow: 0px 1px 5px #979797 + margin-left: 10px + margin-bottom: 5px + +.servicesFilter + margin-top: clamp(25px,15%,35px) + height: 100% + overflow: hidden + + & .servicesFilterList + overflow-y: auto + height: 92% + +.separtorLine + margin-top: 10px + border: 1px solid #E9EBF8 diff --git a/ui-common/src/components/ServiceMapModal/ServiceMapModal.tsx b/ui-common/src/components/ServiceMapModal/ServiceMapModal.tsx new file mode 100644 index 000000000..a81ea6a0f --- /dev/null +++ b/ui-common/src/components/ServiceMapModal/ServiceMapModal.tsx @@ -0,0 +1,227 @@ +import React, { useState, useEffect, useCallback, useMemo } from "react"; +import { Box, Fade, Modal, Backdrop, Button } from "@material-ui/core"; +import { toast } from "react-toastify"; +import spinnerStyle from '../UI/style/Spinner.module.sass'; +import spinnerImg from 'assets/spinner.svg'; +import Graph from "react-graph-vis"; +import debounce from 'lodash/debounce'; +import ServiceMapOptions from './ServiceMapOptions' +import { useCommonStyles } from "../../helpers/commonStyle"; +import refreshIcon from "assets/refresh.svg"; +import closeIcon from "assets/close.svg" +import styles from './ServiceMapModal.module.sass' +import SelectList from "../UI/SelectList"; +import { GraphData, ServiceMapGraph } from "./ServiceMapModalTypes" +import { ResizableBox } from "react-resizable" +import "react-resizable/css/styles.css" +import { Utils } from "../../helpers/Utils"; + +const modalStyle = { + position: 'absolute', + top: '6%', + left: '50%', + transform: 'translate(-50%, 0%)', + width: '89vw', + height: '82vh', + bgcolor: 'background.paper', + borderRadius: '5px', + boxShadow: 24, + p: 4, + color: '#000', + padding: "25px 15px" +}; + +interface LegentLabelProps { + color: string, + name: string +} + +const LegentLabel: React.FC = ({ color, name }) => { + return +
+ {name} + +
+
+} + +const protocols = [ + { key: "http", value: "HTTP", component: }, + { key: "http/2", value: "HTTP/2", component: }, + { key: "grpc", value: "gRPC", component: }, + { key: "amqp", value: "AMQP", component: }, + { key: "kafka", value: "KAFKA", component: }, + { key: "redis", value: "REDIS", component: },] + + +interface ServiceMapModalProps { + isOpen: boolean; + onOpen: () => void; + onClose: () => void; + getServiceMapDataApi: () => Promise +} + +export const ServiceMapModal: React.FC = ({ isOpen, onClose, getServiceMapDataApi }) => { + const commonClasses = useCommonStyles(); + const [isLoading, setIsLoading] = useState(true); + const [graphData, setGraphData] = useState({ nodes: [], edges: [] }); + const [filteredProtocols, setFilteredProtocols] = useState(protocols.map(x => x.key)) + const [filteredServices, setFilteredServices] = useState([]) + const [serviceMapApiData, setServiceMapApiData] = useState({ edges: [], nodes: [] }) + const [servicesSearchVal, setServicesSearchVal] = useState("") + const [graphOptions, setGraphOptions] = useState(ServiceMapOptions); + + const getServiceMapData = useCallback(async () => { + try { + setIsLoading(true) + + const serviceMapData: ServiceMapGraph = await getServiceMapDataApi() + setServiceMapApiData(serviceMapData) + const newGraphData: GraphData = { nodes: [], edges: [] } + + if (serviceMapData.nodes) { + newGraphData.nodes = serviceMapData.nodes.map(mapNodesDatatoGraph) + } + + if (serviceMapData.edges) { + newGraphData.edges = serviceMapData.edges.map(mapEdgesDatatoGraph) + } + + setGraphData(newGraphData) + } catch (ex) { + toast.error("An error occurred while loading Mizu Service Map, see console for mode details"); + console.error(ex); + } finally { + setIsLoading(false) + } + // eslint-disable-next-line + }, [isOpen]) + + const mapNodesDatatoGraph = node => { + return { + id: node.id, + value: node.count, + label: (node.entry.name === "unresolved") ? node.name : `${node.entry.name} (${node.name})`, + title: "Count: " + node.name, + isResolved: node.entry.resolved + } + } + + const mapEdgesDatatoGraph = edge => { + return { + from: edge.source.id, + to: edge.destination.id, + value: edge.count, + label: edge.count.toString(), + color: { + color: edge.protocol.backgroundColor, + highlight: edge.protocol.backgroundColor + }, + } + } + const mapToKeyValForFilter = (arr) => arr.map(mapNodesDatatoGraph) + .map((edge) => { return { key: edge.label, value: edge.label } }) + .sort((a, b) => { return a.key.localeCompare(b.key) }); + + const getServicesForFilter = useMemo(() => { + + const resolved = mapToKeyValForFilter(serviceMapApiData.nodes?.filter(x => x.resolved)) + const unResolved = mapToKeyValForFilter(serviceMapApiData.nodes?.filter(x => !x.resolved)) + return [...resolved, ...unResolved] + }, [serviceMapApiData]) + + const filterServiceMap = (newProtocolsFilters?: any[], newServiceFilters?: string[]) => { + const filterProt = newProtocolsFilters || filteredProtocols + const filterService = newServiceFilters || filteredServices || getServicesForFilter.map(x => x.key) + setFilteredProtocols(filterProt) + setFilteredServices(filterService) + const newGraphData: GraphData = { + nodes: serviceMapApiData.nodes?.map(mapNodesDatatoGraph).filter(node => filterService.includes(node.label)), + edges: serviceMapApiData.edges?.filter(edge => filterProt.includes(edge.protocol.name)).map(mapEdgesDatatoGraph) + } + setGraphData(newGraphData); + } + + useEffect(() => { + const resolvedServices = getServicesForFilter.map(x => x.key).filter(serviceName => !Utils.isIpAddress(serviceName)) + setFilteredServices(resolvedServices) + filterServiceMap(filteredProtocols, resolvedServices) + }, [getServicesForFilter]) + + useEffect(() => { + getServiceMapData() + }, [getServiceMapData]) + + useEffect(() => { + if (graphData?.nodes?.length === 0) return; + let options = { ...graphOptions }; + options.physics.barnesHut.avoidOverlap = graphData?.nodes?.length > 10 ? 0 : 1; + setGraphOptions(options); + // eslint-disable-next-line + }, [graphData?.nodes?.length]) + + const refreshServiceMap = debounce(() => { + getServiceMapData(); + }, 500); + + return ( + + + +
+ {/* TODO: remove error missing height */} + +
+
+
+ +
+
+
+ setServicesSearchVal(event.target.value)} /> +
+ filterServiceMap(null, newServicesForFilter)} /> +
+
+
+
+
+
+
+ + close onClose()} style={{ cursor: "pointer" }}> +
+ {isLoading &&
+ spinner +
} + {!isLoading &&
+ +
+ } +
+
+
+
+
+ ); +} diff --git a/ui-common/src/components/ServiceMapModal/ServiceMapModalTypes.ts b/ui-common/src/components/ServiceMapModal/ServiceMapModalTypes.ts new file mode 100644 index 000000000..c5b498279 --- /dev/null +++ b/ui-common/src/components/ServiceMapModal/ServiceMapModalTypes.ts @@ -0,0 +1,60 @@ +export interface GraphData { + nodes: Node[]; + edges: Edge[]; +} + +export interface Node { + id: number; + value: number; + label: string; + title?: string; + color?: object; +} + +export interface Edge { + from: number; + to: number; + value: number; + label: string; + title?: string; + color?: object; +} + +export interface ServiceMapNode { + id: number; + name: string; + entry: Entry; + count: number; + resolved: boolean; +} + +export interface ServiceMapEdge { + source: ServiceMapNode; + destination: ServiceMapNode; + count: number; + protocol: Protocol; +} + +export interface ServiceMapGraph { + nodes: ServiceMapNode[]; + edges: ServiceMapEdge[]; +} + +export interface Entry { + ip: string; + port: string; + name: string; +} + +export interface Protocol { + name: string; + abbr: string; + macro: string; + version: string; + backgroundColor: string; + foregroundColor: string; + fontSize: number; + referenceLink: string; + ports: string[]; + priority: number; +} \ No newline at end of file diff --git a/ui-common/src/components/ServiceMapModal/ServiceMapOptions.ts b/ui-common/src/components/ServiceMapModal/ServiceMapOptions.ts new file mode 100644 index 000000000..fffff982a --- /dev/null +++ b/ui-common/src/components/ServiceMapModal/ServiceMapOptions.ts @@ -0,0 +1,83 @@ +const ServiceMapOptions = { + physics: { + enabled: true, + solver: 'barnesHut', + barnesHut: { + theta: 0.5, + gravitationalConstant: -2000, + centralGravity: 0.3, + springLength: 180, + springConstant: 0.04, + damping: 0.09, + avoidOverlap: 0 + }, + }, + layout: { + hierarchical: false, + randomSeed: 1 // always on node 1 + }, + nodes: { + shape: 'dot', + chosen: true, + color: { + background: '#27AE60', + border: '#000000', + highlight: { + background: '#27AE60', + border: '#000000', + }, + }, + font: { + color: '#343434', + size: 14, // px + face: 'arial', + background: 'none', + strokeWidth: 0, // px + strokeColor: '#ffffff', + align: 'center', + multi: false + }, + borderWidth: 1.5, + borderWidthSelected: 2.5, + labelHighlightBold: true, + opacity: 1, + shadow: true, + }, + edges: { + chosen: true, + dashes: false, + arrowStrikethrough: false, + arrows: { + to: { + enabled: true, + }, + middle: { + enabled: false, + }, + from: { + enabled: false, + } + }, + smooth: { + enabled: true, + type: 'dynamic', + roundness: 1.0 + }, + font: { + color: '#343434', + size: 12, // px + face: 'arial', + background: 'none', + strokeWidth: 2, // px + strokeColor: '#ffffff', + align: 'horizontal', + multi: false, + }, + labelHighlightBold: true, + selectionWidth: 1, + shadow: true, + }, + autoResize: true, +}; + +export default ServiceMapOptions diff --git a/ui-common/src/components/ServiceMapModal/assets/close.svg b/ui-common/src/components/ServiceMapModal/assets/close.svg new file mode 100644 index 000000000..1221c0331 --- /dev/null +++ b/ui-common/src/components/ServiceMapModal/assets/close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui-common/src/components/ServiceMapModal/assets/refresh.svg b/ui-common/src/components/ServiceMapModal/assets/refresh.svg new file mode 100644 index 000000000..de0615856 --- /dev/null +++ b/ui-common/src/components/ServiceMapModal/assets/refresh.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui-common/src/components/ServiceMapModal/assets/spinner.svg b/ui-common/src/components/ServiceMapModal/assets/spinner.svg new file mode 100644 index 000000000..16ac582fa --- /dev/null +++ b/ui-common/src/components/ServiceMapModal/assets/spinner.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ui-common/src/components/UI/NoDataMessage.tsx b/ui-common/src/components/UI/NoDataMessage.tsx new file mode 100644 index 000000000..c12d302fa --- /dev/null +++ b/ui-common/src/components/UI/NoDataMessage.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import circleImg from 'assets/dotted-circle.svg'; +import styles from './style/NoDataMessage.module.sass' + +export interface Props { + messageText: string; +} + +const NoDataMessage: React.FC = ({ messageText = "No data found" }) => { + return ( +
+
+ No data Found +
{messageText}
+
+
+ ); +}; + +export default NoDataMessage; diff --git a/ui-common/src/components/UI/Radio.tsx b/ui-common/src/components/UI/Radio.tsx new file mode 100644 index 000000000..1ba9ea4f4 --- /dev/null +++ b/ui-common/src/components/UI/Radio.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +export interface Props { + checked: boolean; + onToggle: (checked: boolean) => any; + disabled?: boolean; +} + +const Radio: React.FC = ({ checked, onToggle, disabled, ...props }) => { + return ( +
+ onToggle(event.target.checked)} {...props} /> +
+ ); +}; + +export default Radio; diff --git a/ui-common/src/components/UI/SelectList.tsx b/ui-common/src/components/UI/SelectList.tsx new file mode 100644 index 000000000..e9bb02a07 --- /dev/null +++ b/ui-common/src/components/UI/SelectList.tsx @@ -0,0 +1,103 @@ +import React, { useMemo } from "react"; +import Radio from "./Radio"; +import styles from './style/SelectList.module.sass' +import NoDataMessage from "./NoDataMessage"; +import Checkbox from "./Checkbox"; + + +export interface Props { + items; + tableName: string; + checkedValues?: string[]; + multiSelect: boolean; + searchValue?: string; + setCheckedValues: (newValues) => void; + tableClassName? + checkBoxWidth?: string +} + +const SelectList: React.FC = ({ items, tableName, checkedValues = [], multiSelect = true, searchValue = "", setCheckedValues, tableClassName, + checkBoxWidth = 50 }) => { + const noItemsMessage = "No items to show"; + const enabledItemsLength = useMemo(() => items.filter(item => !item.disabled).length, [items]); + + const filteredValues = useMemo(() => { + return items.filter((listValue) => listValue?.value?.includes(searchValue)); + }, [items, searchValue]) + + const toggleValue = (checkedKey) => { + if (!multiSelect) { + const newCheckedValues = []; + newCheckedValues.push(checkedKey); + setCheckedValues(newCheckedValues); + } + else { + const newCheckedValues = [...checkedValues]; + let index = newCheckedValues.indexOf(checkedKey); + if (index > -1) + newCheckedValues.splice(index, 1); + else + newCheckedValues.push(checkedKey); + setCheckedValues(newCheckedValues); + } + } + + const toggleAll = () => { + const newCheckedValues = [...checkedValues]; + if (newCheckedValues.length === enabledItemsLength) setCheckedValues([]); + else { + items.forEach((obj) => { + if (!obj.disabled && !newCheckedValues.includes(obj.key)) + newCheckedValues.push(obj.key); + }) + setCheckedValues(newCheckedValues); + } + } + + const dataFieldFunc = (listValue) => listValue.component ? listValue.component : + + {listValue.value} + + + const tableHead = multiSelect ? + + {tableName} + : + + {tableName} + + + const tableBody = filteredValues.length === 0 ? + + + + + + : + filteredValues?.map(listValue => { + return + + {multiSelect && toggleValue(listValue.key)} />} + {!multiSelect && toggleValue(listValue.key)} />} + + + {dataFieldFunc(listValue)} + + + } + ) + + return
+ + + {tableHead} + + + {tableBody} + +
+
+} + +export default SelectList; \ No newline at end of file diff --git a/ui-common/src/components/UI/assets/dotted-circle.svg b/ui-common/src/components/UI/assets/dotted-circle.svg new file mode 100644 index 000000000..2017acec3 --- /dev/null +++ b/ui-common/src/components/UI/assets/dotted-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui-common/src/components/UI/index.ts b/ui-common/src/components/UI/index.ts index 45a5c79cd..481196b54 100644 --- a/ui-common/src/components/UI/index.ts +++ b/ui-common/src/components/UI/index.ts @@ -6,7 +6,9 @@ import Checkbox from "./Checkbox" import { StatusBar } from "./StatusBar"; import CustomModal from "./CustomModal"; import { InformationIcon } from "./InformationIcon"; +import SelectList from "./SelectList"; +import NoDataMessage from "./NoDataMessage"; -export {LoadingOverlay,Select,Tabs,Tooltip,Checkbox,CustomModal,InformationIcon} -export {StatusBar} \ No newline at end of file +export { LoadingOverlay, Select, Tabs, Tooltip, Checkbox, CustomModal, InformationIcon, SelectList, NoDataMessage } +export { StatusBar } \ No newline at end of file diff --git a/ui-common/src/components/UI/style/NoDataMessage.module.sass b/ui-common/src/components/UI/style/NoDataMessage.module.sass new file mode 100644 index 000000000..68c074d2f --- /dev/null +++ b/ui-common/src/components/UI/style/NoDataMessage.module.sass @@ -0,0 +1,32 @@ +@import '../../../variables.module' + +.messageContainer + width: 100% + margin-top: 20px + + &__noData + display: flex + justify-content: space-between + flex-direction: column + height: 95px + margin: 2% + align-items: center + align-content: center + padding-top: 3% + padding-bottom: 3% + + & .container + display: flex + justify-content: space-between + flex-direction: column + height: 95px + margin: 1% + align-items: center + align-content: center + + &-message + font-style: normal + font-weight: 600 + font-size: 12px + line-height: 15px + color: $light-gray diff --git a/ui-common/src/components/UI/style/SelectList.module.sass b/ui-common/src/components/UI/style/SelectList.module.sass new file mode 100644 index 000000000..c822d5dd0 --- /dev/null +++ b/ui-common/src/components/UI/style/SelectList.module.sass @@ -0,0 +1,31 @@ +@import '../../../variables.module' + +.selectListTable + table + width: 100% + margin-top: 20px + height: 100% + + tbody + display: block + + th + color: $blue-gray + text-align: left + padding: 10px + + tr + border-bottom-width: 1px + border-bottom-color: $data-background-color + border-bottom-style: solid + display: table + table-layout: fixed + width: 100% + + td + color: $light-gray + padding: 10px + font-size: 16px + +.nowrap + white-space: nowrap diff --git a/ui-common/src/components/style/Spinner.module.sass b/ui-common/src/components/UI/style/Spinner.module.sass similarity index 69% rename from ui-common/src/components/style/Spinner.module.sass rename to ui-common/src/components/UI/style/Spinner.module.sass index 3f689dc41..b713b6cbd 100644 --- a/ui-common/src/components/style/Spinner.module.sass +++ b/ui-common/src/components/UI/style/Spinner.module.sass @@ -1,7 +1,6 @@ -@import "../../variables.module" +@import "../../../variables.module" .spinnerContainer display: flex justify-content: center margin-bottom: 10px - \ No newline at end of file diff --git a/ui-common/src/helpers/Utils.ts b/ui-common/src/helpers/Utils.ts new file mode 100644 index 000000000..f22176879 --- /dev/null +++ b/ui-common/src/helpers/Utils.ts @@ -0,0 +1,6 @@ +const IP_ADDRESS_REGEX = /([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3})(:([0-9]{1,5}))?/ + + +export class Utils { + static isIpAddress = (address: string): boolean => IP_ADDRESS_REGEX.test(address) +} \ No newline at end of file diff --git a/ui-common/src/index.tsx b/ui-common/src/index.tsx index 6fc55ffa4..08db78d49 100644 --- a/ui-common/src/index.tsx +++ b/ui-common/src/index.tsx @@ -1,10 +1,11 @@ import TrafficViewer from './components/TrafficViewer/TrafficViewer'; import * as UI from "./components/UI" import { StatusBar } from './components/UI'; -import useWS,{DEFAULT_QUERY} from './hooks/useWS'; -import {AnalyzeButton} from "./components/AnalyzeButton/AnalyzeButton" +import useWS, { DEFAULT_QUERY } from './hooks/useWS'; +import { AnalyzeButton } from "./components/AnalyzeButton/AnalyzeButton" import OasModal from './components/OasModal/OasModal'; +import { ServiceMapModal } from './components/ServiceMapModal/ServiceMapModal'; -export {UI,AnalyzeButton, StatusBar, OasModal} -export { useWS, DEFAULT_QUERY} +export { UI, AnalyzeButton, StatusBar, OasModal, ServiceMapModal } +export { useWS, DEFAULT_QUERY } export default TrafficViewer; diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 9ce3823c4..bdf203c1a 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,12 +1,12 @@ -import { useState} from 'react'; +import { useState } from 'react'; import './App.sass'; -import {Header} from "./components/Header/Header"; -import {TrafficPage} from "./components/Pages/TrafficPage/TrafficPage"; -import { ServiceMapModal } from './components/ServiceMapModal/ServiceMapModal'; -import {useRecoilState} from "recoil"; +import { Header } from "./components/Header/Header"; +import { TrafficPage } from "./components/Pages/TrafficPage/TrafficPage"; +import { ServiceMapModal } from '@up9/mizu-common'; +import { useRecoilState } from "recoil"; import serviceMapModalOpenAtom from "./recoil/serviceMapModalOpen"; import oasModalOpenAtom from './recoil/oasModalOpen/atom'; -import {OasModal} from '@up9/mizu-common'; +import { OasModal } from '@up9/mizu-common'; import Api from './helpers/api'; const api = Api.getInstance() @@ -19,20 +19,20 @@ const App = () => { return (
-
- - {window["isServiceMapEnabled"] && + + {window["isServiceMapEnabled"] && setServiceMapModalOpen(true)} onClose={() => setServiceMapModalOpen(false)} + getServiceMapDataApi={api.serviceMapData} />} + {window["isOasEnabled"] && setOasModalOpen(false)} />} - {window["isOasEnabled"] && setOasModalOpen(false)} - />} -
+ ); } diff --git a/ui/src/components/AnalyzeButton/AnalyzeButton.tsx b/ui/src/components/AnalyzeButton/AnalyzeButton.tsx deleted file mode 100644 index 01f021e22..000000000 --- a/ui/src/components/AnalyzeButton/AnalyzeButton.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import {Button} from "@material-ui/core"; -import React from "react"; -import {UI} from "@up9/mizu-common"; -import logo_up9 from "../assets/logo_up9.svg"; -import {makeStyles} from "@material-ui/core/styles"; - -const useStyles = makeStyles(() => ({ - tooltip: { - backgroundColor: "#3868dc", - color: "white", - fontSize: 13, - }, -})); - -interface AnalyseButtonProps { - analyzeStatus: any -} - -export const AnalyzeButton: React.FC = ({analyzeStatus}) => { - - const classes = useStyles(); - - const analysisMessage = analyzeStatus?.isRemoteReady ? - - - - - - - - - - -
StatusAvailable
Messages{analyzeStatus?.sentCount}
-
: - analyzeStatus?.sentCount > 0 ? - - - - - - - - - - - - - -
StatusProcessing
Messages{analyzeStatus?.sentCount}
Please allow a few minutes for the analysis to complete
-
: - - - - - - - - - - -
StatusWaiting for traffic
Messages{analyzeStatus?.sentCount}
-
- - return (
- -
- -
-
-
); -} diff --git a/ui/src/components/ServiceMapModal/ServiceMapModal.sass b/ui/src/components/ServiceMapModal/ServiceMapModal.sass deleted file mode 100644 index 973bbee11..000000000 --- a/ui/src/components/ServiceMapModal/ServiceMapModal.sass +++ /dev/null @@ -1,31 +0,0 @@ -@import "../../variables.module" - -.legend-scale ul - margin-top: -29px - margin-left: -27px - padding: 0 - float: left - list-style: none - - li - display: block - float: left - width: 50px - margin-bottom: 6px - text-align: center - font-size: 80% - list-style: none - -ul.legend-labels li span - display: block - float: left - height: 15px - width: 50px - -.legend-source - font-size: 70% - color: #999 - clear: both - -a - color: #777 \ No newline at end of file diff --git a/ui/src/components/ServiceMapModal/ServiceMapModal.tsx b/ui/src/components/ServiceMapModal/ServiceMapModal.tsx deleted file mode 100644 index f120ce5b5..000000000 --- a/ui/src/components/ServiceMapModal/ServiceMapModal.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import React, { useState, useEffect, useCallback } from "react"; -import { Box, Fade, Modal, Backdrop, Button } from "@material-ui/core"; -import { toast } from "react-toastify"; -import Api from "../../helpers/api"; -import spinnerStyle from '../style/Spinner.module.sass'; -import './ServiceMapModal.sass'; -import spinnerImg from '../assets/spinner.svg'; -import Graph from "react-graph-vis"; -import debounce from 'lodash/debounce'; -import ServiceMapOptions from './ServiceMapOptions' -import { useCommonStyles } from "../../helpers/commonStyle"; -import refresh from "../assets/refresh.svg"; -import close from "../assets/close.svg"; -import { TOAST_CONTAINER_ID } from "../../consts"; - -interface GraphData { - nodes: Node[]; - edges: Edge[]; -} - -interface Node { - id: number; - value: number; - label: string; - title?: string; - color?: object; -} - -interface Edge { - from: number; - to: number; - value: number; - label: string; - title?: string; - color?: object; - font?: object; -} - -interface ServiceMapNode { - id: number; - name: string; - entry: Entry; - count: number; -} - -interface ServiceMapEdge { - source: ServiceMapNode; - destination: ServiceMapNode; - count: number; - protocol: Protocol; -} - -interface ServiceMapGraph { - nodes: ServiceMapNode[]; - edges: ServiceMapEdge[]; -} - -interface Entry { - ip: string; - port: string; - name: string; -} - -interface Protocol { - name: string; - abbr: string; - macro: string; - version: string; - backgroundColor: string; - foregroundColor: string; - fontSize: number; - referenceLink: string; - ports: string[]; - priority: number; -} - -interface ServiceMapModalProps { - isOpen: boolean; - onOpen: () => void; - onClose: () => void; -} - -const modalStyle = { - position: 'absolute', - top: '6%', - left: '50%', - transform: 'translate(-50%, 0%)', - width: '89vw', - height: '82vh', - bgcolor: 'background.paper', - borderRadius: '5px', - boxShadow: 24, - p: 4, - color: '#000', -}; - -const api = Api.getInstance(); - -export const ServiceMapModal: React.FC = ({ isOpen, onOpen, onClose }) => { - const commonClasses = useCommonStyles(); - const [isLoading, setIsLoading] = useState(true); - const [graphData, setGraphData] = useState({ nodes: [], edges: [] }); - const [graphOptions, setGraphOptions] = useState(ServiceMapOptions); - - const getServiceMapData = useCallback(async () => { - try { - setIsLoading(true) - - const serviceMapData: ServiceMapGraph = await api.serviceMapData() - const newGraphData: GraphData = { nodes: [], edges: [] } - - if (serviceMapData.nodes) { - newGraphData.nodes = serviceMapData.nodes.map(node => { - return { - id: node.id, - value: node.count, - label: (node.entry.name === "unresolved") ? node.name : `${node.entry.name} (${node.name})`, - title: "Count: " + node.name, - } - }) - } - - if (serviceMapData.edges) { - newGraphData.edges = serviceMapData.edges.map(edge => { - return { - from: edge.source.id, - to: edge.destination.id, - value: edge.count, - label: edge.count.toString(), - color: { - color: edge.protocol.backgroundColor, - highlight: edge.protocol.backgroundColor - }, - font: { - color: edge.protocol.backgroundColor, - strokeColor: edge.protocol.backgroundColor - }, - } - }) - } - - setGraphData(newGraphData) - - } catch (ex) { - toast.error("An error occurred while loading Mizu Service Map, see console for mode details", { containerId: TOAST_CONTAINER_ID }); - console.error(ex); - } finally { - setIsLoading(false) - } - // eslint-disable-next-line - }, [isOpen]) - - useEffect(() => { - if(graphData?.nodes?.length === 0) return; - let options = {...graphOptions}; - options.physics.barnesHut.avoidOverlap = graphData?.nodes?.length > 10 ? 0 : 1; - setGraphOptions(options); - // eslint-disable-next-line - },[graphData?.nodes?.length]) - - useEffect(() => { - getServiceMapData(); - return () => setGraphData({ nodes: [], edges: [] }) - }, [getServiceMapData]) - - const refreshServiceMap = debounce(() => { - getServiceMapData(); - }, 500); - - return ( - - - - {isLoading &&
- spinner -
} - {!isLoading &&
-
-
- -
- close onClose()} style={{ cursor: "pointer" }}/> -
- -
-
    -
  • HTTP
  • -
  • HTTP/2
  • -
  • gRPC
  • -
  • AMQP
  • -
  • KAFKA
  • -
  • REDIS
  • -
-
-
} -
-
-
- ); - -} diff --git a/ui/src/components/ServiceMapModal/ServiceMapOptions.ts b/ui/src/components/ServiceMapModal/ServiceMapOptions.ts deleted file mode 100644 index b525f66dc..000000000 --- a/ui/src/components/ServiceMapModal/ServiceMapOptions.ts +++ /dev/null @@ -1,174 +0,0 @@ - -const minNodeScaling = 10 -const maxNodeScaling = 30 - -const minEdgeScaling = 1 -const maxEdgeScaling = maxNodeScaling / 2 - -const minLabelScaling = 11 -const maxLabelScaling = 16 -const selectedNodeColor = "#0C0B1A" -const selectedNodeBorderColor = "#205CF5" -const selectedNodeLabelColor = "#205CF5" -const selectedEdgeLabelColor = "#205CF5" - -const customScaling = (min, max, total, value) => { - if (max === min) { - return 0.5; - } - else { - const scale = 1 / (max - min); - return Math.max(0, (value - min) * scale); - } -} - -const nodeSelected = (values, id, selected, hovering) => { - values.color = selectedNodeColor; - values.borderColor = selectedNodeBorderColor; - values.borderWidth = 4; -} - -const nodeLabelSelected = (values, id, selected, hovering) => { - values.size = values.size + 1; - values.color = selectedNodeLabelColor; - values.strokeColor = selectedNodeLabelColor; - values.strokeWidth = 0.2 -} - -const edgeSelected = (values, id, selected, hovering) => { - values.opacity = 0.4; - values.width = values.width + 1; -} - -const edgeLabelSelected = (values, id, selected, hovering) => { - values.size = values.size + 1; - values.color = selectedEdgeLabelColor; - values.strokeColor = selectedEdgeLabelColor; - values.strokeWidth = 0.2 -} - -const nodeOptions = { - shape: 'dot', - chosen: { - node: nodeSelected, - label: nodeLabelSelected, - }, - color: { - background: '#494677', - border: selectedNodeColor, - }, - font: { - color: selectedNodeColor, - size: 11, // px - face: 'Roboto', - background: '#FFFFFFBF', - strokeWidth: 0.2, // px - strokeColor: selectedNodeColor, - align: 'center', - multi: false, - }, - // defines the node min and max sizes when zoom in/out, based on the node value - scaling: { - min: minNodeScaling, - max: maxNodeScaling, - // defines the label scaling size in px - label: { - enabled: true, - min: minLabelScaling, - max: maxLabelScaling, - maxVisible: maxLabelScaling, - drawThreshold: 5, - }, - customScalingFunction: customScaling, - }, - borderWidth: 2, - labelHighlightBold: true, - opacity: 1, - shadow: true, -} - -const edgeOptions = { - chosen: { - edge: edgeSelected, - label: edgeLabelSelected, - }, - dashes: false, - arrowStrikethrough: false, - arrows: { - to: { - enabled: true, - }, - middle: { - enabled: false, - }, - from: { - enabled: false, - } - }, - smooth: { - enabled: true, - type: 'dynamic', - roundness: 1.0 - }, - font: { - color: '#000000', - size: 11, // px - face: 'Roboto', - background: '#FFFFFFCC', - strokeWidth: 0.2, // px - strokeColor: '#000000', - align: 'horizontal', - multi: false, - }, - scaling: { - min: minEdgeScaling, - max: maxEdgeScaling, - label: { - enabled: true, - min: minLabelScaling, - max: maxLabelScaling, - maxVisible: maxLabelScaling, - drawThreshold: 5 - }, - customScalingFunction: customScaling, - }, - labelHighlightBold: true, - selectionWidth: 1, - shadow: true, -} - -const ServiceMapOptions = { - physics: { - enabled: true, - solver: 'barnesHut', - barnesHut: { - theta: 0.5, - gravitationalConstant: -2000, - centralGravity: 0.4, - springLength: 180, - springConstant: 0.04, - damping: 0.2, - avoidOverlap: 0 - }, - }, - layout: { - hierarchical: false, - randomSeed: 1 // always on node 1 - }, - nodes: nodeOptions, - edges: edgeOptions, - autoResize: true, - interaction: { - selectable: true, - selectConnectedEdges: true, - multiselect: true, - dragNodes: true, - dragView: true, - hover: true, - hoverConnectedEdges: true, - zoomView: true, - zoomSpeed: 1, - }, -}; - -export default ServiceMapOptions diff --git a/ui/src/components/style/Spinner.module.sass b/ui/src/components/style/Spinner.module.sass deleted file mode 100644 index 3f689dc41..000000000 --- a/ui/src/components/style/Spinner.module.sass +++ /dev/null @@ -1,7 +0,0 @@ -@import "../../variables.module" - -.spinnerContainer - display: flex - justify-content: center - margin-bottom: 10px - \ No newline at end of file diff --git a/ui/src/configs/shortcutsKeyboard.ts b/ui/src/configs/shortcutsKeyboard.ts deleted file mode 100644 index b5f8fcad0..000000000 --- a/ui/src/configs/shortcutsKeyboard.ts +++ /dev/null @@ -1,7 +0,0 @@ -const dictionary = { - ctrlEnter : [{metaKey : true, code:"Enter"}, {ctrlKey:true, code:"Enter"}], // support Ctrl/command - enter : [{code:"Enter"}] -}; - - -export default dictionary; \ No newline at end of file diff --git a/ui/src/helpers/api.js b/ui/src/helpers/api.js index 1db29859f..73ff11691 100644 --- a/ui/src/helpers/api.js +++ b/ui/src/helpers/api.js @@ -25,21 +25,11 @@ export default class Api { source = null; } - serviceMapStatus = async () => { - const response = await client.get("/servicemap/status"); - return response.data; - } - serviceMapData = async () => { const response = await client.get(`/servicemap/get`); return response.data; } - serviceMapReset = async () => { - const response = await client.get(`/servicemap/reset`); - return response.data; - } - tapStatus = async () => { const response = await client.get("/status/tap"); return response.data; diff --git a/ui/src/helpers/routes.ts b/ui/src/helpers/routes.ts deleted file mode 100644 index ecd81c030..000000000 --- a/ui/src/helpers/routes.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum RouterRoutes { - LOGIN = "/login", - SETUP = "/setup", - SETTINGS = "/settings" -} diff --git a/ui/src/hooks/useKeyPress.ts b/ui/src/hooks/useKeyPress.ts deleted file mode 100644 index caf864eb3..000000000 --- a/ui/src/hooks/useKeyPress.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useCallback, useEffect, useLayoutEffect, useRef } from 'react'; - -const useKeyPress = (eventConfigs, callback, node = null) => { - // implement the callback ref pattern - const callbackRef = useRef(callback); - useLayoutEffect(() => { - callbackRef.current = callback; - }); - - // handle what happens on key press - const handleKeyPress = useCallback( - (event) => { - - // check if one of the key is part of the ones we want - if (eventConfigs.some((eventConfig) => Object.keys(eventConfig).every(nameKey => eventConfig[nameKey] === event[nameKey]))) { - event.stopPropagation() - event.preventDefault(); - callbackRef.current(event); - } - }, - [eventConfigs] - ); - - useEffect(() => { - // target is either the provided node or the document - const targetNode = node ?? document; - // attach the event listener - targetNode && - targetNode.addEventListener("keydown", handleKeyPress); - - // remove the event listener - return () => - targetNode && - targetNode.removeEventListener("keydown", handleKeyPress); - }, [handleKeyPress, node]); -}; - -export default useKeyPress; \ No newline at end of file diff --git a/ui/src/recoil/entPage/atom.ts b/ui/src/recoil/entPage/atom.ts deleted file mode 100644 index 03650db83..000000000 --- a/ui/src/recoil/entPage/atom.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { atom } from "recoil" - -const entPageAtom = atom({ - key: "entPageAtom", - default: 0 -}) - -export default entPageAtom diff --git a/ui/src/recoil/entPage/index.ts b/ui/src/recoil/entPage/index.ts deleted file mode 100644 index 900da75d1..000000000 --- a/ui/src/recoil/entPage/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import atom from "./atom"; - -enum Page { - Traffic, - Setup, - Login -} - -export { Page }; - -export default atom; diff --git a/ui/src/recoil/entries/atom.ts b/ui/src/recoil/entries/atom.ts deleted file mode 100644 index 3a12120c3..000000000 --- a/ui/src/recoil/entries/atom.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { atom } from "recoil"; - -const entriesAtom = atom({ - key: "entriesAtom", - default: [] -}); - -export default entriesAtom; diff --git a/ui/src/recoil/entries/index.ts b/ui/src/recoil/entries/index.ts deleted file mode 100644 index b97835b9f..000000000 --- a/ui/src/recoil/entries/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import atom from "./atom"; - -export default atom diff --git a/ui/src/recoil/tappingStatus/atom.ts b/ui/src/recoil/tappingStatus/atom.ts deleted file mode 100644 index e16c72747..000000000 --- a/ui/src/recoil/tappingStatus/atom.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { atom } from "recoil"; -import {TappingStatusPod} from "./index"; - -const tappingStatusAtom = atom({ - key: "tappingStatusAtom", - default: null as TappingStatusPod[] -}); - -export default tappingStatusAtom; diff --git a/ui/src/recoil/tappingStatus/details.ts b/ui/src/recoil/tappingStatus/details.ts deleted file mode 100644 index b387686f6..000000000 --- a/ui/src/recoil/tappingStatus/details.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {selector} from "recoil"; -import tappingStatusAtom from "./atom"; - -const tappingStatusDetails = selector({ - key: 'tappingStatusDetails', - get: ({get}) => { - const tappingStatus = get(tappingStatusAtom); - const uniqueNamespaces = Array.from(new Set(tappingStatus.map(pod => pod.namespace))); - const amountOfPods = tappingStatus.length; - const amountOfTappedPods = tappingStatus.filter(pod => pod.isTapped).length; - const amountOfUntappedPods = amountOfPods - amountOfTappedPods; - - return { - uniqueNamespaces, - amountOfPods, - amountOfTappedPods, - amountOfUntappedPods, - }; - }, -}); - -export default tappingStatusDetails; diff --git a/ui/src/recoil/tappingStatus/index.ts b/ui/src/recoil/tappingStatus/index.ts deleted file mode 100644 index 672aab9e1..000000000 --- a/ui/src/recoil/tappingStatus/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import atom from "./atom"; -import tappingStatusDetails from './details'; - -interface TappingStatusPod { - name: string; - namespace: string; - isTapped: boolean; -} - -interface TappingStatus { - pods: TappingStatusPod[]; -} - -export type {TappingStatus, TappingStatusPod}; -export {tappingStatusDetails}; - -export default atom;