Skip to content

Commit

Permalink
Add a plan details item to edit boot disk
Browse files Browse the repository at this point in the history
Signed-off-by: yaacov <[email protected]>
  • Loading branch information
yaacov committed Jun 17, 2024
1 parent fc1b129 commit 9fcd1c4
Show file tree
Hide file tree
Showing 10 changed files with 619 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@
"Assessment": "Assessment",
"Authentication type": "Authentication type",
"Bandwidth": "Bandwidth",
"Boot from first root device": "Boot from first root device",
"Boot from the first hard drive": "Boot from the first hard drive",
"Boot from the first partition on the first hard drive": "Boot from the first partition on the first hard drive",
"Boot from the first partition on the second hard drive": "Boot from the first partition on the second hard drive",
"Boot from the second hard drive": "Boot from the second hard drive",
"Boot from the second partition on the first hard drive": "Boot from the second partition on the first hard drive",
"Boot from the second partition on the second hard drive": "Boot from the second partition on the second hard drive",
"CA certificate": "CA certificate",
"CA certificate - disabled when 'Skip certificate validation' is selected": "CA certificate - disabled when 'Skip certificate validation' is selected",
"CA certificate - leave empty to use system CA certificates": "CA certificate - leave empty to use system CA certificates",
Expand All @@ -60,6 +67,7 @@
"Cannot retrieve certificate": "Cannot retrieve certificate",
"Category": "Category",
"Certificate change detected": "Certificate change detected",
"Choose the root filesystem to be converted.": "Choose the root filesystem to be converted.",
"Clear all filters": "Clear all filters",
"Click the pencil for setting provider web UI link": "Click the pencil for setting provider web UI link",
"Click the update credentials button to save your changes, button is disabled until a change is detected.": "Click the update credentials button to save your changes, button is disabled until a change is detected.",
Expand Down Expand Up @@ -142,6 +150,7 @@
"Edit Provider Credentials": "Edit Provider Credentials",
"Edit provider credentials.\n Use this link to edit the providers credentials instead of editing the secret directly.": "Edit provider credentials.\n Use this link to edit the providers credentials instead of editing the secret directly.",
"Edit provider web UI link": "Edit provider web UI link",
"Edit root device": "Edit root device",
"Edit Snapshot polling interval (seconds)": "Edit Snapshot polling interval (seconds)",
"Edit StorageMap": "Edit StorageMap",
"Edit target namespace": "Edit target namespace",
Expand Down Expand Up @@ -179,6 +188,7 @@
"Filter by template": "Filter by template",
"Filter by tenant": "Filter by tenant",
"Filter provider": "Filter provider",
"First root device": "First root device",
"Flavor": "Flavor",
"Folder": "Folder",
"GPUs/Host Devices": "GPUs/Host Devices",
Expand Down Expand Up @@ -382,6 +392,7 @@
"Restore default columns": "Restore default columns",
"Return to the providers list page": "Return to the providers list page",
"Reveal values": "Reveal values",
"Root device": "Root device",
"Run the migration plan.": "Run the migration plan.",
"Running": "Running",
"Running virtual machines": "Running virtual machines",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
import React from 'react';

import {
Button,
Divider,
MenuToggle,
MenuToggleElement,
Text,
TextInputGroup,
TextInputGroupMain,
TextInputGroupUtilities,
} from '@patternfly/react-core';
import { Select, SelectList, SelectOption, SelectOptionProps } from '@patternfly/react-core/next';
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';

/**
* Props for the FilterableSelect component.
*/
export interface FilterableSelectProps {
/** Array of options to display in the select dropdown */
selectOptions: SelectOptionProps[];
/** The currently selected value */
value: string;
/** Callback function when an option is selected */
onSelect: (value: string | number) => void;
/** Whether the user can create new options */
canCreate?: boolean;
/** Placeholder text for the input field */
placeholder?: string;
/** Label to display when no results are found */
noResultFoundLabel?: string;
/** Label to display for the option to create a new item */
createNewOptionLabel?: string;
}

