Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

refactor topology #2229

Merged
merged 8 commits into from
Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
337 changes: 322 additions & 15 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions shell/app/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2138,6 +2138,7 @@
"error page": "error page",
"error rate": "error rate",
"error ratio": "error ratio",
"error request count": "error request count",
"error source": "error source",
"error stack": "error stack",
"error statistics": "error statistics",
Expand Down
1 change: 1 addition & 0 deletions shell/app/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -2138,6 +2138,7 @@
"error page": "出错页面",
"error rate": "错误率",
"error ratio": "错误比例",
"error request count": "错误请求次数",
"error source": "错误来源",
"error stack": "错误堆栈",
"error statistics": "错误统计",
Expand Down
35 changes: 29 additions & 6 deletions shell/app/modules/msp/env-overview/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,44 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import getTopologyRouter from 'msp/env-overview/topology/router';
import serviceListRouter from 'msp/env-overview/service-list/router';
import i18n from 'i18n';
import getGatewayIngressMonitorRouter from 'gateway-ingress/router';
import getEIRouter from 'external-insight/router';

const tabs = [
{
key: 'topology',
name: i18n.t('msp:topology'),
},
{
key: 'service-list',
name: i18n.t('msp:service list'),
},
];
const getEnvOverViewRouter = (): RouteConfigItem => {
return {
path: ':terminusKey',
tabs,
pageName: i18n.t('msp:global topology'),
breadcrumbName: i18n.t('msp:global topology'),
alwaysShowTabKey: 'topology',
routes: [
{
breadcrumbName: i18n.t('msp:global topology'),
layout: { fullHeight: true },
getComp: (cb) => cb(import('msp/pages/micro-service-overview')),
tabs,
alwaysShowTabKey: 'topology',
layout: { fullHeight: true, noWrapper: true },
getComp: (cb) => cb(import('msp/env-overview/topology/pages/topology')),
},
getTopologyRouter(),
serviceListRouter(),
{
path: 'topology',
tabs,
alwaysShowTabKey: 'topology',
layout: { fullHeight: true, noWrapper: true },
getComp: (cb) => cb(import('msp/env-overview/topology/pages/topology')),
routes: [getGatewayIngressMonitorRouter(), getEIRouter()],
},
serviceListRouter(tabs),
],
};
};
Expand Down
6 changes: 4 additions & 2 deletions shell/app/modules/msp/env-overview/service-list/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ const serviceAnalysisRoutes = [
},
];

