Line data Source code
1 : /*
2 : * Copyright (C) 2018 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 50 : import cockpit from "cockpit";
7 50 : 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 50 : 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 4 : export async function install_dialog(pkg, options) {
36 4 : let data = null;
37 4 : let error_message = null;
38 4 : let progress_message = null;
39 4 : let cancel = null;
40 4 : let done = null;
41 :
42 4 : if (!Array.isArray(pkg))
43 2 : pkg = [pkg];
44 :
45 4 : options = options || { };
46 :
47 4 : const package_manager = await getPackageManager();
48 1 : const prom = new Promise((resolve, reject) => { done = f => { if (f) resolve(); else reject(); } });
49 :
50 4 : let dialog = null;
51 4 : function update() {
52 4 : let extra_details = null;
53 4 : let remove_details = null;
54 4 : let footer_message = null;
55 :
56 4 : const missing_name = <strong>{pkg.join(", ")}</strong>;
57 :
58 4 : if (data && data.extra_names.length > 0)
59 4 : extra_details = (
60 1 : <div className="scale-up-ct">
61 1 : {_("Additional packages:")}
62 1 : <ul className="package-list-ct">{data.extra_names.map(id => <li key={id}>{id}</li>)}</ul>
63 1 : </div>
64 : );
65 :
66 4 : if (data && data.remove_names.length > 0)
67 4 : 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 4 : if (progress_message)
75 4 : footer_message = (
76 0 : <Flex spaceItems={{ default: 'spaceItemsSm' }} alignItems={{ default: 'alignItemsCenter' }}>
77 0 : <span>{ progress_message }</span>
78 0 : <Spinner size="sm" />
79 0 : </Flex>
80 : );
81 3 : else if (data?.download_size) {
82 3 : footer_message = (
83 3 : <div>
84 3 : { fmt_to_fragments(_("Total size: $0"), <strong>{cockpit.format_bytes(data.download_size)}</strong>) }
85 3 : </div>
86 : );
87 3 : }
88 :
89 4 : const body = {
90 4 : id: "dialog",
91 4 : title: options.title || _("Install software"),
92 4 : body: (
93 4 : <div className="scroll">
94 4 : <p>{ fmt_to_fragments(options.text || _("$0 will be installed."), missing_name) }</p>
95 4 : { remove_details }
96 4 : { extra_details }
97 4 : </div>
98 : ),
99 4 : static_error: error_message,
100 4 : };
101 :
102 4 : const footer = {
103 4 : actions: [
104 4 : {
105 4 : caption: _("Install"),
106 4 : style: "primary",
107 4 : clicked: install_missing,
108 4 : disabled: data == null
109 4 : }
110 4 : ],
111 4 : idle_message: footer_message,
112 0 : dialog_done: f => { if (!f && cancel) cancel(); done(f) }
113 4 : };
114 :
115 4 : if (dialog) {
116 4 : dialog.setProps(body);
117 4 : dialog.setFooterProps(footer);
118 4 : } else {
119 4 : dialog = show_modal_dialog(body, footer);
120 4 : }
121 4 : }
122 :
123 4 : function check_missing() {
124 4 : package_manager.check_missing_packages(pkg,
125 0 : p => {
126 0 : cancel = p.cancel;
127 0 : let pm = null;
128 0 : if (p.waiting)
129 0 : pm = _("Waiting for other software management operations to finish");
130 : else
131 0 : pm = _("Checking installed software");
132 0 : if (pm != progress_message) {
133 0 : progress_message = pm;
134 0 : update();
135 0 : }
136 0 : })
137 4 : .then(d => {
138 4 : if (d.unavailable_names.length > 0)
139 1 : error_message = cockpit.format(_("$0 is not available from any repository."),
140 1 : d.unavailable_names[0]);
141 : else
142 4 : data = d;
143 4 : progress_message = null;
144 4 : cancel = null;
145 4 : update();
146 4 : })
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 4 : }
154 :
155 4 : function install_missing(progress_cb) {
156 4 : return package_manager.install_missing_packages(data,
157 3 : p => {
158 3 : 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 3 : let fmt;
163 3 : if (p.info == InstallProgressType.DOWNLOADING)
164 3 : fmt = _("Downloading $0");
165 3 : else if (p.info == InstallProgressType.REMOVING)
166 0 : fmt = _("Removing $0");
167 : else
168 3 : fmt = _("Installing $0");
169 3 : text = fmt_to_fragments(fmt, <strong>{p.package}</strong>);
170 3 : }
171 3 : progress_cb(text, p.cancel);
172 3 : });
173 4 : }
174 :
175 4 : update();
176 4 : check_missing();
177 4 : return prom;
178 4 : }
|