mirror of
https://github.com/portainer/portainer.git
synced 2025-07-23 15:29:42 +02:00
fix(app): surface placement rules from form [EE-6553] (#11816)
This commit is contained in:
parent
1ba4b590f4
commit
b6daee2850
3 changed files with 89 additions and 3 deletions
|
@ -27,6 +27,7 @@ import { ApplicationAutoScalingTable } from './ApplicationAutoScalingTable';
|
||||||
import { ApplicationEnvVarsTable } from './ApplicationEnvVarsTable';
|
import { ApplicationEnvVarsTable } from './ApplicationEnvVarsTable';
|
||||||
import { ApplicationVolumeConfigsTable } from './ApplicationVolumeConfigsTable';
|
import { ApplicationVolumeConfigsTable } from './ApplicationVolumeConfigsTable';
|
||||||
import { ApplicationPersistentDataTable } from './ApplicationPersistentDataTable';
|
import { ApplicationPersistentDataTable } from './ApplicationPersistentDataTable';
|
||||||
|
import { PlacementsTable } from './PlacementsTable';
|
||||||
|
|
||||||
export function ApplicationDetailsWidget() {
|
export function ApplicationDetailsWidget() {
|
||||||
const stateAndParams = useCurrentStateAndParams();
|
const stateAndParams = useCurrentStateAndParams();
|
||||||
|
@ -140,6 +141,7 @@ export function ApplicationDetailsWidget() {
|
||||||
appName={name}
|
appName={name}
|
||||||
app={app}
|
app={app}
|
||||||
/>
|
/>
|
||||||
|
{!externalApp && <PlacementsTable app={app} />}
|
||||||
</WidgetBody>
|
</WidgetBody>
|
||||||
</Widget>
|
</Widget>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<div className="text-muted mb-4 mt-6 flex items-center">
|
||||||
|
<Icon icon={Minimize2} className="!mr-2" />
|
||||||
|
Placement preferences and constraints
|
||||||
|
</div>
|
||||||
|
{!formPlacements.length && (
|
||||||
|
<TextTip color="blue">
|
||||||
|
This application has no pod preference or constraint rules from the
|
||||||
|
application form. See the application YAML for other placement rules.
|
||||||
|
</TextTip>
|
||||||
|
)}
|
||||||
|
{formPlacements.length > 0 && (
|
||||||
|
<table className="table">
|
||||||
|
<thead>
|
||||||
|
<tr className="text-muted">
|
||||||
|
<td className="w-1/3">Key</td>
|
||||||
|
<td className="w-2/3">Value(s)</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{formPlacements.map((placement, i) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td>{placement.key}</td>
|
||||||
|
<td>{placement.values?.join(', ')}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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>('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;
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ export function PlacementsDatatable({
|
||||||
<ExpandableDatatable
|
<ExpandableDatatable
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
getRowCanExpand={(row) => !row.original.acceptsApplication}
|
getRowCanExpand={(row) => !row.original.acceptsApplication}
|
||||||
title="Placement constraints/preferences"
|
title="Nodes vs. placement constraints/preferences"
|
||||||
titleIcon={Minimize2}
|
titleIcon={Minimize2}
|
||||||
dataset={dataset}
|
dataset={dataset}
|
||||||
settingsManager={tableState}
|
settingsManager={tableState}
|
||||||
|
@ -51,8 +51,8 @@ export function PlacementsDatatable({
|
||||||
</TextTip>
|
</TextTip>
|
||||||
) : (
|
) : (
|
||||||
<TextTip color="blue">
|
<TextTip color="blue">
|
||||||
The placement table helps you understand whether or not this
|
The table below shows whether or not this application can be
|
||||||
application can be deployed on a specific node.
|
deployed on the nodes listed.
|
||||||
</TextTip>
|
</TextTip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue