Skip to content

Commit 59b1552

Browse files
authored
Merge pull request #744 from kinvolk/endpoints
Add a new entry to show endpoints
2 parents 59cc754 + 10e1ac3 commit 59b1552

File tree

16 files changed

+1503
-38
lines changed

16 files changed

+1503
-38
lines changed

frontend/src/components/Sidebar/__snapshots__/Sidebar.stories.storyshot

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,28 @@ exports[`Storyshots Sidebar/Sidebar Closed 1`] = `
553553
/>
554554
</a>
555555
</li>
556+
<li>
557+
<a
558+
aria-disabled="false"
559+
class="MuiButtonBase-root MuiListItem-root makeStyles-link makeStyles-linkActive MuiListItem-gutters MuiListItem-button"
560+
href="/"
561+
role="button"
562+
tabindex="0"
563+
>
564+
<div
565+
class="MuiListItemText-root"
566+
>
567+
<span
568+
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
569+
>
570+
Endpoints
571+
</span>
572+
</div>
573+
<span
574+
class="MuiTouchRipple-root"
575+
/>
576+
</a>
577+
</li>
556578
<li>
557579
<a
558580
aria-disabled="false"
@@ -1287,6 +1309,28 @@ exports[`Storyshots Sidebar/Sidebar Open 1`] = `
12871309
/>
12881310
</a>
12891311
</li>
1312+
<li>
1313+
<a
1314+
aria-disabled="false"
1315+
class="MuiButtonBase-root MuiListItem-root makeStyles-link makeStyles-linkActive MuiListItem-gutters MuiListItem-button"
1316+
href="/"
1317+
role="button"
1318+
tabindex="0"
1319+
>
1320+
<div
1321+
class="MuiListItemText-root"
1322+
>
1323+
<span
1324+
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
1325+
>
1326+
Endpoints
1327+
</span>
1328+
</div>
1329+
<span
1330+
class="MuiTouchRipple-root"
1331+
/>
1332+
</a>
1333+
</li>
12901334
<li>
12911335
<a
12921336
aria-disabled="false"

