|
| 1 | +import React, { Fragment, useState } from "react"; |
| 2 | +import { EuiSuperSelect, EuiText } from "@elastic/eui"; |
| 3 | +import { useMerlinApi } from "../../../../../hooks/useMerlinApi"; |
| 4 | + |
| 5 | +const DEFAULT_VALUE = "__default__"; |
| 6 | +const HOSTNAME_LABEL = "kubernetes.io/hostname"; |
| 7 | + |
| 8 | +const taintSummary = (taints) => |
| 9 | + taints && taints.length > 0 |
| 10 | + ? taints |
| 11 | + .map((t) => `${t.key}=${t.value || ""}:${t.effect || ""}`) |
| 12 | + .join(", ") |
| 13 | + : "no taints"; |
| 14 | + |
| 15 | +/** |
| 16 | + * NodeSelect - a dropdown to pin a model onto a specific node. |
| 17 | + * |
| 18 | + * Selecting a node sets the node selector to that node's hostname (pin) and adds |
| 19 | + * a toleration for each of the node's taints (permit), so the pod can land there. |
| 20 | + * |
| 21 | + * Props |
| 22 | + * environment – environment name (used to fetch that cluster's nodes) |
| 23 | + * onSelect(sel, tols) – called with the new node_selector + tolerations |
| 24 | + */ |
| 25 | +export const NodeSelect = ({ environment, onSelect }) => { |
| 26 | + const [selected, setSelected] = useState(DEFAULT_VALUE); |
| 27 | + |
| 28 | + const [{ data: nodes, isLoaded }] = useMerlinApi( |
| 29 | + `/environments/${environment}/nodes`, |
| 30 | + {}, |
| 31 | + [], |
| 32 | + !!environment |
| 33 | + ); |
| 34 | + |
| 35 | + const options = [ |
| 36 | + { |
| 37 | + value: DEFAULT_VALUE, |
| 38 | + inputDisplay: "Default — any available node", |
| 39 | + }, |
| 40 | + ...(nodes || []).map((node) => ({ |
| 41 | + value: node.name, |
| 42 | + inputDisplay: node.name, |
| 43 | + disabled: !node.ready, |
| 44 | + dropdownDisplay: ( |
| 45 | + <Fragment> |
| 46 | + <strong> |
| 47 | + {node.name} |
| 48 | + {!node.ready ? " (not ready)" : ""} |
| 49 | + </strong> |
| 50 | + <EuiText size="s" color="subdued"> |
| 51 | + <p>{taintSummary(node.taints)}</p> |
| 52 | + </EuiText> |
| 53 | + </Fragment> |
| 54 | + ), |
| 55 | + })), |
| 56 | + ]; |
| 57 | + |
| 58 | + const onChange = (value) => { |
| 59 | + setSelected(value); |
| 60 | + if (value === DEFAULT_VALUE) { |
| 61 | + onSelect({}, []); |
| 62 | + return; |
| 63 | + } |
| 64 | + const node = (nodes || []).find((n) => n.name === value); |
| 65 | + if (!node) return; |
| 66 | + |
| 67 | + const nodeSelector = { [HOSTNAME_LABEL]: node.name }; |
| 68 | + const tolerations = (node.taints || []).map((t) => ({ |
| 69 | + key: t.key, |
| 70 | + operator: t.value ? "Equal" : "Exists", |
| 71 | + ...(t.value ? { value: t.value } : {}), |
| 72 | + ...(t.effect ? { effect: t.effect } : {}), |
| 73 | + })); |
| 74 | + onSelect(nodeSelector, tolerations); |
| 75 | + }; |
| 76 | + |
| 77 | + return ( |
| 78 | + <EuiSuperSelect |
| 79 | + fullWidth |
| 80 | + options={options} |
| 81 | + valueOfSelected={selected} |
| 82 | + onChange={onChange} |
| 83 | + isLoading={!!environment && !isLoaded} |
| 84 | + hasDividers |
| 85 | + /> |
| 86 | + ); |
| 87 | +}; |
0 commit comments