mirror of https://github.com/portainer/portainer
				
				
				
			
		
			
				
	
	
		
			133 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			133 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
import { Service } from 'kubernetes-types/core/v1';
 | 
						|
import { useMemo } from 'react';
 | 
						|
 | 
						|
import { EnvironmentId } from '@/react/portainer/environments/types';
 | 
						|
import { useIngresses } from '@/react/kubernetes/ingresses/queries';
 | 
						|
import { Ingress } from '@/react/kubernetes/ingresses/types';
 | 
						|
import { Authorized } from '@/react/hooks/useUser';
 | 
						|
 | 
						|
import { Link } from '@@/Link';
 | 
						|
 | 
						|
type Props = {
 | 
						|
  environmentId: EnvironmentId;
 | 
						|
  namespace: string;
 | 
						|
  appServices?: Service[];
 | 
						|
};
 | 
						|
 | 
						|
export function ApplicationIngressesTable({
 | 
						|
  environmentId,
 | 
						|
  namespace,
 | 
						|
  appServices,
 | 
						|
}: Props) {
 | 
						|
  const namespaceIngresses = useIngresses(environmentId, {
 | 
						|
    withServices: true,
 | 
						|
  });
 | 
						|
  // getIngressPathsForAppServices could be expensive, so memoize it
 | 
						|
  const ingressPathsForAppServices = useMemo(
 | 
						|
    () => getIngressPathsForAppServices(namespaceIngresses.data, appServices),
 | 
						|
    [namespaceIngresses.data, appServices]
 | 
						|
  );
 | 
						|
 | 
						|
  if (!ingressPathsForAppServices.length) {
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  return (
 | 
						|
    <table className="mt-4 table">
 | 
						|
      <tbody>
 | 
						|
        <tr className="text-muted">
 | 
						|
          <td className="w-[15%]">Ingress name</td>
 | 
						|
          <td className="w-[10%]">Service name</td>
 | 
						|
          <td className="w-[10%]">Host</td>
 | 
						|
          <td className="w-[10%]">Port</td>
 | 
						|
          <td className="w-[10%]">Path</td>
 | 
						|
          <td className="w-[15%]">HTTP Route</td>
 | 
						|
        </tr>
 | 
						|
        {ingressPathsForAppServices?.map((ingressPath, index) => (
 | 
						|
          <tr key={index}>
 | 
						|
            <td>
 | 
						|
              <Authorized authorizations="K8sIngressesW">
 | 
						|
                <Link
 | 
						|
                  to="kubernetes.ingresses.edit"
 | 
						|
                  params={{ name: ingressPath.ingressName, namespace }}
 | 
						|
                  data-cy={`ingress-link-${ingressPath.ingressName}`}
 | 
						|
                >
 | 
						|
                  {ingressPath.ingressName}
 | 
						|
                </Link>
 | 
						|
              </Authorized>
 | 
						|
            </td>
 | 
						|
            <td>{ingressPath.serviceName}</td>
 | 
						|
            <td>{ingressPath.host}</td>
 | 
						|
            <td>{ingressPath.port}</td>
 | 
						|
            <td>{ingressPath.path}</td>
 | 
						|
            <td>
 | 
						|
              <a
 | 
						|
                target="_blank"
 | 
						|
                rel="noopener noreferrer"
 | 
						|
                href={`${ingressPath.secure ? 'https' : 'http'}://${
 | 
						|
                  ingressPath.host
 | 
						|
                }${ingressPath.path}`}
 | 
						|
              >
 | 
						|
                {ingressPath.host}
 | 
						|
                {ingressPath.path}
 | 
						|
              </a>
 | 
						|
            </td>
 | 
						|
          </tr>
 | 
						|
        ))}
 | 
						|
      </tbody>
 | 
						|
    </table>
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
type IngressPath = {
 | 
						|
  ingressName: string;
 | 
						|
  serviceName: string;
 | 
						|
  port: number;
 | 
						|
  secure: boolean;
 | 
						|
  host: string;
 | 
						|
  path: string;
 | 
						|
};
 | 
						|
 | 
						|
function getIngressPathsForAppServices(
 | 
						|
  ingresses?: Ingress[],
 | 
						|
  services?: Service[]
 | 
						|
): IngressPath[] {
 | 
						|
  if (!ingresses || !services) {
 | 
						|
    return [];
 | 
						|
  }
 | 
						|
  const matchingIngressesPaths = ingresses.flatMap((ingress) => {
 | 
						|
    // for each ingress get an array of ingress paths that match the app services
 | 
						|
    if (!ingress.Paths) {
 | 
						|
      return [];
 | 
						|
    }
 | 
						|
    const matchingIngressPaths = ingress.Paths?.filter(
 | 
						|
      (path) =>
 | 
						|
        services?.some((service) => {
 | 
						|
          const servicePorts = service.spec?.ports?.map((port) => port.port);
 | 
						|
          // include the ingress if the ingress path has a matching service name and port
 | 
						|
          return (
 | 
						|
            path.ServiceName === service.metadata?.name &&
 | 
						|
            servicePorts?.includes(path.Port)
 | 
						|
          );
 | 
						|
        })
 | 
						|
    ).map((path) => {
 | 
						|
      const secure =
 | 
						|
        (ingress.TLS &&
 | 
						|
          ingress.TLS.filter(
 | 
						|
            (tls) => tls.Hosts && tls.Hosts.includes(path.Host)
 | 
						|
          ).length > 0) ??
 | 
						|
        false;
 | 
						|
      return {
 | 
						|
        ingressName: ingress.Name,
 | 
						|
        serviceName: path.ServiceName,
 | 
						|
        port: path.Port,
 | 
						|
        secure,
 | 
						|
        host: path.Host,
 | 
						|
        path: path.Path,
 | 
						|
      };
 | 
						|
    });
 | 
						|
    return matchingIngressPaths;
 | 
						|
  });
 | 
						|
  return matchingIngressesPaths;
 | 
						|
}
 |