LCOV - code coverage report
Current view: top level - pkg/apps - application.jsx Coverage Total Hit
Test: cockpit Lines: 93.9 % 115 108
Test Date: 2026-06-17 06:28:00

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2017 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6            6 : import cockpit from "cockpit";
       7            6 : import React from "react";
       8              : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
       9              : import { Breadcrumb, BreadcrumbItem } from "@patternfly/react-core/dist/esm/components/Breadcrumb/index.js";
      10              : import { Card, CardBody, CardHeader, CardTitle } from '@patternfly/react-core/dist/esm/components/Card/index.js';
      11              : import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
      12              : import { Page, PageBreadcrumb, PageSection } from "@patternfly/react-core/dist/esm/components/Page/index.js";
      13              : import { Stack } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js";
      14              : import { ExternalLinkAltIcon } from '@patternfly/react-icons';
      15              : 
      16              : import { icon_url, launch, ProgressBar, CancelButton, reload_bridge_packages, ProgressReporter } from "./utils";
      17              : 
      18              : import "./application.scss";
      19              : import { getPackageManager } from "packagemanager.js";
      20              : 
      21            6 : const _ = cockpit.gettext;
      22              : 
      23            2 : async function install_package(pkgname, progress_cb) {
      24            2 :     const packagemanager = await getPackageManager();
      25            2 :     await packagemanager.install_packages([pkgname], progress_cb);
      26            2 :     await reload_bridge_packages();
      27            2 : }
      28              : 
      29            2 : async function remove_package(filename, progress_cb) {
      30            2 :     const progress = new ProgressReporter(0, 1, progress_cb);
      31            2 :     const packagemanager = await getPackageManager();
      32            2 :     const pkgnames = await packagemanager.find_file_packages([filename], progress.progress_reporter);
      33            2 :     progress.base = 1;
      34            2 :     progress.range = 99;
      35            2 :     await packagemanager.remove_packages(pkgnames, progress.progress_reporter);
      36            2 :     await reload_bridge_packages();
      37            2 : }
      38              : 
      39            6 : export const ActionButton = ({ comp, progress, action }) => {
      40            2 :     function install(comp) {
      41            2 :         action(install_package, comp.pkgname, _("Installing"), comp.id);
      42            2 :     }
      43              : 
      44            2 :     function remove(comp) {
      45            2 :         action(remove_package, comp.file, _("Removing"), comp.id);
      46            2 :     }
      47              : 
      48            3 :     if (progress) {
      49            3 :         return <CancelButton data={progress} />;
      50            3 :     } else if (comp.installed) {
      51            2 :         return <Button variant="danger" onClick={() => remove(comp)}>{_("Remove")}</Button>;
      52            4 :     } else {
      53            2 :         return <Button variant="secondary" onClick={() => install(comp)}>{_("Install")}</Button>;
      54            6 :     }
      55            6 : };
      56              : 
      57            3 : export const Application = ({ metainfo_db, id, progress, action }) => {
      58            3 :     if (!id)
      59            1 :         return null;
      60              : 
      61            3 :     const comp = metainfo_db.components[id];
      62              : 
      63            3 :     function render_homepage_link(urls) {
      64            2 :         return urls.map((url, index) => {
      65            2 :             if (url.type == 'homepage') {
      66            2 :                 return (
      67            2 :                     <Button isInline variant="link" component='a' href={url.link}
      68            2 :                             key={"project-url-" + index}
      69            2 :                             target="_blank" rel="noopener noreferrer"
      70            2 :                             icon={<ExternalLinkAltIcon />}
      71            2 :                             iconPosition="right">
      72            2 :                         {_("View project website")}
      73            2 :                     </Button>
      74              :                 );
      75            0 :             } else {
      76            0 :                 return null;
      77            0 :             }
      78            2 :         });
      79            3 :     }
      80              : 
      81              :     // Render a description in the form returned by the AppStream
      82              :     // parser, which is a list of paragraphs and lists.
      83              : 
      84            3 :     function render_description(description) {
      85            3 :         if (!description)
      86            1 :             return <p>{_("No description provided.")}</p>;
      87              : 
      88            3 :         return description.map((paragraph, index) => {
      89            1 :             if (paragraph.tag == 'ul') {
      90            0 :                 return <ul key={`paragraph-${index}`}>{paragraph.items.map(item => <li key={item}>{item}</li>)}</ul>;
      91            1 :             } else if (paragraph.tag == 'ol') {
      92            0 :                 return <ol key={`paragraph-${index}`}>{paragraph.items.map(item => <li key={item}>{item}</li>)}</ol>;
      93            1 :             } else {
      94            3 :                 return <p key={`paragraph-${index}`}>{paragraph}</p>;
      95            3 :             }
      96            3 :         });
      97            3 :     }
      98              : 
      99              :     // Render the icon, name, homepage link, summary, description, and screenshots of the component,
     100              :     // plus the UI for installing and removing it.
     101              : 
     102            3 :     function render_comp() {
     103            3 :         if (!comp)
     104            1 :             return <div>{_("Unknown application")}</div>;
     105              : 
     106            3 :         let progress_or_launch;
     107            1 :         if (progress) {
     108            1 :             progress_or_launch = <ProgressBar data={progress} />;
     109            1 :         } else if (comp.installed) {
     110            0 :             progress_or_launch = <Button variant="link" onClick={() => launch(comp)}>{_("Go to application")}</Button>;
     111            1 :         } else {
     112            3 :             progress_or_launch = null;
     113            3 :         }
     114              : 
     115            3 :         return (
     116            3 :             <Card isPlain>
     117            3 :                 <CardHeader actions={{
     118            3 :                     actions: <>{progress_or_launch}<ActionButton comp={comp} progress={progress} action={action} /></>,
     119            3 :                 }}>
     120            3 :                     <CardTitle>
     121            3 :                         <Flex alignItems={{ default: 'alignItemsCenter' }}>
     122            3 :                             <img src={icon_url(comp.icon)} alt="" />
     123            3 :                             <span>{comp.summary}</span>
     124            3 :                         </Flex>
     125            3 :                     </CardTitle>
     126            3 :                 </CardHeader>
     127            3 :                 <CardBody>
     128            3 :                     <Stack hasGutter>
     129            3 :                         {render_homepage_link(comp.urls)}
     130            3 :                         <div className="app-description">{render_description(comp.description)}</div>
     131            3 :                         {comp.screenshots.length
     132            1 :                             ? <div className="text-center">
     133            0 :                                 { comp.screenshots.map((s, index) => <img key={`comp-${index}`} className="app-screenshot" alt="" src={s.full} />) }
     134            1 :                             </div>
     135            3 :                             : null}
     136            3 :                     </Stack>
     137            3 :                 </CardBody>
     138            3 :             </Card>
     139              :         );
     140            3 :     }
     141              : 
     142            3 :     return (
     143            3 :         <Page id="app-page"
     144            3 :               className="application-details pf-m-no-sidebar">
     145            3 :             <PageBreadcrumb hasBodyWrapper={false} stickyOnBreakpoint={{ default: "top" }}>
     146            3 :                 <Breadcrumb>
     147            3 :                     <BreadcrumbItem to="#/">{_("Applications")}</BreadcrumbItem>
     148            1 :                     <BreadcrumbItem isActive>{comp ? comp.name : id}</BreadcrumbItem>
     149            3 :                 </Breadcrumb>
     150            3 :             </PageBreadcrumb>
     151            3 :             <PageSection hasBodyWrapper={false}>
     152            3 :                 {render_comp()}
     153            3 :             </PageSection>
     154            3 :         </Page>
     155              :     );
     156            3 : };
        

Generated by: LCOV version 2.0-1