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 @@ -15,6 +15,7 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan
- [#8](https://github.com/kobsio/kobs/pull/8): Add new page to directly query a configured Prometheus datasource.
- [#10](https://github.com/kobsio/kobs/pull/10): Add Elasticsearch as datasource for Application logs.
- [#12](https://github.com/kobsio/kobs/pull/12): :warning: *Breaking change:* :warning: Add plugin system and readd Prometheus and Elasticsearch as plugins.
- [#13](https://github.com/kobsio/kobs/pull/13): Add Jaeger plugin to show traces for an Application and to compare traces.

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const App: React.FunctionComponent = () => {
<Route exact={true} path="/applications" component={Applications} />
<Route exact={true} path="/applications/:cluster/:namespace/:name" component={Application} />
<Route exact={true} path="/resources" component={Resources} />
<Route exact={true} path="/plugins/:name" component={Plugins} />
<Route exact={false} path="/plugins/:name" component={Plugins} />
</Switch>
</Page>
</Router>
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/Title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const Title: React.FunctionComponent<ITitleProps> = ({ title, subtitle, size }:
return (
<span>
<span className={`pf-c-title pf-m-${size}`}>{title}</span>
<span className="pf-u-pl-sm pf-u-font-size-sm pf-u-color-200">{subtitle}</span>
<span className="pf-u-pl-sm pf-u-font-size-sm pf-u-color-400">{subtitle}</span>
</span>
);
};
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/applications/ApplicationTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Tab, TabTitleText, Tabs } from '@patternfly/react-core';
import React from 'react';

import { Plugin } from 'proto/plugins_pb';
import { Plugin } from 'proto/plugins_grpc_web_pb';

interface IApplicationTabsProps {
activeTab: string;
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/plugins/Plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Alert, AlertVariant } from '@patternfly/react-core';
import React, { useContext } from 'react';

import { IPluginsContext, PluginsContext } from 'context/PluginsContext';
import { Plugin as IPlugin } from 'proto/plugins_pb';
import { Plugin as IPlugin } from 'proto/plugins_grpc_web_pb';
import { plugins } from 'utils/plugins';

interface IPluginProps {
Expand Down
13 changes: 8 additions & 5 deletions app/src/components/plugins/PluginDataMissing.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Button, EmptyState, EmptyStateBody, EmptyStateIcon, Title } from '@patternfly/react-core';
import { Brand, Button, EmptyState, EmptyStateBody, EmptyStateIcon, Title } from '@patternfly/react-core';
import React from 'react';

interface IPluginDataMissingProps {
title: string;
description: string;
documentation: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
icon: React.ComponentType<any>;
type: string;
}

// PluginDataMissing is the component, which is displayed when the user defines a plugin in an Application CR, but the
Expand All @@ -15,12 +14,16 @@ interface IPluginDataMissingProps {
const PluginDataMissing: React.FunctionComponent<IPluginDataMissingProps> = ({
title,
description,
icon,
documentation,
type,
}: IPluginDataMissingProps) => {
const PluginIcon: React.FunctionComponent = () => {
return <Brand src={`/img/plugins/${type}.png`} alt="Logo" width="128px" />;
};

return (
<EmptyState>
<EmptyStateIcon icon={icon} />
<EmptyStateIcon variant="container" component={PluginIcon} />
<Title headingLevel="h4" size="lg">
{title}
</Title>
Expand Down
3 changes: 1 addition & 2 deletions app/src/components/resources/ToolbarItemNamespaces.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';

import { GetNamespacesRequest, GetNamespacesResponse } from 'proto/clusters_pb';
import { ClustersPromiseClient } from 'proto/clusters_grpc_web_pb';
import { ClustersPromiseClient, GetNamespacesRequest, GetNamespacesResponse } from 'proto/clusters_grpc_web_pb';
import { apiURL } from 'utils/constants';

// clustersService is the Clusters gRPC service, which is used to get all namespaces for the selected clusters.
Expand Down
9 changes: 7 additions & 2 deletions app/src/context/ClustersContext.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Alert, AlertActionLink, AlertVariant, Spinner } from '@patternfly/react-core';
import React, { useCallback, useEffect, useState } from 'react';

import { GetCRDsRequest, GetCRDsResponse, GetClustersRequest, GetClustersResponse } from 'proto/clusters_pb';
import {
ClustersPromiseClient,
GetCRDsRequest,
GetCRDsResponse,
GetClustersRequest,
GetClustersResponse,
} from 'proto/clusters_grpc_web_pb';
import { IResources, customResourceDefinition } from 'utils/resources';
import { ClustersPromiseClient } from 'proto/clusters_grpc_web_pb';
import { apiURL } from 'utils/constants';

// clustersService is the Clusters gRPC service, which is used to get all clusters and Custom Resource Definitions.
Expand Down
3 changes: 1 addition & 2 deletions app/src/context/PluginsContext.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Alert, AlertActionLink, AlertVariant, Spinner } from '@patternfly/react-core';
import React, { useCallback, useEffect, useState } from 'react';

import { GetPluginsRequest, GetPluginsResponse, PluginShort } from 'proto/plugins_pb';
import { PluginsPromiseClient } from 'proto/plugins_grpc_web_pb';
import { GetPluginsRequest, GetPluginsResponse, PluginShort, PluginsPromiseClient } from 'proto/plugins_grpc_web_pb';
import { apiURL } from 'utils/constants';

// pluginsService is the Plugins gRPC service, which is used to get all configured plugins.
Expand Down
3 changes: 1 addition & 2 deletions app/src/plugins/elasticsearch/ElasticsearchPlugin.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useState } from 'react';
import ListIcon from '@patternfly/react-icons/dist/js/icons/list-icon';

import ElasticsearchLogs from 'plugins/elasticsearch/ElasticsearchLogs';
import ElasticsearchPluginToolbar from 'plugins/elasticsearch/ElasticsearchPluginToolbar';
Expand Down Expand Up @@ -45,7 +44,7 @@ const ElasticsearchPlugin: React.FunctionComponent<IPluginProps> = ({
title="Elasticsearch properties are missing"
description="The Elasticsearch properties are missing in your CR for this application. Visit the documentation to learn more on how to use the Elasticsearch plugin in an Application CR."
documentation="https://kobs.io"
icon={ListIcon}
type="elasticsearch"
/>
);
}
Expand Down
23 changes: 23 additions & 0 deletions app/src/plugins/jaeger/JaegerPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Route, Switch } from 'react-router-dom';
import React from 'react';

import { IPluginPageProps } from 'utils/plugins';
import JaegerPageCompare from 'plugins/jaeger/JaegerPageCompare';
import JaegerPageTraces from 'plugins/jaeger/JaegerPageTraces';

// JaegerPage is the component, which implements the page component for the Jaeger plugin. The Jaeger plugin supports,
// two routes, one to search for traces and another one to render a single trace and compare it with another one.
const JaegerPage: React.FunctionComponent<IPluginPageProps> = ({ name, description }: IPluginPageProps) => {
return (
<Switch>
<Route exact={true} path={`/plugins/${name}`}>
<JaegerPageTraces name={name} description={description} />
</Route>
<Route exact={true} path={`/plugins/${name}/trace/:traceID`}>
<JaegerPageCompare name={name} />
</Route>
</Switch>
);
};

export default JaegerPage;
68 changes: 68 additions & 0 deletions app/src/plugins/jaeger/JaegerPageCompare.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Grid, GridItem } from '@patternfly/react-core';
import React, { useEffect, useState } from 'react';
import { useHistory, useLocation, useParams } from 'react-router-dom';
import CloseIcon from '@patternfly/react-icons/dist/js/icons/close-icon';

import JaegerPageCompareInput from 'plugins/jaeger/JaegerPageCompareInput';
import JaegerPageCompareTrace from 'plugins/jaeger/JaegerPageCompareTrace';

interface IJaegerPageCompareParams {
traceID: string;
}

interface IJaegerPageCompareProps {
name: string;
}

// JaegerPageCompare is the component, which renders two traces for comparison.
const JaegerPageCompare: React.FunctionComponent<IJaegerPageCompareProps> = ({ name }: IJaegerPageCompareProps) => {
const params = useParams<IJaegerPageCompareParams>();
const history = useHistory();
const location = useLocation();
const [compareTrace, setCompareTrace] = useState<string>('');

// changeCompareTrace is used to set the trace id, which should be compared against the trace id, which is provided as
// parameter in the URL.
const changeCompareTrace = (traceID: string): void => {
history.push({
pathname: location.pathname,
search: `?compare=${traceID}`,
});
};

// useEffect is used to set the options every time the search location for the current URL changes. The URL is changed
// via the changeOptions function. When the search location is changed we modify the options state.
useEffect(() => {
const params = new URLSearchParams(location.search);
const traceID = params.get('compare');
setCompareTrace(traceID ? traceID : '');
}, [location.search]);

return (
<React.Fragment>
<Grid>
<GridItem span={compareTrace ? 6 : 12}>
<JaegerPageCompareTrace
name={name}
traceID={params.traceID}
headerComponent={!compareTrace ? <JaegerPageCompareInput changeCompareTrace={changeCompareTrace} /> : null}
/>
</GridItem>

{compareTrace ? (
<GridItem span={compareTrace ? 6 : 12}>
<JaegerPageCompareTrace
name={name}
traceID={compareTrace}
headerComponent={
<CloseIcon style={{ cursor: 'pointer', float: 'right' }} onClick={(): void => changeCompareTrace('')} />
}
/>
</GridItem>
) : null}
</Grid>
</React.Fragment>
);
};

export default JaegerPageCompare;
40 changes: 40 additions & 0 deletions app/src/plugins/jaeger/JaegerPageCompareInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Button, ButtonVariant, InputGroup, InputGroupText, TextInput } from '@patternfly/react-core';
import React, { useState } from 'react';
import SearchIcon from '@patternfly/react-icons/dist/js/icons/search-icon';

interface IJaegerPageCompareInputProps {
changeCompareTrace: (traceID: string) => void;
}

// JaegerPageCompareInput is the component, which allows the user to provide a second trace id, to compare two traces.
const JaegerPageCompareInput: React.FunctionComponent<IJaegerPageCompareInputProps> = ({
changeCompareTrace,
}: IJaegerPageCompareInputProps) => {
const [compareTrace, setCompareTrace] = useState<string>('');

// onEnter is used to detect if the user pressed the "ENTER" key. If this is the case we are calling the
// changeCompareTrace function to trigger the comparision of traces.
const onEnter = (e: React.KeyboardEvent<HTMLDivElement> | undefined): void => {
if (e?.key === 'Enter' && !e.shiftKey) {
changeCompareTrace(compareTrace);
}
};

return (
<InputGroup>
<InputGroupText>Trace ID:</InputGroupText>
<TextInput
id="trace-id-to-compare-with"
type="search"
value={compareTrace}
onChange={setCompareTrace}
onKeyDown={onEnter}
/>
<Button variant={ButtonVariant.control} onClick={(): void => changeCompareTrace(compareTrace)}>
<SearchIcon />
</Button>
</InputGroup>
);
};

export default JaegerPageCompareInput;
Loading