frontend/src/components/Sidebar/prepareRoutes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ function prepareRoutes(t: (arg: string) => string) {
9090
name: 'services',
9191
label: t('glossary|Services'),
9292
},
93+
{
94+
name: 'endpoints',
95+
label: t('glossary|Endpoints'),
96+
},
9397
{
9498
name: 'ingresses',
9599
label: t('glossary|Ingresses'),

frontend/src/components/common/Resource/Resource.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import * as monaco from 'monaco-editor';
1515
import React, { isValidElement, PropsWithChildren } from 'react';
1616
import { useTranslation } from 'react-i18next';
1717
import { generatePath, Link as RouterLink, NavLinkProps, useLocation } from 'react-router-dom';
18-
import { ApiError } from '../../../lib/k8s/apiProxy';
1918
import {
2019
KubeCondition,
2120
KubeContainer,
@@ -185,8 +184,6 @@ export interface DetailsGridProps
185184
export function DetailsGrid(props: DetailsGridProps) {
186185
const { sectionsFunc, resourceType, name, namespace, children, ...otherMainInfoSectionProps } =
187186
props;
188-
const [item, setItem] = React.useState<KubeObject | null>(null);
189-
const [error, setError] = React.useState<ApiError | string | null>(null);
190187
const location = useLocation<{ backLink: NavLinkProps['location'] }>();
191188

192189
const backLink: string | undefined = React.useMemo(() => {
@@ -210,9 +207,7 @@ export function DetailsGrid(props: DetailsGridProps) {
210207
return createRouteURL(route);
211208
}, []);
212209

213-
resourceType.useApiGet(setItem, name, namespace || undefined, (err: ApiError) =>
214-
setError(err.message)
215-
);
210+
const [item, error] = resourceType.useGet(name, namespace);
216211

217212
return (
218213
<PageGrid>

frontend/src/components/common/SimpleTable.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,19 @@ export default function SimpleTable(props: SimpleTableProps) {
164164
[data, currentData]
165165
);
166166

167-
function defaultSortingFunction(column: SimpleTableGetterColumn) {
167+
function defaultSortingFunction(column: SimpleTableDatumColumn | SimpleTableGetterColumn) {
168168
const sort = column?.sort;
169169
function defaultSortingReal(item1: any, item2: any) {
170-
let getterFunc = column.getter;
170+
let getterFunc = (column as SimpleTableGetterColumn).getter;
171171
if (!!sort && typeof sort === 'function') {
172172
getterFunc = sort;
173173
}
174+
// If instead of a getter function, we have a datum, then we use it to fetch the values for
175+
// comparison.
176+
const datum = (column as SimpleTableDatumColumn).datum;
177+
if (!getterFunc && !!datum) {
178+
getterFunc = (item: any) => item[datum];
179+
}
174180
const value1 = getterFunc(item1);
175181
const value2 = getterFunc(item2);
176182

@@ -199,9 +205,7 @@ export default function SimpleTable(props: SimpleTableProps) {
199205
(typeof sortFunction === 'boolean' && sortFunction) ||
200206
(typeof sortFunction === 'function' && sortFunction.length === 1)
201207
) {
202-
setDisplayData(
203-
data.slice().sort(defaultSortingFunction(columnAskingForSort as SimpleTableGetterColumn))
204-
);
208+
setDisplayData(data.slice().sort(defaultSortingFunction(columnAskingForSort)));
205209
return;
206210
}
207211

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { useLocation, useParams } from 'react-router-dom';
3+
import { ResourceClasses } from '../../lib/k8s';
4+
import Endpoints, { KubeEndpoint } from '../../lib/k8s/endpoints';
5+
import { Link, SectionHeader } from '../common';
6+
import Empty from '../common/EmptyContent';
7+
import { DetailsGrid } from '../common/Resource';
8+
import { SectionBox } from '../common/SectionBox';
9+
import SimpleTable from '../common/SimpleTable';
10+
11+
export default function EndpointDetails() {
12+
const { namespace, name } = useParams<{ namespace: string; name: string }>();
13+
const location = useLocation();
14+
const { t } = useTranslation('glossary');
15+
16+
return (
17+
<DetailsGrid
18+
resourceType={Endpoints}
19+
name={name}
20+
namespace={namespace}
21+
title={t('Endpoint')}
22+
sectionsFunc={(item: KubeEndpoint | null) =>
23+
item && (
24+
<>
25+
<SectionBox title={t('Subsets')} />
26+
<>
27+
{!item.subsets?.length ? (
28+
<SectionBox>
29+
<Empty>{t('resource|No data to be shown.')}</Empty>
30+
</SectionBox>
31+
) : (
32+
item.subsets?.map((subset, i) => (
33+
<SectionBox key={`subsetDetails_${i}`} outterBoxProps={{ pb: 3 }}>
34+
<SectionHeader noPadding title={t('Addresses')} headerStyle="label" />
35+
<SimpleTable
36+
data={subset?.addresses || []}
37+
columns={[
38+
{
39+
label: t('IP'),
40+
getter: address => address.ip,
41+
},
42+
{
43+
label: t('Hostname'),
44+
getter: address => address.hostname,
45+
},
46+
{
47+
label: t('Target'),
48+
getter: address => {
49+
const targetRefClass = !!address.targetRef?.kind
50+
? ResourceClasses[address.targetRef?.kind]
51+
: null;
52+
if (!!targetRefClass) {
53+
return (
54+
<Link
55+
routeName={targetRefClass.detailsRoute}
56+
params={{
57+
name: address.targetRef.name,
58+
namespace: address.targetRef.namespace,
59+
}}
60+
state={{
61+
backLink: location,
62+
}}
63+
>
64+
{address.targetRef.name}
65+
</Link>
66+
);
67+
} else {
68+
return address.targetRef?.name || '';
69+
}
70+
},
71+
},
72+
]}
73+
/>
74+
<SectionHeader noPadding title={t('Ports')} headerStyle="label" />
75+
<SimpleTable
76+
data={subset?.ports || []}
77+
columns={[
78+
{
79+
label: t('Name'),
80+
datum: 'name',
81+
sort: true,
82+
},
83+
{
84+
label: t('Port'),
85+
datum: 'port',
86+
sort: true,
87+
},
88+
{
89+
label: t('Protocol'),
90+
datum: 'protocol',
91+
sort: true,
92+
},
93+
]}
94+
defaultSortingColumn={1}
95+
/>
96+
</SectionBox>
97+
))
98+
)}
99+
</>
100+
</>
101+
)
102+
}
103+
/>
104+
);
105+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { Meta, Story } from '@storybook/react/types-6-0';
2+
import { KubeObjectClass } from '../../lib/k8s/cluster';
3+
import Endpoints, { KubeEndpoint } from '../../lib/k8s/endpoints';
4+
import { TestContext } from '../../test';
5+
import EndpointDetails from './Details';
6+
7+
const usePhonyGet: KubeObjectClass['useGet'] = (name, namespace) => {
8+
return [
9+
new Endpoints({
10+
kind: 'Endpoints',
11+
apiVersion: 'v1',
12+
metadata: {
13+
name,
14+
namespace,
15+
uid: 'phony',
16+
creationTimestamp: new Date('2020-04-25').toISOString(),
17+
resourceVersion: '1',
18+
selfLink: '0',
19+
},
20+
subsets: [
21+
{
22+
addresses: [
23+
{
24+
ip: '127.0.0.1',
25+
nodeName: 'mynode',
26+
targetRef: {
27+
kind: 'Pod',
28+
namespace: 'MyNamespace',
29+
name: 'mypod',
30+
uid: 'phony-pod',
31+
resourceVersion: '1',
32+
apiVersion: 'v1',
33+
},
34+
},
35+
{
36+
ip: '127.0.0.2',
37+
nodeName: 'mynode',
38+
targetRef: {
39+
kind: 'Pod',
40+
namespace: 'MyNamespace',
41+
name: 'mypod-1',
42+
uid: 'phony-pod-1',
43+
resourceVersion: '1',
44+
apiVersion: 'v1',
45+
},
46+
},
47+
],
48+
ports: [
49+
{
50+
name: 'myport',
51+
port: 8080,
52+
protocol: 'TCP',
53+
},
54+
],
55+
},
56+
],
57+
} as KubeEndpoint),
58+
null,
59+
() => {},
60+
() => {},
61+
] as any;
62+
};
63+
64+
export default {
65+
title: 'endpoints/EndpointsDetailsView',
66+
component: EndpointDetails,
67+
argTypes: {},
68+
decorators: [
69+
Story => {
70+
return (
71+
<TestContext routerMap={{ namespace: 'my-namespace', name: 'my-endpoint' }}>
72+
<Story />
73+
</TestContext>
74+
);
75+
},
76+
],
77+
} as Meta;
78+
79+
interface MockerStory {
80+
useGet?: KubeObjectClass['useGet'];
81+
useList?: KubeObjectClass['useList'];
82+
}
83+
84+
const Template: Story = (args: MockerStory) => {
85+
if (!!args.useGet) {
86+
Endpoints.useGet = args.useGet;
87+
}
88+
if (!!args.useList) {
89+
Endpoints.useList = args.useList;
90+
}
91+
92+
return <EndpointDetails />;
93+
};
94+
95+
export const Default = Template.bind({});
96+
Default.args = {
97+
useGet: usePhonyGet,
98+
};
99+
100+
export const NoItemYet = Template.bind({});
101+
NoItemYet.args = {
102+
useGet: () => [null, null, () => {}, () => {}] as any,
103+
};
104+
105+
export const Error = Template.bind({});
106+
Error.args = {
107+
useGet: () => [null, 'Phony error is phony!', () => {}, () => {}] as any,
108+
};

0 commit comments

Comments
 (0)