LCOV - code coverage report
Current view: top level - pkg/lib - cockpit-components-install-dialog.jsx Coverage Total Hit
Test: cockpit Lines: 84.0 % 125 105
Test Date: 2026-06-17 06:28:00

            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 : }
        

Generated by: LCOV version 2.0-1