LCOV - code coverage report
Current view: top level - pkg/packagekit - autoupdates.jsx Coverage Total Hit
Test: cockpit Lines: 36.6 % 391 143
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            1 : import cockpit from "cockpit";
       7            1 : import React, { useState } from "react";
       8              : import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
       9              : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
      10              : import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
      11              : import { Form, FormGroup } from "@patternfly/react-core/dist/esm/components/Form/index.js";
      12              : import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect/index.js";
      13              : import {
      14              :     Modal, ModalBody, ModalFooter, ModalHeader
      15              : } from '@patternfly/react-core/dist/esm/components/Modal/index.js';
      16              : import { Radio } from "@patternfly/react-core/dist/esm/components/Radio/index.js";
      17              : import { TimePicker } from "@patternfly/react-core/dist/esm/components/TimePicker/index.js";
      18              : 
      19              : import { install_dialog } from "cockpit-components-install-dialog.jsx";
      20              : import { useDialogs } from "dialogs.jsx";
      21              : 
      22              : import { debug } from "./utils";
      23              : 
      24            1 : const _ = cockpit.gettext;
      25              : 
      26              : /**
      27              :  * Package manager specific implementations; PackageKit does not cover
      28              :  * automatic updates, so we have to implement dnf-automatic and
      29              :  * unattended-upgrades configuration ourselves
      30              :  */
      31              : 
      32            1 : class ImplBase {
      33            1 :     constructor() {
      34            1 :         this.supported = true; // false if system was customized in a way that we cannot parse
      35            1 :         this.enabled = null; // boolean
      36            1 :         this.type = null; // "all" or "security"
      37            1 :         this.day = null; // systemd.time(7) day of week (e. g. "mon"), or empty for daily
      38            1 :         this.time = null; // systemd.time(7) time (e. g. "06:00") or empty for "any time"
      39            1 :         this.installed = null; // boolean
      40            1 :         this.packageName = null; // name of the package providing automatic updates
      41            1 :     }
      42              : 
      43              :     // Init data members. Return a promise that resolves when done.
      44            0 :     async getConfig() {
      45            0 :         throw new Error("abstract method");
      46            0 :     }
      47              : 
      48              :     // Update configuration for given non-null values, and update member variables on success;
      49              :     // return a promise that resolves when done, or fails when configuration writing fails
      50            0 :     async setConfig(enabled, type, day, time) {
      51            0 :         throw new Error("abstract method", enabled, type, day, time);
      52            0 :     }
      53              : 
      54            1 :     parseCalendar(spec) {
      55              :         // see systemd.time(7); we only support what we write, otherwise we treat it as custom config and "unsupported"
      56            1 :         const daysOfWeek = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
      57            1 :         const validTime = /^((|0|1)[0-9]|2[0-3]):[0-5][0-9]$/;
      58              : 
      59            1 :         const words = spec.trim().toLowerCase()
      60            1 :                 .split(/\s+/);
      61              : 
      62              :         // check if we have a day of week
      63            1 :         if (daysOfWeek.indexOf(words[0]) >= 0) {
      64            1 :             this.day = words.shift();
      65            1 :         } else if (words[0] === '*-*-*') {
      66            1 :             this.day = ""; // daily with "all matches" date specification
      67            1 :             words.shift();
      68            1 :         } else {
      69            1 :             this.day = ""; // daily without date specification
      70            1 :         }
      71              : 
      72              :         // now there should only be a time left
      73            1 :         if (words.length == 1 && validTime.test(words[0]))
      74            1 :             this.time = words[0].replace(/^0+/, "");
      75              :         else
      76            1 :             this.supported = false;
      77            1 :     }
      78            1 : }
      79              : 
      80            1 : class Dnf4Impl extends ImplBase {
      81            0 :     async getConfig() {
      82            0 :         this.packageName = "dnf-automatic";
      83              : 
      84            0 :         try {
      85              :             // - dnf 4 has two ways to enable automatic updates: Either by enabling dnf-automatic-install.timer
      86              :             //   or by setting "apply_updates = yes" in the config file and enabling dnf-automatic.timer
      87              :             // - the config file determines whether to apply security updates only
      88              :             // - by default this runs every day (OnUnitInactiveSec=1d), but the timer can be changed with a timer unit
      89              :             //   drop-in, so get the last line
      90            0 :             const output = await cockpit.script(
      91            0 :                 "set -e; if rpm -q " + this.packageName + " >/dev/null; then echo installed; fi; " +
      92              :                 "if grep -q '^[ \\t]*upgrade_type[ \\t]*=[ \\t]*security' /etc/dnf/automatic.conf; then echo security; fi; " +
      93              :                 "TIMER=dnf-automatic-install.timer; " +
      94              :                 "if systemctl --quiet is-enabled dnf-automatic-install.timer 2>/dev/null; then echo enabled; " +
      95              :                 "elif systemctl --quiet is-enabled dnf-automatic.timer 2>/dev/null && grep -q '^[ \t]*apply_updates[ \t]*=[ \t]*yes' " +
      96              :                 "    /etc/dnf/automatic.conf; then echo enabled; TIMER=dnf-automatic.timer; " +
      97              :                 "fi; " +
      98              :                 'OUT=$(systemctl cat $TIMER 2>/dev/null || true); ' +
      99              :                 'echo "$OUT" | grep "^OnUnitInactiveSec= *[^ ]" | tail -n1; ' +
     100              :                 'echo "$OUT" | grep "^OnCalendar= *[^ ]" | tail -n1; ',
     101            0 :                 [], { err: "message" });
     102              : 
     103            0 :             this.installed = (output.indexOf("installed\n") >= 0);
     104            0 :             this.enabled = (output.indexOf("enabled\n") >= 0);
     105            0 :             this.type = (output.indexOf("security\n") >= 0) ? "security" : "all";
     106              : 
     107              :             // if we have OnCalendar=, use that (we disable OnUnitInactiveSec= in our drop-in)
     108            0 :             const calIdx = output.indexOf("OnCalendar=");
     109            0 :             if (calIdx >= 0) {
     110            0 :                 this.parseCalendar(output.substring(calIdx).split('\n')[0].split("=")[1]);
     111            0 :             } else {
     112            0 :                 if (output.indexOf("InactiveSec=1d\n") >= 0)
     113            0 :                     this.day = this.time = "";
     114            0 :                 else if (this.installed)
     115            0 :                     this.supported = false;
     116            0 :             }
     117              : 
     118            0 :             debug(`dnf4 getConfig: supported ${this.supported}, enabled ${this.enabled}, type ${this.type}, day ${this.day}, time ${this.time}, installed ${this.installed}; raw response '${output}'`);
     119            0 :         } catch (error) {
     120            0 :             console.error("dnf4 getConfig failed:", error);
     121            0 :             this.supported = false;
     122            0 :         }
     123            0 :     }
     124              : 
     125            0 :     async setConfig(enabled, type, day, time) {
     126            0 :         const timerConf = "/etc/systemd/system/dnf-automatic-install.timer.d/time.conf";
     127            0 :         let script = "set -e; ";
     128              : 
     129            0 :         if (type !== null) {
     130            0 :             const value = (type == "security") ? "security" : "default";
     131              : 
     132              :             // normally upgrade_type = should already be in the file, so replace that line;
     133              :             // if it's not already present, append it to the file
     134            0 :             script += "sed -i '/\\bupgrade_type\\b[ \\t]*=/ { h; s/^.*$/upgrade_type = " + value + "/ }; " +
     135            0 :                       "$ { x; /^$/ { s//upgrade_type = " + value + "/; H }; x }' /etc/dnf/automatic.conf; ";
     136            0 :         }
     137              : 
     138              :         // if we enable through Cockpit, make sure that starting the timer doesn't start the .service right away,
     139              :         // due to the packaged default OnBootSec=1h; just set a reasonable initial time which will trigger the code below
     140            0 :         if (enabled && !this.enabled && !this.time && !this.day)
     141            0 :             time = "6:00";
     142              : 
     143            0 :         if (time !== null || day !== null) {
     144            0 :             if (day === "" && time === "") {
     145              :                 // restore defaults
     146            0 :                 script += "rm -f " + timerConf + "; ";
     147            0 :             } else {
     148            0 :                 if (day == null)
     149            0 :                     day = this.day;
     150            0 :                 if (time == null)
     151            0 :                     time = this.time;
     152            0 :                 script += "mkdir -p /etc/systemd/system/dnf-automatic-install.timer.d; ";
     153            0 :                 script += "printf '[Timer]\\nOnBootSec=\\nOnCalendar=" + day + " " + time + "\\n' > " + timerConf + "; ";
     154            0 :                 script += "systemctl daemon-reload; ";
     155            0 :             }
     156            0 :         }
     157              : 
     158            0 :         if (enabled !== null) {
     159            0 :             const rebootConf = "/etc/systemd/system/dnf-automatic-install.service.d/autoreboot.conf";
     160              : 
     161            0 :             script += "systemctl " + (enabled ? "enable" : "disable") + " --now dnf-automatic-install.timer; ";
     162              : 
     163            0 :             if (enabled) {
     164              :                 /* dnf 4.15+ supports automatic reboots; check if the config option exists, and if so, change the
     165              :                    default to "when-needed"; but be strict about the format, to avoid changing a customized setting */
     166            0 :                 script += "if grep '^[[:space:]]*reboot\\b' /etc/dnf/automatic.conf; then ";
     167            0 :                 script += "  sed -i 's/^reboot = never$/reboot = when-needed/' /etc/dnf/automatic.conf; ";
     168              :                 // and drop the previous hack on upgrades */
     169            0 :                 script += "  rm -f " + rebootConf + "; ";
     170              :                 /* HACK for older dnf: enable automatic reboots after updating; dnf-automatic does not leave a log
     171              :                    file behind for deciding whether it actually installed anything, so resort to grepping the journal
     172              :                    for the last run (https://bugzilla.redhat.com/show_bug.cgi?id=1491190) */
     173            0 :                 script += "else ";
     174            0 :                 script += "  mkdir -p /etc/systemd/system/dnf-automatic-install.service.d; ";
     175            0 :                 script += "  printf '[Service]\\nExecStartPost=/bin/sh -ec \"" +
     176              :                           "if systemctl status --no-pager --lines=100 dnf-automatic-install.service| grep -q ===========$$; then " +
     177            0 :                           "shutdown -r +5 rebooting after applying package updates; fi\"\\n' > " + rebootConf + "; ";
     178            0 :                 script += "  systemctl daemon-reload; ";
     179            0 :                 script += "fi";
     180            0 :             } else {
     181              :                 // also make sure that the legacy unit name is disabled; this can fail if the unit does not exist
     182            0 :                 script += "systemctl disable --now dnf-automatic.timer 2>/dev/null || true; ";
     183            0 :                 script += "rm -f " + rebootConf + "; ";
     184            0 :             }
     185            0 :         }
     186              : 
     187            0 :         debug(`dnf4 setConfig(${enabled}, "${type}", "${day}", "${time}"): script "${script}"`);
     188              : 
     189            0 :         try {
     190            0 :             await cockpit.script(script, [], { superuser: "require" });
     191            0 :             debug("dnf4 setConfig: configuration updated successfully");
     192            0 :             if (enabled !== null)
     193            0 :                 this.enabled = enabled;
     194            0 :             if (type !== null)
     195            0 :                 this.type = type;
     196            0 :             if (day !== null)
     197            0 :                 this.day = day;
     198            0 :             if (time !== null)
     199            0 :                 this.time = time;
     200            0 :         } catch (error) {
     201            0 :             console.error("dnf4 setConfig failed:", error.toString());
     202            0 :         }
     203            0 :     }
     204            1 : }
     205              : 
     206            1 : class Dnf5Impl extends ImplBase {
     207            1 :     async getConfig() {
     208            1 :         this.packageName = "dnf5-plugin-automatic";
     209            1 :         this.configFile = "/etc/dnf/dnf5-plugins/automatic.conf";
     210              : 
     211            1 :         try {
     212            1 :             await cockpit.spawn(["rpm", "-q", this.packageName], { err: "ignore" });
     213            1 :             this.installed = true;
     214            1 :         } catch (ex) {
     215            1 :             this.installed = false;
     216            1 :             debug("dnf5 getConfig: not installed:", ex);
     217            1 :             return;
     218            1 :         }
     219              : 
     220              :         // - dnf 5 only has a single timer dnf5-automatic.timer and a config file with
     221              :         //   "apply_updates" (yes/no) and "upgrade_type" (default/security)
     222              :         // - by default this runs every day (OnCalendar)
     223            1 :         try {
     224            1 :             const output = await cockpit.script(
     225            1 :                 "set -eu;" +
     226            1 :                 "if grep -q '^[ \\t]*upgrade_type[ \\t]*=[ \\t]*security' " + this.configFile + "; then echo security; fi; " +
     227              :                 "if systemctl --quiet is-enabled dnf5-automatic.timer && " +
     228            1 :                 "  grep -q '^[ \t]*apply_updates[ \t]*=[ \t]*yes' " + this.configFile + "; then echo enabled; fi; " +
     229              :                 'OUT=$(systemctl cat dnf5-automatic.timer || true); ' +
     230              :                 'echo "$OUT" | grep "^OnUnitInactiveSec= *[^ ]" | tail -n1; ' +
     231              :                 'echo "$OUT" | grep "^OnCalendar= *[^ ]" | tail -n1; ',
     232            1 :                 [], { err: "message" });
     233              : 
     234            1 :             this.enabled = (output.indexOf("enabled\n") >= 0);
     235            1 :             this.type = (output.indexOf("security\n") >= 0) ? "security" : "all";
     236              : 
     237              :             // if we have OnCalendar=, use that (we disable OnUnitInactiveSec= in our drop-in)
     238            1 :             const calIdx = output.indexOf("OnCalendar=");
     239            1 :             if (calIdx >= 0) {
     240            1 :                 this.parseCalendar(output.substring(calIdx).split('\n')[0].split("=")[1]);
     241            1 :             } else {
     242            1 :                 if (output.indexOf("InactiveSec=1d\n") >= 0)
     243            1 :                     this.day = this.time = "";
     244            1 :                 else if (this.installed)
     245            1 :                     this.supported = false;
     246            1 :             }
     247              : 
     248            1 :             debug(`dnf5 getConfig: supported ${this.supported}, enabled ${this.enabled}, type ${this.type}, day ${this.day}, time ${this.time}, installed ${this.installed}; raw response '${output}'`);
     249            1 :         } catch (error) {
     250            1 :             console.error("dnf5 getConfig failed:", error);
     251            1 :             this.supported = false;
     252            1 :         }
     253            1 :     }
     254              : 
     255            0 :     async setConfig(enabled, type, day, time) {
     256            0 :         const timerConfD = "/etc/systemd/system/dnf5-automatic.timer.d";
     257            0 :         const timerConf = timerConfD + "/time.conf";
     258            0 :         let script = "set -e; ";
     259              : 
     260              :         // there's no default config file, admins are supposed to put their own settings into a new file
     261            0 :         const settings = [];
     262              : 
     263            0 :         if (type !== null)
     264            0 :             settings.push(["upgrade_type", (type == "security") ? "security" : "default"]);
     265              : 
     266            0 :         if (time !== null || day !== null) {
     267            0 :             if (day === "" && time === "") {
     268              :                 // restore defaults
     269            0 :                 script += "rm -f " + timerConf + "; ";
     270            0 :             } else {
     271            0 :                 if (day == null)
     272            0 :                     day = this.day;
     273            0 :                 if (time == null)
     274            0 :                     time = this.time;
     275            0 :                 script += "mkdir -p " + timerConfD + "; ";
     276            0 :                 script += "printf '[Timer]\\nOnBootSec=\\nOnCalendar=" + day + " " + time + "\\n' > " + timerConf + "; ";
     277            0 :                 script += "systemctl daemon-reload; ";
     278            0 :             }
     279            0 :         }
     280              : 
     281            0 :         if (enabled !== null) {
     282            0 :             script += "systemctl " + (enabled ? "enable" : "disable") + " --now dnf5-automatic.timer; ";
     283              : 
     284            0 :             if (enabled) {
     285            0 :                 settings.push(["apply_updates", "yes"]);
     286            0 :                 settings.push(["reboot", "when-needed"]);
     287            0 :             }
     288            0 :         }
     289              : 
     290            0 :         debug(`dnf5 setConfig(${enabled}, "${type}", "${day}", "${time}"): script "${script}", settings ${settings}`);
     291              : 
     292            0 :         try {
     293            0 :             if (settings.length > 0) {
     294              :                 // parse/update automatic.conf with the new settings; modify existing or append
     295            0 :                 await cockpit.file(this.configFile, { superuser: "require" }).modify(content => {
     296            0 :                     const lines = content ? content.split('\n') : [];
     297            0 :                     settings.forEach(([key, value]) => {
     298            0 :                         const idx = lines.findIndex(line => line.startsWith(key));
     299            0 :                         if (idx >= 0)
     300            0 :                             lines[idx] = key + " = " + value;
     301              :                         else
     302              :                             // let's avoid context sensitive parsing/writing; multiple sections are ok
     303            0 :                             lines.push(`[commands]\n${key} = ${value}`);
     304            0 :                     });
     305            0 :                     return lines.join('\n');
     306            0 :                 });
     307            0 :             }
     308              : 
     309            0 :             await cockpit.script(script, [], { superuser: "require" });
     310            0 :             debug("dnf5 setConfig: configuration updated successfully");
     311            0 :             if (enabled !== null)
     312            0 :                 this.enabled = enabled;
     313            0 :             if (type !== null)
     314            0 :                 this.type = type;
     315            0 :             if (day !== null)
     316            0 :                 this.day = day;
     317            0 :             if (time !== null)
     318            0 :                 this.time = time;
     319            0 :         } catch (error) {
     320            0 :             console.error("dnf5 setConfig failed:", error.toString());
     321            0 :         }
     322            0 :     }
     323            1 : }
     324              : 
     325              : // Returns a promise for instantiating "backend"; this will never fail, if
     326              : // automatic updates are not supported, backend will be null.
     327            1 : export function getBackend(packagekit_backend, forceReinit) {
     328            1 :     if (!getBackend.promise || forceReinit) {
     329            1 :         debug("getBackend() called first time or forceReinit passed, initializing promise");
     330            1 :         getBackend.promise = new Promise((resolve, reject) => {
     331            1 :             if (packagekit_backend === "dnf5") {
     332            1 :                 const backend = new Dnf5Impl();
     333            1 :                 backend.getConfig().then(() => resolve(backend));
     334            1 :             } else if (packagekit_backend === "dnf") {
     335              :                 // we need to do this runtime check -- you can e.g. install dnf5 on Fedora 40, but it's not the "main" dnf
     336            1 :                 cockpit.spawn(["dnf", "--version"], { err: "message" })
     337            0 :                         .then(version => {
     338            0 :                             const backend = version.includes("dnf5") ? new Dnf5Impl() : new Dnf4Impl();
     339            0 :                             backend.getConfig().then(() => resolve(backend));
     340            0 :                         })
     341            0 :                         .catch(ex => {
     342            0 :                             console.error("failed to run dnf --version:", ex);
     343            0 :                             resolve(null);
     344            0 :                         });
     345            1 :             } else {
     346              :                 // TODO: apt backend
     347            1 :                 resolve(null);
     348            1 :             }
     349            1 :         });
     350            1 :     }
     351            1 :     return getBackend.promise;
     352            1 : }
     353              : 
     354            0 : const AutoUpdatesDialog = ({ backend }) => {
     355            0 :     const Dialogs = useDialogs();
     356            0 :     const [pending, setPending] = useState(false);
     357            0 :     const [enabled, setEnabled] = useState(backend.enabled);
     358            0 :     const [type, setType] = useState(backend.type);
     359            0 :     const [day, setDay] = useState(backend.day);
     360            0 :     const [time, setTime] = useState(backend.time && backend.time.padStart(5, "0"));
     361              : 
     362            0 :     function save(event) {
     363            0 :         setPending(true);
     364            0 :         backend.setConfig(enabled, type, day, time)
     365            0 :                 .finally(Dialogs.close);
     366              : 
     367            0 :         if (event)
     368            0 :             event.preventDefault();
     369            0 :         return false;
     370            0 :     }
     371              : 
     372            0 :     return (
     373            0 :         <Modal position="top" variant="small" id="automatic-updates-dialog" isOpen
     374            0 :                onClose={Dialogs.close}>
     375            0 :             <ModalHeader title={_("Automatic updates")} />
     376            0 :             <ModalBody>
     377            0 :                 <Form isHorizontal onSubmit={save}>
     378            0 :                     <FormGroup fieldId="type" label={_("Type")} hasNoPaddingTop>
     379            0 :                         <Radio isChecked={!enabled}
     380            0 :                                onChange={() => { setEnabled(false); setType(null) }}
     381            0 :                                isDisabled={pending}
     382            0 :                                label={_("No updates")}
     383            0 :                                id="no-updates"
     384            0 :                                name="type" />
     385            0 :                         <Radio isChecked={enabled && type === "security"}
     386            0 :                                onChange={() => { setEnabled(true); setType("security") }}
     387            0 :                                isDisabled={pending}
     388            0 :                                label={_("Security updates only")}
     389            0 :                                id="security-updates"
     390            0 :                                name="type" />
     391            0 :                         <Radio isChecked={enabled && type === "all"}
     392            0 :                                onChange={() => { setEnabled(true); setType("all") }}
     393            0 :                                isDisabled={pending}
     394            0 :                                label={_("All updates")}
     395            0 :                                id="all-updates"
     396            0 :                                name="type" />
     397            0 :                     </FormGroup>
     398              : 
     399            0 :                     {enabled &&
     400            0 :                     <>
     401            0 :                         <FormGroup fieldId="when" label={_("When")}>
     402            0 :                             <Flex className="auto-update-group">
     403            0 :                                 <FormSelect id="auto-update-day"
     404            0 :                                              isDisabled={pending}
     405            0 :                                              value={day == "" ? "everyday" : day}
     406            0 :                                              onChange={(_, d) => setDay(d == "everyday" ? "" : d) }>
     407            0 :                                     <FormSelectOption value="everyday" label={_("every day")} />
     408            0 :                                     <FormSelectOption value="mon" label={_("Mondays")} />
     409            0 :                                     <FormSelectOption value="tue" label={_("Tuesdays")} />
     410            0 :                                     <FormSelectOption value="wed" label={_("Wednesdays")} />
     411            0 :                                     <FormSelectOption value="thu" label={_("Thursdays")} />
     412            0 :                                     <FormSelectOption value="fri" label={_("Fridays")} />
     413            0 :                                     <FormSelectOption value="sat" label={_("Saturdays")} />
     414            0 :                                     <FormSelectOption value="sun" label={_("Sundays")} />
     415            0 :                                 </FormSelect>
     416              : 
     417            0 :                                 <span className="auto-conf-text">{_("at")}</span>
     418              : 
     419            0 :                                 <TimePicker time={time} is24Hour
     420            0 :                                              menuAppendTo={() => document.body}
     421            0 :                                              id="auto-update-time" isDisabled={pending}
     422            0 :                                              invalidFormatErrorMessage={_("Invalid time format")}
     423            0 :                                              onChange={(_, time) => setTime(time)} />
     424            0 :                             </Flex>
     425            0 :                         </FormGroup>
     426              : 
     427            0 :                         <Alert variant="info" title={_("This host will reboot after updates are installed.")} isInline />
     428            0 :                     </>}
     429            0 :                 </Form>
     430            0 :             </ModalBody>
     431            0 :             <ModalFooter>
     432            0 :                 <Button variant="primary"
     433            0 :                         isLoading={pending}
     434            0 :                         isDisabled={pending}
     435            0 :                         onClick={save}>
     436            0 :                     {_("Save changes")}
     437            0 :                 </Button>
     438            0 :                 <Button variant="link"
     439            0 :                         isDisabled={pending}
     440            0 :                         onClick={Dialogs.close}>
     441            0 :                     {_("Cancel")}
     442            0 :                 </Button>
     443            0 :             </ModalFooter>
     444            0 :         </Modal>);
     445            0 : };
     446              : 
     447            1 : export const AutoUpdates = ({ privileged, packagekit_backend, initial_backend }) => {
     448            1 :     const Dialogs = useDialogs();
     449            1 :     const [backend, setBackend] = useState(initial_backend);
     450              : 
     451            1 :     if (!backend)
     452            1 :         return null;
     453              : 
     454            1 :     let state = null;
     455            1 :     if (!backend.enabled)
     456            1 :         state = _("Disabled");
     457            1 :     if (!backend.installed)
     458            1 :         state = _("Not set up");
     459              : 
     460            1 :     const days = {
     461            1 :         "": _("every day"),
     462            1 :         mon: _("every Monday"),
     463            1 :         tue: _("every Tuesday"),
     464            1 :         wed: _("every Wednesday"),
     465            1 :         thu: _("every Thursday"),
     466            1 :         fri: _("every Friday"),
     467            1 :         sat: _("every Saturday"),
     468            1 :         sun: _("every Sunday")
     469            1 :     };
     470              : 
     471            1 :     let desc = null;
     472              : 
     473            1 :     if (backend.enabled && backend.supported) {
     474            1 :         const day = days[backend.day];
     475            1 :         const time = backend.time;
     476            1 :         desc = backend.type == "security"
     477            1 :             ? cockpit.format(_("Security updates will be applied $0 at $1"), day, time)
     478            1 :             : cockpit.format(_("Updates will be applied $0 at $1"), day, time);
     479            1 :     }
     480              : 
     481            1 :     if (privileged && backend.installed && !backend.supported)
     482            1 :         return (
     483            1 :             <div id="autoupdates-settings">
     484            1 :                 <Alert isInline
     485            1 :                        variant="info"
     486            1 :                        className="autoupdates-card-error"
     487            1 :                        title={_("Failed to parse unit files for dnf-automatic.timer or dnf-automatic-install.timer. Please remove custom overrides to configure automatic updates.")} />
     488            1 :             </div>
     489              :         );
     490              : 
     491            1 :     return (
     492            1 :         <div id="autoupdates-settings">
     493            1 :             <Flex alignItems={{ default: 'alignItemsCenter' }}>
     494            1 :                 <Flex grow={{ default: 'grow' }} alignItems={{ default: 'alignItemsBaseline' }}>
     495            1 :                     <FlexItem>
     496            1 :                         <b>{_("Automatic updates")}</b>
     497            1 :                     </FlexItem>
     498            1 :                     <FlexItem>
     499            1 :                         {state}
     500            1 :                     </FlexItem>
     501            1 :                 </Flex>
     502            1 :                 <Flex>
     503            1 :                     <Button variant="secondary"
     504            1 :                             isDisabled={!privileged}
     505            1 :                             size="sm"
     506            0 :                             onClick={() => {
     507            0 :                                 if (!backend.installed) {
     508            0 :                                     install_dialog(backend.packageName)
     509            0 :                                             .then(() => {
     510            0 :                                                 getBackend(packagekit_backend, true).then(b => {
     511            0 :                                                     setBackend(b);
     512            0 :                                                     Dialogs.show(<AutoUpdatesDialog backend={b} />);
     513            0 :                                                 });
     514            0 :                                             }, () => null);
     515            0 :                                 } else {
     516            0 :                                     Dialogs.show(<AutoUpdatesDialog backend={backend} />);
     517            0 :                                 }
     518            0 :                             }}>
     519            1 :                         {!backend.installed ? _("Enable") : _("Edit")}
     520            1 :                     </Button>
     521            1 :                 </Flex>
     522            1 :             </Flex>
     523            1 :             {desc}
     524            1 :         </div>);
     525            1 : };
        

Generated by: LCOV version 2.0-1