LCOV - code coverage report
Current view: top level - pkg/storaged/legacy-vdo - legacy-vdo.jsx Coverage Total Hit
Test: cockpit Lines: 1.8 % 271 5
Test Date: 2026-07-13 10:00:01

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2023 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6          113 : import cockpit from "cockpit";
       7          113 : import React from "react";
       8              : import client from "../client";
       9              : 
      10              : import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
      11              : import { Card, CardBody } from '@patternfly/react-core/dist/esm/components/Card/index.js';
      12              : import { DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
      13              : 
      14              : import { block_short_name, get_active_usage, teardown_active_usage, fmt_size, decode_filename, reload_systemd } from "../utils.js";
      15              : import {
      16              :     dialog_open, SizeSlider, BlockingMessage, TeardownMessage, init_teardown_usage
      17              : } from "../dialog.jsx";
      18              : import { StorageButton, StorageOnOff } from "../storage-controls.jsx";
      19              : 
      20              : import { StorageCard, new_page, new_card } from "../pages.jsx";
      21              : import { make_block_page } from "../block/create-pages.jsx";
      22              : 
      23              : import inotify_py from "inotify.py";
      24              : import vdo_monitor_py from "./vdo-monitor.py";
      25              : 
      26          113 : const _ = cockpit.gettext;
      27              : 
      28            0 : export function make_legacy_vdo_page(parent, vdo, backing_block, next_card) {
      29            0 :     const block = client.slashdevs_block[vdo.dev];
      30              : 
      31            0 :     function stop() {
      32            0 :         const usage = get_active_usage(client, block ? block.path : "/", _("stop"));
      33              : 
      34            0 :         if (usage.Blocking) {
      35            0 :             dialog_open({
      36            0 :                 Title: cockpit.format(_("$0 is in use"), vdo.name),
      37            0 :                 Body: BlockingMessage(usage),
      38            0 :             });
      39            0 :             return;
      40            0 :         }
      41              : 
      42            0 :         if (usage.Teardown) {
      43            0 :             dialog_open({
      44            0 :                 Title: cockpit.format(_("Confirm stopping of $0"),
      45            0 :                                       vdo.name),
      46            0 :                 Teardown: TeardownMessage(usage),
      47            0 :                 Action: {
      48            0 :                     Title: _("Stop"),
      49            0 :                     action: function () {
      50            0 :                         return teardown_active_usage(client, usage)
      51            0 :                                 .then(function () {
      52            0 :                                     return vdo.stop();
      53            0 :                                 });
      54            0 :                     }
      55            0 :                 },
      56            0 :                 Inits: [
      57            0 :                     init_teardown_usage(client, usage)
      58            0 :                 ]
      59            0 :             });
      60            0 :         } else {
      61            0 :             return vdo.stop();
      62            0 :         }
      63            0 :     }
      64              : 
      65            0 :     function delete_() {
      66            0 :         const usage = get_active_usage(client, block ? block.path : "/", _("delete"));
      67              : 
      68            0 :         if (usage.Blocking) {
      69            0 :             dialog_open({
      70            0 :                 Title: cockpit.format(_("$0 is in use"), vdo.name),
      71            0 :                 Body: BlockingMessage(usage),
      72            0 :             });
      73            0 :             return;
      74            0 :         }
      75              : 
      76            0 :         function wipe_with_teardown(block) {
      77            0 :             return block.Format("empty", { 'tear-down': { t: 'b', v: true } }).then(reload_systemd);
      78            0 :         }
      79              : 
      80            0 :         function teardown_configs() {
      81            0 :             if (block) {
      82            0 :                 return wipe_with_teardown(block);
      83            0 :             } else {
      84            0 :                 return vdo.start()
      85            0 :                         .then(function () {
      86            0 :                             return client.wait_for(() => client.slashdevs_block[vdo.dev])
      87            0 :                                     .then(function (block) {
      88            0 :                                         return wipe_with_teardown(block)
      89            0 :                                                 .catch(error => {
      90              :                                                     // systemd might have mounted it, let's try unmounting
      91            0 :                                                     const block_fsys = client.blocks_fsys[block.path];
      92            0 :                                                     if (block_fsys) {
      93            0 :                                                         return block_fsys.Unmount({})
      94            0 :                                                                 .then(() => wipe_with_teardown(block));
      95            0 :                                                     } else {
      96            0 :                                                         return Promise.reject(error);
      97            0 :                                                     }
      98            0 :                                                 });
      99            0 :                                     });
     100            0 :                         });
     101            0 :             }
     102            0 :         }
     103              : 
     104            0 :         dialog_open({
     105            0 :             Title: cockpit.format(_("Permanently delete $0?"), vdo.name),
     106            0 :             Body: TeardownMessage(usage),
     107            0 :             Action: {
     108            0 :                 Title: _("Delete"),
     109            0 :                 Danger: _("Deleting erases all data on a VDO device."),
     110            0 :                 action: function () {
     111            0 :                     return (teardown_active_usage(client, usage)
     112            0 :                             .then(teardown_configs)
     113            0 :                             .then(function () {
     114            0 :                                 return vdo.remove();
     115            0 :                             }));
     116            0 :                 }
     117            0 :             },
     118            0 :             Inits: [
     119            0 :                 init_teardown_usage(client, usage)
     120            0 :             ]
     121            0 :         });
     122            0 :     }
     123              : 
     124            0 :     const vdo_card = new_card({
     125            0 :         title: cockpit.format(_("VDO device $0"), vdo.name),
     126            0 :         next: next_card,
     127            0 :         page_location: ["vdo", vdo.name],
     128            0 :         page_name: block_short_name(backing_block),
     129            0 :         page_size: vdo.logical_size,
     130            0 :         job_path: backing_block.path,
     131            0 :         component: VDODetails,
     132            0 :         props: { client, vdo },
     133            0 :         actions: [
     134            0 :             (block
     135            0 :                 ? { title: _("Stop"), action: stop }
     136            0 :                 : { title: _("Start"), action: () => vdo.start() }
     137              :             ),
     138            0 :             { title: _("Delete"), action: delete_, danger: true }
     139            0 :         ],
     140            0 :     });
     141              : 
     142            0 :     if (block) {
     143            0 :         make_block_page(parent, block, vdo_card);
     144            0 :     } else {
     145            0 :         new_page(parent, vdo_card);
     146            0 :     }
     147            0 : }
     148              : 
     149          113 : class VDODetails extends React.Component {
     150            0 :     constructor() {
     151            0 :         super();
     152            0 :         this.poll_path = null;
     153            0 :         this.state = { stats: null };
     154            0 :     }
     155              : 
     156            0 :     ensure_polling(enable) {
     157            0 :         const client = this.props.client;
     158            0 :         const vdo = this.props.vdo;
     159            0 :         const block = client.slashdevs_block[vdo.dev];
     160            0 :         const path = enable && block ? vdo.dev : null;
     161              : 
     162            0 :         let buf = "";
     163              : 
     164            0 :         if (this.poll_path === path)
     165            0 :             return;
     166              : 
     167            0 :         if (this.poll_path) {
     168            0 :             this.poll_process.close();
     169            0 :             this.setState({ stats: null });
     170            0 :         }
     171              : 
     172            0 :         if (path)
     173            0 :             this.poll_process = cockpit.spawn([client.legacy_vdo_overlay.python, "--", "-", path], { superuser: "require" })
     174            0 :                     .input(inotify_py + vdo_monitor_py)
     175            0 :                     .stream((data) => {
     176            0 :                         buf += data;
     177            0 :                         const lines = buf.split("\n");
     178            0 :                         buf = lines[lines.length - 1];
     179            0 :                         if (lines.length >= 2) {
     180            0 :                             this.setState({ stats: JSON.parse(lines[lines.length - 2]) });
     181            0 :                         }
     182            0 :                     });
     183            0 :         this.poll_path = path;
     184            0 :     }
     185              : 
     186            0 :     componentDidMount() {
     187            0 :         this.ensure_polling(true);
     188            0 :     }
     189              : 
     190            0 :     componentDidUpdate() {
     191            0 :         this.ensure_polling(true);
     192            0 :     }
     193              : 
     194            0 :     componentWillUnmount() {
     195            0 :         this.ensure_polling(false);
     196            0 :     }
     197              : 
     198            0 :     render() {
     199            0 :         const client = this.props.client;
     200            0 :         const vdo = this.props.vdo;
     201            0 :         const block = client.slashdevs_block[vdo.dev];
     202            0 :         const backing_block = client.slashdevs_block[vdo.backing_dev];
     203              : 
     204            0 :         function force_delete() {
     205            0 :             return vdo.force_remove();
     206            0 :         }
     207              : 
     208            0 :         if (vdo.broken) {
     209            0 :             return (
     210            0 :                 <Card isPlain>
     211            0 :                     <Alert variant='danger' isInline
     212            0 :                            title={_("The creation of this VDO device did not finish and the device can't be used.")}
     213            0 :                            actionClose={<StorageButton onClick={force_delete}>
     214            0 :                                {_("Remove device")}
     215            0 :                            </StorageButton>} />
     216            0 :                 </Card>
     217              :             );
     218            0 :         }
     219              : 
     220            0 :         const alerts = [];
     221            0 :         if (backing_block && backing_block.Size > vdo.physical_size)
     222            0 :             alerts.push(
     223            0 :                 <Alert variant='warning' isInline key="unused"
     224            0 :                        actionClose={<StorageButton onClick={vdo.grow_physical}>{_("Grow to take all space")}</StorageButton>}
     225            0 :                        title={_("This VDO device does not use all of its backing device.")}>
     226            0 :                     { cockpit.format(_("Only $0 of $1 are used."),
     227            0 :                                      fmt_size(vdo.physical_size),
     228            0 :                                      fmt_size(backing_block.Size))}
     229            0 :                 </Alert>);
     230              : 
     231            0 :         function grow_logical() {
     232            0 :             dialog_open({
     233            0 :                 Title: cockpit.format(_("Grow logical size of $0"), vdo.name),
     234            0 :                 Fields: [
     235            0 :                     SizeSlider("lsize", _("Logical size"),
     236            0 :                                {
     237            0 :                                    max: 5 * vdo.logical_size,
     238            0 :                                    min: vdo.logical_size,
     239            0 :                                    round: 512,
     240            0 :                                    value: vdo.logical_size,
     241            0 :                                    allow_infinite: true
     242            0 :                                })
     243            0 :                 ],
     244            0 :                 Action: {
     245            0 :                     Title: _("Grow"),
     246            0 :                     action: function (vals) {
     247            0 :                         if (vals.lsize > vdo.logical_size)
     248            0 :                             return vdo.grow_logical(vals.lsize).then(() => {
     249            0 :                                 if (block && block.IdUsage == "filesystem")
     250            0 :                                     return cockpit.spawn(["fsadm", "resize",
     251            0 :                                         decode_filename(block.Device)],
     252            0 :                                                          { superuser: "require", err: "message" });
     253            0 :                             });
     254            0 :                     }
     255            0 :                 }
     256            0 :             });
     257            0 :         }
     258              : 
     259            0 :         function fmt_perc(num) {
     260            0 :             if (num || num == 0)
     261            0 :                 return num + "%";
     262              :             else
     263            0 :                 return "--";
     264            0 :         }
     265              : 
     266            0 :         const stats = this.state.stats;
     267              : 
     268            0 :         const header = (
     269            0 :             <StorageCard card={this.props.card} alerts={alerts}>
     270            0 :                 <CardBody>
     271            0 :                     <DescriptionList className="pf-m-horizontal-on-sm">
     272            0 :                         <DescriptionListGroup>
     273            0 :                             <DescriptionListTerm>{_("Device file")}</DescriptionListTerm>
     274            0 :                             <DescriptionListDescription>{vdo.dev}</DescriptionListDescription>
     275            0 :                         </DescriptionListGroup>
     276              : 
     277            0 :                         <DescriptionListGroup>
     278            0 :                             <DescriptionListTerm>{_("Physical")}</DescriptionListTerm>
     279            0 :                             <DescriptionListDescription>
     280            0 :                                 { stats
     281            0 :                                     ? cockpit.format(_("$0 data + $1 overhead used of $2 ($3)"),
     282            0 :                                                      fmt_size(stats.dataBlocksUsed * stats.blockSize),
     283            0 :                                                      fmt_size(stats.overheadBlocksUsed * stats.blockSize),
     284            0 :                                                      fmt_size(vdo.physical_size),
     285            0 :                                                      fmt_perc(stats.usedPercent))
     286            0 :                                     : fmt_size(vdo.physical_size)
     287              :                                 }
     288            0 :                             </DescriptionListDescription>
     289            0 :                         </DescriptionListGroup>
     290              : 
     291            0 :                         <DescriptionListGroup>
     292            0 :                             <DescriptionListTerm>{_("Logical")}</DescriptionListTerm>
     293            0 :                             <DescriptionListDescription>
     294            0 :                                 { stats
     295            0 :                                     ? cockpit.format(_("$0 used of $1 ($2 saved)"),
     296            0 :                                                      fmt_size(stats.logicalBlocksUsed * stats.blockSize),
     297            0 :                                                      fmt_size(vdo.logical_size),
     298            0 :                                                      fmt_perc(stats.savingPercent))
     299            0 :                                     : fmt_size(vdo.logical_size)
     300            0 :                                 }
     301            0 :                                 &nbsp; <StorageButton onClick={grow_logical}>{_("Grow")}</StorageButton>
     302            0 :                             </DescriptionListDescription>
     303            0 :                         </DescriptionListGroup>
     304              : 
     305            0 :                         <DescriptionListGroup>
     306            0 :                             <DescriptionListTerm>{_("Index memory")}</DescriptionListTerm>
     307            0 :                             <DescriptionListDescription>{fmt_size(vdo.index_mem * 1024 * 1024 * 1024)}</DescriptionListDescription>
     308            0 :                         </DescriptionListGroup>
     309              : 
     310            0 :                         <DescriptionListGroup>
     311            0 :                             <DescriptionListTerm>{_("Compression")}</DescriptionListTerm>
     312            0 :                             <DescriptionListDescription>
     313            0 :                                 <StorageOnOff state={vdo.compression}
     314            0 :                                               aria-label={_("Use compression")}
     315            0 :                                               onChange={() => vdo.set_compression(!vdo.compression)} />
     316            0 :                             </DescriptionListDescription>
     317            0 :                         </DescriptionListGroup>
     318              : 
     319            0 :                         <DescriptionListGroup>
     320            0 :                             <DescriptionListTerm>{_("Deduplication")}</DescriptionListTerm>
     321            0 :                             <DescriptionListDescription>
     322            0 :                                 <StorageOnOff state={vdo.deduplication}
     323            0 :                                               aria-label={_("Use deduplication")}
     324            0 :                                               onChange={() => vdo.set_deduplication(!vdo.deduplication)} />
     325            0 :                             </DescriptionListDescription>
     326            0 :                         </DescriptionListGroup>
     327            0 :                     </DescriptionList>
     328            0 :                 </CardBody>
     329            0 :             </StorageCard>
     330              :         );
     331              : 
     332            0 :         return header;
     333            0 :     }
     334          113 : }
        

Generated by: LCOV version 2.0-1