/**
* A filterable select component that allows users to select from a list of options,
* with the ability to filter the options and create new ones if `canCreate` is enabled.
*
* @param {FilterableSelectProps} props The props for the FilterableSelect component.
* @returns {JSX.Element} The rendered FilterableSelect component.
*/
export const FilterableSelect: React.FunctionComponent<FilterableSelectProps> = ({
selectOptions: initialSelectOptions,
value,
onSelect: onSelect,
canCreate,
placeholder = 'Select item',
noResultFoundLabel = 'No results found',
createNewOptionLabel = 'Create new option:',
}) => {
const [isOpen, setIsOpen] = React.useState(false);
const [selectedItem, setSelectedItem] = React.useState<string>(value);
/**
* inputValue: The current value displayed in the input field.
* This is the value the user types in.
*/
const [inputValue, setInputValue] = React.useState<string>(value);
/**
* filterValue: The value used to filter the options.
* This is typically synchronized with inputValue, but they can be different if needed.
*/
const [filterValue, setFilterValue] = React.useState<string>('');
const [selectOptions, setSelectOptions] =
React.useState<SelectOptionProps[]>(initialSelectOptions);
const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | null>(null);

const menuRef = React.useRef<HTMLDivElement>(null);
const textInputRef = React.useRef<HTMLInputElement>();

/**
* Sets the selected item and triggers the onSelect callback.
*
* @param {string} value The value to set as selected.
*/
const setSelected = (value: string) => {
setSelectedItem(value);
setFilterValue('');

// Call the external on select hook.
onSelect(value);
};

/**
* Updates the select options based on the filter value.
*/
React.useEffect(() => {
let newSelectOptions: SelectOptionProps[] = initialSelectOptions;

// Filter menu items based on the text input value when one exists
if (filterValue) {
newSelectOptions = initialSelectOptions.filter((menuItem) =>
String(menuItem.itemId).toLowerCase().includes(filterValue.toLowerCase()),
);

// When no options are found after filtering, display 'No results found'
if (!newSelectOptions.length) {
newSelectOptions = [{ isDisabled: true, children: noResultFoundLabel }];
}
}

setSelectOptions(newSelectOptions);
}, [filterValue, initialSelectOptions, noResultFoundLabel]);

/**
* Toggles the open state of the select dropdown.
*/
const onToggleClick = () => {
setIsOpen(!isOpen);
};

/**
* Handles item selection from the dropdown.
*
* @param {React.MouseEvent<Element, MouseEvent> | undefined} _event The click event.
* @param {string | number | undefined} itemId The id of the selected item.
*/
const onItemSelect = (
_event: React.MouseEvent<Element, MouseEvent> | undefined,
itemId: string | number | undefined,
) => {
if (itemId !== undefined) {
setInputValue(itemId as string);
setFilterValue(itemId as string);
setSelected(itemId as string);
}
setIsOpen(false);
setFocusedItemIndex(null);
};

/**
* Handles changes in the text input.
*
* @param {React.FormEvent<HTMLInputElement>} _event The input event.
* @param {string} value The new input value.
*/
const onTextInputChange = (_event: React.FormEvent<HTMLInputElement>, value: string) => {
setInputValue(value);
setFilterValue(value);
};

/**
* Handles arrow key navigation within the dropdown.
*
* @param {string} key The key pressed.
*/
const handleMenuArrowKeys = (key: string) => {
let indexToFocus;

if (isOpen) {
if (key === 'ArrowUp') {
// When no index is set or at the first index, focus to the last, otherwise decrement focus index
if (focusedItemIndex === null || focusedItemIndex === 0) {
indexToFocus = selectOptions.length - 1;
} else {
indexToFocus = focusedItemIndex - 1;
}
}

if (key === 'ArrowDown') {
// When no index is set or at the last index, focus to the first, otherwise increment focus index
if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
indexToFocus = 0;
} else {
indexToFocus = focusedItemIndex + 1;
}
}

setFocusedItemIndex(indexToFocus);
}
};

