LCOV - code coverage report
Current view: top level - pkg/lib - cockpit-components-install-dialog.jsx Coverage Total Hit
Test: cockpit Lines: 95.2 % 125 119
Test Date: 2026-06-25 09:20:42

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2018 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6          274 : import cockpit from "cockpit";
       7          274 : 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          274 : 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 : }
        

Generated by: LCOV version 2.0-1