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 && (
+
+
+
+ Key |
+ Value(s) |
+
+
+
+ {formPlacements.map((placement, i) => (
+
+ {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.
)
}