Line data Source code
1 : /*
2 : * Copyright (C) 2018 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 273 : import cockpit from "cockpit";
7 273 : import React from "react";
8 :
9 : import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
10 : import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
11 : import { WarningTriangleIcon } from "@patternfly/react-icons";
12 :
13 : import { show_modal_dialog } from "cockpit-components-dialog.jsx";
14 : import { getPackageManager, InstallProgressType } from "packagemanager";
15 :
16 : import "cockpit-components-install-dialog.css";
17 : import { fmt_to_fragments } from "utils";
18 :
19 273 : const _ = cockpit.gettext;
20 :
21 : /* Calling install_dialog will open a dialog that lets the user
22 : * install the given package.
23 : *
24 : * The install_dialog function returns a promise that is fulfilled when the dialog closes after
25 : * a successful installation. The promise is rejected when the user cancels the dialog.
26 : *
27 : * If the package is already installed before the dialog opens, we still go
28 : * through all the motions and the dialog closes successfully without doing
29 : * anything when the use hits "Install".
30 : *
31 : * You shouldn't call install_dialog unless you know that PackageKit is available.
32 : * (If you do anyway, the resulting D-Bus errors will be shown to the user.)
33 : */
34 :
35 9 : export async function install_dialog(pkg, options) {
36 9 : let data = null;
37 9 : let error_message = null;
38 9 : let progress_message = null;
39 9 : let cancel = null;
40 9 : let done = null;
41 :
42 9 : if (!Array.isArray(pkg))
43 7 : pkg = [pkg];
44 :
45 7 : options = options || { };
46 :
47 9 : const package_manager = await getPackageManager();
48 2 : const prom = new Promise((resolve, reject) => { done = f => { if (f) resolve(); else reject(); } });
49 :
50 9 : let dialog = null;
51 9 : function update() {
52 9 : let extra_details = null;
53 9 : let remove_details = null;
54 9 : let footer_message = null;
55 :
56 9 : const missing_name = <strong>{pkg.join(", ")}</strong>;
57 :
58 8 : if (data && data.extra_names.length > 0)
59 9 : extra_details = (
60 2 : <div className="scale-up-ct">
61 2 : {_("Additional packages:")}
62 2 : <ul className="package-list-ct">{data.extra_names.map(id => <li key={id}>{id}</li>)}</ul>
63 2 : </div>
64 : );
65 :
66 8 : if (data && data.remove_names.length > 0)
67 9 : remove_details = (
68 1 : <div className="scale-up-ct">
69 1 : <WarningTriangleIcon /> {_("Removals:")}
70 1 : <ul className="package-list">{data.remove_names.map(id => <li key={id}>{id}</li>)}</ul>
71 1 : </div>
72 : );
73 :
74 9 : if (progress_message)
75 9 : footer_message = (
76 1 : <Flex spaceItems={{ default: 'spaceItemsSm' }} alignItems={{ default: 'alignItemsCenter' }}>
77 1 : <span>{ progress_message }</span>
78 1 : <Spinner size="sm" />
79 1 : </Flex>
80 : );
81 7 : else if (data?.download_size) {
82 7 : footer_message = (
83 7 : <div>
84 7 : { fmt_to_fragments(_("Total size: $0"), <strong>{cockpit.format_bytes(data.download_size)}</strong>) }
85 7 : </div>
86 : );
87 7 : }
88 :
89 9 : const body = {
90 9 : id: "dialog",
91 7 : title: options.title || _("Install software"),
92 9 : body: (
93 9 : <div className="scroll">
94 8 : <p>{ fmt_to_fragments(options.text || _("$0 will be installed."), missing_name) }</p>
95 9 : { remove_details }
96 9 : { extra_details }
97 9 : </div>
98 : ),
99 9 : static_error: error_message,
100 9 : };
101 :
102 9 : const footer = {
103 9 : actions: [
104 9 : {
105 9 : caption: _("Install"),
106 9 : style: "primary",
107 9 : clicked: install_missing,
108 9 : disabled: data == null
109 9 : }
110 9 : ],
111 9 : idle_message: footer_message,
112 0 : dialog_done: f => { if (!f && cancel) cancel(); done(f) }
113 9 : };
114 :
115 8 : if (dialog) {
116 8 : dialog.setProps(body);
117 8 : dialog.setFooterProps(footer);
118 8 : } else {
119 9 : dialog = show_modal_dialog(body, footer);
120 9 : }
121 9 : }
122 :
123 9 : function check_missing() {
124 9 : package_manager.check_missing_packages(pkg,
125 1 : p => {
126 1 : cancel = p.cancel;
127 1 : let pm = null;
128 1 : if (p.waiting)
129 0 : pm = _("Waiting for other software management operations to finish");
130 : else
131 1 : pm = _("Checking installed software");
132 1 : if (pm != progress_message) {
133 1 : progress_message = pm;
134 1 : update();
135 1 : }
136 1 : })
137 8 : .then(d => {
138 8 : if (d.unavailable_names.length > 0)
139 2 : error_message = cockpit.format(_("$0 is not available from any repository."),
140 2 : d.unavailable_names[0]);
141 : else
142 8 : data = d;
143 8 : progress_message = null;
144 8 : cancel = null;
145 8 : update();
146 8 : })
147 1 : .catch(e => {
148 1 : progress_message = null;
149 1 : cancel = null;
150 1 : error_message = e.toString();
151 1 : update();
152 1 : });
153 9 : }
154 :
155 8 : function install_missing(progress_cb) {
156 8 : return package_manager.install_missing_packages(data,
157 7 : p => {
158 7 : let text = null;
159 0 : if (p.waiting) {
160 0 : text = _("Waiting for other software management operations to finish");
161 0 : } else if (p.package) {
162 7 : let fmt;
163 7 : if (p.info == InstallProgressType.DOWNLOADING)
164 6 : fmt = _("Downloading $0");
165 7 : else if (p.info == InstallProgressType.REMOVING)
166 0 : fmt = _("Removing $0");
167 : else
168 7 : fmt = _("Installing $0");
169 7 : text = fmt_to_fragments(fmt, <strong>{p.package}</strong>);
170 7 : }
171 7 : progress_cb(text, p.cancel);
172 7 : });
173 8 : }
174 :
175 9 : update();
176 9 : check_missing();
177 9 : return prom;
178 9 : }
|