Skip to content

Commit b210aac

Browse files
authored
Merge pull request #535 from devtron-labs/feat/extend-node-bulk-actions
feat: extend node bulk actions (cordon, un-cordon & drain) & other fixes
2 parents 95c18e1 + 3596708 commit b210aac

File tree

16 files changed

+262
-53
lines changed

16 files changed

+262
-53
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "1.5.9",
3+
"version": "1.5.10",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",
Lines changed: 8 additions & 0 deletions
Loading

src/Assets/Icon/ic-medium-pause.svg

Lines changed: 3 additions & 0 deletions
Loading

src/Assets/Icon/ic-medium-play.svg

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Checkbox } from '@Common/Checkbox'
2+
import { Tooltip } from '@Common/Tooltip'
3+
import { CHECKBOX_VALUE } from '@Common/Types'
4+
import { ReactComponent as ICTimer } from '@Icons/ic-timer.svg'
5+
import { ChangeEvent, FocusEvent } from 'react'
6+
import { DRAIN_NODE_MODAL_MESSAGING, NODE_DRAIN_OPTIONS_CHECKBOX_CONFIG } from './constants'
7+
import { AdditionalConfirmationModalOptionsProps, NodeDrainRequest } from './types'
8+
9+
const NodeDrainOptions = ({
10+
optionsData,
11+
setOptionsData: setNodeDrainOptions,
12+
children,
13+
}: AdditionalConfirmationModalOptionsProps<NodeDrainRequest['nodeDrainOptions']>) => {
14+
const nodeDrainOptions: NodeDrainRequest['nodeDrainOptions'] = optionsData ?? {
15+
gracePeriodSeconds: -1,
16+
deleteEmptyDirData: false,
17+
disableEviction: false,
18+
force: false,
19+
ignoreAllDaemonSets: false,
20+
}
21+
22+
const handleGracePeriodOnChange = (e: ChangeEvent<HTMLInputElement>) => {
23+
setNodeDrainOptions({
24+
...nodeDrainOptions,
25+
gracePeriodSeconds: e.target.value ? Number(e.target.value) : -1,
26+
})
27+
}
28+
29+
const handleGracePeriodOnBlur = (e: FocusEvent<HTMLInputElement>) => {
30+
if (!e.target.value || Number(e.target.value) < -1) {
31+
e.target.value = '-1'
32+
}
33+
}
34+
35+
const getCheckboxOnChangeHandler =
36+
(key: keyof NodeDrainRequest['nodeDrainOptions']) => (e: ChangeEvent<HTMLInputElement>) => {
37+
setNodeDrainOptions({
38+
...nodeDrainOptions,
39+
[key]: e.target.checked,
40+
})
41+
}
42+
43+
return (
44+
<div className="flexbox-col dc__gap-12 w-100">
45+
<div>
46+
<div className="flexbox dc__gap-8 dc__align-items-center px-8 py-2">
47+
<ICTimer className="icon-dim-20 dc__no-shrink scn-7" />
48+
<Tooltip content={DRAIN_NODE_MODAL_MESSAGING.GracePeriod.infoText} alwaysShowTippyOnHover>
49+
<span className="fs-13 cn-9 lh-20 dc__underline-dotted">Grace period</span>
50+
</Tooltip>
51+
<span className="flex left dc__border br-4 cn-9 fw-4 fs-13 lh-20 dc__overflow-hidden">
52+
<input
53+
name="grace-period"
54+
type="number"
55+
autoComplete="off"
56+
min={-1}
57+
defaultValue={nodeDrainOptions.gracePeriodSeconds}
58+
className="px-8 py-4 lh-20 w-60 dc__no-border"
59+
onChange={handleGracePeriodOnChange}
60+
onBlur={handleGracePeriodOnBlur}
61+
/>
62+
<span className="flex px-8 py-4 dc__border--left">sec</span>
63+
</span>
64+
</div>
65+
66+
{NODE_DRAIN_OPTIONS_CHECKBOX_CONFIG.map(({ key, infoText, label }) => (
67+
<Checkbox
68+
key={key}
69+
value={CHECKBOX_VALUE.CHECKED}
70+
isChecked={nodeDrainOptions[key]}
71+
dataTestId="disable-eviction"
72+
rootClassName="mt-0 mb-0 ml-8 mr-8 cn-9 fs-13 py-6 px-8 form__checkbox__root--gap-8"
73+
onChange={getCheckboxOnChangeHandler(key)}
74+
>
75+
<Tooltip content={infoText} alwaysShowTippyOnHover>
76+
<span className="dc__underline-dotted">{label}</span>
77+
</Tooltip>
78+
</Checkbox>
79+
))}
80+
</div>
81+
82+
{children}
83+
</div>
84+
)
85+
}
86+
87+
export default NodeDrainOptions