export default () => ({
export default (pageTabs) => ({
path: 'service-list',
breadcrumbName: i18n.t('msp:service list'),
tabs: pageTabs,
alwaysShowTabKey: 'service-list',
routes: [
{
path: ':applicationId',
breadcrumbName: i18n.t('msp:service list'),
routes: [
{
path: ':serviceId',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2021 Terminus, Inc.
//
// This program is free software: you can use, redistribute, and/or modify
// it under the terms of the GNU Affero General Public License, version 3
// or later ("AGPL"), as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import React from 'react';

import { ConnectionLineComponentProps, getBezierPath, Node } from 'react-flow-renderer';

import { getEdgeParams } from './utils';

const FloatingConnectionLine: React.FC<ConnectionLineComponentProps> = ({
targetX,
targetY,
sourcePosition,
targetPosition,
sourceNode,
}) => {
if (!sourceNode) {
return null;
}

const targetNode = {
id: 'connection-target',
__rf: { width: 1, height: 1, position: { x: targetX, y: targetY } },
} as Node;

const { sx, sy } = getEdgeParams(sourceNode, targetNode);
const d = getBezierPath({
sourceX: sx,
sourceY: sy,
sourcePosition,
targetPosition,
targetX,
targetY,
});

return (
<g>
<path fill="none" stroke="#f00" strokeWidth={1.5} className="animated" d={d} />
<circle cx={targetX} cy={targetY} fill="#fff" r={3} stroke="#f00" strokeWidth={1.5} />
</g>
);
};

export default FloatingConnectionLine;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) 2021 Terminus, Inc.
//
// This program is free software: you can use, redistribute, and/or modify
// it under the terms of the GNU Affero General Public License, version 3
// or later ("AGPL"), as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import React, { CSSProperties, FC, useMemo } from 'react';
import { EdgeProps, getBezierPath, getMarkerEnd, useStoreState } from 'react-flow-renderer';

import { getEdgeParams } from './utils';

const FloatingEdge: FC<EdgeProps> = ({ id, source, target, arrowHeadType, markerEndId, style }) => {
const nodes = useStoreState((state) => state.nodes);
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);

const sourceNode = useMemo(() => nodes.find((n) => n.id === source), [source, nodes]);
const targetNode = useMemo(() => nodes.find((n) => n.id === target), [target, nodes]);

if (!sourceNode || !targetNode) {
return null;
}

const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);

const d = getBezierPath({
sourceX: sx,
sourceY: sy,
sourcePosition: sourcePos,
targetPosition: targetPos,
targetX: tx,
targetY: ty,
});

return (
<g className="react-flow__connection">
<path id={id} className="react-flow__edge-path" d={d} markerEnd={markerEnd} style={style as CSSProperties} />
</g>
);
};
export default FloatingEdge;
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) 2021 Terminus, Inc.
//
// This program is free software: you can use, redistribute, and/or modify
// it under the terms of the GNU Affero General Public License, version 3
// or later ("AGPL"), as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import { ArrowHeadType, Node, Position, XYPosition } from 'react-flow-renderer';

// this helper function returns the intersection point
// of the line between the center of the intersectionNode and the target node
function getNodeIntersection(intersectionNode: Node, targetNode: Node): XYPosition {
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
const {
width: intersectionNodeWidth,
height: intersectionNodeHeight,
position: intersectionNodePosition,
} = intersectionNode.__rf;
const targetPosition = targetNode.__rf.position;

const w = intersectionNodeWidth / 2;
const h = intersectionNodeHeight / 2;

const x2 = intersectionNodePosition.x + w;
const y2 = intersectionNodePosition.y + h;
const x1 = targetPosition.x + w;
const y1 = targetPosition.y + h;

const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h);
const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h);
const a = 1 / (Math.abs(xx1) + Math.abs(yy1));
const xx3 = a * xx1;
const yy3 = a * yy1;
const x = w * (xx3 + yy3) + x2;
const y = h * (-xx3 + yy3) + y2;

return { x, y };
}

// returns the position (top,right,bottom or right) passed node compared to the intersection point
function getEdgePosition(node: Node, intersectionPoint: XYPosition) {
const n = { ...node.__rf.position, ...node.__rf };
const nx = Math.round(n.x);
const ny = Math.round(n.y);
const px = Math.round(intersectionPoint.x);
const py = Math.round(intersectionPoint.y);

if (px <= nx + 1) {
return Position.Left;
}
if (px >= nx + n.width - 1) {
return Position.Right;
}
if (py <= ny + 1) {
return Position.Top;
}
if (py >= n.y + n.height - 1) {
return Position.Bottom;
}

return Position.Top;
}

// returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
export function getEdgeParams(source: Node, target: Node) {
const sourceIntersectionPoint = getNodeIntersection(source, target);
const targetIntersectionPoint = getNodeIntersection(target, source);

const sourcePos = getEdgePosition(source, sourceIntersectionPoint);
const targetPos = getEdgePosition(target, targetIntersectionPoint);

return {
sx: sourceIntersectionPoint.x,
sy: sourceIntersectionPoint.y,
tx: targetIntersectionPoint.x,
ty: targetIntersectionPoint.y,
sourcePos,
targetPos,
};
}

export function createElements() {
const elements = [];
const center = { x: window.innerWidth / 2, y: window.innerHeight / 2 };

elements.push({ id: 'target', data: { label: 'Target' }, position: center });

for (let i = 0; i < 8; i++) {
const degrees = i * (360 / 8);
const radians = degrees * (Math.PI / 180);
const x = 250 * Math.cos(radians) + center.x;
const y = 250 * Math.sin(radians) + center.y;

elements.push({ id: `${i}`, data: { label: 'Source' }, position: { x, y } });

elements.push({
id: `edge-${i}`,
target: 'target',
source: `${i}`,
type: 'floating',
arrowHeadType: ArrowHeadType.Arrow,
});
}

return elements;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) 2021 Terminus, Inc.
//
// This program is free software: you can use, redistribute, and/or modify
// it under the terms of the GNU Affero General Public License, version 3
// or later ("AGPL"), as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import React from 'react';
import CommonNode from './common-node';
import Hexagon from '../progress/hexagon';
import ErdaIcon from 'common/components/erda-icon';
import { NodeProps } from 'react-flow-renderer';
import './index.scss';

const iconMap = {
Mysql: 'mysql',
RocketMQ: 'RocketMQ',
Redis: 'redis',
default: 'morenzhongjianjian',
};
const AddonNode: React.FC<NodeProps<TOPOLOGY.TopoNode>> = (props) => {
return (
<CommonNode {...props}>
{(data: TOPOLOGY.TopoNode['metaData']) => {
const { error_rate, count } = data.metric;
const iconType = iconMap[data.type] ?? iconMap.default;
return (
<div className="addon-node">
<Hexagon stroke={['#798CF1', '#D84B65']} width={60} strokeWidth={2} percent={error_rate}>
<div className="h-full">
<div className="text-white mt-4 text-center">{count}</div>
<div className="mt-1.5 text-center text-darkgray">
<ErdaIcon type={iconType} color="currentColor" size={22} />
</div>
</div>
</Hexagon>
</div>
);
}}
</CommonNode>
);
};

export default AddonNode;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) 2021 Terminus, Inc.
//
// This program is free software: you can use, redistribute, and/or modify
// it under the terms of the GNU Affero General Public License, version 3
// or later ("AGPL"), as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import React from 'react';
import CommonNode from 'msp/env-overview/topology/pages/topology/component/nodes/common-node';
import { NodeProps } from 'react-flow-renderer';
import Circular from 'msp/env-overview/topology/pages/topology/component/progress/circular';

const ApiGatewayNode: React.FC<NodeProps<TOPOLOGY.TopoNode>> = (props) => {
return (
<CommonNode {...props}>
{(data: TOPOLOGY.TopoNode['metaData']) => {
const { error_rate, count } = data.metric;
return (
<div className="api-gateway-node service-node">
<Circular stroke={['#798CF1', '#D84B65']} width={60} strokeWidth={4} percent={error_rate}>
<div className="h-full flex justify-center items-center">
<div className="count flex justify-center items-center">
<div>{count}</div>
</div>
</div>
</Circular>
<div className="service-name p-1 text-white absolute overflow-ellipsis overflow-hidden whitespace-nowrap w-28 text-center rounded-sm">
{data.name}
</div>
</div>
);
}}
</CommonNode>
);
};

export default ApiGatewayNode;
Loading