Line data Source code
1 43 : import React, { useState } from 'react';
2 :
3 : import { Button } from "@patternfly/react-core/dist/esm/components/Button";
4 : import { Checkbox } from "@patternfly/react-core/dist/esm/components/Checkbox";
5 : import { List, ListItem } from "@patternfly/react-core/dist/esm/components/List";
6 : import {
7 : Modal
8 : } from '@patternfly/react-core/dist/esm/deprecated/components/Modal';
9 : import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex";
10 :
11 : import cockpit from 'cockpit';
12 :
13 : import * as client from './client.js';
14 : import * as utils from './util.js';
15 :
16 : import "@patternfly/patternfly/utilities/Spacing/spacing.css";
17 :
18 43 : const _ = cockpit.gettext;
19 :
20 4 : function ImageOptions({ images, checked, user, handleChange, name, showCheckbox }) {
21 4 : const [isExpanded, onToggle] = useState(false);
22 4 : let shownImages = images;
23 4 : if (!isExpanded) {
24 4 : shownImages = shownImages.slice(0, 5);
25 4 : }
26 :
27 0 : if (shownImages.length === 0) {
28 0 : return null;
29 0 : }
30 4 : const listNameId = "list-" + name;
31 :
32 4 : return (
33 4 : <Flex flex={{ default: 'column' }}>
34 4 : {showCheckbox &&
35 2 : <Checkbox
36 2 : label={user.uid === 0
37 2 : ? _("Delete unused system images:")
38 2 : : cockpit.format(_("Delete unused images of user $0:"), user.name)}
39 2 : isChecked={checked}
40 2 : id={name}
41 2 : name={name}
42 1 : onChange={(_, val) => handleChange(val)}
43 2 : aria-owns={listNameId}
44 2 : />
45 : }
46 4 : <List id={listNameId}>
47 4 : {shownImages.map((image, index) =>
48 4 : <ListItem className="pf-v6-u-ml-md" key={index}>
49 4 : {utils.image_name(image)}
50 4 : </ListItem>
51 4 : )}
52 4 : {!isExpanded && images.length > 5 &&
53 0 : <Button onClick={onToggle} variant="link" isInline>
54 0 : {_("Show more")}
55 0 : </Button>
56 : }
57 4 : </List>
58 4 : </Flex>
59 : );
60 4 : }
61 :
62 4 : const PruneUnusedImagesModal = ({ close, unusedImages, onAddNotification, users }) => {
63 4 : const unusedOwners = users.filter(user => unusedImages.some(image => image.uid === user.uid));
64 4 : const [isPruning, setPruning] = useState(false);
65 4 : const [deleteOwners, setDeleteOwners] = useState(unusedOwners);
66 :
67 4 : const handlePruneUnusedImages = () => {
68 4 : setPruning(true);
69 :
70 4 : const actions = deleteOwners.map(owner => client.pruneUnusedImages(owner.con));
71 4 : Promise.all(actions).then(close)
72 0 : .catch(ex => {
73 0 : const error = _("Failed to prune unused images");
74 0 : onAddNotification({ type: 'danger', error, errorDetail: ex.message });
75 0 : close();
76 0 : });
77 4 : };
78 :
79 4 : const showCheckboxes = unusedOwners.length > 1;
80 :
81 1 : const onCheckChange = (user, checked) => setDeleteOwners(prevState => {
82 1 : return checked ? prevState.concat([user]) : prevState.filter(u => u !== user);
83 1 : });
84 :
85 4 : return (
86 4 : <Modal isOpen
87 4 : onClose={close}
88 4 : position="top" variant="medium"
89 4 : title={cockpit.format(_("Prune unused images"))}
90 4 : footer={<>
91 4 : <Button id="btn-img-delete" variant="danger"
92 4 : spinnerAriaValueText={isPruning ? _("Pruning images") : undefined}
93 4 : isLoading={isPruning}
94 4 : isDisabled={deleteOwners.length === 0}
95 4 : onClick={handlePruneUnusedImages}>
96 4 : {isPruning ? _("Pruning images") : _("Prune")}
97 4 : </Button>
98 0 : <Button variant="link" onClick={() => close()}>{_("Cancel")}</Button>
99 4 : </>}
100 : >
101 4 : <Flex flex={{ default: 'column' }}>
102 4 : {unusedOwners.map(user => (
103 4 : <ImageOptions key={user.name}
104 4 : images={unusedImages.filter(image => image.uid === user.uid)}
105 4 : name={"deleteImages-" + user.name}
106 4 : checked={deleteOwners.some(u => u.uid === user.uid)}
107 1 : handleChange={checked => onCheckChange(user, checked)}
108 4 : showCheckbox={showCheckboxes}
109 4 : user={user} />))
110 : }
111 4 : </Flex>
112 4 : </Modal>
113 : );
114 4 : };
115 :
116 43 : export default PruneUnusedImagesModal;
|