diff --git a/.tekton/forklift-console-plugin-pull-request.yaml b/.tekton/forklift-console-plugin-pull-request.yaml
index 46e918968..235b389eb 100644
--- a/.tekton/forklift-console-plugin-pull-request.yaml
+++ b/.tekton/forklift-console-plugin-pull-request.yaml
@@ -30,6 +30,8 @@ spec:
value: build/Containerfile
- name: path-context
value: .
+ - name: build-source-image
+ value: "true"
pipelineSpec:
description: |
This pipeline is ideal for building container images from a Containerfile while maintaining trust after pipeline customization.
@@ -134,7 +136,7 @@ spec:
- name: name
value: init
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:0523b51c28375a3f222da91690e22eff11888ebc98a0c73c468af44762265c69
+ value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:60063fefe88e111d129cb59caff97c912722927c8a0f750253553d4c527a2396
- name: kind
value: task
resolver: bundles
@@ -225,7 +227,7 @@ spec:
- name: name
value: buildah-oci-ta
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.2@sha256:decef0e000a05daad9dd43b707c8b3a96b6125ff5a4ee096fd3e8c23a2881b9e
+ value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.2@sha256:e0f1ec1ec43ba0f9533dd70fe76a3c24ac3ca14ddd83036099c8073c94dc8258
- name: kind
value: task
resolver: bundles
@@ -254,7 +256,7 @@ spec:
- name: name
value: build-image-index
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:a89c141c8d35b2e9d9904c92c9b128f7ccf36681adac7f7422b4537b8bb077e7
+ value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:37328a4b2fc686435531ba423c26c2051822a4e70b06088c4d8eaf0e8fa6d65b
- name: kind
value: task
resolver: bundles
@@ -304,7 +306,7 @@ spec:
- name: name
value: deprecated-image-check
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.4@sha256:5a1a165fa02270f0a947d8a2131ee9d8be0b8e9d34123828c2bef589e504ee84
+ value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.4@sha256:f8efb0b22692fad908a1a75f8d5c0b6ed3b0bcd2a9853577e7be275e5bac1bb8
- name: kind
value: task
resolver: bundles
@@ -326,7 +328,7 @@ spec:
- name: name
value: clair-scan
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:0a5421111e7092740398691d5bd7c125cc0896f29531d19414bb5724ae41692a
+ value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:e428b37d253621365ffb24d4053e5f3141988ae6a30fce1c8ba73b7211396eb0
- name: kind
value: task
resolver: bundles
@@ -372,7 +374,7 @@ spec:
- name: name
value: sast-snyk-check-oci-ta
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.3@sha256:1119722a2d31b831d1aa336fd8cced0a5016c95466b6b59a58bbf3585735850f
+ value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.3@sha256:6d232347739a0366dcfc4e40afbcb5d1937dd3fea8952afb1bd6a4b0c5d1c1f5
- name: kind
value: task
resolver: bundles
@@ -394,7 +396,7 @@ spec:
- name: name
value: clamav-scan
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:6e08cf608240f57442ca5458f3c0dade3558f4f2953be8ea939232f5d5378d58
+ value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:d78221853f7ff2befc6669dd0eeb91e6611ae84ac7754150ea0f071d92ff41cb
- name: kind
value: task
resolver: bundles
@@ -414,7 +416,7 @@ spec:
- name: name
value: apply-tags
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.1@sha256:87fd7fc0e937aad1a8db9b6e377d7e444f53394dafde512d68adbea6966a4702
+ value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.1@sha256:0767c115d4ba4854d106c9cdfabdc1f1298bc2742a3fea4fefbac4b9c5873d6e
- name: kind
value: task
resolver: bundles
@@ -454,7 +456,7 @@ spec:
- name: name
value: rpms-signature-scan
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:8f3b23bf1b0ef55cc79d28604d2397a0101ac9c0c42ae26e26532eb2778c801b
+ value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:ec536e55a039052823ba74e07db3175554fb046649671d1fefd776ca064d00ac
- name: kind
value: task
resolver: bundles
diff --git a/.tekton/forklift-console-plugin-push.yaml b/.tekton/forklift-console-plugin-push.yaml
index 9d0a46460..d2e76e0e7 100644
--- a/.tekton/forklift-console-plugin-push.yaml
+++ b/.tekton/forklift-console-plugin-push.yaml
@@ -29,6 +29,8 @@ spec:
value: build/Containerfile
- name: path-context
value: .
+ - name: build-source-image
+ value: "true"
pipelineSpec:
description: |
This pipeline is ideal for building container images from a Containerfile while maintaining trust after pipeline customization.
@@ -133,7 +135,7 @@ spec:
- name: name
value: init
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:0523b51c28375a3f222da91690e22eff11888ebc98a0c73c468af44762265c69
+ value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:60063fefe88e111d129cb59caff97c912722927c8a0f750253553d4c527a2396
- name: kind
value: task
resolver: bundles
@@ -224,7 +226,7 @@ spec:
- name: name
value: buildah-oci-ta
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.2@sha256:decef0e000a05daad9dd43b707c8b3a96b6125ff5a4ee096fd3e8c23a2881b9e
+ value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.2@sha256:e0f1ec1ec43ba0f9533dd70fe76a3c24ac3ca14ddd83036099c8073c94dc8258
- name: kind
value: task
resolver: bundles
@@ -253,7 +255,7 @@ spec:
- name: name
value: build-image-index
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:a89c141c8d35b2e9d9904c92c9b128f7ccf36681adac7f7422b4537b8bb077e7
+ value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:37328a4b2fc686435531ba423c26c2051822a4e70b06088c4d8eaf0e8fa6d65b
- name: kind
value: task
resolver: bundles
@@ -303,7 +305,7 @@ spec:
- name: name
value: deprecated-image-check
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.4@sha256:5a1a165fa02270f0a947d8a2131ee9d8be0b8e9d34123828c2bef589e504ee84
+ value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.4@sha256:f8efb0b22692fad908a1a75f8d5c0b6ed3b0bcd2a9853577e7be275e5bac1bb8
- name: kind
value: task
resolver: bundles
@@ -325,7 +327,7 @@ spec:
- name: name
value: clair-scan
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:0a5421111e7092740398691d5bd7c125cc0896f29531d19414bb5724ae41692a
+ value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:e428b37d253621365ffb24d4053e5f3141988ae6a30fce1c8ba73b7211396eb0
- name: kind
value: task
resolver: bundles
@@ -371,7 +373,7 @@ spec:
- name: name
value: sast-snyk-check-oci-ta
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.3@sha256:1119722a2d31b831d1aa336fd8cced0a5016c95466b6b59a58bbf3585735850f
+ value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.3@sha256:6d232347739a0366dcfc4e40afbcb5d1937dd3fea8952afb1bd6a4b0c5d1c1f5
- name: kind
value: task
resolver: bundles
@@ -393,7 +395,7 @@ spec:
- name: name
value: clamav-scan
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:6e08cf608240f57442ca5458f3c0dade3558f4f2953be8ea939232f5d5378d58
+ value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:d78221853f7ff2befc6669dd0eeb91e6611ae84ac7754150ea0f071d92ff41cb
- name: kind
value: task
resolver: bundles
@@ -413,7 +415,7 @@ spec:
- name: name
value: apply-tags
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.1@sha256:87fd7fc0e937aad1a8db9b6e377d7e444f53394dafde512d68adbea6966a4702
+ value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.1@sha256:0767c115d4ba4854d106c9cdfabdc1f1298bc2742a3fea4fefbac4b9c5873d6e
- name: kind
value: task
resolver: bundles
@@ -453,7 +455,7 @@ spec:
- name: name
value: rpms-signature-scan
- name: bundle
- value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:8f3b23bf1b0ef55cc79d28604d2397a0101ac9c0c42ae26e26532eb2778c801b
+ value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:ec536e55a039052823ba74e07db3175554fb046649671d1fefd776ca064d00ac
- name: kind
value: task
resolver: bundles
diff --git a/packages/common/src/components/TypeaheadSelect/TypeaheadSelect.tsx b/packages/common/src/components/TypeaheadSelect/TypeaheadSelect.tsx
new file mode 100644
index 000000000..f82af1f82
--- /dev/null
+++ b/packages/common/src/components/TypeaheadSelect/TypeaheadSelect.tsx
@@ -0,0 +1,425 @@
+import React from 'react';
+
+import {
+ Button,
+ MenuToggle,
+ MenuToggleElement,
+ MenuToggleProps,
+ Select,
+ SelectList,
+ SelectOption,
+ SelectOptionProps,
+ SelectProps,
+ TextInputGroup,
+ TextInputGroupMain,
+ TextInputGroupUtilities,
+} from '@patternfly/react-core';
+import { TimesIcon } from '@patternfly/react-icons';
+
+export interface TypeaheadSelectOption extends Omit {
+ /** Content of the select option. */
+ content: string | number;
+ /** Value of the select option. */
+ value: string | number;
+ /** Indicator for option being selected */
+ isSelected?: boolean;
+}
+
+export interface TypeaheadSelectProps extends Omit {
+ /** Options of the select */
+ selectOptions: TypeaheadSelectOption[];
+ /** Callback triggered on selection. */
+ onSelect?: (
+ _event:
+ | React.MouseEvent
+ | React.KeyboardEvent
+ | undefined,
+ selection: string | number,
+ ) => void;
+ /** Callback triggered when the select opens or closes. */
+ onToggle?: (nextIsOpen: boolean) => void;
+ /** Callback triggered when the text in the input field changes. */
+ onInputChange?: (newValue: string) => void;
+ /** Function to return items matching the current filter value */
+ filterFunction?: (
+ filterValue: string,
+ options: TypeaheadSelectOption[],
+ ) => TypeaheadSelectOption[];
+ /** Callback triggered when the clear button is selected */
+ onClearSelection?: () => void;
+ /** Flag to allow clear current selection */
+ allowClear?: boolean;
+ /** Placeholder text for the select input. */
+ placeholder?: string;
+ /** Flag to indicate if the typeahead select allows new items */
+ isCreatable?: boolean;
+ /** Flag to indicate if create option should be at top of typeahead */
+ isCreateOptionOnTop?: boolean;
+ /** Message to display to create a new option */
+ createOptionMessage?: string | ((newValue: string) => string);
+ /** Message to display when no options are available. */
+ noOptionsAvailableMessage?: string;
+ /** Message to display when no options match the filter. */
+ noOptionsFoundMessage?: string | ((filter: string) => string);
+ /** Flag indicating the select should be disabled. */
+ isDisabled?: boolean;
+ /** Width of the toggle. */
+ toggleWidth?: string;
+ /** Additional props passed to the toggle. */
+ toggleProps?: MenuToggleProps;
+}
+
+const defaultNoOptionsFoundMessage = (filter: string) => `No results found for "${filter}"`;
+const defaultCreateOptionMessage = (newValue: string) => `Create "${newValue}"`;
+const defaultFilterFunction = (filterValue: string, options: TypeaheadSelectOption[]) =>
+ options.filter((o) => String(o.content).toLowerCase().includes(filterValue.toLowerCase()));
+
+export const TypeaheadSelect: React.FC = ({
+ innerRef,
+ selectOptions,
+ onSelect,
+ onToggle,
+ onInputChange,
+ filterFunction = defaultFilterFunction,
+ onClearSelection,
+ allowClear,
+ placeholder = 'Select an option',
+ noOptionsAvailableMessage = 'No options are available',
+ noOptionsFoundMessage = defaultNoOptionsFoundMessage,
+ isCreatable = false,
+ isCreateOptionOnTop = false,
+ createOptionMessage = defaultCreateOptionMessage,
+ isDisabled,
+ toggleWidth,
+ toggleProps,
+ ...props
+}: TypeaheadSelectProps) => {
+ const [isOpen, setIsOpen] = React.useState(false);
+ const [filterValue, setFilterValue] = React.useState('');
+ const [isFiltering, setIsFiltering] = React.useState(false);
+ const [focusedItemIndex, setFocusedItemIndex] = React.useState(null);
+ const [activeItemId, setActiveItemId] = React.useState(null);
+ const textInputRef = React.useRef();
+
+ const NO_RESULTS = 'no results';
+
+ const selected = React.useMemo(
+ () => selectOptions.find((option) => option.value === props.selected || option.isSelected),
+ [props.selected, selectOptions],
+ );
+
+ const filteredSelections = React.useMemo(() => {
+ let newSelectOptions: TypeaheadSelectOption[] = selectOptions;
+
+ // Filter menu items based on the text input value when one exists
+ if (isFiltering && filterValue) {
+ newSelectOptions = filterFunction(filterValue, selectOptions);
+
+ if (
+ isCreatable &&
+ filterValue.trim() &&
+ !newSelectOptions.find((o) => String(o.content).toLowerCase() === filterValue.toLowerCase())
+ ) {
+ const createOption = {
+ content:
+ typeof createOptionMessage === 'string'
+ ? createOptionMessage
+ : createOptionMessage(filterValue),
+ value: filterValue,
+ };
+ newSelectOptions = isCreateOptionOnTop
+ ? [createOption, ...newSelectOptions]
+ : [...newSelectOptions, createOption];
+ }
+
+ // When no options are found after filtering, display 'No results found'
+ if (!newSelectOptions.length) {
+ newSelectOptions = [
+ {
+ isAriaDisabled: true,
+ content:
+ typeof noOptionsFoundMessage === 'string'
+ ? noOptionsFoundMessage
+ : noOptionsFoundMessage(filterValue),
+ value: NO_RESULTS,
+ },
+ ];
+ }
+ }
+
+ // When no options are available, display 'No options available'
+ if (!newSelectOptions.length) {
+ newSelectOptions = [
+ {
+ isAriaDisabled: true,
+ content: noOptionsAvailableMessage,
+ value: NO_RESULTS,
+ },
+ ];
+ }
+
+ return newSelectOptions;
+ }, [
+ isFiltering,
+ filterValue,
+ filterFunction,
+ selectOptions,
+ noOptionsFoundMessage,
+ isCreatable,
+ isCreateOptionOnTop,
+ createOptionMessage,
+ noOptionsAvailableMessage,
+ ]);
+
+ React.useEffect(() => {
+ if (isFiltering) {
+ openMenu();
+ }
+ // Don't update on openMenu changes
+ }, [isFiltering]);
+
+ const setActiveAndFocusedItem = (itemIndex: number) => {
+ setFocusedItemIndex(itemIndex);
+ const focusedItem = selectOptions[itemIndex];
+ setActiveItemId(String(focusedItem.value));
+ };
+
+ const resetActiveAndFocusedItem = () => {
+ setFocusedItemIndex(null);
+ setActiveItemId(null);
+ };
+
+ const openMenu = () => {
+ if (!isOpen) {
+ if (onToggle) {
+ onToggle(true);
+ }
+ setIsOpen(true);
+ }
+ };
+
+ const closeMenu = () => {
+ if (onToggle) {
+ onToggle(false);
+ }
+ setIsOpen(false);
+ resetActiveAndFocusedItem();
+ setIsFiltering(false);
+ setFilterValue(String(selected?.content ?? ''));
+ };
+
+ const onInputClick = () => {
+ if (!isOpen) {
+ openMenu();
+ }
+ setTimeout(() => {
+ textInputRef.current?.focus();
+ }, 100);
+ };
+
+ const selectOption = (
+ _event:
+ | React.MouseEvent
+ | React.KeyboardEvent
+ | undefined,
+ option: TypeaheadSelectOption,
+ ) => {
+ if (onSelect) {
+ onSelect(_event, option.value);
+ }
+ closeMenu();
+ };
+
+ const handleSelect = (
+ _event: React.MouseEvent | undefined,
+ value: string | number | undefined,
+ ) => {
+ if (value && value !== NO_RESULTS) {
+ const optionToSelect = selectOptions.find((option) => option.value === value);
+ if (optionToSelect) {
+ selectOption(_event, optionToSelect);
+ } else if (isCreatable) {
+ selectOption(_event, { value, content: value });
+ }
+ }
+ };
+
+ const onTextInputChange = (_event: React.FormEvent, value: string) => {
+ setFilterValue(value || '');
+ setIsFiltering(true);
+ if (onInputChange) {
+ onInputChange(value);
+ }
+
+ resetActiveAndFocusedItem();
+ };
+
+ const handleMenuArrowKeys = (key: string) => {
+ let indexToFocus = 0;
+
+ openMenu();
+
+ if (filteredSelections.every((option) => option.isDisabled)) {
+ return;
+ }
+
+ 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 = filteredSelections.length - 1;
+ } else {
+ indexToFocus = focusedItemIndex - 1;
+ }
+
+ // Skip disabled options
+ while (filteredSelections[indexToFocus].isDisabled) {
+ indexToFocus--;
+ if (indexToFocus === -1) {
+ indexToFocus = filteredSelections.length - 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 === filteredSelections.length - 1) {
+ indexToFocus = 0;
+ } else {
+ indexToFocus = focusedItemIndex + 1;
+ }
+
+ // Skip disabled options
+ while (filteredSelections[indexToFocus].isDisabled) {
+ indexToFocus++;
+ if (indexToFocus === filteredSelections.length) {
+ indexToFocus = 0;
+ }
+ }
+ }
+
+ setActiveAndFocusedItem(indexToFocus);
+ };
+
+ const onInputKeyDown = (event: React.KeyboardEvent) => {
+ const focusedItem = focusedItemIndex !== null ? filteredSelections[focusedItemIndex] : null;
+
+ switch (event.key) {
+ case 'Enter':
+ if (
+ isOpen &&
+ focusedItem &&
+ focusedItem.value !== NO_RESULTS &&
+ !focusedItem.isAriaDisabled
+ ) {
+ selectOption(event, focusedItem);
+ }
+
+ openMenu();
+
+ break;
+ case 'ArrowUp':
+ case 'ArrowDown':
+ event.preventDefault();
+ handleMenuArrowKeys(event.key);
+ break;
+ }
+ };
+
+ const onToggleClick = () => {
+ if (!isOpen) {
+ openMenu();
+ } else {
+ closeMenu();
+ }
+ textInputRef.current?.focus();
+ };
+
+ const onClearButtonClick = () => {
+ if (isFiltering && filterValue) {
+ if (selected && onSelect) {
+ onSelect(undefined, selected.value);
+ }
+ setFilterValue('');
+ if (onInputChange) {
+ onInputChange('');
+ }
+ setIsFiltering(false);
+ }
+
+ resetActiveAndFocusedItem();
+ textInputRef.current?.focus();
+
+ if (onClearSelection) {
+ onClearSelection();
+ }
+ };
+
+ const toggle = (toggleRef: React.Ref) => (
+
+
+
+ {(isFiltering && filterValue) || (allowClear && selected) ? (
+
+ }
+ variant="plain"
+ onClick={onClearButtonClick}
+ aria-label="Clear input value"
+ />
+
+ ) : null}
+
+
+ );
+
+ return (
+
+ );
+};
diff --git a/packages/common/src/components/TypeaheadSelect/index.ts b/packages/common/src/components/TypeaheadSelect/index.ts
new file mode 100644
index 000000000..2d1d44920
--- /dev/null
+++ b/packages/common/src/components/TypeaheadSelect/index.ts
@@ -0,0 +1,3 @@
+// @index(['./*', /__/g], f => `export * from '${f.path}';`)
+export * from './TypeaheadSelect';
+// @endindex
diff --git a/packages/common/src/components/index.ts b/packages/common/src/components/index.ts
index e561934ae..54b33dbf8 100644
--- a/packages/common/src/components/index.ts
+++ b/packages/common/src/components/index.ts
@@ -10,4 +10,5 @@ export * from './LoadingDots';
export * from './Page';
export * from './QueryClientHoc';
export * from './TableView';
+export * from './TypeaheadSelect';
// @endindex
diff --git a/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json b/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json
index ed746e087..92ae50477 100644
--- a/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json
+++ b/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json
@@ -1,15 +1,19 @@
{
"{{Canceled}} canceled": "{{Canceled}} canceled",
+ "{{canceled}} VMs canceled": "{{canceled}} VMs canceled",
"{{completed}} / {{total}}": "{{completed}} / {{total}}",
"{{dateLabel}} Failed: {{value}}": "{{dateLabel}} Failed: {{value}}",
"{{dateLabel}} Running: {{value}}": "{{dateLabel}} Running: {{value}}",
"{{dateLabel}} Succeeded: {{value}}": "{{dateLabel}} Succeeded: {{value}}",
+ "{{error}} VMs failed": "{{error}} VMs failed",
"{{label}} field is missing from the secret data.": "{{label}} field is missing from the secret data.",
"{{name}} Details": "{{name}} Details",
"{{selectedLength}} hosts selected.": "{{selectedLength}} hosts selected.",
"{{success}} of {{total}} VMs migrated": "{{success}} of {{total}} VMs migrated",
+ "{{success}} VMs succeeded": "{{success}} VMs succeeded",
"{{total}} VM": "{{total}} VM",
"{{total}} VM_plural": "{{total}} VMs",
+ "{{total}} VMs": "{{total}} VMs",
"{{vmCount}} VMs selected ": "{{vmCount}} VMs selected ",
"{children}": "{children}",
"24 hours": "24 hours",
@@ -154,7 +158,6 @@
"Edit migration plan transfer network": "Edit migration plan transfer network",
"Edit NetworkMap": "Edit NetworkMap",
"Edit Plan": "Edit Plan",
- "Edit plan name": "Edit plan name",
"Edit Precopy interval (minutes)": "Edit Precopy interval (minutes)",
"Edit Provider": "Edit Provider",
"Edit Provider Credentials": "Edit Provider Credentials",
@@ -265,11 +268,9 @@
"Multiple NICs on the same network": "Multiple NICs on the same network",
"Name": "Name",
"Name is primarily intended for creation idempotence and configuration definition. Cannot be updated.": "Name is primarily intended for creation idempotence and configuration definition. Cannot be updated.",
- "Name is required and must be a unique within a namespace and valid Kubernetes name.": "Name is required and must be a unique within a namespace and valid Kubernetes name.",
"Namespace": "Namespace",
"Namespace defines the space within which each name must be unique.\n An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation.\n Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.": "Namespace defines the space within which each name must be unique.\n An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation.\n Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.",
"Namespace defines the space within which each name must be unique.\n An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation.\n Not all objects are required to be scoped to a namespace -\n the value of this field for those objects will be empty.": "Namespace defines the space within which each name must be unique.\n An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation.\n Not all objects are required to be scoped to a namespace -\n the value of this field for those objects will be empty.",
- "Namespace is not defined": "Namespace is not defined",
"Network for data transfer": "Network for data transfer",
"Network interfaces": "Network interfaces",
"Network Map name re-generated": "Network Map name re-generated",
@@ -387,6 +388,7 @@
"Provider details": "Provider details",
"Provider inventory": "Provider inventory",
"Provider resource name": "Provider resource name",
+ "Provider type": "Provider type",
"Provider web UI link": "Provider web UI link",
"Provider YAML": "Provider YAML",
"Providers": "Providers",
@@ -417,7 +419,6 @@
"Select a namespace": "Select a namespace",
"Select a provider": "Select a provider",
"Select migration network": "Select migration network",
- "Select provider type": "Select provider type",
"Select source provider": "Select source provider",
"Select virtual machines": "Select virtual machines",
"Select vSphere provider endpoint type.": "Select vSphere provider endpoint type.",
@@ -455,6 +456,7 @@
"Start migration": "Start migration",
"Started at": "Started at",
"Status": "Status",
+ "Status details": "Status details",
"Storage": "Storage",
"Storage classes": "Storage classes",
"Storage domains": "Storage domains",
@@ -511,6 +513,7 @@
"Token": "Token",
"Total CPU count:": "Total CPU count:",
"Total memory:": "Total memory:",
+ "Total of {{total}} VMs are planned for migration:": "Total of {{total}} VMs are planned for migration:",
"Total virtual machines": "Total virtual machines",
"Total: {{length}}": "Total: {{length}}",
"Transfer Network": "Transfer Network",
diff --git a/packages/forklift-console-plugin/src/modules/Plans/actions/PlanActionsDropdownItems.tsx b/packages/forklift-console-plugin/src/modules/Plans/actions/PlanActionsDropdownItems.tsx
index 3d22954f5..541d95c15 100644
--- a/packages/forklift-console-plugin/src/modules/Plans/actions/PlanActionsDropdownItems.tsx
+++ b/packages/forklift-console-plugin/src/modules/Plans/actions/PlanActionsDropdownItems.tsx
@@ -21,6 +21,7 @@ import {
isPlanArchived,
isPlanExecuting,
PlanData,
+ PlanPhase,
} from '../utils';
export const PlanActionsDropdownItems = ({ data }: PlanActionsDropdownItemsProps) => {
@@ -93,7 +94,9 @@ export const PlanActionsDropdownItems = ({ data }: PlanActionsDropdownItemsProps
{t('Archive Plan')}
diff --git a/packages/forklift-console-plugin/src/modules/Plans/modals/ArchiveModal.tsx b/packages/forklift-console-plugin/src/modules/Plans/modals/ArchiveModal.tsx
index e99fbf42c..805573071 100644
--- a/packages/forklift-console-plugin/src/modules/Plans/modals/ArchiveModal.tsx
+++ b/packages/forklift-console-plugin/src/modules/Plans/modals/ArchiveModal.tsx
@@ -8,7 +8,7 @@ import { PlanModel, V1beta1Plan } from '@kubev2v/types';
import { K8sModel, k8sPatch } from '@openshift-console/dynamic-plugin-sdk';
import { Alert, Button, Modal, ModalVariant } from '@patternfly/react-core';
-import { getPlanPhase } from '../utils';
+import { getPlanPhase, PlanPhase } from '../utils';
/**
* Props for the DeleteModal component
@@ -70,7 +70,7 @@ export const ArchiveModal: React.FC = ({ title, resource, red
const actions = [
}
- {phase === 'Running' && }
+ {phase === PlanPhase.Running && }
{alertMessage}
);
diff --git a/packages/forklift-console-plugin/src/modules/Plans/modals/PlanDeleteModal.tsx b/packages/forklift-console-plugin/src/modules/Plans/modals/PlanDeleteModal.tsx
index d87c48284..6336879a5 100644
--- a/packages/forklift-console-plugin/src/modules/Plans/modals/PlanDeleteModal.tsx
+++ b/packages/forklift-console-plugin/src/modules/Plans/modals/PlanDeleteModal.tsx
@@ -9,7 +9,7 @@ import { V1beta1Plan } from '@kubev2v/types';
import { k8sDelete, K8sGroupVersionKind, K8sModel } from '@openshift-console/dynamic-plugin-sdk';
import { Alert, Button, Modal, ModalVariant } from '@patternfly/react-core';
-import { getPlanPhase } from '../utils';
+import { getPlanPhase, PlanPhase } from '../utils';
/**
* Props for the DeleteModal component
@@ -131,8 +131,8 @@ export const PlanDeleteModal: React.FC = ({
Are you sure you want to delete {name}?
)}
- {phase === 'Running' && }
- {phase !== 'Archived' && }
+ {phase === PlanPhase.Running && }
+ {phase !== PlanPhase.Archived && }
{typeof owner === 'object' && }
{alertMessage}
diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/constants/planPhases.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/constants/planPhases.ts
index cb6a2b058..0ca05c486 100644
--- a/packages/forklift-console-plugin/src/modules/Plans/utils/constants/planPhases.ts
+++ b/packages/forklift-console-plugin/src/modules/Plans/utils/constants/planPhases.ts
@@ -9,16 +9,16 @@ import { PlanPhase } from '../types';
* This array is intended to be used for creating filter dropdowns, where users can select a plan phase to filter the results shown.
*/
export const planPhases: { id: PlanPhase; label: string }[] = [
- { id: 'Error', label: 'Error' },
- { id: 'vmError', label: 'VM Error' },
- { id: 'Unknown', label: 'Unknown' },
- { id: 'Archiving', label: 'Archiving' },
- { id: 'Archived', label: 'Archived' },
- { id: 'Failed', label: 'Failed' },
- { id: 'Canceled', label: 'Canceled' },
- { id: 'Succeeded', label: 'Succeeded' },
- { id: 'Running', label: 'Running' },
- { id: 'Ready', label: 'Ready' },
- { id: 'Warning', label: 'Warning' },
- { id: 'NotReady', label: 'Not Ready' },
+ { id: PlanPhase.Error, label: PlanPhase.Error },
+ { id: PlanPhase.vmError, label: PlanPhase.vmError },
+ { id: PlanPhase.Unknown, label: PlanPhase.Unknown },
+ { id: PlanPhase.Archiving, label: PlanPhase.Archiving },
+ { id: PlanPhase.Archived, label: PlanPhase.Archived },
+ { id: PlanPhase.Failed, label: PlanPhase.Failed },
+ { id: PlanPhase.Canceled, label: PlanPhase.Canceled },
+ { id: PlanPhase.Succeeded, label: PlanPhase.Succeeded },
+ { id: PlanPhase.Running, label: PlanPhase.Running },
+ { id: PlanPhase.Ready, label: PlanPhase.Ready },
+ { id: PlanPhase.Warning, label: PlanPhase.Warning },
+ { id: PlanPhase.NotReady, label: PlanPhase.NotReady },
];
diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPhaseLabel.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPhaseLabel.ts
deleted file mode 100644
index 1c5daa414..000000000
--- a/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPhaseLabel.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { PlanPhase } from '../types';
-
-export const getPhaseLabel = (phase: PlanPhase) => phaseLabels?.[phase] ?? 'Unknown';
-
-const phaseLabels: Record = {
- // t('Ready')
- Ready: 'Ready',
- // t('Not Ready')
- NotReady: 'Not Ready',
- // t('Running')
- Running: 'Running',
- // t('Succeeded')
- Succeeded: 'Succeeded',
- // t('Canceled')
- Canceled: 'Canceled',
- // t('Failed')
- Failed: 'Failed',
- // t('Archived')
- Archived: 'Archived',
- // t('Archiving')
- Archiving: 'Archiving',
- // t('Error')
- Error: 'Error',
- // t('Warning')
- Warning: 'Warning',
- // t('Some VMs Failed')
- vmError: 'Some VMs Failed',
- // t('Unknown')
- Unknown: 'Unknown',
-};
diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanPhase.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanPhase.ts
index c5712f3c8..ce480752c 100644
--- a/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanPhase.ts
+++ b/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanPhase.ts
@@ -5,37 +5,37 @@ import { PlanData, PlanPhase } from '../types';
export const getPlanPhase = (data: PlanData): PlanPhase => {
const plan = data?.obj;
- if (!plan) return 'Unknown';
+ if (!plan) return PlanPhase.Unknown;
// Check condition type
const conditions = getConditions(plan);
if (!conditions || conditions?.length < 1) {
- return 'Unknown';
+ return PlanPhase.Unknown;
}
// Check for Archived
if (plan?.spec?.archived && !conditions.includes('Archived')) {
- return 'Archiving';
+ return PlanPhase.Archiving;
}
if (conditions.includes('Archived')) {
- return 'Archived';
+ return PlanPhase.Archived;
}
// Check for Succeeded
if (conditions.includes('Succeeded')) {
- return 'Succeeded';
+ return PlanPhase.Succeeded;
}
// Check for Canceled
if (conditions.includes('Canceled')) {
- return 'Canceled';
+ return PlanPhase.Canceled;
}
// CHeck for Running
if (conditions.includes('Executing')) {
- return 'Running';
+ return PlanPhase.Running;
}
// Check condition category
@@ -44,18 +44,18 @@ export const getPlanPhase = (data: PlanData): PlanPhase => {
);
if (isCritical) {
- return 'Error';
+ return PlanPhase.Error;
}
// Check for vm errors
const vmError = plan?.status?.migration?.vms?.find((vm) => vm?.error);
if (conditions.includes('Failed')) {
- return 'Failed';
+ return PlanPhase.Failed;
}
if (vmError) {
- return 'vmError';
+ return PlanPhase.vmError;
}
// Check condition category
@@ -64,14 +64,14 @@ export const getPlanPhase = (data: PlanData): PlanPhase => {
);
if (isWarn) {
- return 'Warning';
+ return PlanPhase.Warning;
}
if (conditions.includes('Ready')) {
- return 'Ready';
+ return PlanPhase.Ready;
}
- return 'NotReady';
+ return PlanPhase.NotReady;
};
export const canPlanStart = (plan: V1beta1Plan) => {
@@ -107,20 +107,20 @@ export const isPlanEditable = (plan: V1beta1Plan) => {
const planStatus = getPlanPhase({ obj: plan });
return (
- planStatus === 'Unknown' ||
- planStatus === 'Canceled' ||
- planStatus === 'Error' ||
- planStatus === 'Failed' ||
- planStatus === 'vmError' ||
- planStatus === 'Warning' ||
- planStatus === 'Ready'
+ planStatus === PlanPhase.Unknown ||
+ planStatus === PlanPhase.Canceled ||
+ planStatus === PlanPhase.Error ||
+ planStatus === PlanPhase.Failed ||
+ planStatus === PlanPhase.vmError ||
+ planStatus === PlanPhase.Warning ||
+ planStatus === PlanPhase.Ready
);
};
export const isPlanArchived = (plan: V1beta1Plan) => {
const planStatus = getPlanPhase({ obj: plan });
- return planStatus === 'Archiving' || planStatus === 'Archived';
+ return planStatus === PlanPhase.Archiving || planStatus === PlanPhase.Archived;
};
const getConditions = (obj: V1beta1Plan) =>
diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanProgressVariant.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanProgressVariant.ts
index 249fa34ad..c8ff964dd 100644
--- a/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanProgressVariant.ts
+++ b/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanProgressVariant.ts
@@ -6,17 +6,17 @@ export const getPlanProgressVariant = (phase: PlanPhase): ProgressVariant => {
let progressVariant;
switch (phase) {
- case 'Error':
- case 'Failed':
+ case PlanPhase.Error:
+ case PlanPhase.Failed:
progressVariant = ProgressVariant.danger;
break;
- case 'Unknown':
- case 'Archived':
- case 'Canceled':
- case 'vmError':
+ case PlanPhase.Unknown:
+ case PlanPhase.Archived:
+ case PlanPhase.Canceled:
+ case PlanPhase.vmError:
progressVariant = ProgressVariant.warning;
break;
- case 'Succeeded':
+ case PlanPhase.Succeeded:
progressVariant = ProgressVariant.success;
break;
}
diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/index.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/index.ts
index d2865d95b..f499b37f3 100644
--- a/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/index.ts
+++ b/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/index.ts
@@ -1,7 +1,6 @@
// @index(['./*', /style/g], f => `export * from '${f.path}';`)
export * from './getMigrationPhase';
export * from './getMigrationVmsCounts';
-export * from './getPhaseLabel';
export * from './getPlanPhase';
export * from './getPlanProgressVariant';
// @endindex
diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanPhase.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanPhase.ts
index 249f56b65..67d31e063 100644
--- a/packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanPhase.ts
+++ b/packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanPhase.ts
@@ -1,13 +1,26 @@
-export type PlanPhase =
- | 'Error'
- | 'vmError'
- | 'Unknown'
- | 'Archiving'
- | 'Archived'
- | 'Failed'
- | 'Canceled'
- | 'Succeeded'
- | 'Running'
- | 'Ready'
- | 'Warning'
- | 'NotReady';
+export enum PlanPhase {
+ // t('Error')
+ Error = 'Error',
+ // t('Some VMs Failed')
+ vmError = 'Some VMs Failed',
+ // t('Unknown')
+ Unknown = 'Unknown',
+ // t('Archiving')
+ Archiving = 'Archiving',
+ // t('Archived')
+ Archived = 'Archived',
+ // t('Failed')
+ Failed = 'Failed',
+ // t('Canceled')
+ Canceled = 'Canceled',
+ // t('Succeeded')
+ Succeeded = 'Succeeded',
+ // t('Running')
+ Running = 'Running',
+ // t('Ready')
+ Ready = 'Ready',
+ // t('Warning')
+ Warning = 'Warning',
+ // t('Not Ready')
+ NotReady = 'Not Ready',
+}
diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/create/PlanCreatePage.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/create/PlanCreatePage.tsx
index b7b4127c7..67554ed5c 100644
--- a/packages/forklift-console-plugin/src/modules/Plans/views/create/PlanCreatePage.tsx
+++ b/packages/forklift-console-plugin/src/modules/Plans/views/create/PlanCreatePage.tsx
@@ -5,11 +5,10 @@ import ProvidersCreateVmMigrationPage from 'src/modules/Providers/views/migrate/
import { startCreate } from 'src/modules/Providers/views/migrate/reducer/actions';
import { useFetchEffects } from 'src/modules/Providers/views/migrate/useFetchEffects';
import { useSaveEffect } from 'src/modules/Providers/views/migrate/useSaveEffect';
-import { ForkliftTrans } from 'src/utils/i18n';
import { ProviderModelGroupVersionKind, V1beta1Provider } from '@kubev2v/types';
-import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk';
-import { Alert, PageSection, Title } from '@patternfly/react-core';
+import { useActiveNamespace, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk';
+import { PageSection, Title } from '@patternfly/react-core';
import { Wizard } from '@patternfly/react-core/deprecated';
import { findProviderByID } from './components';
@@ -22,8 +21,13 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
// Get optional initial state context
const { data } = useCreateVmMigrationData();
const history = useHistory();
- const defaultNamespace = process?.env?.DEFAULT_NAMESPACE || 'default';
const startAtStep = data?.provider !== undefined ? 2 : 1;
+ const [activeNamespace, setActiveNamespace] = useActiveNamespace();
+ const defaultNamespace = process?.env?.DEFAULT_NAMESPACE || 'default';
+ const projectName =
+ data?.projectName ||
+ (activeNamespace === '#ALL_NS#' ? 'openshift-mtv' : activeNamespace) ||
+ defaultNamespace;
// Init Select source provider form state
const [filterState, filterDispatch] = useReducer(planCreatePageReducer, {
@@ -38,6 +42,7 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
isList: true,
namespace,
});
+
const selectedProvider =
filterState.selectedProviderUID !== ''
? findProviderByID(filterState.selectedProviderUID, providers)
@@ -45,7 +50,12 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
// Init Create migration plan form state
const [state, dispatch, emptyContext] = useFetchEffects({
- data: { selectedVms: filterState.selectedVMs, provider: selectedProvider || data?.provider },
+ data: {
+ projectName,
+ selectedVms: filterState.selectedVMs,
+ provider: selectedProvider || data?.provider,
+ planName: data?.planName,
+ },
});
useSaveEffect(state, dispatch);
@@ -55,9 +65,11 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
name: 'Select source provider',
component: (
@@ -89,21 +101,6 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
return (
<>
- {!namespace && (
-
-
- This plan will be created in {defaultNamespace} namespace, if you
- wish to choose another namespace please cancel, and choose a namespace from the top
- bar.
-
-
- )}
-
{'Create migration plan'}
@@ -113,7 +110,10 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
navAriaLabel={`${title} steps`}
mainAriaLabel={`${title} content`}
steps={steps}
- onSave={() => dispatch(startCreate())}
+ onSave={() => {
+ setActiveNamespace(state.underConstruction.projectName);
+ dispatch(startCreate());
+ }}
onClose={() => history.goBack()}
startAtStep={startAtStep}
/>
diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanCreateForm.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanCreateForm.tsx
index 36e39ff51..4916ba616 100644
--- a/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanCreateForm.tsx
+++ b/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanCreateForm.tsx
@@ -2,7 +2,14 @@ import React from 'react';
import { SelectableCard } from 'src/modules/Providers/utils/components/Gallery/SelectableCard';
import { SelectableGallery } from 'src/modules/Providers/utils/components/Gallery/SelectableGallery';
import { VmData } from 'src/modules/Providers/views';
-import { useForkliftTranslation } from 'src/utils';
+import { useCreateVmMigrationData } from 'src/modules/Providers/views/migrate';
+import {
+ PageAction,
+ setPlanName,
+ setProjectName as setProjectNameAction,
+} from 'src/modules/Providers/views/migrate/reducer/actions';
+import { CreateVmMigrationPageState } from 'src/modules/Providers/views/migrate/types';
+import { ForkliftTrans, useForkliftTranslation } from 'src/utils';
import { FormGroupWithHelpText } from '@kubev2v/common';
import { V1beta1Provider } from '@kubev2v/types';
@@ -13,14 +20,19 @@ import { PlanCreatePageState } from '../states';
import { ChipsToolbarProviders } from './ChipsToolbarProviders';
import { createProviderCardItems } from './createProviderCardItems';
import { FiltersToolbarProviders } from './FiltersToolbarProviders';
+import { PlanNameTextField } from './PlanNameTextField';
+import { ProjectNameSelect } from './ProjectNameSelect';
export type PlanCreateFormProps = {
providers: V1beta1Provider[];
filterState: PlanCreatePageState;
+ state: CreateVmMigrationPageState;
+ projectName: string;
filterDispatch: React.Dispatch<{
type: string;
payload?: string | string[] | VmData[];
}>;
+ dispatch: (action: PageAction) => void;
};
/**
@@ -30,11 +42,17 @@ export type PlanCreateFormProps = {
export const PlanCreateForm: React.FC = ({
providers,
filterState,
+ state,
+ projectName,
filterDispatch,
+ dispatch,
}) => {
const { t } = useForkliftTranslation();
-
+ const { data, setData } = useCreateVmMigrationData();
const providerCardItems = createProviderCardItems(providers);
+ const providerNamespaces = [
+ ...new Set(providers.map((provider) => provider.metadata?.namespace)),
+ ];
const onChange = (id: string) => {
filterDispatch({ type: 'SELECT_PROVIDER', payload: id || '' });
@@ -43,6 +61,36 @@ export const PlanCreateForm: React.FC = ({
return (
+ );
+};
diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/create/ProvidersCreatePage.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/create/ProvidersCreatePage.tsx
index 86fe094b3..d572fe78e 100644
--- a/packages/forklift-console-plugin/src/modules/Providers/views/create/ProvidersCreatePage.tsx
+++ b/packages/forklift-console-plugin/src/modules/Providers/views/create/ProvidersCreatePage.tsx
@@ -2,9 +2,10 @@ import React, { useReducer } from 'react';
import { useHistory } from 'react-router';
import { Base64 } from 'js-base64';
import SectionHeading from 'src/components/headers/SectionHeading';
-import { ForkliftTrans, useForkliftTranslation } from 'src/utils/i18n';
+import { useForkliftTranslation } from 'src/utils/i18n';
import { IoK8sApiCoreV1Secret, ProviderModelRef, V1beta1Provider } from '@kubev2v/types';
+import { useActiveNamespace } from '@openshift-console/dynamic-plugin-sdk';
import {
Alert,
Button,
@@ -29,6 +30,7 @@ import './ProvidersCreatePage.style.css';
interface ProvidersCreatePageState {
newSecret: IoK8sApiCoreV1Secret;
newProvider: V1beta1Provider;
+ projectName: string;
validationError: ValidationMsg;
apiError: Error | null;
}
@@ -39,24 +41,26 @@ export const ProvidersCreatePage: React.FC<{
const { t } = useForkliftTranslation();
const history = useHistory();
const [isLoading, toggleIsLoading] = useToggle();
-
+ const [activeNamespace, setActiveNamespace] = useActiveNamespace();
const [providerNames] = useK8sWatchProviderNames({ namespace });
-
const defaultNamespace = process?.env?.DEFAULT_NAMESPACE || 'default';
+ const projectName = activeNamespace === '#ALL_NS#' ? 'openshift-mtv' : activeNamespace;
+ const initialNamespace = namespace || projectName || defaultNamespace;
const initialState: ProvidersCreatePageState = {
+ projectName,
newSecret: {
...secretTemplate,
metadata: {
...secretTemplate.metadata,
- namespace: namespace || defaultNamespace,
+ namespace: initialNamespace,
},
},
newProvider: {
...providerTemplate,
metadata: {
...providerTemplate.metadata,
- namespace: namespace || defaultNamespace,
+ namespace: initialNamespace,
},
},
validationError: { type: 'error', msg: 'Missing provider name' },
@@ -109,6 +113,21 @@ export const ProvidersCreatePage: React.FC<{
apiError: null,
};
}
+ case 'SET_PROJECT_NAME': {
+ const value = action.payload;
+ let validationError: ValidationMsg = { type: 'default' };
+
+ if (!value) {
+ validationError = { type: 'error', msg: 'Missing project name' };
+ }
+
+ return {
+ ...state,
+ validationError,
+ projectName: String(value),
+ apiError: null,
+ };
+ }
case 'SET_API_ERROR': {
const value = action.payload as Error | null;
return { ...state, apiError: value };
@@ -230,26 +249,13 @@ export const ProvidersCreatePage: React.FC<{
)}
- {!namespace && (
-
-
- This provider will be created in {defaultNamespace} namespace, if you
- wish to choose another namespace please cancel, and choose a namespace from the top
- bar.
-
-
- )}
-
dispatch({ type: 'SET_PROJECT_NAME', payload: value })}
providerNames={providerNames}
/>
@@ -259,7 +265,10 @@ export const ProvidersCreatePage: React.FC<{