src/Pages/ResourceBrowser/ResourceBrowser.Types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export interface K8sResourceListPayloadType {
5959
}
6060

6161
export type K8sResourceDetailDataType = {
62-
[key: string]: string | number | object
62+
[key: string]: string | number | object | boolean
6363
}
6464

6565
export interface K8sResourceDetailType {
@@ -71,11 +71,17 @@ export interface BulkSelectionActionWidgetProps {
7171
count: number
7272
handleOpenBulkDeleteModal: () => void
7373
handleClearBulkSelection: () => void
74+
handleOpenCordonNodeModal: () => void
75+
handleOpenUncordonNodeModal: () => void
76+
handleOpenDrainNodeModal: () => void
7477
handleOpenRestartWorkloadModal: () => void
7578
parentRef: RefObject<HTMLDivElement>
7679
showBulkRestartOption: boolean
80+
showNodeListingOptions: boolean
7781
}
7882

83+
export type RBBulkOperationType = 'restart' | 'delete' | 'cordon' | 'uncordon' | 'drain'
84+
7985
export interface CreateResourceRequestBodyType {
8086
appId: string
8187
clusterId: number
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,84 @@
11
import { SelectPickerOptionType } from '@Shared/Components'
2+
import { ReactComponent as ICMediumPlay } from '@Icons/ic-medium-play.svg'
3+
import { ReactComponent as ICMediumPause } from '@Icons/ic-medium-pause.svg'
4+
import { ReactComponent as ICCleanBrush } from '@Icons/ic-medium-clean-brush.svg'
5+
import { NodeDrainRequest } from './types'
26

37
export const ALL_NAMESPACE_OPTION: Readonly<Pick<SelectPickerOptionType<string>, 'value' | 'label'>> = {
48
value: 'all',
59
label: 'All namespaces',
610
}
11+
12+
export const DRAIN_NODE_MODAL_MESSAGING = {
13+
DrainIcon: ICCleanBrush,
14+
GracePeriod: {
15+
heading: 'Grace period',
16+
infoText:
17+
'Period of time in seconds given to each pod to terminate gracefully. If negative, the default value specified in the pod will be used.',
18+
},
19+
DeleteEmptyDirectoryData: {
20+
heading: 'Delete empty directory data',
21+
infoText: 'Enabling this field will delete the pods using empty directory data when the node is drained.',
22+
},
23+
DisableEviction: {
24+
heading: 'Disable eviction (use with caution)',
25+
infoText: `Enabling this field will force drain to use delete, even if eviction is supported. This will bypass checking PodDisruptionBudgets.
26+
Note: Make sure to use with caution.`,
27+
},
28+
ForceDrain: {
29+
heading: 'Force drain',
30+
infoText:
31+
'Enabling this field will force drain a node even if there are pods that do not declare a controller.',
32+
},
33+
IgnoreDaemonSets: {
34+
heading: 'Ignore DaemonSets',
35+
infoText: 'Enabling this field will ignore DaemonSet-managed pods.',
36+
},
37+
Actions: {
38+
infoText: 'Drain will cordon off the node and evict all pods of the node.',
39+
drain: 'Drain',
40+
draining: 'Draining node',
41+
cancel: 'Cancel',
42+
},
43+
}
44+
45+
export const CORDON_NODE_MODAL_MESSAGING = {
46+
UncordonIcon: ICMediumPlay,
47+
CordonIcon: ICMediumPause,
48+
cordonInfoText:
49+
'Cordoning a node will mark it as unschedulable. By cordoning a node, you can be sure that no new pods will be scheduled on the node.',
50+
uncordonInfoText:
51+
'Uncordoning this node will mark this node as schedulable. By uncordoning a node, you will allow pods to be scheduled on this node.',
52+
cordon: 'Cordon',
53+
uncordon: 'Uncordon',
54+
cordoning: 'Cordoning node',
55+
uncordoning: 'Uncordoning node',
56+
cancel: 'Cancel',
57+
}
58+
59+
export const NODE_DRAIN_OPTIONS_CHECKBOX_CONFIG: {
60+
key: Exclude<keyof NodeDrainRequest['nodeDrainOptions'], 'gracePeriodSeconds'>
61+
infoText: string
62+
label: string
63+
}[] = [
64+
{
65+
key: 'deleteEmptyDirData',
66+
infoText: DRAIN_NODE_MODAL_MESSAGING.DeleteEmptyDirectoryData.infoText,
67+
label: DRAIN_NODE_MODAL_MESSAGING.DeleteEmptyDirectoryData.heading,
68+
},
69+
{
70+
key: 'disableEviction',
71+
infoText: DRAIN_NODE_MODAL_MESSAGING.DisableEviction.infoText,
72+
label: DRAIN_NODE_MODAL_MESSAGING.DisableEviction.heading,
73+
},
74+
{
75+
key: 'force',
76+
infoText: DRAIN_NODE_MODAL_MESSAGING.ForceDrain.infoText,
77+
label: DRAIN_NODE_MODAL_MESSAGING.ForceDrain.heading,
78+
},
79+
{
80+
key: 'ignoreAllDaemonSets',
81+
infoText: DRAIN_NODE_MODAL_MESSAGING.IgnoreDaemonSets.infoText,
82+
label: DRAIN_NODE_MODAL_MESSAGING.IgnoreDaemonSets.heading,
83+
},
84+
] as const

src/Pages/ResourceBrowser/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ export * from './Helper'
1919
export * from './constants'
2020
export * from './types'
2121
export * from './service'
22+
export { default as NodeDrainOptions } from './NodeDrainOptions'

src/Pages/ResourceBrowser/service.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { get, post, trash } from '@Common/Api'
1+
import { get, post, put, trash } from '@Common/Api'
22
import { ROUTES } from '@Common/Constants'
33
import { APIOptions, ResponseType } from '@Common/Types'
44
import {
@@ -10,7 +10,7 @@ import {
1010
ResourceListPayloadType,
1111
ResourceType,
1212
} from './ResourceBrowser.Types'
13-
import { ClusterDetail } from './types'
13+
import { ClusterDetail, NodeCordonRequest } from './types'
1414

1515
export const getK8sResourceList = (
1616
resourceListPayload: K8sResourceListPayloadType,
@@ -34,4 +34,14 @@ export const deleteNodeCapacity = (
3434
abortControllerRef?: APIOptions['abortControllerRef'],
3535
): Promise<ResponseType> => trash(ROUTES.NODE_CAPACITY, requestPayload, { abortControllerRef })
3636

37+
export const cordonNodeCapacity = (
38+
requestPayload: NodeCordonRequest,
39+
abortControllerRef?: APIOptions['abortControllerRef'],
40+
): Promise<ResponseType> => put(`${ROUTES.NODE_CAPACITY}/cordon`, requestPayload, { abortControllerRef })
41+
42+
export const drainNodeCapacity = (
43+
requestPayload: NodeActionRequest,
44+
abortControllerRef?: APIOptions['abortControllerRef'],
45+
): Promise<ResponseType> => put(`${ROUTES.NODE_CAPACITY}/drain`, requestPayload, { abortControllerRef })
46+
3747
export const getClusterListRaw = () => get<ClusterDetail[]>(ROUTES.CLUSTER_LIST_RAW)

0 commit comments

Comments
 (0)