mirror of https://github.com/portainer/portainer
fix(ingress): ingress indicate missing services EE-4358 (#7794)
parent
e9de484c3e
commit
ae0b9b1e30
|
@ -36,6 +36,8 @@
|
|||
placeholder="80"
|
||||
ng-min="1"
|
||||
ng-max="65535"
|
||||
min="1"
|
||||
max="65535"
|
||||
ng-change="$ctrl.servicePort($index)"
|
||||
required
|
||||
ng-disabled="$ctrl.originalIngresses.length === 0 || ($ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled)"
|
||||
|
@ -70,6 +72,8 @@
|
|||
placeholder="80"
|
||||
ng-min="1"
|
||||
ng-max="65535"
|
||||
min="1"
|
||||
max="65535"
|
||||
required
|
||||
ng-disabled="$ctrl.originalIngresses.length === 0 || ($ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled)"
|
||||
ng-change="$ctrl.onChangeServicePort()"
|
||||
|
@ -105,6 +109,8 @@
|
|||
placeholder="30080"
|
||||
ng-min="30000"
|
||||
ng-max="32767"
|
||||
min="30000"
|
||||
max="32767"
|
||||
required
|
||||
ng-change="$ctrl.onChangeNodePort()"
|
||||
data-cy="k8sAppCreate-nodeportPort_{{ $index }}"
|
||||
|
@ -139,6 +145,8 @@
|
|||
placeholder="80"
|
||||
ng-min="1"
|
||||
ng-max="65535"
|
||||
min="1"
|
||||
max="65535"
|
||||
required
|
||||
ng-disabled="$ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled"
|
||||
data-cy="k8sAppCreate-loadbalancerPort_{{ $index }}"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 form-inline">
|
||||
<div class="col-sm-5" style="padding-left: 0px">
|
||||
<div class="col-sm-6" style="padding-left: 0px">
|
||||
<select
|
||||
class="form-control"
|
||||
ng-model="$ctrl.state.selected"
|
||||
|
|
|
@ -29,7 +29,10 @@ export function IngressDataTable() {
|
|||
const environmentId = useEnvironmentId();
|
||||
|
||||
const nsResult = useNamespaces(environmentId);
|
||||
const result = useIngresses(environmentId, Object.keys(nsResult?.data || {}));
|
||||
const ingressesQuery = useIngresses(
|
||||
environmentId,
|
||||
Object.keys(nsResult?.data || {})
|
||||
);
|
||||
|
||||
const settings = useStore();
|
||||
|
||||
|
@ -40,11 +43,11 @@ export function IngressDataTable() {
|
|||
|
||||
return (
|
||||
<Datatable
|
||||
dataset={result.data || []}
|
||||
dataset={ingressesQuery.data || []}
|
||||
storageKey="ingressClassesNameSpace"
|
||||
columns={columns}
|
||||
settingsStore={settings}
|
||||
isLoading={result.isLoading}
|
||||
isLoading={ingressesQuery.isLoading}
|
||||
emptyContentLabel="No supported ingresses found"
|
||||
titleOptions={{
|
||||
icon: 'svg-route',
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { CellProps, Column } from 'react-table';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
import { Badge } from '@@/Badge';
|
||||
|
||||
import { Ingress, TLS, Path } from '../../types';
|
||||
|
||||
|
@ -31,11 +32,17 @@ export const ingressRules: Column<Ingress> = {
|
|||
const isHttp = isHTTP(row.original.TLS || [], path.Host);
|
||||
return (
|
||||
<div key={`${path.Host}${path.Path}${path.ServiceName}:${path.Port}`}>
|
||||
<span className="flex px-2 flex-nowrap items-center gap-1">
|
||||
{link(path.Host, path.Path, isHttp)}
|
||||
<span className="px-2">
|
||||
<Icon icon="arrow-right" feather />
|
||||
</span>
|
||||
{`${path.ServiceName}:${path.Port}`}
|
||||
{!path.HasService && (
|
||||
<Badge type="warn" className="ml-1 gap-1">
|
||||
<Icon icon="alert-triangle" feather />
|
||||
Service doesn't exist
|
||||
</Badge>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
withInvalidate,
|
||||
} from '@/react-tools/react-query';
|
||||
|
||||
import { getServices } from '../services/service';
|
||||
|
||||
import {
|
||||
getIngresses,
|
||||
getIngress,
|
||||
|
@ -65,11 +67,43 @@ export function useIngresses(
|
|||
'ingress',
|
||||
],
|
||||
async () => {
|
||||
const ingresses = await Promise.all(
|
||||
const settledIngressesPromise = await Promise.allSettled(
|
||||
namespaces.map((namespace) => getIngresses(environmentId, namespace))
|
||||
);
|
||||
const ingresses = settledIngressesPromise
|
||||
.filter(isFulfilled)
|
||||
?.map((i) => i.value);
|
||||
// flatten the array and remove empty ingresses
|
||||
const filteredIngresses = ingresses.flat().filter((ing) => ing);
|
||||
|
||||
// get all services in only the namespaces that the ingresses are in to find missing services
|
||||
const uniqueNamespacesWithIngress = [
|
||||
...new Set(filteredIngresses.map((ing) => ing?.Namespace)),
|
||||
];
|
||||
const settledServicesPromise = await Promise.allSettled(
|
||||
uniqueNamespacesWithIngress.map((ns) => getServices(environmentId, ns))
|
||||
);
|
||||
const services = settledServicesPromise
|
||||
.filter(isFulfilled)
|
||||
?.map((s) => s.value)
|
||||
.flat();
|
||||
|
||||
// check if each ingress path service has a service that still exists
|
||||
filteredIngresses.forEach((ing, iIndex) => {
|
||||
const servicesInNamespace = services?.filter(
|
||||
(service) => service?.Namespace === ing?.Namespace
|
||||
);
|
||||
const serviceNamesInNamespace = servicesInNamespace?.map(
|
||||
(service) => service.Name
|
||||
);
|
||||
ing.Paths.forEach((path, pIndex) => {
|
||||
if (!serviceNamesInNamespace?.includes(path.ServiceName)) {
|
||||
filteredIngresses[iIndex].Paths[pIndex].HasService = false;
|
||||
} else {
|
||||
filteredIngresses[iIndex].Paths[pIndex].HasService = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
return filteredIngresses;
|
||||
},
|
||||
{
|
||||
|
@ -156,3 +190,9 @@ export function useIngressControllers(
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
function isFulfilled<T>(
|
||||
input: PromiseSettledResult<T>
|
||||
): input is PromiseFulfilledResult<T> {
|
||||
return input.status === 'fulfilled';
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ export interface Path {
|
|||
Port: number;
|
||||
Path: string;
|
||||
PathType: string;
|
||||
HasService?: boolean;
|
||||
}
|
||||
|
||||
export interface TLS {
|
||||
|
|
|
@ -6,10 +6,6 @@
|
|||
margin-bottom: 5px !important;
|
||||
}
|
||||
|
||||
.header > div:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.header .meta div {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
|
Loading…
Reference in New Issue