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

feat: enhance Ingress details view #3205

Merged
merged 5 commits into from
Aug 22, 2024
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
8 changes: 8 additions & 0 deletions public/i18n/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ common:
resources: Resources
nodeInfo: Node Info
node-details: Node Details
specification: Specification
started-at: Started At
status: Status
value: Value
Expand Down Expand Up @@ -877,17 +878,24 @@ ingresses:
backend: Backend
default-backend: Default Backend
host: Host
host-name: Host name
hosts: Hosts
ingress-class-name: Ingress class name
ip: IP
kind: Kind
load-balancers: Load Balancers
path: Path
path-type: Path type
paths: Paths
port: Port
ports: Ports
port-name: Port name
port-number: Port number
protocol: Protocol
rules: Rules
secret-name: Secret name
service-name: Service name
tls: TLS
message:
rules-not-found: Rules not found
name_singular: Ingress
Expand Down
6 changes: 5 additions & 1 deletion src/resources/Ingresses/IngressCreate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import * as _ from 'lodash';

Expand All @@ -19,6 +19,9 @@ export default function IngressCreate({
const [ingress, setIngress] = useState(
_.cloneDeep(initialIngress) || createIngressTemplate(namespaceId),
);
const [initialUnchangedResource] = useState(
initialIngress || createIngressTemplate(namespaceId),
);
const [initialResource] = useState(
initialIngress || createIngressTemplate(namespaceId),
);
Expand All @@ -31,6 +34,7 @@ export default function IngressCreate({
singularName={t('ingresses.name_singular')}
resource={ingress}
initialResource={initialResource}
initialUnchangedResource={initialUnchangedResource}
setResource={setIngress}
onlyYaml
onChange={onChange}
Expand Down
82 changes: 61 additions & 21 deletions src/resources/Ingresses/IngressDetails.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,47 @@
import { useTranslation } from 'react-i18next';

import { ResourceDetails } from 'shared/components/ResourceDetails/ResourceDetails';
import { EMPTY_TEXT_PLACEHOLDER } from 'shared/constants';

import { Rules } from './Rules';
import { DefaultBackendPanel } from './DefaultBackendPanel';
import IngressCreate from './IngressCreate';
import { ResourceDescription } from 'resources/Ingresses';
import { EventsList } from 'shared/components/EventsList';
import { filterByResource } from 'hooks/useMessageList';
import { StatusBadge } from 'shared/components/StatusBadge/StatusBadge';
import { IngressStatus } from './IngressStatus';
import { IngressSpecification } from './IngressSpecification';
import { useState } from 'react';

export function IngressDetails(props) {
const { t } = useTranslation();
const [totalPorts, setTotalPorts] = useState(0);
const [healthyPorts, setHealthyPorts] = useState(0);

const getLoadBalancer = ingress => {
if (ingress.status.loadBalancer?.ingress) {
return ingress.status.loadBalancer?.ingress
.map(endpoint => endpoint.ip || endpoint.hostname)
.join(', ');
} else {
return EMPTY_TEXT_PLACEHOLDER;
}
};
const calculatePorts = ingress => {
let allPorts = 0;
let healthyPorts = 0;

ingress?.status?.loadBalancer?.ingress?.forEach(element => {
element?.ports?.forEach(port => {
allPorts++;
if (!port.error) healthyPorts++;
});
});

const customColumns = [
{
header: t('ingresses.labels.load-balancers'),
value: getLoadBalancer,
},
{
header: t('ingresses.labels.ingress-class-name'),
value: ingress => ingress.spec.ingressClassName,
},
];
setTotalPorts(allPorts);
setHealthyPorts(healthyPorts);
};
Copy link
Contributor

@chriskari chriskari Aug 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can merge these two methods, using a single loop for both

also, is the naming pods vs. ports intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it was a typo :)


const customComponents = [];

customComponents.push(
resource =>
(resource.spec?.ingressClassName || resource.spec?.tls) && (
<IngressSpecification resource={resource} />
),
);

customComponents.push(resource =>
resource.spec.defaultBackend ? (
<DefaultBackendPanel
Expand All @@ -49,12 +57,44 @@ export function IngressDetails(props) {
) : null,
);

customComponents.push(() => (
<EventsList
key="events"
namespace={props.namespace}
filter={filterByResource('Ingress', props.resourceName)}
hideInvolvedObjects={true}
/>
));

const statusBadge = resource => {
calculatePorts(resource);
const portsStatus =
totalPorts === 0
? 'Information'
: totalPorts === healthyPorts
? 'Success'
: 'Error';

return (
<StatusBadge
type={portsStatus}
tooltipContent={'Healthy Ports'}
>{`${healthyPorts} / ${totalPorts}`}</StatusBadge>
);
};

return (
<ResourceDetails
customColumns={customColumns}
customComponents={customComponents}
description={ResourceDescription}
createResourceForm={IngressCreate}
statusBadge={statusBadge}
customConditionsComponents={[
{
header: t('ingresses.labels.load-balancers'),
value: resource => <IngressStatus resource={resource} />,
},
]}
{...props}
/>
);
Expand Down
44 changes: 44 additions & 0 deletions src/resources/Ingresses/IngressSpecification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useTranslation } from 'react-i18next';
import { GenericList } from 'shared/components/GenericList/GenericList';
import { LayoutPanelRow } from 'shared/components/LayoutPanelRow/LayoutPanelRow';
import { Tokens } from 'shared/components/Tokens';
import { UI5Panel } from 'shared/components/UI5Panel/UI5Panel';
import { EMPTY_TEXT_PLACEHOLDER } from 'shared/constants';

export const IngressSpecification = ({ resource }) => {
const { t } = useTranslation();

return (
<>
<UI5Panel title={t('common.headers.specification')}>
{resource.spec.ingressClassName && (
<LayoutPanelRow
name={t('ingresses.labels.ingress-class-name')}
value={resource.spec.ingressClassName}
/>
)}
{resource.spec?.tls && (
<GenericList
title={t('ingresses.labels.tls')}
headerRenderer={() => [
t('ingresses.labels.hosts'),
t('ingresses.labels.secret-name'),
]}
rowRenderer={tls => [
tls?.hosts ? (
<Tokens tokens={tls?.hosts} />
) : (
EMPTY_TEXT_PLACEHOLDER
),
tls?.secretName ?? EMPTY_TEXT_PLACEHOLDER,
]}
entries={resource.spec?.tls}
searchSettings={{
showSearchField: false,
}}
/>
)}
</UI5Panel>
</>
);
};
102 changes: 102 additions & 0 deletions src/resources/Ingresses/IngressStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { ConditionList } from 'shared/components/ConditionList/ConditionList';
import { EMPTY_TEXT_PLACEHOLDER } from 'shared/constants';
import { spacing } from '@ui5/webcomponents-react-base';
import './IngressStatus.scss';

export const IngressStatus = ({ resource }) => {
const { t } = useTranslation();
const ingresses = useMemo(() => {
return resource?.status?.loadBalancer?.ingress?.map(ingress => ingress);
}, [resource]);

return ingresses ? (
<ConditionList
className="load-balancers"
conditions={ingresses.map(ingress => ({
header: {
titleText: ingress.hostname ? (
<div>
<span
style={{ ...spacing.sapUiTinyMarginEnd }}
className="title bsl-has-color-status-4"
>
{`${t('ingresses.labels.host-name')}:`}
</span>
{`${ingress.hostname}`}
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we replace this somehow with a UI5 component?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, that's how we are doing it in metadata and status - Labels have slightly different color

) : (
<div>
<span
style={{ ...spacing.sapUiTinyMarginEnd }}
className="title bsl-has-color-status-4"
>
{`${t('ingresses.labels.ip')}:`}
</span>
{`${ingress.ip}`}
</div>
),
},
customContent: [
ingress.hostname
? {
header: t('ingresses.labels.host-name'),
value: ingress.hostname,
className: 'load-balancers__content',
}
: null,
ingress.ip
? {
header: t('ingresses.labels.ip'),
value: ingress.ip,
className: 'load-balancers__content',
}
: null,
{
header: t('ingresses.labels.ports'),
value: ingress.ports ? (
<ConditionList
conditions={ingress?.ports?.map(port => {
return {
header: {
titleText: port.port,
status: port.error ? 'Error' : '',
},
customContent: [
port.protocol
? {
header: t('ingresses.labels.protocol'),
value: port.protocol,
}
: null,
port.error
? {
header: 'Error',
value: port.error,
}
: null,
],
};
})}
/>
) : (
EMPTY_TEXT_PLACEHOLDER
),
className: ingress.ports ? '' : 'load-balancers__content',
},
],
}))}
/>
) : (
<div
className="content bsl-has-color-text-1"
style={{
...spacing.sapUiSmallMarginBegin,
...spacing.sapUiSmallMarginBottom,
}}
>
{EMPTY_TEXT_PLACEHOLDER}
</div>
);
};
11 changes: 11 additions & 0 deletions src/resources/Ingresses/IngressStatus.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.load-balancers {
.expandable-item__message:not(.load-balancers__content) {
.title {
margin-left: 0.5rem !important;
}
}

&__content {
margin-left: 1.5rem !important;
}
}
13 changes: 9 additions & 4 deletions src/shared/components/ConditionList/ConditionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,32 @@ import {
CustomContent,
ExpandableListItem,
} from '../ExpandableListItem/ExpandableListItem';
import { ReactNode } from 'react';

type ConditionListProps = {
conditions: [ConditionItem];
className?: string;
};

type ConditionItem = {
message: string;
header: ConditionHeader;
message?: string;
customContent?: CustomContent[];
};
type ConditionHeader = {
titleText: string;
titleText: string | ReactNode;
status?: string;
};

export const ConditionList = ({ conditions }: ConditionListProps) => {
export const ConditionList = ({
conditions,
className,
}: ConditionListProps) => {
if (!conditions) {
return null;
}
return (
<List>
<List className={className}>
{conditions?.map((cond, index) => (
<ExpandableListItem
key={index}
Expand Down
Loading
Loading