diff --git a/src/Containers.jsx b/src/Containers.jsx
index e145814a3..b18f40675 100644
--- a/src/Containers.jsx
+++ b/src/Containers.jsx
@@ -1,9 +1,9 @@
-import React, { useState } from 'react';
+import React from 'react';
import { Badge } from "@patternfly/react-core/dist/esm/components/Badge";
import { Button } from "@patternfly/react-core/dist/esm/components/Button";
import { Card, CardBody, CardHeader, CardTitle } from "@patternfly/react-core/dist/esm/components/Card";
import { Divider } from "@patternfly/react-core/dist/esm/components/Divider";
-import { Dropdown, DropdownItem, DropdownSeparator, KebabToggle } from '@patternfly/react-core/dist/esm/deprecated/components/Dropdown/index.js';
+import { DropdownItem } from '@patternfly/react-core/dist/esm/components/Dropdown/index.js';
import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex";
import { Popover } from "@patternfly/react-core/dist/esm/components/Popover";
import { LabelGroup } from "@patternfly/react-core/dist/esm/components/Label";
@@ -39,18 +39,17 @@ import { PodActions } from './PodActions.jsx';
import { PodCreateModal } from './PodCreateModal.jsx';
import PruneUnusedContainersModal from './PruneUnusedContainersModal.jsx';
+import { KebabDropdown } from "cockpit-components-dropdown.jsx";
+
const _ = cockpit.gettext;
const ContainerActions = ({ container, healthcheck, onAddNotification, localImages, updateContainer }) => {
const Dialogs = useDialogs();
const { version } = utils.usePodmanInfo();
- const [isActionsKebabOpen, setActionsKebabOpen] = useState(false);
const isRunning = container.State.Status == "running";
const isPaused = container.State.Status === "paused";
const deleteContainer = (event) => {
- setActionsKebabOpen(false);
-
if (container.State.Status == "running") {
const handleForceRemoveContainer = () => {
const id = container ? container.Id : "";
@@ -78,8 +77,6 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
const stopContainer = (force) => {
const args = {};
- setActionsKebabOpen(false);
-
if (force)
args.t = 0;
client.postContainer(container.isSystem, "stop", container.Id, args)
@@ -90,8 +87,6 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
};
const startContainer = () => {
- setActionsKebabOpen(false);
-
client.postContainer(container.isSystem, "start", container.Id, {})
.catch(ex => {
const error = cockpit.format(_("Failed to start container $0"), container.Name); // not-covered: OS error
@@ -100,8 +95,6 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
};
const resumeContainer = () => {
- setActionsKebabOpen(false);
-
client.postContainer(container.isSystem, "unpause", container.Id, {})
.catch(ex => {
const error = cockpit.format(_("Failed to resume container $0"), container.Name); // not-covered: OS error
@@ -110,8 +103,6 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
};
const pauseContainer = () => {
- setActionsKebabOpen(false);
-
client.postContainer(container.isSystem, "pause", container.Id, {})
.catch(ex => {
const error = cockpit.format(_("Failed to pause container $0"), container.Name); // not-covered: OS error
@@ -120,15 +111,11 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
};
const commitContainer = () => {
- setActionsKebabOpen(false);
-
Dialogs.show();
};
const runHealthcheck = () => {
- setActionsKebabOpen(false);
-
client.runHealthcheck(container.isSystem, container.Id)
.catch(ex => {
const error = cockpit.format(_("Failed to run health check on container $0"), container.Name); // not-covered: OS error
@@ -139,8 +126,6 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
const restartContainer = (force) => {
const args = {};
- setActionsKebabOpen(false);
-
if (force)
args.t = 0;
client.postContainer(container.isSystem, "restart", container.Id, args)
@@ -151,8 +136,6 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
};
const renameContainer = () => {
- setActionsKebabOpen(false);
-
if (container.State.Status !== "running" ||
version.localeCompare("3.0.1", undefined, { numeric: true, sensitivity: 'base' }) >= 0) {
Dialogs.show( {
- setActionsKebabOpen(false);
-
Dialogs.show();
};
const restoreContainer = () => {
- setActionsKebabOpen(false);
-
Dialogs.show();
};
@@ -222,7 +201,7 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
if (container.isSystem && !isPaused) {
actions.push(
- ,
+ ,
checkpointContainer()}>
{_("Checkpoint")}
@@ -243,7 +222,7 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
}
if (container.isSystem && container.State?.CheckpointPath) {
actions.push(
- ,
+ ,
restoreContainer()}>
{_("Restore")}
@@ -256,7 +235,7 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
}
}
- actions.push();
+ actions.push();
actions.push(
commitContainer()}>
@@ -265,7 +244,7 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
);
if (isRunning && healthcheck !== "") {
- actions.push();
+ actions.push();
actions.push(
runHealthcheck()}>
@@ -274,7 +253,7 @@ const ContainerActions = ({ container, healthcheck, onAddNotification, localImag
);
}
- actions.push();
+ actions.push();
actions.push(
);
- const kebab = (
- setActionsKebabOpen(isOpen)} />}
- isOpen={isActionsKebabOpen}
- isPlain
- position="right"
- dropdownItems={actions} />
- );
-
- return kebab;
+ return ;
};
export let onDownloadContainer = function funcOnDownloadContainer(container) {
@@ -319,27 +290,18 @@ const localize_health = (state) => {
};
const ContainerOverActions = ({ handlePruneUnusedContainers, unusedContainers }) => {
- const [isActionsKebabOpen, setIsActionsKebabOpen] = useState(false);
-
- return (
- setIsActionsKebabOpen(!isActionsKebabOpen)} id="containers-actions-dropdown" />}
- isOpen={isActionsKebabOpen}
- isPlain
- position="right"
- dropdownItems={[
- {
- setIsActionsKebabOpen(false);
- handlePruneUnusedContainers();
- }}
- isDisabled={unusedContainers.length === 0}>
- {_("Prune unused containers")}
- ,
- ]} />
- );
+ const actions = [
+ handlePruneUnusedContainers()}
+ isDisabled={unusedContainers.length === 0}>
+ {_("Prune unused containers")}
+ ,
+ ];
+
+ return ;
};
class Containers extends React.Component {
diff --git a/src/Dropdown.jsx b/src/Dropdown.jsx
deleted file mode 100644
index d78ffffe1..000000000
--- a/src/Dropdown.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React, { useState } from 'react';
-import { Dropdown, DropdownItem, DropdownToggle, DropdownToggleAction } from '@patternfly/react-core/dist/esm/deprecated/components/Dropdown/index.js';
-
-export const DropDown = ({ actions }) => {
- const [isOpen, setIsOpen] = useState(false);
- const dropdownItems = actions
- .map(button => {
- return (
-
- {button.label}
-
- );
- });
-
- return (
- setIsOpen(!isOpen)}
- id={actions[0].label + "-dropdown"}
- toggle={
-
- {actions[0].label}
-
- ]}
- splitButtonVariant="action"
- onToggle={(_event, open) => setIsOpen(open)}
- />
- }
- isOpen={isOpen}
- dropdownItems={dropdownItems}
- />
- );
-};
-DropDown.defaultProps = {
- actions: [{ label: '' }]
-};
diff --git a/src/Images.jsx b/src/Images.jsx
index 72957d5fe..c559caa40 100644
--- a/src/Images.jsx
+++ b/src/Images.jsx
@@ -1,7 +1,7 @@
-import React, { useState } from 'react';
+import React from 'react';
import { Button } from "@patternfly/react-core/dist/esm/components/Button";
import { Card, CardBody, CardFooter, CardHeader, CardTitle } from "@patternfly/react-core/dist/esm/components/Card";
-import { Dropdown, DropdownItem, KebabToggle } from '@patternfly/react-core/dist/esm/deprecated/components/Dropdown/index.js';
+import { DropdownItem } from '@patternfly/react-core/dist/esm/components/Dropdown/index.js';
import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex";
import { ExpandableSection } from "@patternfly/react-core/dist/esm/components/ExpandableSection";
import { Text, TextVariants } from "@patternfly/react-core/dist/esm/components/Text";
@@ -23,6 +23,8 @@ import { useDialogs, DialogsContext } from "dialogs.jsx";
import './Images.css';
import '@patternfly/react-styles/css/utilities/Sizing/sizing.css';
+import { KebabDropdown } from "cockpit-components-dropdown.jsx";
+
const _ = cockpit.gettext;
class Images extends React.Component {
@@ -328,44 +330,40 @@ class Images extends React.Component {
}
const ImageOverActions = ({ handleDownloadNewImage, handlePruneUsedImages, unusedImages }) => {
- const [isActionsKebabOpen, setIsActionsKebabOpen] = useState(false);
+ const actions = [
+ handleDownloadNewImage()}
+ >
+ {_("Download new image")}
+ ,
+ handlePruneUsedImages()}
+ isDisabled={unusedImages.length === 0}
+ isAriaDisabled={unusedImages.length === 0}
+ >
+ {_("Prune unused images")}
+
+ ];
return (
- setIsActionsKebabOpen(!isActionsKebabOpen)} id="image-actions-dropdown" />}
- isOpen={isActionsKebabOpen}
- isPlain
- position="right"
- dropdownItems={[
- {
- setIsActionsKebabOpen(false);
- handleDownloadNewImage();
- }}>
- {_("Download new image")}
- ,
- {
- setIsActionsKebabOpen(false);
- handlePruneUsedImages();
- }}
- isDisabled={unusedImages.length === 0}
- isAriaDisabled={unusedImages.length === 0}>
- {_("Prune unused images")}
- ,
- ]} />
+
);
};
const ImageActions = ({ image, onAddNotification, user, systemServiceAvailable, userServiceAvailable }) => {
const Dialogs = useDialogs();
- const [isActionsKebabOpen, setIsActionsKebabOpen] = useState(false);
const runImage = () => {
- setIsActionsKebabOpen(false);
Dialogs.show(
{(podmanInfo) => (
@@ -387,7 +385,6 @@ const ImageActions = ({ image, onAddNotification, user, systemServiceAvailable,
};
const removeImage = () => {
- setIsActionsKebabOpen(false);
Dialogs.show();
};
@@ -406,31 +403,25 @@ const ImageActions = ({ image, onAddNotification, user, systemServiceAvailable,
);
- const extraActions = (
- setIsActionsKebabOpen(!isActionsKebabOpen)} />}
- isOpen={isActionsKebabOpen}
- isPlain
- position="right"
- dropdownItems={[
-
- {_("Create container")}
- ,
-
- {_("Delete")}
-
- ]} />
- );
+ const dropdownActions = [
+
+ {_("Create container")}
+ ,
+
+ {_("Delete")}
+
+ ];
return (
<>
{runImageAction}
- {extraActions}
+
>
);
};
diff --git a/src/PodActions.jsx b/src/PodActions.jsx
index 5e7f92bfe..c01ad771b 100644
--- a/src/PodActions.jsx
+++ b/src/PodActions.jsx
@@ -3,12 +3,14 @@ import React, { useState } from 'react';
import { Button } from "@patternfly/react-core/dist/esm/components/Button";
import { Alert } from "@patternfly/react-core/dist/esm/components/Alert";
import { Modal } from "@patternfly/react-core/dist/esm/components/Modal";
-import { Dropdown, DropdownItem, DropdownPosition, DropdownSeparator, KebabToggle } from '@patternfly/react-core/dist/esm/deprecated/components/Dropdown/index.js';
+import { Divider } from '@patternfly/react-core/dist/esm/components/Divider/index.js';
+import { DropdownItem } from '@patternfly/react-core/dist/esm/components/Dropdown/index.js';
import { List, ListItem } from "@patternfly/react-core/dist/esm/components/List";
import { Stack } from "@patternfly/react-core/dist/esm/layouts/Stack";
import cockpit from 'cockpit';
import { useDialogs } from "dialogs.jsx";
+import { KebabDropdown } from "cockpit-components-dropdown.jsx";
import * as client from './client.js';
@@ -69,7 +71,6 @@ const PodDeleteModal = ({ pod }) => {
export const PodActions = ({ onAddNotification, pod }) => {
const Dialogs = useDialogs();
- const [isOpen, setOpen] = useState(false);
const dropdownItems = [];
// Possible Pod Statuses can be found here https://github.com/containers/podman/blob/main/libpod/define/podstate.go
@@ -168,7 +169,7 @@ export const PodActions = ({ onAddNotification, pod }) => {
}
if (dropdownItems.length > 1) {
- dropdownItems.push();
+ dropdownItems.push();
}
dropdownItems.push(
{
return null;
return (
- setOpen(!isOpen)}
- position={DropdownPosition.right}
- toggle={ setOpen(value)} id={"pod-" + pod.Name + (pod.isSystem ? "-system" : "-user") + "-action-toggle"} />}
- isOpen={isOpen}
- isPlain
- dropdownItems={dropdownItems} />
+
);
};
diff --git a/test/check-application b/test/check-application
index d3e8332af..41109d43d 100755
--- a/test/check-application
+++ b/test/check-application
@@ -214,11 +214,11 @@ class TestApplication(testlib.MachineCase):
def performContainerAction(self, container, cmd):
b = self.browser
- b.click(f"#containers-containers tbody tr:contains('{container}') .pf-v5-c-dropdown__toggle")
- b.click(f"#containers-containers tbody tr:contains('{container}') .pf-v5-c-dropdown__menu li:contains({cmd})")
+ b.click(f"#containers-containers tbody tr:contains('{container}') .pf-v5-c-menu-toggle")
+ b.click(f"#containers-containers tbody tr:contains('{container}') button.pf-v5-c-menu__item:contains({cmd})")
def getContainerAction(self, container, cmd):
- return f"#containers-containers tbody tr:contains('{container}') .pf-v5-c-dropdown__menu li:contains({cmd})"
+ return f"#containers-containers tbody tr:contains('{container}') button.pf-v5-c-menu__item:contains({cmd})"
def toggleExpandedContainer(self, container):
b = self.browser
@@ -291,8 +291,8 @@ class TestApplication(testlib.MachineCase):
b = self.browser
b.click(f"#pod-{podName}-{podOwner}-action-toggle")
- b.click(f"ul[aria-labelledby=pod-{podName}-{podOwner}-action-toggle] li > button.pod-action-{action.lower()}")
- b.wait_not_present(f"ul[aria-labelledby=pod-{podName}-{podOwner}-action-toggle]")
+ b.click(f"ul.pf-v5-c-menu__list li > button.pod-action-{action.lower()}")
+ b.wait_not_present("ul.pf-v5-c-menu__list")
def getStartTime(self, container: str, *, auth: bool) -> str:
# don't format the raw time strings from the API, force json format
@@ -578,6 +578,10 @@ class TestApplication(testlib.MachineCase):
def _testBasic(self, auth):
b = self.browser
+ def clickDeleteImage(image_sel):
+ b.click(f'{image_sel} .pf-v5-c-menu-toggle')
+ b.click(image_sel + " button.btn-delete")
+
if not auth:
self.allow_browser_errors("Failed to start system podman.socket.*")
@@ -629,7 +633,7 @@ class TestApplication(testlib.MachineCase):
hello_sel = f"#containers-images tbody tr[data-row-id=\"{images[IMG_HELLO_LATEST]}{auth}\"]".lower()
b.wait_visible(hello_sel)
b.click(hello_sel + " td.pf-v5-c-table__toggle button")
- b.click(hello_sel + " .pf-v5-c-dropdown__toggle")
+ b.click(hello_sel + " .pf-v5-c-menu-toggle")
b.wait_visible(hello_sel + " button.btn-delete")
b.wait_in_text("#containers-images tbody.pf-m-expanded tr .image-details:first-child", "Command/run.sh")
# Show history
@@ -799,8 +803,7 @@ class TestApplication(testlib.MachineCase):
b.wait_in_text(busybox_sel + " + tr", f"{IMG_BUSYBOX}:3")
b.wait_in_text(busybox_sel + " + tr", f"{IMG_BUSYBOX}:4")
- b.click(busybox_sel + " .pf-v5-c-dropdown__toggle")
- b.click(busybox_sel + " button.btn-delete")
+ clickDeleteImage(busybox_sel)
self.assertTrue(b.get_checked(f".pf-v5-c-check__input[aria-label='{IMG_BUSYBOX_LATEST}']"))
b.set_checked(f".pf-v5-c-check__input[aria-label='{IMG_BUSYBOX}:1']", True)
b.set_checked(f".pf-v5-c-check__input[aria-label='{IMG_BUSYBOX}:3']", True)
@@ -811,8 +814,7 @@ class TestApplication(testlib.MachineCase):
b.wait_not_in_text(busybox_sel + " + tr", f"{IMG_BUSYBOX}:1")
b.wait_not_in_text(busybox_sel + " + tr", f"{IMG_BUSYBOX}:3")
- b.click(busybox_sel + " .pf-v5-c-dropdown__toggle")
- b.click(busybox_sel + " button.btn-delete")
+ clickDeleteImage(busybox_sel)
b.click("#delete-all")
self.assertTrue(b.get_checked(f".pf-v5-c-check__input[aria-label='{IMG_BUSYBOX_LATEST}']"))
self.assertTrue(b.get_checked(f".pf-v5-c-check__input[aria-label='{IMG_BUSYBOX}:2']"))
@@ -851,8 +853,7 @@ class TestApplication(testlib.MachineCase):
alpine_sel = f"#containers-images tbody tr[data-row-id=\"{images[IMG_ALPINE_LATEST]}{auth}\"]".lower()
b.wait_visible(alpine_sel)
b.click(alpine_sel + " td.pf-v5-c-table__toggle button")
- b.click(alpine_sel + " .pf-v5-c-dropdown__toggle")
- b.click(alpine_sel + " button.btn-delete")
+ clickDeleteImage(alpine_sel)
self.confirm_modal("Delete")
self.confirm_modal("Force delete")
b.wait_not_present(alpine_sel)
@@ -898,8 +899,7 @@ class TestApplication(testlib.MachineCase):
# Delete intermediate images
intermediate_image_sel = "#containers-images tbody:last-child:contains(':')"
b.click(".listing-action button:contains('Show intermediate images')")
- b.click(intermediate_image_sel + " .pf-v5-c-dropdown__toggle")
- b.click(intermediate_image_sel + " button.btn-delete")
+ clickDeleteImage(intermediate_image_sel)
self.confirm_modal("Delete")
b.wait_not_present(intermediate_image_sel)
@@ -916,8 +916,7 @@ class TestApplication(testlib.MachineCase):
# Delete intermediate image which is in use
self.execute(auth, f"podman untag {IMG_INTERMEDIATE}")
- b.click(intermediate_image_sel + " .pf-v5-c-dropdown__toggle")
- b.click(intermediate_image_sel + " button.btn-delete")
+ clickDeleteImage(intermediate_image_sel)
self.confirm_modal("Delete")
self.confirm_modal("Force delete")
b.wait_not_in_text("#containers-images", ":")
@@ -1144,7 +1143,7 @@ class TestApplication(testlib.MachineCase):
b.click(sel + " td.pf-v5-c-table__toggle button")
# Click the delete icon on the image row
- b.click(sel + " .pf-v5-c-dropdown__toggle")
+ b.click(sel + " .pf-v5-c-menu-toggle")
b.click(sel + ' button.btn-delete')
if another:
@@ -1254,7 +1253,7 @@ class TestApplication(testlib.MachineCase):
container_sha = self.execute(auth, "podman inspect --format '{{.Id}}' swamped-crate").strip()
self.waitContainer(container_sha, auth, name='swamped-crate', image=IMG_BUSYBOX,
state='Exited', owner="system" if auth else "admin")
- b.click("#containers-containers tbody tr:contains('swamped-crate') .pf-v5-c-dropdown__toggle")
+ b.click("#containers-containers tbody tr:contains('swamped-crate') .pf-v5-c-menu-toggle")
if not auth:
# Checkpoint/restore is not supported on user containers yet - the related buttons should not be shown
@@ -1264,7 +1263,7 @@ class TestApplication(testlib.MachineCase):
# Health check is not set up
b.wait_not_present(self.getContainerAction('swamped-crate', 'Run health check'))
- b.click("#containers-containers tbody tr:contains('swamped-crate') .pf-v5-c-dropdown__toggle")
+ b.click("#containers-containers tbody tr:contains('swamped-crate') .pf-v5-c-menu-toggle")
# Start the container
self.performContainerAction(IMG_BUSYBOX, "Start")
@@ -1313,10 +1312,10 @@ class TestApplication(testlib.MachineCase):
self.waitContainerRow(IMG_BUSYBOX)
if not auth:
# Check that the checkpoint option is not present for rootless
- b.click(f"#containers-containers tbody tr:contains('{IMG_BUSYBOX}') .pf-v5-c-dropdown__toggle")
+ b.click(f"#containers-containers tbody tr:contains('{IMG_BUSYBOX}') .pf-v5-c-menu-toggle")
b.wait_visible(self.getContainerAction(IMG_BUSYBOX, 'Force stop'))
b.wait_not_present(self.getContainerAction(IMG_BUSYBOX, 'Checkpoint'))
- b.click(f"#containers-containers tbody tr:contains('{IMG_BUSYBOX}') .pf-v5-c-dropdown__toggle")
+ b.click(f"#containers-containers tbody tr:contains('{IMG_BUSYBOX}') .pf-v5-c-menu-toggle")
# Stop the container
self.performContainerAction(IMG_BUSYBOX, "Force stop")
@@ -1395,9 +1394,9 @@ class TestApplication(testlib.MachineCase):
b.wait(lambda: self.execute(True, "podman ps --all | grep -e swamped-crate -e Exited"))
# Check that the restore option is not present (i.e. start is a regular button)
- b.click(f"#containers-containers tbody tr:contains('{IMG_BUSYBOX}') .pf-v5-c-dropdown__toggle")
+ b.click(f"#containers-containers tbody tr:contains('{IMG_BUSYBOX}') .pf-v5-c-menu-toggle")
b.wait_not_present(self.getContainerAction(IMG_BUSYBOX, 'Restore'))
- b.click(f"#containers-containers tbody tr:contains('{IMG_BUSYBOX}') .pf-v5-c-dropdown__toggle")
+ b.click(f"#containers-containers tbody tr:contains('{IMG_BUSYBOX}') .pf-v5-c-menu-toggle")
# Start the container
self.performContainerAction("swamped-crate", "Start")
@@ -2200,7 +2199,7 @@ class TestApplication(testlib.MachineCase):
# By default we have 3 unused images, start one.
self.execute(auth or root, f"podman run -d --name used_image --stop-timeout 0 {IMG_ALPINE} sh")
b.click("#image-actions-dropdown")
- b.click("button:contains(Prune unused images)")
+ b.click("#prune-unused-images-button")
if auth:
b.wait_js_func("ph_count_check", ".pf-v5-c-modal-box__body .pf-v5-c-list li",
@@ -2226,7 +2225,7 @@ class TestApplication(testlib.MachineCase):
# Prune button should now be disabled
b.click("#image-actions-dropdown")
- b.wait_visible("button:contains(Prune unused images).pf-m-disabled")
+ b.wait_visible(".pf-m-disabled.pf-v5-c-menu__list-item:contains(Prune unused images)")
def testPruneUnusedImagesSystemSelections(self):
""" Test the prune unused images selection options"""
@@ -2266,7 +2265,7 @@ class TestApplication(testlib.MachineCase):
# Prune button should now be disabled
b.click("#image-actions-dropdown")
- b.wait_visible("button:contains(Prune unused images).pf-m-disabled")
+ b.wait_visible(".pf-v5-c-menu__list-item.pf-m-disabled:contains(Prune unused images)")
def testPruneUnusedContainersSystem(self):
self._testPruneUnusedContainersSystem(True)