diff --git a/src/ImageRunModal.jsx b/src/ImageRunModal.jsx
index 9e334b73..411b12fb 100644
--- a/src/ImageRunModal.jsx
+++ b/src/ImageRunModal.jsx
@@ -9,11 +9,12 @@ import { Modal } from "@patternfly/react-core/dist/esm/components/Modal";
import { NumberInput } from "@patternfly/react-core/dist/esm/components/NumberInput";
import { Popover } from "@patternfly/react-core/dist/esm/components/Popover";
import { Radio } from "@patternfly/react-core/dist/esm/components/Radio";
+import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
import { Tab, TabTitleText, Tabs } from "@patternfly/react-core/dist/esm/components/Tabs";
import { Text, TextContent, TextList, TextListItem, TextVariants } from "@patternfly/react-core/dist/esm/components/Text";
import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput";
import { ToggleGroup, ToggleGroupItem } from "@patternfly/react-core/dist/esm/components/ToggleGroup";
-import { Select, SelectGroup, SelectOption, SelectVariant } from "@patternfly/react-core/dist/esm/deprecated/components/Select";
+import { Bullseye } from "@patternfly/react-core/dist/esm/layouts/Bullseye/index.js";
import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex";
import { Grid, GridItem } from "@patternfly/react-core/dist/esm/layouts/Grid";
import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons';
@@ -23,6 +24,7 @@ import { debounce } from 'throttle-debounce';
import cockpit from 'cockpit';
import { DynamicListForm } from 'cockpit-components-dynamic-list.jsx';
+import { TypeaheadSelect } from 'cockpit-components-typeahead-select';
import { onDownloadContainer, onDownloadContainerFinished } from './Containers.jsx';
import { EnvVar, validateEnvVar } from './Env.jsx';
@@ -583,44 +585,33 @@ export class ImageRunModal extends React.Component {
const input = this.buildFilterRegex(searchText, false);
- const results = imageRegistries
- .map((reg, index) => {
- const filtered = (reg in images ? images[reg] : [])
- .filter(image => {
- if (image.isSystem && !isSystem) {
- return false;
- }
- if ('isSystem' in image && !image.isSystem && isSystem) {
- return false;
- }
- return image.Name.search(input) !== -1;
- })
- .map((image, index) => {
- return (
-
- );
- });
-
- if (filtered.length === 0) {
- return [];
- } else {
- return (
-
- {filtered}
-
- );
- }
- })
- .filter(group => group.length !== 0); // filter out empty groups
+ const results = [];
+ imageRegistries.forEach(reg => {
+ let need_header = this.state.searchByRegistry == 'all';
+ (reg in images ? images[reg] : [])
+ .filter(image => {
+ if (image.isSystem && !isSystem) {
+ return false;
+ }
+ if ('isSystem' in image && !image.isSystem && isSystem) {
+ return false;
+ }
+ return image.Name.search(input) !== -1;
+ })
+ .forEach(image => {
+ if (need_header) {
+ results.push({ key: results.length, decorator: "header", content: reg });
+ need_header = false;
+ }
- // Remove when there is a filter selected.
- if (this.state.searchByRegistry !== 'all' && imageRegistries.length === 1 && results.length === 1) {
- return results[0].props.children;
- }
+ results.push({
+ key: results.length,
+ value: image,
+ content: image.toString(),
+ description: image.Description
+ });
+ });
+ });
return results;
};
@@ -804,6 +795,12 @@ export class ImageRunModal extends React.Component {
);
+ const spinnerOptions = (
+ this.state.searchInProgress
+ ? [{ value: "_searching", content: , isDisabled: true }]
+ : []
+ );
+
/* ignore Enter key, it otherwise opens the first popover help; this clears
* the search input and is still irritating from other elements like check boxes */
const defaultBody = (
@@ -901,30 +898,22 @@ export class ImageRunModal extends React.Component {
}
>
-
+ // We do our own filtering when producing imageListOptions
+ filterFunction={(filterValue, options) => options}
+ selectOptions={imageListOptions.concat(spinnerOptions)}
+ footer={footer} />
{(image || localImage) &&
diff --git a/test/check-application b/test/check-application
index 2e867b31..7e5c22ad 100755
--- a/test/check-application
+++ b/test/check-application
@@ -504,28 +504,29 @@ class TestApplication(testlib.MachineCase):
# Check showing of entrypoint
b.click("#containers-containers-create-container-btn")
- b.click("#create-image-image-select-typeahead")
- b.click(f'button.pf-v5-c-select__menu-item:contains("{IMG_REGISTRY}")')
+ b.click("#create-image-image input")
+ b.click(f'button.pf-v5-c-menu__item:contains("{IMG_REGISTRY}")')
b.wait_val("#run-image-dialog-command", '/etc/docker/registry/config.yml')
b.wait_text("#run-image-dialog-entrypoint", '/entrypoint.sh')
# Deleting image will cleanup both command and entrypoint
- b.click("button.pf-v5-c-select__toggle-clear")
+ b.click("button[aria-label='Clear input value']")
b.wait_val("#run-image-dialog-command", '')
b.wait_not_present("#run-image-dialog-entrypoint")
# Edited command will not be cleared
- b.click("#create-image-image-select-typeahead")
- b.click(f'button.pf-v5-c-select__menu-item:contains("{IMG_REGISTRY}")')
+ b.click("#create-image-image input")
+ time.sleep(1) # wait for TypeaheadSelect focus shenanigans
+ b.click(f'button.pf-v5-c-menu__item:contains("{IMG_REGISTRY}")')
b.wait_val("#run-image-dialog-command", '/etc/docker/registry/config.yml')
b.set_input_text("#run-image-dialog-command", '/etc/docker/registry/config.yaml')
- b.click("button.pf-v5-c-select__toggle-clear")
+ b.click("button[aria-label='Clear input value']")
b.wait_not_present("#run-image-dialog-entrypoint")
b.wait_val("#run-image-dialog-command", '/etc/docker/registry/config.yaml')
# Setting a new image will still keep the old command and not prefill it
- b.click("#create-image-image-select-typeahead")
- b.click(f'button.pf-v5-c-select__menu-item:contains({IMG_ALPINE})')
+ b.click("#create-image-image input")
+ b.click(f'button.pf-v5-c-menu__item:contains({IMG_ALPINE})')
b.wait_visible("#run-image-dialog-pull-latest-image")
b.wait_val("#run-image-dialog-command", '/etc/docker/registry/config.yaml')
@@ -553,8 +554,8 @@ class TestApplication(testlib.MachineCase):
# Test the isSystem boolean for searching
# https://github.com/cockpit-project/cockpit-podman/pull/891
b.click("#containers-containers-create-container-btn")
- b.set_input_text("#create-image-image-select-typeahead", "registry")
- b.wait_visible('button.pf-v5-c-select__menu-item:contains("registry")')
+ b.set_input_text("#create-image-image input", "registry")
+ b.wait_visible('button.pf-v5-c-menu__item:contains("registry")')
self.confirm_modal("Cancel")
def testBasicUser(self):
@@ -875,9 +876,9 @@ class TestApplication(testlib.MachineCase):
# Intermediate images are not shown in create container dialog
b.click("#containers-containers-create-container-btn")
b.wait_visible('div.pf-v5-c-modal-box header:contains("Create container")')
- b.click("#create-image-image-select-typeahead")
- b.wait_visible(f".pf-v5-c-select__menu-item:contains('{IMG_REGISTRY}')")
- b.wait_not_present(".pf-v5-c-select__menu-item:contains('none')")
+ b.click("#create-image-image input")
+ b.wait_visible(f".pf-v5-c-menu__item:contains('{IMG_REGISTRY}')")
+ b.wait_not_present(".pf-v5-c-menu__item:contains('none')")
b.click(".pf-v5-c-modal-box .btn-cancel")
b.wait_not_present(".pf-v5-c-modal-box")
@@ -1750,40 +1751,40 @@ class TestApplication(testlib.MachineCase):
# Test special characters.
# Initially both the registry image and Alpine are shown
- b.click("#create-image-image")
- b.wait_visible(f'button.pf-v5-c-select__menu-item:contains("{IMG_ALPINE_LATEST}")')
- b.wait_visible(f'button.pf-v5-c-select__menu-item:contains("{IMG_REGISTRY_LATEST}")')
+ b.click("#create-image-image input")
+ b.wait_visible(f'button.pf-v5-c-menu__item:contains("{IMG_ALPINE_LATEST}")')
+ b.wait_visible(f'button.pf-v5-c-menu__item:contains("{IMG_REGISTRY_LATEST}")')
# Filter it down to just Alpine
- b.set_input_text("#create-image-image-select-typeahead", "|alpi*ne?\\")
- b.wait_not_present(f'button.pf-v5-c-select__menu-item:contains("{IMG_REGISTRY_LATEST}")')
- b.wait_visible(f'button.pf-v5-c-select__menu-item:contains("{IMG_ALPINE_LATEST}")')
+ b.set_input_text("#create-image-image input", "|alpi*ne?\\")
+ b.wait_not_present(f'button.pf-v5-c-menu__item:contains("{IMG_REGISTRY_LATEST}")')
+ b.wait_visible(f'button.pf-v5-c-menu__item:contains("{IMG_ALPINE_LATEST}")')
# No local results found
- b.set_input_text("#create-image-image-select-typeahead", "notfound")
+ b.set_input_text("#create-image-image input", "notfound")
b.click('button.pf-v5-c-toggle-group__button:contains("Local")')
- b.wait_text("button.pf-v5-c-select__menu-item.pf-m-disabled", "No images found")
+ b.wait_text("button.pf-v5-c-menu__item[disabled]", "No images found")
# Local results found
- b.set_input_text("#create-image-image-select-typeahead", "registry")
- b.wait_text("button.pf-v5-c-select__menu-item", IMG_REGISTRY_LATEST)
+ b.set_input_text("#create-image-image input", "registry")
+ b.wait_text("button.pf-v5-c-menu__item:not([disabled])", IMG_REGISTRY_LATEST)
if auth:
b.assert_pixels(".pf-v5-c-modal-box", "image-select", skip_layouts=["rtl"])
# Local registry
b.click('button.pf-v5-c-toggle-group__button:contains("localhost:5000")')
- b.set_input_text("#create-image-image-select-typeahead", "my-busybox", blur=False)
- b.wait_text("button.pf-v5-c-select__menu-item:not(.pf-m-disabled)", "localhost:5000/my-busybox")
+ b.set_input_text("#create-image-image input", "my-busybox", blur=False)
+ b.wait_text("button.pf-v5-c-menu__item:not([disabled])", "localhost:5000/my-busybox")
# pressing Enter does not disturb the dialog or open popup help
- b.wait_val("#create-image-image-select-typeahead", "my-busybox")
+ b.wait_val("#create-image-image input", "my-busybox")
b.key("Enter")
time.sleep(0.3)
self.assertFalse(b.is_present(".pf-v5-c-popover"))
- b.wait_val("#create-image-image-select-typeahead", "my-busybox")
+ b.wait_val("#create-image-image input", "my-busybox")
# Select image
- b.click('button.pf-v5-c-select__menu-item:contains("localhost:5000/my-busybox")')
+ b.click('button.pf-v5-c-menu__item:contains("localhost:5000/my-busybox")')
# Remote image, no pull latest image option
b.wait_not_present("#run-image-dialog-pull-latest-image")
@@ -1807,14 +1808,14 @@ class TestApplication(testlib.MachineCase):
b.set_input_text("#run-image-dialog-name", container_name)
# Local registry
- b.set_input_text("#create-image-image-select-typeahead", "no-container")
- b.wait_text("button.pf-v5-c-select__menu-item.pf-m-disabled", "No images found")
+ b.set_input_text("#create-image-image input", "no-container")
+ b.wait_text("button.pf-v5-c-menu__item[disabled]", "No images found")
# Search with full url
- b.set_input_text("#create-image-image-select-typeahead", "localhost:5000/my-busybox")
+ b.set_input_text("#create-image-image input", "localhost:5000/my-busybox")
b.click('button.pf-v5-c-toggle-group__button:contains("Local")')
# Select image
- b.click('button.pf-v5-c-select__menu-item:contains("localhost:5000/my-busybox")')
+ b.click('button.pf-v5-c-menu__item:contains("localhost:5000/my-busybox")')
# Pull the latest image
b.set_checked("#run-image-dialog-pull-latest-image", True)
@@ -1839,20 +1840,20 @@ class TestApplication(testlib.MachineCase):
b.set_input_text("#run-image-dialog-name", container_name)
# Open image dropdown, it should list both Alpine and Busybox from the local repository
- b.click("#create-image-image")
- b.wait_visible(f'button.pf-v5-c-select__menu-item:contains("{IMG_ALPINE_LATEST}")')
- b.wait_visible(f'button.pf-v5-c-select__menu-item:contains("{IMG_BUSYBOX_LATEST}")')
+ b.click("#create-image-image input")
+ b.wait_visible(f'button.pf-v5-c-menu__item:contains("{IMG_ALPINE_LATEST}")')
+ b.wait_visible(f'button.pf-v5-c-menu__item:contains("{IMG_BUSYBOX_LATEST}")')
# Filter it down to just Busybox
- b.set_input_text("#create-image-image-select-typeahead", IMG_BUSYBOX_LATEST)
- b.wait_visible(f'button.pf-v5-c-select__menu-item:contains("{IMG_BUSYBOX_LATEST}")')
- b.wait_not_present(f'button.pf-v5-c-select__menu-item:contains("{IMG_ALPINE_LATEST}")')
+ b.set_input_text("#create-image-image input", IMG_BUSYBOX_LATEST)
+ b.wait_visible(f'button.pf-v5-c-menu__item:contains("{IMG_BUSYBOX_LATEST}")')
+ b.wait_not_present(f'button.pf-v5-c-menu__item:contains("{IMG_ALPINE_LATEST}")')
# Switch to another view; different images have different registries, so don't assume their name, just
# that at least one more exists
b.click('.image-search-footer .pf-v5-c-toggle-group__item:nth-of-type(3) button')
- b.wait_in_text(".pf-v5-c-select__menu-list", "No images found")
+ b.wait_in_text(".pf-v5-c-menu", "No images found")
# Back to local and let's select Busybox and create the container
b.click('button.pf-v5-c-toggle-group__button:contains("Local")')
- b.click(f'button.pf-v5-c-select__menu-item:contains("{IMG_BUSYBOX_LATEST}")')
+ b.click(f'button.pf-v5-c-menu__item:contains("{IMG_BUSYBOX_LATEST}")')
b.click('.pf-v5-c-modal-box__footer #create-image-create-run-btn')
b.wait(lambda: self.getContainerAttr(container_name, "State", sel) in 'Running')
@@ -1896,7 +1897,8 @@ class TestApplication(testlib.MachineCase):
b.wait_visible('div.pf-v5-c-modal-box header:contains("Create container")')
# Inspect and fill modal dialog
- b.wait_val("#create-image-image-select-typeahead", IMG_BUSYBOX_LATEST)
+ b.wait_val("#create-image-image input", IMG_BUSYBOX_LATEST)
+ time.sleep(1) # wait for TypeaheadSelect focus shenanigans
# Check that there is autogenerated name and then overwrite it
b.wait_not_val("#run-image-dialog-name", "")
@@ -2189,7 +2191,7 @@ class TestApplication(testlib.MachineCase):
b.click(f'#containers-images tbody tr:contains("{IMG_BUSYBOX}") .ct-container-create')
b.wait_visible('div.pf-v5-c-modal-box header:contains("Create container")')
- b.wait_val("#create-image-image-select-typeahead", IMG_BUSYBOX_LATEST)
+ b.wait_val("#create-image-image input", IMG_BUSYBOX_LATEST)
b.set_input_text("#run-image-dialog-name", "busybox-without-publish")
# Set up command line
@@ -2272,7 +2274,7 @@ class TestApplication(testlib.MachineCase):
b.click(f'#containers-images tbody tr:contains("{IMG_BUSYBOX}") .ct-container-create')
b.wait_visible('div.pf-v5-c-modal-box header:contains("Create container")')
- b.wait_val("#create-image-image-select-typeahead", IMG_BUSYBOX_LATEST)
+ b.wait_val("#create-image-image input", IMG_BUSYBOX_LATEST)
b.set_input_text("#run-image-dialog-name", container_name)
b.set_input_text("#run-image-dialog-command", "sh -c sleep infinity")
@@ -2813,20 +2815,20 @@ class TestApplication(testlib.MachineCase):
b.set_input_text("#run-image-dialog-name", container_name)
# Open image dropdown, it should list both Alpine and Busybox from the local repository
- b.click("#create-image-image")
- b.wait_visible(f'button.pf-v5-c-select__menu-item:contains("{IMG_ALPINE_LATEST}")')
- b.wait_visible(f'button.pf-v5-c-select__menu-item:contains("{IMG_BUSYBOX_LATEST}")')
+ b.click("#create-image-image input")
+ b.wait_visible(f'button.pf-v5-c-menu__item:contains("{IMG_ALPINE_LATEST}")')
+ b.wait_visible(f'button.pf-v5-c-menu__item:contains("{IMG_BUSYBOX_LATEST}")')
# Filter it down to just Busybox
- b.set_input_text("#create-image-image-select-typeahead", IMG_BUSYBOX_LATEST)
- b.wait_visible(f'button.pf-v5-c-select__menu-item:contains("{IMG_BUSYBOX_LATEST}")')
- b.wait_not_present(f'button.pf-v5-c-select__menu-item:contains("{IMG_ALPINE_LATEST}")')
+ b.set_input_text("#create-image-image input", IMG_BUSYBOX_LATEST)
+ b.wait_visible(f'button.pf-v5-c-menu__item:contains("{IMG_BUSYBOX_LATEST}")')
+ b.wait_not_present(f'button.pf-v5-c-menu__item:contains("{IMG_ALPINE_LATEST}")')
# Switch to another view; different images have different registries, so don't assume their name, just
# that at least one more exists
b.click('.image-search-footer .pf-v5-c-toggle-group__item:nth-of-type(3) button')
- b.wait_in_text(".pf-v5-c-select__menu-list", "No images found")
+ b.wait_in_text(".pf-v5-c-menu", "No images found")
# Back to local and let's select Busybox and create the container
b.click('button.pf-v5-c-toggle-group__button:contains("Local")')
- b.click(f'button.pf-v5-c-select__menu-item:contains("{IMG_BUSYBOX_LATEST}")')
+ b.click(f'button.pf-v5-c-menu__item:contains("{IMG_BUSYBOX_LATEST}")')
b.click('.pf-v5-c-modal-box__footer #create-image-create-run-btn')
b.wait_not_present("#run-image-dialog-name")
@@ -3131,15 +3133,15 @@ class TestApplication(testlib.MachineCase):
b.click("#containers-containers button.pf-v5-c-button.pf-m-primary")
b.set_input_text("#run-image-dialog-name", "new-busybox")
# Searching for container by prefix fails
- b.set_input_text("#create-image-image-select-typeahead", "localhost:80/my-busy")
- b.wait_text("button.pf-v5-c-select__menu-item.pf-m-disabled", "No images found")
+ b.set_input_text("#create-image-image input", "localhost:80/my-busy")
+ b.wait_text("button.pf-v5-c-menu__item[disabled]", "No images found")
b.wait_in_text(".pf-v5-c-alert.pf-m-danger", "couldn't search registry")
# Giving full image name finds valid manifest therefore image is pullable
- b.set_input_text("#create-image-image-select-typeahead", container_name)
- b.wait_text("button.pf-v5-c-select__menu-item:not(.pf-m-disabled)", container_name)
+ b.set_input_text("#create-image-image input", container_name)
+ b.wait_text("button.pf-v5-c-menu__item:not([disabled])", container_name)
# Select image and create a container
- b.click(f"button.pf-v5-c-select__menu-item:contains({container_name})")
+ b.click(f"button.pf-v5-c-menu__item:contains({container_name})")
b.click("#create-image-create-btn")
# Wait for download to finish
@@ -3150,25 +3152,25 @@ class TestApplication(testlib.MachineCase):
b.click("#containers-containers button.pf-v5-c-button.pf-m-primary")
# "couldn't search registry" error is hidden when some local image is found
- b.set_input_text("#create-image-image-select-typeahead", "")
- b.set_input_text("#create-image-image-select-typeahead", "localhost:80/my-busy")
- b.wait_text("button.pf-v5-c-select__menu-item:not(.pf-m-disabled)", f"{container_name}:latest")
+ b.set_input_text("#create-image-image input", "")
+ b.set_input_text("#create-image-image input", "localhost:80/my-busy")
+ b.wait_text("button.pf-v5-c-menu__item:not([disabled])", f"{container_name}:latest")
b.wait_not_present(".pf-v5-c-alert.pf-m-danger")
b.click('button.pf-v5-c-toggle-group__button:contains("Local")')
# Error is shown again when no image was found locally
- b.set_input_text("#create-image-image-select-typeahead", "localhost:80/their-busy")
- b.wait_text("button.pf-v5-c-select__menu-item.pf-m-disabled", "No images found")
+ b.set_input_text("#create-image-image input", "localhost:80/their-busy")
+ b.wait_text("button.pf-v5-c-menu__item[disabled]", "No images found")
b.wait_in_text(".pf-v5-c-alert.pf-m-danger", "couldn't search registry")
# Searching for full container name with tag finds the image
b.set_input_text("#run-image-dialog-name", "tagged-busybox")
# Search should still work with spaces around image name
- b.set_input_text("#create-image-image-select-typeahead", " localhost:80/my-busybox:nosearch-tag ")
+ b.set_input_text("#create-image-image input", " localhost:80/my-busybox:nosearch-tag ")
b.click('button.pf-v5-c-toggle-group__button:contains("All")')
- b.wait_text("button.pf-v5-c-select__menu-item:not(.pf-m-disabled)", container_name + ":nosearch-tag")
- b.wait_js_cond("document.getElementsByClassName('pf-v5-c-select__menu-item').length === 1")
- b.click(f"button.pf-v5-c-select__menu-item:contains({container_name + ":nosearch-tag"})")
+ b.wait_text("button.pf-v5-c-menu__item:not([disabled])", container_name + ":nosearch-tag")
+ b.wait_js_cond("document.querySelectorAll('.pf-v5-c-menu__item:not([disabled])').length === 1")
+ b.click(f"button.pf-v5-c-menu__item:contains({container_name + ":nosearch-tag"})")
b.click("#create-image-create-btn")
# Wait for download to finish
@@ -3178,9 +3180,9 @@ class TestApplication(testlib.MachineCase):
# Check that manifest search with tag also works on searchable repository
b.click("#containers-containers button.pf-v5-c-button.pf-m-primary")
- b.set_input_text("#create-image-image-select-typeahead", "localhost:5000/my-busybox:search-tag")
- b.wait_text("button.pf-v5-c-select__menu-item:not(.pf-m-disabled)", "localhost:5000/my-busybox:search-tag")
- b.wait_js_cond("document.getElementsByClassName('pf-v5-c-select__menu-item').length === 1")
+ b.set_input_text("#create-image-image input", "localhost:5000/my-busybox:search-tag")
+ b.wait_text("button.pf-v5-c-menu__item:not([disabled])", "localhost:5000/my-busybox:search-tag")
+ b.wait_js_cond("document.querySelectorAll('.pf-v5-c-menu__item:not([disabled])').length === 1")
if __name__ == '__main__':
diff --git a/test/reference b/test/reference
index 18c2db44..960d3140 160000
--- a/test/reference
+++ b/test/reference
@@ -1 +1 @@
-Subproject commit 18c2db442c0d3d13f0752981456351e2701aeb86
+Subproject commit 960d3140db01ff2226edb71af25a9df9fa829493