diff --git a/src/common/k8s-api/endpoints/ingress.api.ts b/src/common/k8s-api/endpoints/ingress.api.ts index 254f68a411..afc29605a0 100644 --- a/src/common/k8s-api/endpoints/ingress.api.ts +++ b/src/common/k8s-api/endpoints/ingress.api.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { KubeObject } from "../kube-object"; -import { autoBind, iter } from "../../utils"; +import { KubeObject, TypedLocalObjectReference } from "../kube-object"; +import { autoBind, hasTypedProperty, isString, iter } from "../../utils"; import { IMetrics, metricsApi } from "./metrics.api"; import { KubeApi } from "../kube-api"; import type { KubeJsonApiData } from "../kube-json-api"; @@ -41,40 +41,58 @@ export interface ILoadBalancerIngress { } // extensions/v1beta1 -interface IExtensionsBackend { +export interface ExtensionsBackend { serviceName: string; servicePort: number | string; } // networking.k8s.io/v1 -interface INetworkingBackend { - service: IIngressService; +export interface NetworkingBackend { + service?: IngressService; } -export type IIngressBackend = IExtensionsBackend | INetworkingBackend; +export type IngressBackend = (ExtensionsBackend | NetworkingBackend) & { + resource?: TypedLocalObjectReference; +}; -export interface IIngressService { +export interface IngressService { name: string; - port: { - name?: string; - number?: number; - }; + port: RequireExactlyOne<{ + name: string; + number: number; + }>; +} + +function isExtensionsBackend(backend: IngressBackend): backend is ExtensionsBackend { + return hasTypedProperty(backend, "serviceName", isString); } /** * Format an ingress backend into the name of the service and port * @param backend The ingress target */ -export function getBackendServiceNamePort(backend: IIngressBackend): string { - // .service is available with networking.k8s.io/v1, otherwise using extensions/v1beta1 interface +export function getBackendServiceNamePort(backend: IngressBackend): string { + if (isExtensionsBackend(backend)) { + return `${backend.serviceName}:${backend.servicePort}`; + } - if ("service" in backend) { + if (backend.service) { const { name, port } = backend.service; return `${name}:${port.number ?? port.name}`; } - return `${backend.serviceName}:${backend.servicePort}`; + return ""; +} + +export interface IngressRule { + host?: string; + http?: { + paths: { + path?: string; + backend: IngressBackend; + }[]; + }; } export interface Ingress { @@ -82,23 +100,15 @@ export interface Ingress { tls?: { secretName: string; }[]; - rules?: { - host?: string; - http?: { - paths: { - path?: string; - backend: IIngressBackend; - }[]; - }; - }[]; + rules?: IngressRule[]; // extensions/v1beta1 - backend?: IExtensionsBackend; + backend?: ExtensionsBackend; /** * The default backend which is exactly on of: * - service * - resource */ - defaultBackend?: RequireExactlyOne `${url} ⇢ ${service}`); + return computeRouteDeclarations(this).map(({ url, service }) => `${url} ⇢ ${service}`); } - getRouteDecls(): ComputedIngressRoute[] { - const { spec: { tls = [], rules = [] }} = this; - const protocol = tls.length === 0 - ? "http" - : "https"; - - return rules.flatMap(({ host = "*", http: { paths } = { paths: [] }}) => ( - paths.map(({ path = "/", backend }) => ({ - url: `${protocol}://${host}${path}`, - service: getBackendServiceNamePort(backend), - })) - )); - } - - getServiceNamePort(): IExtensionsBackend { + getServiceNamePort(): ExtensionsBackend { const { spec: { backend, defaultBackend } = {}} = this; const serviceName = defaultBackend?.service?.name ?? backend?.serviceName; @@ -170,7 +172,7 @@ export class Ingress extends KubeObject { const httpPort = 80; const tlsPort = 443; // Note: not using the port name (string) - const servicePort = defaultBackend?.service.port.number ?? backend?.servicePort; + const servicePort = defaultBackend?.service?.port.number ?? backend?.servicePort; if (rules && rules.length > 0) { if (rules.some(rule => Object.prototype.hasOwnProperty.call(rule, "http"))) { @@ -196,6 +198,24 @@ export class Ingress extends KubeObject { } } +export function computeRuleDeclarations(ingress: Ingress, rule: IngressRule): ComputedIngressRoute[] { + const { host = "*", http: { paths } = { paths: [] }} = rule; + const protocol = (ingress.spec.tls?.length ?? 0) > 0 + ? "http" + : "https"; + + return paths.map(({ path = "/", backend }) => ({ + displayAsLink: !host.includes("*"), + pathname: path, + url: `${protocol}://${host}${path}`, + service: getBackendServiceNamePort(backend), + })); +} + +export function computeRouteDeclarations(ingress: Ingress): ComputedIngressRoute[] { + return ingress.getRules().flatMap(rule => computeRuleDeclarations(ingress, rule)); +} + let ingressApi: IngressApi; if (isClusterPageContext()) { diff --git a/src/common/k8s-api/kube-object.ts b/src/common/k8s-api/kube-object.ts index afabeaf638..f8d5940f33 100644 --- a/src/common/k8s-api/kube-object.ts +++ b/src/common/k8s-api/kube-object.ts @@ -111,6 +111,12 @@ export type LabelMatchExpression = { } ); +export interface TypedLocalObjectReference { + apiGroup?: string; + kind: string; + name: string; +} + export interface LabelSelector { matchLabels?: Record; matchExpressions?: LabelMatchExpression[]; diff --git a/src/renderer/components/+network-ingresses/ingress-details.tsx b/src/renderer/components/+network-ingresses/ingress-details.tsx index 35e2dbdab3..b8b66fdca5 100644 --- a/src/renderer/components/+network-ingresses/ingress-details.tsx +++ b/src/renderer/components/+network-ingresses/ingress-details.tsx @@ -15,7 +15,7 @@ import { ResourceMetrics } from "../resource-metrics"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { IngressCharts } from "./ingress-charts"; import { KubeObjectMeta } from "../kube-object-meta"; -import { getBackendServiceNamePort, getMetricsForIngress, type IIngressMetrics } from "../../../common/k8s-api/endpoints/ingress.api"; +import { computeRuleDeclarations, getMetricsForIngress, type IIngressMetrics } from "../../../common/k8s-api/endpoints/ingress.api"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { boundMethod } from "../../utils"; @@ -49,53 +49,45 @@ export class IngressDetails extends React.Component { } renderPaths(ingress: Ingress) { - const { spec: { rules = [], tls = [] }} = ingress; - const protocol = tls.length === 0 - ? "http" - : "https"; - - return rules.map((rule, index) => ( -
- {rule.host && ( -
- <>Host: {rule.host} -
- )} - {rule.http && ( - - - Path - Link - Backends - - { - rule.http.paths - .map(({ path = "/", backend }, index) => { - const link = `${protocol}://${rule.host || "*"}${path}`; - - return ( + return ingress.getRules() + .map((rule, index) => ( +
+ {rule.host && ( +
+ <>Host: {rule.host} +
+ )} + {rule.http && ( +
+ + Path + Link + Backends + + { + computeRuleDeclarations(ingress, rule) + .map(({ displayAsLink, service, url, pathname }) => ( - {path} + {pathname} { - rule.host + displayAsLink ? ( - - {link} + + {url} ) - : link + : url } - {getBackendServiceNamePort(backend)} + {service} - ); - }) - } -
- )} -
- )); + )) + } + + )} + + )); } renderIngressPoints(ingressPoints: ILoadBalancerIngress[]) { diff --git a/src/renderer/components/+network-ingresses/ingresses.scss b/src/renderer/components/+network-ingresses/ingresses.scss index 77fb051876..6b072b5ed5 100644 --- a/src/renderer/components/+network-ingresses/ingresses.scss +++ b/src/renderer/components/+network-ingresses/ingresses.scss @@ -7,6 +7,16 @@ .TableCell { &.rules { flex-grow: 3.0; + overflow-x: scroll; + text-overflow: unset; + + &::-webkit-scrollbar { + display: none; + } + + span:not(:last-of-type) { + margin-right: 1em; + } } &.warning { diff --git a/src/renderer/components/+network-ingresses/ingresses.tsx b/src/renderer/components/+network-ingresses/ingresses.tsx index c62b80360f..4f4305c64c 100644 --- a/src/renderer/components/+network-ingresses/ingresses.tsx +++ b/src/renderer/components/+network-ingresses/ingresses.tsx @@ -13,6 +13,7 @@ import { KubeObjectListLayout } from "../kube-object-list-layout"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import type { IngressRouteParams } from "../../../common/routes"; import { KubeObjectAge } from "../kube-object/age"; +import { computeRouteDeclarations } from "../../../common/k8s-api/endpoints"; enum columnId { name = "name", @@ -56,28 +57,24 @@ export class Ingresses extends React.Component { , ingress.getNs(), ingress.getLoadBalancers().map(lb =>

{lb}

), - ingress.getRouteDecls().map(route => ( - <> - e.stopPropagation()} - > - {route.url} - ⇢ {route.service} - + computeRouteDeclarations(ingress).map(decl => ( + decl.displayAsLink + ? ( + + e.stopPropagation()} + > + {decl.url} + ⇢ {decl.service} + + ) + : {decl.url} ⇢ {decl.service} )), , ]} - tableProps={{ - customRowHeights: (item, lineHeight, paddings) => { - const lines = item.getRoutes().length || 1; - - return lines * lineHeight + paddings; - }, - }} /> ); }