diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationDetailsWidget.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationDetailsWidget.tsx index 8f0d30ec4..b845ee8da 100644 --- a/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationDetailsWidget.tsx +++ b/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationDetailsWidget.tsx @@ -27,6 +27,7 @@ import { ApplicationAutoScalingTable } from './ApplicationAutoScalingTable'; import { ApplicationEnvVarsTable } from './ApplicationEnvVarsTable'; import { ApplicationVolumeConfigsTable } from './ApplicationVolumeConfigsTable'; import { ApplicationPersistentDataTable } from './ApplicationPersistentDataTable'; +import { PlacementsTable } from './PlacementsTable'; export function ApplicationDetailsWidget() { const stateAndParams = useCurrentStateAndParams(); @@ -140,6 +141,7 @@ export function ApplicationDetailsWidget() { appName={name} app={app} /> + {!externalApp && } diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/PlacementsTable.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/PlacementsTable.tsx new file mode 100644 index 000000000..4e7141198 --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/PlacementsTable.tsx @@ -0,0 +1,84 @@ +import { Minimize2 } from 'lucide-react'; +import { NodeSelectorRequirement, Pod } from 'kubernetes-types/core/v1'; +import { useMemo } from 'react'; + +import { Icon } from '@@/Icon'; +import { TextTip } from '@@/Tip/TextTip'; + +import { Application } from '../../types'; +import { applicationIsKind } from '../../utils'; + +type Props = { + app?: Application; +}; + +export function PlacementsTable({ app }: Props) { + const formPlacements = useAppPlacements(app); + return ( + <> +
+ + Placement preferences and constraints +
+ {!formPlacements.length && ( + + This application has no pod preference or constraint rules from the + application form. See the application YAML for other placement rules. + + )} + {formPlacements.length > 0 && ( + + + + + + + + + {formPlacements.map((placement, i) => ( + + + + + ))} + +
KeyValue(s)
{placement.key}{placement.values?.join(', ')}
+ )} + + ); +} + +// useAppPlacements is a custom hook that returns the placements that relate to the Portainer application form. +function useAppPlacements(app?: Application): NodeSelectorRequirement[] { + const formPlacements = useMemo(() => { + if (!app) { + return []; + } + // firstly get the pod spec + const podSpec = applicationIsKind('Pod', app) + ? app.spec + : app.spec?.template?.spec; + + // secondly filter all placements to get the placements that are related to the Portainer form. They are: + // - preference (s) in spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution + const placements = + podSpec?.affinity?.nodeAffinity + ?.preferredDuringSchedulingIgnoredDuringExecution; + + // - matchExpressions in preference + const placementsWithMatchExpressions = placements?.filter( + (placement) => placement?.preference?.matchExpressions + ); + + // - only matchExpressions items with the operator: In + const portainerPlacements = + placementsWithMatchExpressions?.flatMap( + (placement) => + placement?.preference?.matchExpressions?.filter( + (expression) => expression?.operator === 'In' + ) || [] + ) || []; + return portainerPlacements; + }, [app]); + return formPlacements; +} diff --git a/app/react/kubernetes/applications/DetailsView/PlacementsDatatable/PlacementsDatatable.tsx b/app/react/kubernetes/applications/DetailsView/PlacementsDatatable/PlacementsDatatable.tsx index 2e2f57a9b..47585cbc3 100644 --- a/app/react/kubernetes/applications/DetailsView/PlacementsDatatable/PlacementsDatatable.tsx +++ b/app/react/kubernetes/applications/DetailsView/PlacementsDatatable/PlacementsDatatable.tsx @@ -36,7 +36,7 @@ export function PlacementsDatatable({ !row.original.acceptsApplication} - title="Placement constraints/preferences" + title="Nodes vs. placement constraints/preferences" titleIcon={Minimize2} dataset={dataset} settingsManager={tableState} @@ -51,8 +51,8 @@ export function PlacementsDatatable({ ) : ( - The placement table helps you understand whether or not this - application can be deployed on a specific node. + The table below shows whether or not this application can be + deployed on the nodes listed. ) }