Line data Source code
1 : /*
2 : * Copyright (C) 2018 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 275 : import cockpit from "cockpit";
7 275 : 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 275 : 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 7 : export async function install_dialog(pkg, options) {
36 7 : let data = null;
37 7 : let error_message = null;
38 7 : let progress_message = null;
39 7 : let cancel = null;
40 7 : let done = null;
41 :
42 7 : if (!Array.isArray(pkg))
43 5 : pkg = [pkg];
44 :
45 5 : options = options || { };
46 :
47 7 : const package_manager = await getPackageManager();
48 2 : const prom = new Promise((resolve, reject) => { done = f => { if (f) resolve(); else reject(); } });
49 :
50 7 : let dialog = null;
51 7 : function update() {
52 7 : let extra_details = null;
53 7 : let remove_details = null;
54 7 : let footer_message = null;
55 :
56 7 : const missing_name = <strong>{pkg.join(", ")}</strong>;
57 :
58 7 : if (data && data.extra_names.length > 0)
59 7 : 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 7 : if (data && data.remove_names.length > 0)
67 7 : 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 7 : if (progress_message)
75 7 : 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 6 : else if (data?.download_size) {
82 6 : footer_message = (
83 6 : <div>
84 6 : { fmt_to_fragments(_("Total size: $0"), <strong>{cockpit.format_bytes(data.download_size)}</strong>) }
85 6 : </div>
86 : );
87 6 : }
88 :
89 7 : const body = {
90 7 : id: "dialog",
91 5 : title: options.title || _("Install software"),
92 7 : body: (
93 7 : <div className="scroll">
94 6 : <p>{ fmt_to_fragments(options.text || _("$0 will be installed."), missing_name) }</p>
95 7 : { remove_details }
96 7 : { extra_details }
97 7 : </div>
98 : ),
99 7 : static_error: error_message,
100 7 : };
101 :
102 7 : const footer = {
103 7 : actions: [
104 7 : {
105 7 : caption: _("Install"),
106 7 : style: "primary",
107 7 : clicked: install_missing,
108 7 : disabled: data == null
109 7 : }
110 7 : ],
111 7 : idle_message: footer_message,
112 0 : dialog_done: f => { if (!f && cancel) cancel(); done(f) }
113 7 : };
114 :
115 7 : if (dialog) {
116 7 : dialog.setProps(body);
117 7 : dialog.setFooterProps(footer);
118 7 : } else {
119 7 : dialog = show_modal_dialog(body, footer);
120 7 : }
121 7 : }
122 :
123 7 : function check_missing() {
124 7 : 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 7 : .then(d => {
138 7 : 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 7 : data = d;
143 7 : progress_message = null;
144 7 : cancel = null;
145 7 : update();
146 7 : })
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 7 : }
154 :
155 7 : function install_missing(progress_cb) {
156 7 : return package_manager.install_missing_packages(data,
157 6 : p => {
158 6 : 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 6 : let fmt;
163 6 : if (p.info == InstallProgressType.DOWNLOADING)
164 5 : fmt = _("Downloading $0");
165 6 : else if (p.info == InstallProgressType.REMOVING)
166 0 : fmt = _("Removing $0");
167 : else
168 6 : fmt = _("Installing $0");
169 6 : text = fmt_to_fragments(fmt, <strong>{p.package}</strong>);
170 6 : }
171 6 : progress_cb(text, p.cancel);
172 6 : });
173 7 : }
174 :
175 7 : update();
176 7 : check_missing();
177 7 : return prom;
178 7 : }
|