/**
* Handles keydown events in the text input.
*
* @param {React.KeyboardEvent<HTMLInputElement>} event The keyboard event.
*/
const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
const enabledMenuItems = selectOptions.filter((menuItem) => !menuItem.isDisabled);
const [firstMenuItem] = enabledMenuItems;
const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;

switch (event.key) {
// Select the first available option
case 'Enter':
event.preventDefault();

if (isOpen) {
setInputValue(String(focusedItem?.itemId || filterValue));
setSelected(String(focusedItem?.itemId || filterValue));
}

setIsOpen((prevIsOpen) => !prevIsOpen);
setFocusedItemIndex(null);

break;
case 'Tab':
case 'Escape':
setIsOpen(false);
break;
case 'ArrowUp':
case 'ArrowDown':
handleMenuArrowKeys(event.key);
break;
default:
!isOpen && setIsOpen(true);
}
};

/**
* Renders the toggle component for the dropdown.
*
* @param {React.Ref<any>} toggleRef The reference to the toggle component.
* @returns {JSX.Element} The rendered toggle component.
*/
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
variant="typeahead"
onClick={onToggleClick}
isExpanded={isOpen}
isFullWidth
>
<TextInputGroup isPlain>
<TextInputGroupMain
value={inputValue}
onClick={onToggleClick}
onChange={onTextInputChange}
onKeyDown={onInputKeyDown}
id="typeahead-select-input"
autoComplete="off"
innerRef={textInputRef}
placeholder={placeholder}
/>

<TextInputGroupUtilities>
{!!inputValue && (
<Button
variant="plain"
onClick={() => {
setSelected('');
setInputValue('');
setFilterValue('');
}}
aria-label="Clear input value"
>
<TimesIcon aria-hidden />
</Button>
)}
</TextInputGroupUtilities>
</TextInputGroup>
</MenuToggle>
);

return (
<Select
id="typeahead-select"
ref={menuRef}
isOpen={isOpen}
selected={selectedItem}
onSelect={onItemSelect}
onOpenChange={() => {
setIsOpen(false);
setFilterValue('');
setInputValue(selectedItem);
}}
toggle={toggle}
>
<SelectList>
{selectOptions.map((option, index) => (
<SelectOption
key={option.itemId}
isFocused={focusedItemIndex === index}
className={option.className}
onClick={() => setSelected(option.itemId)}
{...option}
ref={null}
/>
))}
{canCreate && !selectOptions.find((option) => option.itemId === filterValue) && (
<>
<Divider />
<SelectOption
itemId={filterValue}
key={filterValue}
onClick={() => setSelected(filterValue)}
ref={null}
>
<>
<Text>{createNewOptionLabel}</Text>
<Text>{`"${filterValue}"`}</Text>
</>
</SelectOption>
</>
)}
</SelectList>
</Select>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DescriptionList } from '@patternfly/react-core';
import {
PreserveClusterCpuModelDetailsItem,
PreserveStaticIPsDetailsItem,
RootDiskDetailsItem,
SetLUKSEncryptionPasswordsDetailsItem,
TargetNamespaceDetailsItem,
TransferNetworkDetailsItem,
Expand Down Expand Up @@ -75,6 +76,10 @@ export const SettingsSectionInternal: React.FC<SettingsSectionProps> = ({ obj, p
{['vsphere'].includes(sourceProvider?.spec?.type) && (
<SetLUKSEncryptionPasswordsDetailsItem resource={obj} canPatch={permissions.canPatch} />
)}

{['vsphere'].includes(sourceProvider?.spec?.type) && (
<RootDiskDetailsItem resource={obj} canPatch={permissions.canPatch} />
)}
</DescriptionList>
</>
);
Expand Down
Loading

0 comments on commit 9fcd1c4

Please sign in to comment.