Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan
- [#59](https://github.com/kobsio/kobs/pull/59): Add support for Templates via the new Templates CRD. Templates allows a user to reuse plugin definitions accross Applications, Teams and Kubernetes resources.
- [#60](https://github.com/kobsio/kobs/pull/60): Add support for additional Pod annotations and labels in the Helm chart via the new `podAnnotations` and `podLabels` values.
- [#63](https://github.com/kobsio/kobs/pull/63): Add Kiali plugin (in the current version the Kiali plugin only supports the graph feature from Kiali).
- [#66](https://github.com/kobsio/kobs/pull/66): Add edge metrics for Kiali plugin.

### Fixed

Expand Down
4 changes: 3 additions & 1 deletion app/src/components/applications/applications.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* kobsio-application-topology
* The kobsio-application-topology classes are used for the node labels in the topology graph. */
.kobsio-application-topology-label {
border-radius: 3px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 2px 8px 0 rgba(0, 0, 0, 0.19);
Expand Down Expand Up @@ -31,7 +33,7 @@
}

.kobsio-application-topology-label-badge {
background-color: var(--pf-global--palette--blue-200);
background-color: var(--pf-global--primary-color--100);
margin-right: 5px;
min-width: 24px;
padding-left: 0px;
Expand Down
85 changes: 85 additions & 0 deletions app/src/plugins/kiali/KialiChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
Chart,
ChartArea,
ChartAxis,
ChartGroup,
ChartLegendTooltip,
ChartThemeColor,
createContainer,
} from '@patternfly/react-charts';
import React, { useEffect, useRef, useState } from 'react';

import { Data, Metric } from 'proto/kiali_grpc_web_pb';
import { formatTime } from 'utils/helpers';

interface ILabels {
datum: Data.AsObject;
}

export interface IKialiChartProps {
unit: string;
metrics: Metric.AsObject[];
}

const KialiChart: React.FunctionComponent<IKialiChartProps> = ({ unit, metrics }: IKialiChartProps) => {
const refChart = useRef<HTMLDivElement>(null);
const [width, setWidth] = useState<number>(0);
const [height, setHeight] = useState<number>(0);

// useEffect is executed on every render of this component. This is needed, so that we are able to use a width of 100%
// and a static height for the chart.
useEffect(() => {
if (refChart && refChart.current) {
setWidth(refChart.current.getBoundingClientRect().width);
setHeight(refChart.current.getBoundingClientRect().height);
}
}, []);

// In the following we are creating the container for the cursor container, we are generating the data for the legend
// and we are creating the series component for each metric.
const CursorVoronoiContainer = createContainer('voronoi', 'cursor');
const legendData = metrics.map((metric, index) => ({
childName: `index${index}`,
name: metric.stat ? metric.stat : metric.name,
}));
const series = metrics.map((metric, index) => (
<ChartArea key={index} data={metric.dataList} interpolation="monotoneX" name={`index${index}`} />
));

return (
<React.Fragment>
<div style={{ height: '260px', width: '100%' }} ref={refChart}>
<Chart
containerComponent={
<CursorVoronoiContainer
cursorDimension="x"
labels={({ datum }: ILabels): string | null => (datum.y ? `${datum.y} ${unit}` : null)}
labelComponent={
<ChartLegendTooltip
legendData={legendData}
title={(point: Data.AsObject): string => formatTime(Math.floor(point.x / 1000))}
/>
}
mouseFollowTooltips
voronoiDimension="x"
voronoiPadding={{ bottom: 60, left: 60, right: 0, top: 0 }}
/>
}
height={height}
legendData={legendData}
legendPosition="bottom"
padding={{ bottom: 60, left: 60, right: 0, top: 0 }}
scale={{ x: 'time', y: 'linear' }}
themeColor={ChartThemeColor.multiOrdered}
width={width}
>
<ChartAxis dependentAxis={false} showGrid={false} />
<ChartAxis dependentAxis={true} showGrid={true} label={unit} />
<ChartGroup>{series}</ChartGroup>
</Chart>
</div>
</React.Fragment>
);
};

export default KialiChart;
119 changes: 119 additions & 0 deletions app/src/plugins/kiali/KialiDetailsEdge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import {
Badge,
DrawerActions,
DrawerCloseButton,
DrawerHead,
DrawerPanelBody,
DrawerPanelContent,
Tab,
TabTitleText,
Tabs,
} from '@patternfly/react-core';
import React, { useState } from 'react';

import { Edge, NodeWrapper } from 'proto/kiali_grpc_web_pb';
import KialiDetailsEdgeFlags from 'plugins/kiali/KialiDetailsEdgeFlags';
import KialiDetailsEdgeHosts from 'plugins/kiali/KialiDetailsEdgeHosts';
import KialiDetailsEdgeMetrics from 'plugins/kiali/KialiDetailsEdgeMetrics';
import KialiDetailsEdgeTrafficGRPC from 'plugins/kiali/KialiDetailsEdgeTrafficGRPC';
import KialiDetailsEdgeTrafficHTTP from 'plugins/kiali/KialiDetailsEdgeTrafficHTTP';
import { getTitle } from 'plugins/kiali/helpers';

interface IKialiDetailsEdgeProps {
name: string;
duration: number;
edge: Edge.AsObject;
nodes: NodeWrapper.AsObject[];
close: () => void;
}

// KialiDetailsEdge is used as the drawer panel component to display the details about a selected edge.
const KialiDetailsEdge: React.FunctionComponent<IKialiDetailsEdgeProps> = ({
name,
duration,
edge,
nodes,
close,
}: IKialiDetailsEdgeProps) => {
const [activeTab, setActiveTab] = useState<string>(
edge.traffic?.protocol === 'http' ? 'trafficHTTP' : edge.traffic?.protocol === 'grpc' ? 'trafficGRPC' : 'flags',
);

// To display the edge details like metrics, we have to get the source and target node of the edge. After that we,
// generate the title for both nodes and display them in the format "From: ... To: ...".
const sourceNode = nodes.filter((node) => node.data?.id === edge.source);
const sourceTitle =
sourceNode.length === 1 && sourceNode[0].data ? getTitle(sourceNode[0].data) : { badge: 'U', title: 'Unknown' };
const targetNode = nodes.filter((node) => node.data?.id === edge.target);
const targetTitle =
targetNode.length === 1 && targetNode[0].data ? getTitle(targetNode[0].data) : { badge: 'U', title: 'Unknown' };

return (
<DrawerPanelContent minSize="50%">
<DrawerHead>
<span>
<span className="pf-u-font-size-sm pf-u-color-400">From:</span>
<span className="pf-u-pl-sm pf-c-title pf-m-lg">
<Badge isRead={false}>{sourceTitle.badge}</Badge>
<span className="pf-u-pl-sm">{sourceTitle.title}</span>
</span>
<span className="pf-u-pl-lg pf-u-font-size-sm pf-u-color-400">To:</span>
<span className="pf-u-pl-sm pf-c-title pf-m-lg">
<Badge isRead={false}>{targetTitle.badge}</Badge>
<span className="pf-u-pl-sm">{targetTitle.title}</span>
</span>
</span>
<DrawerActions style={{ padding: 0 }}>
<DrawerCloseButton onClose={close} />
</DrawerActions>
</DrawerHead>

<DrawerPanelBody>
<Tabs
activeKey={activeTab}
onSelect={(event, tabIndex): void => setActiveTab(tabIndex.toString())}
className="pf-u-mt-md"
isFilled={true}
mountOnEnter={true}
>
{edge.traffic?.protocol === 'http' ? (
<Tab eventKey="trafficHTTP" title={<TabTitleText>Traffic</TabTitleText>}>
<div style={{ maxWidth: '100%', overflowX: 'scroll', padding: '24px 24px' }}>
<KialiDetailsEdgeTrafficHTTP edge={edge} />
</div>
</Tab>
) : null}
{edge.traffic?.protocol === 'grpc' ? (
<Tab eventKey="trafficGRPC" title={<TabTitleText>Traffic</TabTitleText>}>
<div style={{ maxWidth: '100%', overflowX: 'scroll', padding: '24px 24px' }}>
<KialiDetailsEdgeTrafficGRPC edge={edge} />
</div>
</Tab>
) : null}
<Tab eventKey="flags" title={<TabTitleText>Flags</TabTitleText>}>
<div style={{ maxWidth: '100%', overflowX: 'scroll', padding: '24px 24px' }}>
<KialiDetailsEdgeFlags edge={edge} />
</div>
</Tab>
<Tab eventKey="hosts" title={<TabTitleText>Hosts</TabTitleText>}>
<div style={{ maxWidth: '100%', overflowX: 'scroll', padding: '24px 24px' }}>
<KialiDetailsEdgeHosts edge={edge} />
</div>
</Tab>
</Tabs>

{sourceNode.length === 1 && sourceNode[0].data && targetNode.length === 1 && targetNode[0].data ? (
<KialiDetailsEdgeMetrics
name={name}
duration={duration}
edge={edge}
sourceNode={sourceNode[0].data}
targetNode={targetNode[0].data}
/>
) : null}
</DrawerPanelBody>
</DrawerPanelContent>
);
};

export default KialiDetailsEdge;
45 changes: 45 additions & 0 deletions app/src/plugins/kiali/KialiDetailsEdgeFlags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Card, CardBody, CardTitle } from '@patternfly/react-core';
import { TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import React from 'react';

import { Edge } from 'proto/kiali_grpc_web_pb';

interface IKialiDetailsEdgeFlagsProps {
edge: Edge.AsObject;
}

// KialiDetailsEdgeFlags is the tab content for the flags tab of an edge. It is used to display a table of the responses
// grouped by the flag.
const KialiDetailsEdgeFlags: React.FunctionComponent<IKialiDetailsEdgeFlagsProps> = ({
edge,
}: IKialiDetailsEdgeFlagsProps) => {
return (
<Card isCompact={true}>
<CardTitle>Response flags by HTTP code</CardTitle>
<CardBody>
<TableComposable aria-label="Flags" variant={TableVariant.compact} borders={false}>
<Thead>
<Tr>
<Th>Code</Th>
<Th>Flags</Th>
<Th>Req (%)</Th>
</Tr>
</Thead>
<Tbody>
{edge.traffic?.responsesMap.map((response, i) =>
response[1].flagsMap.map((flag, j) => (
<Tr key={`${i}_${j}`}>
<Td dataLabel="Code">{response[0]}</Td>
<Td dataLabel="Flags">{flag[0]}</Td>
<Td dataLabel="Req (%)">{flag[1]}</Td>
</Tr>
)),
)}
</Tbody>
</TableComposable>
</CardBody>
</Card>
);
};

export default KialiDetailsEdgeFlags;
45 changes: 45 additions & 0 deletions app/src/plugins/kiali/KialiDetailsEdgeHosts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Card, CardBody, CardTitle } from '@patternfly/react-core';
import { TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import React from 'react';

import { Edge } from 'proto/kiali_grpc_web_pb';

interface IKialiDetailsEdgeHostsProps {
edge: Edge.AsObject;
}

// KialiDetailsEdgeHosts is the tab content for the hosts tab of an edge. It is used to display a table of the responses
// grouped by the host.
const KialiDetailsEdgeHosts: React.FunctionComponent<IKialiDetailsEdgeHostsProps> = ({
edge,
}: IKialiDetailsEdgeHostsProps) => {
return (
<Card isCompact={true}>
<CardTitle>Hosts by HTTP code</CardTitle>
<CardBody>
<TableComposable aria-label="Hosts" variant={TableVariant.compact} borders={false}>
<Thead>
<Tr>
<Th>Code</Th>
<Th>Host</Th>
<Th>Req (%)</Th>
</Tr>
</Thead>
<Tbody>
{edge.traffic?.responsesMap.map((response, i) =>
response[1].hostsMap.map((host, j) => (
<Tr key={`${i}_${j}`}>
<Td dataLabel="Code">{response[0]}</Td>
<Td dataLabel="Host">{host[0]}</Td>
<Td dataLabel="Req (%)">{host[1]}</Td>
</Tr>
)),
)}
</Tbody>
</TableComposable>
</CardBody>
</Card>
);
};

export default KialiDetailsEdgeHosts;
57 changes: 57 additions & 0 deletions app/src/plugins/kiali/KialiDetailsEdgeMetrics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';

import { Edge, Node } from 'proto/kiali_grpc_web_pb';
import KialiDetailsEdgeMetricsGRPC from 'plugins/kiali/KialiDetailsEdgeMetricsGRPC';
import KialiDetailsEdgeMetricsHTTP from 'plugins/kiali/KialiDetailsEdgeMetricsHTTP';
import KialiDetailsEdgeMetricsTCP from 'plugins/kiali/KialiDetailsEdgeMetricsTCP';

interface IKialiDetailsEdgeMetricsProps {
name: string;
duration: number;
edge: Edge.AsObject;
sourceNode: Node.AsObject;
targetNode: Node.AsObject;
}

// KialiDetailsEdgeMetrics is a wrapper component for the metrics of an edge. We use this component to decide if we have
// to display the http, tcp or grpc metrics for the edge.
// This component is displayed for all tabs, below the actual tab content.
const KialiDetailsEdgeMetrics: React.FunctionComponent<IKialiDetailsEdgeMetricsProps> = ({
name,
duration,
edge,
sourceNode,
targetNode,
}: IKialiDetailsEdgeMetricsProps) => {
return (
<div style={{ maxWidth: '100%', overflowX: 'scroll', padding: '24px 24px' }}>
{edge.traffic?.protocol === 'tcp' ? (
<KialiDetailsEdgeMetricsTCP
name={name}
duration={duration}
edge={edge}
sourceNode={sourceNode}
targetNode={targetNode}
/>
) : edge.traffic?.protocol === 'http' ? (
<KialiDetailsEdgeMetricsHTTP
name={name}
duration={duration}
edge={edge}
sourceNode={sourceNode}
targetNode={targetNode}
/>
) : edge.traffic?.protocol === 'grpc' ? (
<KialiDetailsEdgeMetricsGRPC
name={name}
duration={duration}
edge={edge}
sourceNode={sourceNode}
targetNode={targetNode}
/>
) : null}
</div>
);
};

export default KialiDetailsEdgeMetrics;
Loading