mirror of https://github.com/portainer/portainer
fix(app): fix app ingress edge cases [EE-5663] (#9150)
Co-authored-by: testa113 <testa113>pull/9181/head
parent
8b11e1678e
commit
c752b98120
|
@ -512,6 +512,17 @@ class KubernetesCreateApplicationController {
|
||||||
/* #region SERVICES UI MANAGEMENT */
|
/* #region SERVICES UI MANAGEMENT */
|
||||||
onServicesChange(services) {
|
onServicesChange(services) {
|
||||||
return this.$async(async () => {
|
return this.$async(async () => {
|
||||||
|
// if the ingress isn't found in the currently loaded ingresses, then refresh the ingresses
|
||||||
|
const ingressNamesUsed = services.flatMap((s) => s.Ports.flatMap((p) => (p.ingressPaths ? p.ingressPaths.flatMap((ip) => ip.IngressName || []) : [])));
|
||||||
|
if (ingressNamesUsed.length) {
|
||||||
|
const uniqueIngressNamesUsed = Array.from(new Set(ingressNamesUsed)); // get the unique ingress names used
|
||||||
|
const ingressNamesLoaded = this.ingresses.map((i) => i.Name);
|
||||||
|
const areAllIngressesLoaded = uniqueIngressNamesUsed.every((ingressNameUsed) => ingressNamesLoaded.includes(ingressNameUsed));
|
||||||
|
if (!areAllIngressesLoaded) {
|
||||||
|
this.refreshIngresses();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update the services
|
||||||
this.formValues.Services = services;
|
this.formValues.Services = services;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1127,7 +1138,6 @@ class KubernetesCreateApplicationController {
|
||||||
this.nodesLabels,
|
this.nodesLabels,
|
||||||
this.ingresses
|
this.ingresses
|
||||||
);
|
);
|
||||||
|
|
||||||
this.originalServicePorts = structuredClone(this.formValues.Services.flatMap((service) => service.Ports));
|
this.originalServicePorts = structuredClone(this.formValues.Services.flatMap((service) => service.Ports));
|
||||||
this.originalIngressPaths = structuredClone(this.originalServicePorts.flatMap((port) => port.ingressPaths).filter((ingressPath) => ingressPath.Host));
|
this.originalIngressPaths = structuredClone(this.originalServicePorts.flatMap((port) => port.ingressPaths).filter((ingressPath) => ingressPath.Host));
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ export function ClusterIpServiceForm({
|
||||||
return (
|
return (
|
||||||
<Card key={portIndex} className="flex flex-col gap-y-3">
|
<Card key={portIndex} className="flex flex-col gap-y-3">
|
||||||
<div className="flex flex-grow flex-wrap justify-between gap-x-4 gap-y-1">
|
<div className="flex flex-grow flex-wrap justify-between gap-x-4 gap-y-1">
|
||||||
<div className="inline-flex min-w-min flex-grow basis-3/4 flex-wrap gap-2">
|
<div className="inline-flex min-w-min flex-grow basis-3/4 flex-wrap gap-x-4 gap-y-1">
|
||||||
<div className="flex min-w-min basis-1/3 flex-col">
|
<div className="flex min-w-min basis-1/3 flex-col">
|
||||||
<ContainerPortInput
|
<ContainerPortInput
|
||||||
serviceIndex={serviceIndex}
|
serviceIndex={serviceIndex}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import { InputGroup } from '@@/form-components/InputGroup';
|
||||||
type Props = {
|
type Props = {
|
||||||
serviceIndex: number;
|
serviceIndex: number;
|
||||||
portIndex: number;
|
portIndex: number;
|
||||||
value?: number;
|
|
||||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
value?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ContainerPortInput({
|
export function ContainerPortInput({
|
||||||
|
|
|
@ -63,13 +63,29 @@ export function AppIngressPathForm({
|
||||||
isEditMode,
|
isEditMode,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// when the hostname options change (e.g. after a namespace change), update the selected ingress to the first available one
|
// when the hostname options change (e.g. after a namespace change) and the host and ingress is no longer available, update the selected ingress to the first available one
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ingressHostOptionsWithCurrentValue) {
|
if (ingressHostOptionsWithCurrentValue) {
|
||||||
|
// some rerenders might not be from a namespace or hostname change so keep the current values if they're still valid
|
||||||
|
const ingressHosts = ingressHostOptionsWithCurrentValue.map(
|
||||||
|
(i) => i.value
|
||||||
|
);
|
||||||
|
const newIngressHostValue = ingressHosts.includes(ingressPath?.Host ?? '')
|
||||||
|
? ingressPath?.Host
|
||||||
|
: ingressHosts[0] ?? '';
|
||||||
|
const ingressNames = ingressHostOptionsWithCurrentValue.map(
|
||||||
|
(i) => i.ingressName
|
||||||
|
);
|
||||||
|
const newIngressNameValue = ingressNames.includes(
|
||||||
|
ingressPath?.IngressName ?? ''
|
||||||
|
)
|
||||||
|
? ingressPath?.IngressName ?? ''
|
||||||
|
: ingressNames[0] ?? '';
|
||||||
|
|
||||||
const newIngressPath = {
|
const newIngressPath = {
|
||||||
...ingressPath,
|
...ingressPath,
|
||||||
Host: ingressHostOptionsWithCurrentValue[0]?.value,
|
Host: newIngressHostValue,
|
||||||
IngressName: ingressHostOptionsWithCurrentValue[0]?.ingressName,
|
IngressName: newIngressNameValue,
|
||||||
};
|
};
|
||||||
onChangeIngressPath(newIngressPath);
|
onChangeIngressPath(newIngressPath);
|
||||||
setSelectedIngress(ingressHostOptionsWithCurrentValue[0] ?? null);
|
setSelectedIngress(ingressHostOptionsWithCurrentValue[0] ?? null);
|
||||||
|
@ -78,7 +94,7 @@ export function AppIngressPathForm({
|
||||||
}, [ingressHostOptionsWithCurrentValue]);
|
}, [ingressHostOptionsWithCurrentValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-wrap gap-x-4">
|
<div className="flex w-full flex-wrap gap-x-4 gap-y-1">
|
||||||
<div className="flex min-w-[250px] basis-1/3 flex-col">
|
<div className="flex min-w-[250px] basis-1/3 flex-col">
|
||||||
<InputGroup size="small">
|
<InputGroup size="small">
|
||||||
<InputGroup.Addon>Hostname</InputGroup.Addon>
|
<InputGroup.Addon>Hostname</InputGroup.Addon>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Plus } from 'lucide-react';
|
import { Loader2, Plus } from 'lucide-react';
|
||||||
import { FormikErrors } from 'formik';
|
import { FormikErrors } from 'formik';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import { Button } from '@@/buttons';
|
||||||
import { SwitchField } from '@@/form-components/SwitchField';
|
import { SwitchField } from '@@/form-components/SwitchField';
|
||||||
import { TextTip } from '@@/Tip/TextTip';
|
import { TextTip } from '@@/Tip/TextTip';
|
||||||
import { Link } from '@@/Link';
|
import { Link } from '@@/Link';
|
||||||
|
import { Icon } from '@@/Icon';
|
||||||
|
|
||||||
import { ServicePortIngressPath } from '../types';
|
import { ServicePortIngressPath } from '../types';
|
||||||
|
|
||||||
|
@ -75,7 +76,12 @@ export function AppIngressPathsForm({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ingressesQuery.isLoading || ingressControllersQuery.isLoading) {
|
if (ingressesQuery.isLoading || ingressControllersQuery.isLoading) {
|
||||||
return <p>Loading ingresses...</p>;
|
return (
|
||||||
|
<p className="text-muted mt-2 flex items-center gap-x-2 text-sm">
|
||||||
|
<Icon icon={Loader2} className="animate-spin-slow" />
|
||||||
|
Loading ingresses...
|
||||||
|
</p>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -154,6 +154,8 @@ export function generateNewIngressesFromFormPaths(
|
||||||
return newIngresses;
|
return newIngresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** prependWithSlash puts a '/' in front of a string if there isn't one there already. If the string is empty, it stays empty */
|
||||||
export function prependWithSlash(path?: string) {
|
export function prependWithSlash(path?: string) {
|
||||||
return path?.startsWith('/') ? path : `/${path}`;
|
if (!path) return '';
|
||||||
|
return path.startsWith('/') ? path : `/${path}`;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue