diff --git a/pkg/storaged/block-details.jsx b/pkg/storaged/block-details.jsx
deleted file mode 100644
index f9deed2b6ddc..000000000000
--- a/pkg/storaged/block-details.jsx
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * This file is part of Cockpit.
- *
- * Copyright (C) 2017 Red Hat, Inc.
- *
- * Cockpit is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * Cockpit is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Cockpit; If not, see .
- */
-
-import cockpit from "cockpit";
-import React from "react";
-
-import { Card, CardBody, CardTitle } from "@patternfly/react-core/dist/esm/components/Card/index.js";
-import { DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
-
-import * as utils from "./utils.js";
-import { StdDetailsLayout } from "./details.jsx";
-import * as Content from "./content-views.jsx";
-
-const _ = cockpit.gettext;
-
-export class BlockDetails extends React.Component {
- render() {
- const block = this.props.block;
-
- const header = (
-
- {_("Block")}
-
-
-
- {_("storage", "Capacity")}
- { utils.fmt_size_long(block.Size) }
-
-
- {_("storage", "Device file")}
- { utils.block_name(block) }
-
-
-
-
- );
-
- const content = ;
-
- return ;
- }
-}
diff --git a/pkg/storaged/block/create-pages.jsx b/pkg/storaged/block/create-pages.jsx
new file mode 100644
index 000000000000..2ab82396eca1
--- /dev/null
+++ b/pkg/storaged/block/create-pages.jsx
@@ -0,0 +1,110 @@
+/*
+ * This file is part of Cockpit.
+ *
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * Cockpit is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * Cockpit is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Cockpit; If not, see .
+ */
+
+import client from "../client";
+
+import { get_fstab_config } from "../filesystem/utils.jsx";
+
+import { make_partition_table_page } from "../partitions/partition-table.jsx";
+import { make_legacy_vdo_page } from "../legacy-vdo/legacy-vdo.jsx";
+
+import { make_unrecognized_data_card } from "./unrecognized-data.jsx";
+import { make_unformatted_data_card } from "./unformatted-data.jsx";
+import { make_locked_encrypted_data_card } from "../crypto/locked-encrypted-data.jsx";
+import { make_filesystem_card } from "../filesystem/filesystem.jsx";
+import { make_lvm2_physical_volume_card } from "../lvm2/physical-volume.jsx";
+import { make_mdraid_disk_card } from "../mdraid/mdraid-disk.jsx";
+import { make_stratis_blockdev_card } from "../stratis/blockdev.jsx";
+import { make_swap_card } from "../swap/swap.jsx";
+import { make_encryption_card } from "../crypto/encryption.jsx";
+
+import { new_page } from "../pages.jsx";
+
+/* CARD must have page_name, page_location, and page_size set.
+ */
+
+export function make_block_page(parent, block, card) {
+ let is_crypto = block.IdUsage == 'crypto';
+ let content_block = is_crypto ? client.blocks_cleartext[block.path] : block;
+ const fstab_config = get_fstab_config(content_block || block, true);
+
+ const block_stratis_blockdev = client.blocks_stratis_blockdev[block.path];
+ const block_stratis_stopped_pool = client.blocks_stratis_stopped_pool[block.path];
+ const legacy_vdo = client.legacy_vdo_overlay.find_by_backing_block(block);
+
+ const is_stratis = ((content_block && content_block.IdUsage == "raid" && content_block.IdType == "stratis") ||
+ (block_stratis_blockdev && client.stratis_pools[block_stratis_blockdev.Pool]) ||
+ block_stratis_stopped_pool);
+
+ if (client.blocks_ptable[block.path]) {
+ make_partition_table_page(parent, block, card);
+ return;
+ }
+
+ if (legacy_vdo) {
+ make_legacy_vdo_page(parent, legacy_vdo, block, card);
+ return;
+ }
+
+ // Adjust for encryption leaking out of Stratis
+ if (is_crypto && is_stratis) {
+ is_crypto = false;
+ content_block = block;
+ }
+
+ if (is_crypto)
+ card = make_encryption_card(card, block);
+
+ if (!content_block) {
+ if (!is_crypto) {
+ // can not happen unless there is a bug in the code above.
+ console.error("Assertion failure: is_crypto == false");
+ }
+ if (fstab_config.length > 0) {
+ card = make_filesystem_card(card, block, null, fstab_config);
+ } else {
+ card = make_locked_encrypted_data_card(card, block);
+ }
+ } else {
+ const is_filesystem = content_block.IdUsage == 'filesystem';
+ const block_pvol = client.blocks_pvol[content_block.path];
+ const block_swap = client.blocks_swap[content_block.path];
+
+ if (is_filesystem) {
+ card = make_filesystem_card(card, block, content_block, fstab_config);
+ } else if ((content_block.IdUsage == "raid" && content_block.IdType == "LVM2_member") ||
+ (block_pvol && client.vgroups[block_pvol.VolumeGroup])) {
+ card = make_lvm2_physical_volume_card(card, block, content_block);
+ } else if (is_stratis) {
+ card = make_stratis_blockdev_card(card, block, content_block);
+ } else if ((content_block.IdUsage == "raid") ||
+ (client.mdraids[content_block.MDRaidMember])) {
+ card = make_mdraid_disk_card(card, block, content_block);
+ } else if (block_swap ||
+ (content_block.IdUsage == "other" && content_block.IdType == "swap")) {
+ card = make_swap_card(card, block, content_block);
+ } else if (client.blocks_available[content_block.path]) {
+ card = make_unformatted_data_card(card, block, content_block);
+ } else {
+ card = make_unrecognized_data_card(card, block, content_block);
+ }
+ }
+
+ new_page(parent, card);
+}
diff --git a/pkg/storaged/format-dialog.jsx b/pkg/storaged/block/format-dialog.jsx
similarity index 92%
rename from pkg/storaged/format-dialog.jsx
rename to pkg/storaged/block/format-dialog.jsx
index ce7d2369f433..4b21aba617b4 100644
--- a/pkg/storaged/format-dialog.jsx
+++ b/pkg/storaged/block/format-dialog.jsx
@@ -18,8 +18,13 @@
*/
import cockpit from "cockpit";
-import * as utils from "./utils.js";
-import { edit_crypto_config, parse_options, unparse_options, extract_option } from "./utils.js";
+import {
+ edit_crypto_config, parse_options, unparse_options, extract_option,
+ get_parent_blocks, is_netdev,
+ decode_filename, encode_filename, block_name,
+ get_active_usage, reload_systemd, teardown_active_usage,
+ validate_fsys_label,
+} from "../utils.js";
import React from "react";
import { FormHelperText } from "@patternfly/react-core/dist/esm/components/Form/index.js";
@@ -31,18 +36,18 @@ import {
TextInput, PassInput, CheckBoxes, SelectOne, SizeSlider,
BlockingMessage, TeardownMessage,
init_active_usage_processes
-} from "./dialog.jsx";
+} from "../dialog.jsx";
-import { get_fstab_config, is_valid_mount_point } from "./fsys-tab.jsx";
-import { init_existing_passphrase, unlock_with_type } from "./crypto-keyslots.jsx";
-import { job_progress_wrapper } from "./jobs-panel.jsx";
+import { get_fstab_config, is_valid_mount_point } from "../filesystem/utils.jsx";
+import { init_existing_passphrase, unlock_with_type } from "../crypto/keyslots.jsx";
+import { job_progress_wrapper } from "../jobs-panel.jsx";
const _ = cockpit.gettext;
export function initial_tab_options(client, block, for_fstab) {
const options = { };
- utils.get_parent_blocks(client, block.path).forEach(p => {
+ get_parent_blocks(client, block.path).forEach(p => {
// "nofail" is the default for new filesystems with Cockpit so
// that a failure to mount one of them will not prevent
// Cockpit from starting. This allows people to debug and fix
@@ -50,7 +55,7 @@ export function initial_tab_options(client, block, for_fstab) {
//
options.nofail = true;
- if (utils.is_netdev(client, p)) {
+ if (is_netdev(client, p)) {
options._netdev = true;
}
// HACK - https://bugzilla.redhat.com/show_bug.cgi?id=1589541
@@ -126,7 +131,7 @@ export const mount_explanation = {
export function format_dialog(client, path, start, size, enable_dos_extended) {
const block = client.blocks[path];
if (block.IdUsage == "crypto") {
- cockpit.spawn(["cryptsetup", "luksDump", utils.decode_filename(block.Device)], { superuser: true })
+ cockpit.spawn(["cryptsetup", "luksDump", decode_filename(block.Device)], { superuser: true })
.then(output => {
if (output.indexOf("Keyslots:") >= 0) // This is what luksmeta-monitor-hack looks for
return 2;
@@ -155,9 +160,9 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
let title;
if (create_partition)
- title = cockpit.format(_("Create partition on $0"), utils.block_name(block));
+ title = cockpit.format(_("Create partition on $0"), block_name(block));
else
- title = cockpit.format(_("Format $0"), utils.block_name(block));
+ title = cockpit.format(_("Format $0"), block_name(block));
function is_filesystem(vals) {
return vals.type != "empty" && vals.type != "dos-extended";
@@ -206,11 +211,11 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
add_crypto_type("luks1", "LUKS1", false);
add_crypto_type("luks2", "LUKS2", true);
- const usage = utils.get_active_usage(client, create_partition ? null : path, _("format"), _("delete"));
+ const usage = get_active_usage(client, create_partition ? null : path, _("format"), _("delete"));
if (usage.Blocking) {
dialog_open({
- Title: cockpit.format(_("$0 is in use"), utils.block_name(block)),
+ Title: cockpit.format(_("$0 is in use"), block_name(block)),
Body: BlockingMessage(usage)
});
return;
@@ -219,7 +224,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
const crypto_config = block.Configuration.find(c => c[0] == "crypttab");
let crypto_options;
if (crypto_config) {
- crypto_options = (utils.decode_filename(crypto_config[1].options.v)
+ crypto_options = (decode_filename(crypto_config[1].options.v)
.split(",")
.filter(function (s) { return s.indexOf("x-parent") !== 0 })
.join(","));
@@ -266,7 +271,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
Fields: [
TextInput("name", _("Name"),
{
- validate: (name, vals) => utils.validate_fsys_label(name, vals.type),
+ validate: (name, vals) => validate_fsys_label(name, vals.type),
visible: is_filesystem
}),
TextInput("mount_point", _("Mount point"),
@@ -429,7 +434,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
}
opts = opts.concat(parse_options(vals.crypto_options));
- new_crypto_options = { t: 'ay', v: utils.encode_filename(unparse_options(opts)) };
+ new_crypto_options = { t: 'ay', v: encode_filename(unparse_options(opts)) };
const item = {
options: new_crypto_options,
"track-parents": { t: 'b', v: true }
@@ -437,9 +442,9 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
if (!keep_keys) {
if (vals.store_passphrase.on) {
- item["passphrase-contents"] = { t: 'ay', v: utils.encode_filename(vals.passphrase) };
+ item["passphrase-contents"] = { t: 'ay', v: encode_filename(vals.passphrase) };
} else {
- item["passphrase-contents"] = { t: 'ay', v: utils.encode_filename("") };
+ item["passphrase-contents"] = { t: 'ay', v: encode_filename("") };
}
config_items.push(["crypttab", item]);
options["encrypt.passphrase"] = { t: 's', v: vals.passphrase };
@@ -471,9 +476,9 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
mount_point = "/" + mount_point;
config_items.push(["fstab", {
- dir: { t: 'ay', v: utils.encode_filename(mount_point) },
- type: { t: 'ay', v: utils.encode_filename("auto") },
- opts: { t: 'ay', v: utils.encode_filename(mount_options.join(",") || "defaults") },
+ dir: { t: 'ay', v: encode_filename(mount_point) },
+ type: { t: 'ay', v: encode_filename("auto") },
+ opts: { t: 'ay', v: encode_filename(mount_options.join(",") || "defaults") },
freq: { t: 'i', v: 0 },
passno: { t: 'i', v: 0 },
"track-parents": { t: 'b', v: true }
@@ -547,10 +552,10 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
.then(block_crypto => block_crypto.Lock({ })));
}
- return utils.teardown_active_usage(client, usage)
- .then(utils.reload_systemd)
+ return teardown_active_usage(client, usage)
+ .then(reload_systemd)
.then(format)
- .then(new_path => utils.reload_systemd().then(() => new_path))
+ .then(new_path => reload_systemd().then(() => new_path))
.then(maybe_mount);
}
},
diff --git a/pkg/storaged/block/other.jsx b/pkg/storaged/block/other.jsx
new file mode 100644
index 000000000000..fa62110113de
--- /dev/null
+++ b/pkg/storaged/block/other.jsx
@@ -0,0 +1,61 @@
+/*
+ * This file is part of Cockpit.
+ *
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * Cockpit is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * Cockpit is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Cockpit; If not, see .
+ */
+
+import cockpit from "cockpit";
+import React from "react";
+
+import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
+import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
+
+import { StorageCard, StorageDescription, new_card } from "../pages.jsx";
+import { block_name } from "../utils.js";
+import { partitionable_block_actions } from "../partitions/actions.jsx";
+
+import { make_block_page } from "../block/create-pages.jsx";
+
+const _ = cockpit.gettext;
+
+export function make_other_page(parent, block) {
+ const other_card = new_card({
+ title: _("Block device"),
+ next: null,
+ page_block: block,
+ for_summary: true,
+ job_path: block.path,
+ component: OtherCard,
+ props: { block },
+ actions: partitionable_block_actions(block),
+ });
+
+ make_block_page(parent, block, other_card);
+}
+
+const OtherCard = ({ card, block }) => {
+ return (
+
+
+
+ > 8) + ":" + (block.DeviceNumber & 0xFF)} />
+
+
+
+
+ );
+};
diff --git a/pkg/storaged/resize.jsx b/pkg/storaged/block/resize.jsx
similarity index 85%
rename from pkg/storaged/resize.jsx
rename to pkg/storaged/block/resize.jsx
index 9711e71b7fc3..441b459a1916 100644
--- a/pkg/storaged/resize.jsx
+++ b/pkg/storaged/block/resize.jsx
@@ -19,23 +19,93 @@
import React from "react";
import cockpit from "cockpit";
+import client from "../client.js";
+
import {
block_name, get_active_usage, teardown_active_usage,
undo_temporary_teardown, is_mounted_synch, get_partitions
-} from "./utils.js";
+} from "../utils.js";
import {
existing_passphrase_fields, init_existing_passphrase,
request_passphrase_on_error_handler
-} from "./crypto-keyslots.jsx";
+} from "../crypto/keyslots.jsx";
import {
dialog_open, SizeSlider, BlockingMessage, TeardownMessage, SelectSpaces,
init_active_usage_processes
-} from "./dialog.jsx";
-import { std_reply } from "./stratis-utils.js";
-import { pvs_to_spaces } from "./content-views.jsx";
+} from "../dialog.jsx";
+import { std_reply } from "../stratis/utils.jsx";
+import { pvs_to_spaces } from "../lvm2/utils.jsx";
const _ = cockpit.gettext;
+export function check_unused_space(path) {
+ const block = client.blocks[path];
+ const lvm2 = client.blocks_lvm2[path];
+ const lvol = lvm2 && client.lvols[lvm2.LogicalVolume];
+ const part = client.blocks_part[path];
+
+ let size, min_change;
+
+ if (lvol) {
+ size = lvol.Size;
+ min_change = client.vgroups[lvol.VolumeGroup].ExtentSize;
+ } else if (part) {
+ size = part.Size;
+ min_change = 1024 * 1024;
+ } else {
+ return null;
+ }
+
+ if (size != block.Size) {
+ // Let's ignore inconsistent lvol,part/block combinations.
+ // These happen during a resize and the inconsistency will
+ // eventually go away.
+ return null;
+ }
+
+ let content_path = null;
+ let crypto_overhead = 0;
+
+ const crypto = client.blocks_crypto[block.path];
+ const cleartext = client.blocks_cleartext[block.path];
+ if (crypto) {
+ if (crypto.MetadataSize !== undefined && cleartext) {
+ content_path = cleartext.path;
+ crypto_overhead = crypto.MetadataSize;
+ }
+ } else {
+ content_path = path;
+ }
+
+ const fsys = client.blocks_fsys[content_path];
+ const content_block = client.blocks[content_path];
+ const vdo = content_block ? client.legacy_vdo_overlay.find_by_backing_block(content_block) : null;
+ const stratis_bdev = client.blocks_stratis_blockdev[content_path];
+
+ if (fsys && fsys.Size && (size - fsys.Size - crypto_overhead) > min_change && fsys.Resize) {
+ return {
+ volume_size: size - crypto_overhead,
+ content_size: fsys.Size
+ };
+ }
+
+ if (vdo && (size - vdo.physical_size - crypto_overhead) > min_change) {
+ return {
+ volume_size: size - crypto_overhead,
+ content_size: vdo.physical_size
+ };
+ }
+
+ if (stratis_bdev && (size - Number(stratis_bdev.TotalPhysicalSize) - crypto_overhead) > min_change) {
+ return {
+ volume_size: size - crypto_overhead,
+ content_size: Number(stratis_bdev.TotalPhysicalSize)
+ };
+ }
+
+ return null;
+}
+
function lvol_or_part_and_fsys_resize(client, lvol_or_part, size, offline, passphrase, pvs) {
let fsys;
let crypto_overhead;
@@ -81,7 +151,7 @@ function lvol_or_part_and_fsys_resize(client, lvol_or_part, size, offline, passp
// HACK - https://bugzilla.redhat.com/show_bug.cgi?id=1934567
//
// block_fsys.MountedAt might be out of synch with reality
- // here if resizing the crypto container accidentally
+ // here if resizing the crypto card accidentally
// triggered an unmount. Thus, we check synchronously
// whether or not we should be doing a offline resize or
// not.
@@ -117,6 +187,9 @@ function lvol_or_part_and_fsys_resize(client, lvol_or_part, size, offline, passp
return Promise.reject(_("Stratis blockdevs can not be made smaller")); // not-covered: safety check
else
return Promise.resolve();
+ } else if (client.blocks_available[block.path]) {
+ // Growing or shrinking unformatted data, nothing to do
+ return Promise.resolve();
} else if (size < orig_size) {
// This shouldn't happen. But if it does, continuing is harmful, so we throw an error.
return Promise.reject(_("Unrecognized data can not be made smaller here.")); // not-covered: safety check
@@ -170,7 +243,7 @@ export function get_resize_info(client, block, to_fit) {
if (!cleartext) {
info = { };
- shrink_excuse = grow_excuse = _("Encrypted volumes need to be unlocked before they can be resized.");
+ shrink_excuse = grow_excuse = _("Unlock before resizing");
} else {
return get_resize_info(client, cleartext, to_fit);
}
@@ -179,15 +252,20 @@ export function get_resize_info(client, block, to_fit) {
if (!info) {
info = { };
- shrink_excuse = grow_excuse = cockpit.format(_("$0 filesystems can not be resized here."),
+ shrink_excuse = grow_excuse = cockpit.format(_("$0 can not be resized here"),
block.IdType);
} else {
- if (!info.can_shrink)
- shrink_excuse = cockpit.format(_("$0 filesystems can not be made smaller."),
- block.IdType);
- if (!info.can_grow)
- grow_excuse = cockpit.format(_("$0 filesystems can not be made larger."),
- block.IdType);
+ if (!info.can_shrink && !info.can_grow) {
+ shrink_excuse = grow_excuse = cockpit.format(_("$0 can not be resized"),
+ block.IdType);
+ } else {
+ if (!info.can_shrink)
+ shrink_excuse = cockpit.format(_("$0 can not be made smaller"),
+ block.IdType);
+ if (!info.can_grow)
+ grow_excuse = cockpit.format(_("$0 can not be made larger"),
+ block.IdType);
+ }
}
} else if (client.blocks_stratis_blockdev[block.path] && client.features.stratis_grow_blockdevs) {
info = {
@@ -198,7 +276,7 @@ export function get_resize_info(client, block, to_fit) {
shrink_excuse = _("Stratis blockdevs can not be made smaller");
} else if (block.IdUsage == 'raid') {
info = { };
- shrink_excuse = grow_excuse = _("Physical volumes can not be resized here.");
+ shrink_excuse = grow_excuse = _("Physical volumes can not be resized here");
} else if (client.legacy_vdo_overlay.find_by_backing_block(block)) {
info = {
can_shrink: false,
@@ -206,13 +284,20 @@ export function get_resize_info(client, block, to_fit) {
grow_needs_unmount: false
};
shrink_excuse = _("VDO backing devices can not be made smaller");
+ } else if (client.blocks_available[block.path]) {
+ info = {
+ can_shrink: true,
+ can_grow: true,
+ shrink_needs_unmount: false,
+ grow_needs_unmount: false,
+ };
} else {
info = {
can_shrink: false,
can_grow: true,
grow_needs_unmount: true
};
- shrink_excuse = _("Unrecognized data can not be made smaller here.");
+ shrink_excuse = _("Unrecognized data can not be made smaller here");
}
if (to_fit) {
// Shrink to fit doesn't need to resize the content
@@ -220,7 +305,7 @@ export function get_resize_info(client, block, to_fit) {
}
} else {
info = { };
- shrink_excuse = grow_excuse = _("This volume needs to be activated before it can be resized.");
+ shrink_excuse = grow_excuse = _("Activate before resizing");
}
return { info, shrink_excuse, grow_excuse };
diff --git a/pkg/storaged/block/unformatted-data.jsx b/pkg/storaged/block/unformatted-data.jsx
new file mode 100644
index 000000000000..7c4e5ed12c01
--- /dev/null
+++ b/pkg/storaged/block/unformatted-data.jsx
@@ -0,0 +1,39 @@
+/*
+ * This file is part of Cockpit.
+ *
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * Cockpit is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * Cockpit is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Cockpit; If not, see .
+ */
+
+import cockpit from "cockpit";
+import client from "../client";
+
+import { StorageCard, new_card } from "../pages.jsx";
+import { format_dialog } from "./format-dialog.jsx";
+import { std_lock_action } from "../crypto/actions.jsx";
+
+const _ = cockpit.gettext;
+
+export function make_unformatted_data_card(next, backing_block, content_block) {
+ return new_card({
+ title: _("Unformatted data"),
+ next,
+ component: StorageCard,
+ actions: [
+ std_lock_action(backing_block, content_block),
+ { title: _("Format"), action: () => format_dialog(client, backing_block.path), danger: true },
+ ]
+ });
+}
diff --git a/pkg/storaged/block/unrecognized-data.jsx b/pkg/storaged/block/unrecognized-data.jsx
new file mode 100644
index 000000000000..f5056d6524f9
--- /dev/null
+++ b/pkg/storaged/block/unrecognized-data.jsx
@@ -0,0 +1,57 @@
+/*
+ * This file is part of Cockpit.
+ *
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * Cockpit is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * Cockpit is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Cockpit; If not, see .
+ */
+
+import cockpit from "cockpit";
+import React from "react";
+import client from "../client";
+
+import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
+import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
+
+import { StorageCard, StorageDescription, new_card } from "../pages.jsx";
+import { format_dialog } from "./format-dialog.jsx";
+import { std_lock_action } from "../crypto/actions.jsx";
+
+const _ = cockpit.gettext;
+
+export function make_unrecognized_data_card(next, backing_block, content_block) {
+ return new_card({
+ title: _("Unrecognized data"),
+ next,
+ component: UnrecognizedDataCard,
+ props: { backing_block, content_block },
+ actions: [
+ std_lock_action(backing_block, content_block),
+ { title: _("Format"), action: () => format_dialog(client, backing_block.path), danger: true },
+ ]
+ });
+}
+
+export const UnrecognizedDataCard = ({ card, backing_block, content_block }) => {
+ return (
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/pkg/storaged/client.js b/pkg/storaged/client.js
index 99aaba59c424..9772851f7fe2 100644
--- a/pkg/storaged/client.js
+++ b/pkg/storaged/client.js
@@ -26,14 +26,15 @@ import * as utils from './utils.js';
import * as python from "python.js";
import { read_os_release } from "os-release.js";
-import { find_warnings } from "./warnings.jsx";
-
import inotify_py from "inotify.py";
import mount_users_py from "./mount-users.py";
-import nfs_mounts_py from "./nfs-mounts.py";
-import vdo_monitor_py from "./vdo-monitor.py";
-import stratis2_set_key_py from "./stratis2-set-key.py";
-import stratis3_set_key_py from "./stratis3-set-key.py";
+import nfs_mounts_py from "./nfs/nfs-mounts.py";
+import vdo_monitor_py from "./legacy-vdo/vdo-monitor.py";
+import stratis2_set_key_py from "./stratis/stratis2-set-key.py";
+import stratis3_set_key_py from "./stratis/stratis3-set-key.py";
+
+import { reset_pages } from "./pages.jsx";
+import { make_overview_page } from "./overview/overview.jsx";
/* STORAGED CLIENT
*/
@@ -562,18 +563,40 @@ function update_indices() {
client.blocks_partitions[path].sort(function (a, b) { return a.Offset - b.Offset });
}
+ client.iscsi_sessions_drives = { };
+ client.drives_iscsi_session = { };
+ for (path in client.drives) {
+ const block = client.drives_block[path];
+ if (!block)
+ continue;
+ for (const session_path in client.iscsi_sessions) {
+ const session = client.iscsi_sessions[session_path];
+ for (i = 0; i < block.Symlinks.length; i++) {
+ if (utils.decode_filename(block.Symlinks[i]).includes(session.data.target_name)) {
+ client.drives_iscsi_session[path] = session;
+ if (!client.iscsi_sessions_drives[session_path])
+ client.iscsi_sessions_drives[session_path] = [];
+ client.iscsi_sessions_drives[session_path].push(client.drives[path]);
+ }
+ }
+ }
+ }
+
+ client.blocks_available = { };
+ for (path in client.blocks) {
+ block = client.blocks[path];
+ if (utils.is_available_block(client, block))
+ client.blocks_available[path] = true;
+ }
+
client.path_jobs = { };
function enter_job(job) {
if (!job.Objects || !job.Objects.length)
return;
- job.Objects.forEach(function (path) {
- client.path_jobs[path] = job;
- let parent = utils.get_parent(client, path);
- while (parent) {
- path = parent;
- parent = utils.get_parent(client, path);
- }
- client.path_jobs[path] = job;
+ job.Objects.forEach(p => {
+ if (!client.path_jobs[p])
+ client.path_jobs[p] = [];
+ client.path_jobs[p].push(job);
});
}
for (path in client.jobs) {
@@ -581,10 +604,15 @@ function update_indices() {
}
}
-client.update = () => {
- update_indices();
- client.path_warnings = find_warnings(client);
- client.dispatchEvent("changed");
+client.update = (first_time) => {
+ if (first_time)
+ client.ready = true;
+ if (client.ready) {
+ update_indices();
+ reset_pages();
+ make_overview_page();
+ client.dispatchEvent("changed");
+ }
};
function init_model(callback) {
@@ -723,7 +751,7 @@ function init_model(callback) {
client.storaged_client.addEventListener('notify', () => client.update());
- client.update();
+ client.update(true);
callback();
});
});
@@ -819,7 +847,7 @@ function nfs_mounts() {
if (lines.length >= 2) {
self.entries = JSON.parse(lines[lines.length - 2]);
self.fsys_sizes = { };
- client.dispatchEvent('changed');
+ client.update();
}
})
.catch(function (error) {
@@ -842,11 +870,11 @@ function nfs_mounts() {
.then(function (output) {
const data = JSON.parse(output);
self.fsys_sizes[path] = [(data[2] - data[1]) * data[0], data[2] * data[0]];
- client.dispatchEvent('changed');
+ client.update();
})
.catch(function () {
self.fsys_sizes[path] = [0, 0];
- client.dispatchEvent('changed');
+ client.update();
});
return null;
diff --git a/pkg/storaged/content-views.jsx b/pkg/storaged/content-views.jsx
deleted file mode 100644
index 9f62be6b3a5d..000000000000
--- a/pkg/storaged/content-views.jsx
+++ /dev/null
@@ -1,1196 +0,0 @@
-/*
- * This file is part of Cockpit.
- *
- * Copyright (C) 2016 Red Hat, Inc.
- *
- * Cockpit is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * Cockpit is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Cockpit; If not, see .
- */
-
-import cockpit from "cockpit";
-import {
- dialog_open, TextInput, PassInput, SelectOne, SelectOneRadioVertical, SizeSlider, CheckBoxes,
- SelectSpaces, BlockingMessage, TeardownMessage, Message,
- init_active_usage_processes
-} from "./dialog.jsx";
-import * as utils from "./utils.js";
-import { set_crypto_auto_option } from "./utils.js";
-
-import React from "react";
-import { Card, CardBody, CardHeader, CardTitle } from '@patternfly/react-core/dist/esm/components/Card/index.js';
-import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
-import {
- DropdownSeparator
-} from '@patternfly/react-core/dist/esm/deprecated/components/Dropdown/index.js';
-import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
-
-import { ListingTable } from "cockpit-components-table.jsx";
-import { ListingPanel } from 'cockpit-components-listing-panel.jsx';
-import { StorageButton, StorageBarMenu, StorageMenuItem, StorageUsageBar } from "./storage-controls.jsx";
-import * as PK from "packagekit.js";
-import { format_dialog } from "./format-dialog.jsx";
-import { job_progress_wrapper } from "./jobs-panel.jsx";
-
-import { FilesystemTab, is_mounted, mounting_dialog, get_fstab_config } from "./fsys-tab.jsx";
-import { CryptoTab } from "./crypto-tab.jsx";
-import { get_existing_passphrase, unlock_with_type } from "./crypto-keyslots.jsx";
-import { BlockVolTab, PoolVolTab, VDOPoolTab } from "./lvol-tabs.jsx";
-import { PartitionTab } from "./part-tab.jsx";
-import { SwapTab } from "./swap-tab.jsx";
-import { UnrecognizedTab } from "./unrecognized-tab.jsx";
-import { warnings_icon } from "./warnings.jsx";
-
-const _ = cockpit.gettext;
-
-const C_ = cockpit.gettext;
-
-function next_default_logical_volume_name(client, vgroup, prefix) {
- function find_lvol(name) {
- const lvols = client.vgroups_lvols[vgroup.path];
- for (let i = 0; i < lvols.length; i++) {
- if (lvols[i].Name == name)
- return lvols[i];
- }
- return null;
- }
-
- let name;
- for (let i = 0; i < 1000; i++) {
- name = prefix + i.toFixed();
- if (!find_lvol(name))
- break;
- }
-
- return name;
-}
-
-export function pvs_to_spaces(client, pvs) {
- return pvs.map(pvol => {
- const block = client.blocks[pvol.path];
- const parts = utils.get_block_link_parts(client, pvol.path);
- const text = cockpit.format(parts.format, parts.link);
- return { type: 'block', block, size: pvol.FreeSize, desc: text, pvol };
- });
-}
-
-function create_tabs(client, target, options) {
- function endsWith(str, suffix) {
- return str.indexOf(suffix, str.length - suffix.length) !== -1;
- }
-
- const block = endsWith(target.iface, ".Block") ? target : null;
- let is_crypto = (block && block.IdUsage == 'crypto');
- const content_block = is_crypto ? client.blocks_cleartext[block.path] : block;
-
- const block_fsys = content_block && client.blocks_fsys[content_block.path];
- const block_lvm2 = block && client.blocks_lvm2[block.path];
- const block_swap = content_block && client.blocks_swap[content_block.path];
-
- const block_stratis_blockdev = block && client.blocks_stratis_blockdev[block.path];
- const block_stratis_stopped_pool = block && client.blocks_stratis_stopped_pool[block.path];
-
- const lvol = (endsWith(target.iface, ".LogicalVolume")
- ? target
- : block_lvm2 && client.lvols[block_lvm2.LogicalVolume]);
-
- const is_filesystem = (content_block && content_block.IdUsage == 'filesystem');
- const is_stratis = ((content_block && content_block.IdUsage == "raid" && content_block.IdType == "stratis") ||
- (block_stratis_blockdev && client.stratis_pools[block_stratis_blockdev.Pool]) ||
- block_stratis_stopped_pool);
-
- // Adjust for encryption leaking out of Stratis
- if (is_crypto && is_stratis)
- is_crypto = false;
-
- let warnings = client.path_warnings[target.path] || [];
- if (content_block)
- warnings = warnings.concat(client.path_warnings[content_block.path] || []);
- if (lvol)
- warnings = warnings.concat(client.path_warnings[lvol.path] || []);
-
- const tab_actions = [];
- const tab_menu_actions = [];
- const tab_menu_danger_actions = [];
-
- function add_action(title, func) {
- if (tab_actions.length == 0) {
- tab_actions.push({title});
- tab_menu_actions.push({ title, func, only_narrow: true });
- } else {
- add_menu_action(title, func);
- }
- }
-
- function add_danger_action(title, func) {
- if (tab_actions.length == 0) {
- tab_actions.push({title});
- tab_menu_danger_actions.push({ title, func, only_narrow: true });
- } else {
- add_menu_danger_action(title, func);
- }
- }
-
- function add_menu_action(title, func) {
- tab_menu_actions.push({ title, func });
- }
-
- function add_menu_danger_action(title, func) {
- tab_menu_danger_actions.push({ title, func, danger: true });
- }
-
- const tabs = [];
-
- function add_tab(name, renderer, for_content, associated_warnings) {
- let tab_warnings = [];
- if (associated_warnings)
- tab_warnings = warnings.filter(w => associated_warnings.indexOf(w.warning) >= 0);
- if (tab_warnings.length > 0)
- name =
{warnings_icon(tab_warnings)} {name}
;
- tabs.push(
- {
- name,
- renderer,
- data: {
- client,
- block: for_content ? content_block : block,
- lvol,
- warnings: tab_warnings,
- }
- });
- }
-
- function create_thin() {
- const vgroup = lvol && client.vgroups[lvol.VolumeGroup];
- if (!vgroup)
- return;
-
- dialog_open({
- Title: _("Create thin volume"),
- Fields: [
- TextInput("name", _("Name"),
- {
- value: next_default_logical_volume_name(client, vgroup, "lvol"),
- validate: utils.validate_lvm2_name
- }),
- SizeSlider("size", _("Size"),
- {
- value: lvol.Size,
- max: lvol.Size * 3,
- allow_infinite: true,
- round: vgroup.ExtentSize
- })
- ],
- Action: {
- Title: _("Create"),
- action: function (vals) {
- return vgroup.CreateThinVolume(vals.name, vals.size, lvol.path, { });
- }
- }
- });
- }
-
- if (lvol) {
- if (lvol.Type == "pool") {
- add_tab(_("Pool"), PoolVolTab);
- add_action(_("Create thin volume"), create_thin);
- } else {
- add_tab(_("Volume"), BlockVolTab, false, ["unused-space", "partial-lvol"]);
-
- if (client.vdo_vols[lvol.path])
- add_tab(_("VDO pool"), VDOPoolTab);
- }
- }
-
- if (options.is_partition) {
- add_tab(_("Partition"), PartitionTab, false, ["unused-space"]);
- }
-
- let is_unrecognized = false;
-
- if (is_filesystem) {
- add_tab(_("Filesystem"), FilesystemTab, true, ["mismounted-fsys"]);
- } else if (content_block && (content_block.IdUsage == "raid" ||
- client.legacy_vdo_overlay.find_by_backing_block(content_block))) {
- // no tab for these
- } else if (block_swap || (content_block && content_block.IdUsage == "other" && content_block.IdType == "swap")) {
- add_tab(_("Swap"), SwapTab, true);
- } else if (content_block) {
- is_unrecognized = true;
- add_tab(_("Unrecognized data"), UnrecognizedTab, true);
- }
-
- if (is_crypto) {
- const config = client.blocks_crypto[block.path]?.ChildConfiguration.find(c => c[0] == "fstab");
- if (config && !content_block)
- add_tab(_("Filesystem"), FilesystemTab, false, ["mismounted-fsys"]);
- add_tab(_("Encryption"), CryptoTab);
- }
-
- function lock() {
- const crypto = client.blocks_crypto[block.path];
- if (!crypto)
- return;
-
- return crypto.Lock({}).then(() => set_crypto_auto_option(block, false));
- }
-
- function unlock() {
- const crypto = client.blocks_crypto[block.path];
- if (!crypto)
- return;
-
- return get_existing_passphrase(block, true).then(type => {
- return (unlock_with_type(client, block, null, type)
- .then(() => set_crypto_auto_option(block, true))
- .catch(() => unlock_with_passphrase()));
- });
- }
-
- function unlock_with_passphrase() {
- const crypto = client.blocks_crypto[block.path];
- if (!crypto)
- return;
-
- dialog_open({
- Title: _("Unlock"),
- Fields: [
- PassInput("passphrase", _("Passphrase"), {})
- ],
- Action: {
- Title: _("Unlock"),
- action: function (vals) {
- return (crypto.Unlock(vals.passphrase, {})
- .then(() => set_crypto_auto_option(block, true)));
- }
- }
- });
- }
-
- if (is_crypto) {
- if (client.blocks_cleartext[block.path]) {
- if (!block_fsys)
- add_menu_action(_("Lock"), lock);
- } else {
- const config = client.blocks_crypto[block.path]?.ChildConfiguration.find(c => c[0] == "fstab");
- if (config && !content_block)
- add_action(_("Mount"), () => mounting_dialog(client, block, "mount"));
- else
- add_action(_("Unlock"), unlock);
- }
- }
-
- function activate() {
- return lvol.Activate({});
- }
-
- function deactivate() {
- return lvol.Deactivate({});
- }
-
- function create_snapshot() {
- dialog_open({
- Title: _("Create snapshot"),
- Fields: [
- TextInput("name", _("Name"),
- { validate: utils.validate_lvm2_name }),
- ],
- Action: {
- Title: _("Create"),
- action: function (vals) {
- return lvol.CreateSnapshot(vals.name, vals.size || 0, { });
- }
- }
- });
- }
-
- function repair() {
- const vgroup = lvol && client.vgroups[lvol.VolumeGroup];
- if (!vgroup)
- return;
-
- const summary = client.lvols_stripe_summary[lvol.path];
- const missing = summary.reduce((sum, sub) => sum + (sub["/"] ?? 0), 0);
-
- function usable(pvol) {
- // must have some free space and not already used for a
- // subvolume other than those that need to be repaired.
- return pvol.FreeSize > 0 && !summary.some(sub => !sub["/"] && sub[pvol.path]);
- }
-
- const pvs_as_spaces = pvs_to_spaces(client, client.vgroups_pvols[vgroup.path].filter(usable));
- const available = pvs_as_spaces.reduce((sum, spc) => sum + spc.size, 0);
-
- if (available < missing) {
- dialog_open({
- Title: cockpit.format(_("Unable to repair logical volume $0"), lvol.Name),
- Body: {cockpit.format(_("There is not enough space available that could be used for a repair. At least $0 are needed on physical volumes that are not already used for this logical volume."),
- utils.fmt_size(missing))}
- });
- return;
- }
-
- function enough_space(pvs) {
- const selected = pvs.reduce((sum, pv) => sum + pv.size, 0);
- if (selected < missing)
- return cockpit.format(_("An additional $0 must be selected"), utils.fmt_size(missing - selected));
- }
-
- dialog_open({
- Title: cockpit.format(_("Repair logical volume $0"), lvol.Name),
- Body: {cockpit.format(_("Select the physical volumes that should be used to repair the logical volume. At leat $0 are needed."),
- utils.fmt_size(missing))}
,
- Fields: [
- SelectSpaces("pvs", _("Physical Volumes"),
- {
- spaces: pvs_as_spaces,
- validate: enough_space
- }),
- ],
- Action: {
- Title: _("Repair"),
- action: function (vals) {
- return lvol.Repair(vals.pvs.map(spc => spc.block.path), { });
- }
- }
- });
- }
-
- if (lvol) {
- const status_code = client.lvols_status[lvol.path];
- if (status_code == "degraded" || status_code == "degraded-maybe-partial")
- add_action(_("Repair"), repair);
-
- if (lvol.Type != "pool") {
- if (lvol.Active) {
- add_menu_action(_("Deactivate"), deactivate);
- } else {
- add_action(_("Activate"), activate);
- }
- }
- if (client.lvols[lvol.ThinPool]) {
- add_menu_action(_("Create snapshot"), create_snapshot);
- }
- }
-
- function swap_start() {
- return block_swap.Start({});
- }
-
- function swap_stop() {
- return block_swap.Stop({});
- }
-
- if (block_swap) {
- if (block_swap.Active)
- add_menu_action(_("Stop"), swap_stop);
- else
- add_menu_action(_("Start"), swap_start);
- }
-
- function delete_() {
- let block_part;
-
- /* This is called only for logical volumes and partitions
- */
-
- if (block)
- block_part = client.blocks_part[block.path];
-
- let name, danger;
-
- if (lvol) {
- name = utils.lvol_name(lvol);
- danger = _("Deleting a logical volume will delete all data in it.");
- } else if (block_part) {
- name = utils.block_name(block);
- danger = _("Deleting a partition will delete all data in it.");
- }
-
- if (name) {
- const usage = utils.get_active_usage(client, target.path, _("delete"));
-
- if (usage.Blocking) {
- dialog_open({
- Title: cockpit.format(_("$0 is in use"), name),
- Body: BlockingMessage(usage)
- });
- return;
- }
-
- dialog_open({
- Title: cockpit.format(_("Permanently delete $0?"), name),
- Teardown: TeardownMessage(usage),
- Action: {
- Danger: danger,
- Title: _("Delete"),
- disable_on_error: usage.Teardown,
- action: function () {
- return utils.teardown_active_usage(client, usage)
- .then(function () {
- if (lvol)
- return lvol.Delete({ 'tear-down': { t: 'b', v: true } });
- else if (block_part)
- return block_part.Delete({ 'tear-down': { t: 'b', v: true } });
- })
- .then(utils.reload_systemd);
- }
- },
- Inits: [
- init_active_usage_processes(client, usage)
- ]
- });
- }
- }
-
- if (block && !options.is_extended) {
- if (is_unrecognized)
- add_danger_action(_("Format"), () => format_dialog(client, block.path));
- else
- add_menu_danger_action(_("Format"), () => format_dialog(client, block.path));
- }
-
- if (options.is_partition || lvol) {
- add_menu_danger_action(_("Delete"), delete_);
- }
-
- if (block_fsys) {
- if (is_mounted(client, content_block))
- add_menu_action(_("Unmount"), () => mounting_dialog(client, content_block, "unmount"));
- else
- add_action(_("Mount"), () => mounting_dialog(client, content_block, "mount"));
- }
-
- return {
- renderers: tabs,
- actions: tab_actions,
- menu_actions: tab_menu_actions,
- menu_danger_actions: tab_menu_danger_actions,
- warnings
- };
-}
-
-function block_description(client, block, options) {
- let type, used_for, link, size, critical_size;
- const block_stratis_blockdev = client.blocks_stratis_blockdev[block.path];
- const block_stratis_stopped_pool = client.blocks_stratis_stopped_pool[block.path];
- const vdo = client.legacy_vdo_overlay.find_by_backing_block(block);
- const cleartext = client.blocks_cleartext[block.path];
- if (cleartext)
- block = cleartext;
-
- const block_pvol = client.blocks_pvol[block.path];
- let omit_encrypted_label = false;
-
- size = block.Size;
-
- if (block.IdUsage == "crypto" && !cleartext) {
- const [config, mount_point] = get_fstab_config(block, true);
- if (config) {
- type = C_("storage-id-desc", "Filesystem (encrypted)");
- used_for = mount_point;
- } else if (block_stratis_stopped_pool) {
- type = _("Stratis member");
- used_for = block_stratis_stopped_pool;
- link = ["pool", used_for];
- omit_encrypted_label = true;
- } else
- type = C_("storage-id-desc", "Locked encrypted data");
- } else if (block.IdUsage == "filesystem") {
- const [, mount_point] = get_fstab_config(block, true);
- type = cockpit.format(C_("storage-id-desc", "$0 filesystem"), block.IdType);
- if (client.fsys_sizes.data[mount_point])
- size = client.fsys_sizes.data[mount_point];
- used_for = mount_point;
- } else if (block.IdUsage == "raid") {
- if (block_pvol && client.vgroups[block_pvol.VolumeGroup]) {
- const vgroup = client.vgroups[block_pvol.VolumeGroup];
- type = _("LVM2 member");
- used_for = vgroup.Name;
- link = ["vg", used_for];
- size = [block_pvol.Size - block_pvol.FreeSize, block_pvol.Size];
- critical_size = 1;
- } else if (client.mdraids[block.MDRaidMember]) {
- const mdraid = client.mdraids[block.MDRaidMember];
- type = _("RAID member");
- used_for = utils.mdraid_name(mdraid);
- link = ["mdraid", mdraid.UUID];
- } else if (block_stratis_blockdev && client.stratis_pools[block_stratis_blockdev.Pool]) {
- const pool = client.stratis_pools[block_stratis_blockdev.Pool];
- type = _("Stratis member");
- used_for = pool.Name;
- link = ["pool", pool.Uuid];
- omit_encrypted_label = true;
- } else if (block.IdType == "LVM2_member") {
- type = _("LVM2 member");
- } else if (block.IdType == "stratis") {
- type = _("Stratis member");
- omit_encrypted_label = true;
- } else {
- type = _("RAID member");
- }
- } else if (block.IdUsage == "other") {
- if (block.IdType == "swap") {
- type = C_("storage-id-desc", "Swap space");
- } else {
- type = C_("storage-id-desc", "Other data");
- }
- } else if (vdo) {
- type = C_("storage-id-desc", "VDO backing");
- used_for = vdo.name;
- link = ["vdo", vdo.name];
- } else if (client.blocks_swap[block.path]) {
- type = C_("storage-id-desc", "Swap space");
- } else {
- type = C_("storage-id-desc", "Unrecognized data");
- }
-
- if (cleartext && !omit_encrypted_label)
- type = cockpit.format(_("$0 (encrypted)"), type);
-
- return {
- type,
- used_for,
- link,
- size,
- critical_size
- };
-}
-
-function append_row(client, rows, level, key, name, desc, tabs, job_object, options) {
- function menuitem(action) {
- if (action.title)
- return {action.title};
- else
- return ;
- }
-
- let menu = null;
- const menu_actions = tabs.menu_actions || [];
- const menu_danger_actions = tabs.menu_danger_actions || [];
- const menu_actions_wide_count = menu_actions.filter(a => !a.only_narrow).length;
- const menu_danger_actions_wide_count = menu_danger_actions.filter(a => !a.only_narrow).length;
-
- const menu_sep = [];
- if (menu_actions.length > 0 && menu_danger_actions.length > 0)
- menu_sep.push({
- title: null,
- only_narrow: !(menu_actions_wide_count > 0 && menu_danger_actions_wide_count > 0)
- });
-
- if (menu_actions.length + menu_danger_actions.length > 0)
- menu =