diff --git a/pkg/storaged/block/create-pages.jsx b/pkg/storaged/block/create-pages.jsx index 1ef0ed00cbf3..0ed6fedd2e22 100644 --- a/pkg/storaged/block/create-pages.jsx +++ b/pkg/storaged/block/create-pages.jsx @@ -17,16 +17,10 @@ * along with Cockpit; If not, see . */ -import cockpit from "cockpit"; -import React from "react"; import client from "../client"; -import { get_partitions, fmt_size } from "../utils.js"; import { get_fstab_config } from "../filesystem/utils.jsx"; -import { format_dialog } from "./format-dialog.jsx"; -import { StorageSize } from "../storage-controls.jsx"; - import { make_unrecognized_data_page } from "./unrecognized-data.jsx"; import { make_unformatted_data_page } from "./unformatted-data.jsx"; import { make_locked_encrypted_data_page } from "../crypto/locked-encrypted-data.jsx"; @@ -37,84 +31,9 @@ import { make_stratis_blockdev_page } from "../stratis/blockdev.jsx"; import { make_swap_page } from "../swap/swap.jsx"; import { make_partition_table_page } from "../partitions/partition-table.jsx"; import { make_legacy_vdo_page } from "../legacy-vdo/legacy-vdo.jsx"; +import { make_encryption_card } from "../crypto/encryption.jsx"; -import { make_partition_container, delete_partition } from "../partitions/partition.jsx"; -import { make_encryption_container } from "../crypto/encryption.jsx"; - -import { new_page } from "../pages.jsx"; - -const _ = cockpit.gettext; - -/* Creating all the pages - * - * This is where a lot of the hair is. - */ - -export function make_block_pages(parent, block) { - if (client.blocks_ptable[block.path]) - make_partition_pages(parent, block); - else - make_block_page(parent, block, null); -} - -function make_partition_pages(parent, block) { - const block_ptable = client.blocks_ptable[block.path]; - - function make_free_space_page(parent, start, size, enable_dos_extended) { - new_page({ - parent, - name: _("Free space"), - columns: [ - null, - null, - , - ], - actions: [ - { - title: _("Create partition"), - action: () => format_dialog(client, block.path, start, size, - enable_dos_extended), - } - ], - }); - } - - function make_extended_partition_page(parent, partition) { - const page = new_page({ - parent, - name: _("Extended partition"), - columns: [ - null, - null, - fmt_size(partition.size), - ], - actions: [ - { title: _("Delete"), action: () => delete_partition(partition.block, page), danger: true }, - ] - }); - process_partitions(page, partition.partitions, false); - } - - function process_partitions(parent, partitions, enable_dos_extended) { - let i, p; - for (i = 0; i < partitions.length; i++) { - p = partitions[i]; - if (p.type == 'free') - make_free_space_page(parent, p.start, p.size, enable_dos_extended); - else if (p.type == 'container') - make_extended_partition_page(parent, p); - else { - const container = make_partition_container(null, p.block); - make_block_page(parent, p.block, container); - } - } - } - - process_partitions(parent, get_partitions(client, block), - block_ptable.Type == 'dos'); -} - -export function make_block_page(parent, block, container) { +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); @@ -128,12 +47,12 @@ export function make_block_page(parent, block, container) { block_stratis_stopped_pool); if (client.blocks_ptable[block.path]) { - make_partition_table_page(parent, block, container); + make_partition_table_page(parent, block, card); return; } if (legacy_vdo) { - make_legacy_vdo_page(parent, legacy_vdo, block, container); + make_legacy_vdo_page(parent, legacy_vdo, block, card); return; } @@ -144,14 +63,14 @@ export function make_block_page(parent, block, container) { } if (is_crypto) - container = make_encryption_container(container, block); + card = make_encryption_card(card, block); if (!content_block) { // assert(is_crypto); if (fstab_config.length > 0) { - make_filesystem_page(parent, block, null, fstab_config, container); + make_filesystem_page(parent, block, null, fstab_config, card); } else { - make_locked_encrypted_data_page(parent, block, container); + make_locked_encrypted_data_page(parent, block, card); } return; } @@ -161,20 +80,20 @@ export function make_block_page(parent, block, container) { const block_swap = client.blocks_swap[content_block.path]; if (is_filesystem) { - make_filesystem_page(parent, block, content_block, fstab_config, container); + make_filesystem_page(parent, block, content_block, fstab_config, card); } else if ((content_block.IdUsage == "raid" && content_block.IdType == "LVM2_member") || (block_pvol && client.vgroups[block_pvol.VolumeGroup])) { - make_lvm2_physical_volume_page(parent, block, content_block, container); + make_lvm2_physical_volume_page(parent, block, content_block, card); } else if (is_stratis) { - make_stratis_blockdev_page(parent, block, content_block, container); + make_stratis_blockdev_page(parent, block, content_block, card); } else if ((content_block.IdUsage == "raid") || (client.mdraids[content_block.MDRaidMember])) { - make_mdraid_disk_page(parent, block, content_block, container); + make_mdraid_disk_page(parent, block, content_block, card); } else if (block_swap || (content_block && content_block.IdUsage == "other" && content_block.IdType == "swap")) { - make_swap_page(parent, block, content_block, container); + make_swap_page(parent, block, content_block, card); } else if (client.blocks_available[content_block.path]) { - make_unformatted_data_page(parent, block, content_block, container); + make_unformatted_data_page(parent, block, content_block, card); } else { - make_unrecognized_data_page(parent, block, content_block, container); + make_unrecognized_data_page(parent, block, content_block, card); } } diff --git a/pkg/storaged/block/other.jsx b/pkg/storaged/block/other.jsx index 7869021f5c5b..d608b774bf42 100644 --- a/pkg/storaged/block/other.jsx +++ b/pkg/storaged/block/other.jsx @@ -23,9 +23,8 @@ 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 { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; -import { ActionButtons, new_container, block_location } from "../pages.jsx"; +import { StorageCard, new_card, block_location } from "../pages.jsx"; import { block_name } from "../utils.js"; import { partitionable_block_actions } from "../partitions/actions.jsx"; @@ -34,20 +33,21 @@ import { make_block_page } from "../block/create-pages.jsx"; const _ = cockpit.gettext; export function make_other_page(parent, block) { - const container = new_container({ - parent: null, + const card = new_card({ + title: _("Block device"), + next: null, page_location: ["other", block_location(block)], - component: OtherContainer, + component: OtherCard, props: { block }, actions: partitionable_block_actions(block), }); - make_block_page(parent, block, container); + make_block_page(parent, block, card); } -const OtherContainer = ({ container, block }) => { +const OtherCard = ({ card, block }) => { return ( - }> + { - + ); }; diff --git a/pkg/storaged/block/resize.jsx b/pkg/storaged/block/resize.jsx index edacb9da1780..73bd7dbae486 100644 --- a/pkg/storaged/block/resize.jsx +++ b/pkg/storaged/block/resize.jsx @@ -151,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. @@ -313,7 +313,7 @@ export function free_space_after_part(client, part) { for (const p of parts) { if (p.type == "free" && p.start == part.Offset + part.Size) return p.size; - if (p.type == "container") { + if (p.type == "card") { const s = find_it(p.partitions); if (s) return s; diff --git a/pkg/storaged/block/unformatted-data.jsx b/pkg/storaged/block/unformatted-data.jsx index e5a146d18d06..7331e4f8a92e 100644 --- a/pkg/storaged/block/unformatted-data.jsx +++ b/pkg/storaged/block/unformatted-data.jsx @@ -18,49 +18,27 @@ */ import cockpit from "cockpit"; -import React from "react"; import client from "../client"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; - import { - PageContainerStackItems, - new_page, block_location, ActionButtons, page_type, + StorageCard, new_page, new_card, } from "../pages.jsx"; -import { SCard } from "../utils/card.jsx"; import { format_dialog } from "./format-dialog.jsx"; -import { block_name } from "../utils.js"; import { std_lock_action } from "../crypto/actions.jsx"; -import { StorageSize } from "../storage-controls.jsx"; const _ = cockpit.gettext; -export function make_unformatted_data_page(parent, backing_block, content_block, container) { - new_page({ - location: [block_location(backing_block)], - parent, - container, - name: block_name(backing_block), - columns: [ - _("Unformatted data"), - null, - , - ], - component: UnformattedDataPage, - props: { backing_block, content_block }, +export function make_unformatted_data_page(parent, backing_block, content_block, next_card) { + const unformatted_card = new_card({ + title: _("Unformatted data"), + next: next_card, + page_block: backing_block, + component: StorageCard, actions: [ std_lock_action(backing_block, content_block), { title: _("Format"), action: () => format_dialog(client, backing_block.path) }, ] }); -} -export const UnformattedDataPage = ({ page, backing_block, content_block }) => { - return ( - - - } /> - - - ); -}; + new_page(parent, unformatted_card); +} diff --git a/pkg/storaged/block/unrecognized-data.jsx b/pkg/storaged/block/unrecognized-data.jsx index b5fcfa976caf..17d8d57a031c 100644 --- a/pkg/storaged/block/unrecognized-data.jsx +++ b/pkg/storaged/block/unrecognized-data.jsx @@ -22,55 +22,40 @@ import React from "react"; import client from "../client"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; -import { - PageContainerStackItems, - new_page, block_location, ActionButtons, page_type, -} from "../pages.jsx"; -import { SCard } from "../utils/card.jsx"; +import { StorageCard, new_page, new_card } from "../pages.jsx"; import { SDesc } from "../utils/desc.jsx"; import { format_dialog } from "./format-dialog.jsx"; -import { block_name } from "../utils.js"; import { std_lock_action } from "../crypto/actions.jsx"; -import { StorageSize } from "../storage-controls.jsx"; const _ = cockpit.gettext; -export function make_unrecognized_data_page(parent, backing_block, content_block, container) { - new_page({ - location: [block_location(backing_block)], - parent, - container, - name: block_name(backing_block), - columns: [ - _("Unrecognized data"), - null, - , - ], - component: UnrecognizedDataPage, +export function make_unrecognized_data_page(parent, backing_block, content_block, next_card) { + const unrec_card = new_card({ + title: _("Unrecognized data"), + next: next_card, + page_block: backing_block, + 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 }, ] }); + + new_page(parent, unrec_card); } -export const UnrecognizedDataPage = ({ page, backing_block, content_block }) => { +export const UnrecognizedDataCard = ({ card, backing_block, content_block }) => { return ( - - - }> - - - - - - - - - - ); + + + + + + + + + ); }; diff --git a/pkg/storaged/crypto/encryption.jsx b/pkg/storaged/crypto/encryption.jsx index 871466958540..1ad6dc486032 100644 --- a/pkg/storaged/crypto/encryption.jsx +++ b/pkg/storaged/crypto/encryption.jsx @@ -27,11 +27,10 @@ import { useObject, useEvent } from "hooks"; import * as python from "python.js"; import * as timeformat from "timeformat.js"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; import { dialog_open, TextInput, PassInput } from "../dialog.jsx"; import { block_name, encode_filename, decode_filename, parse_options, unparse_options, extract_option, edit_crypto_config } from "../utils.js"; -import { new_container } from "../pages.jsx"; +import { StorageCard, new_card } from "../pages.jsx"; import luksmeta_monitor_hack_py from "./luksmeta-monitor-hack.py"; import { is_mounted } from "../filesystem/utils.jsx"; import { StorageLink } from "../storage-controls.jsx"; @@ -39,11 +38,12 @@ import { CryptoKeyslots } from "./keyslots.jsx"; const _ = cockpit.gettext; -export function make_encryption_container(parent, block) { - return new_container({ - parent, - type_format: _("$0 (encrypted)"), // XXX - icon? - component: EncryptionContainer, +export function make_encryption_card(next, block) { + return new_card({ + title: _("Encryption"), + next, + type_extra: _("encrypted"), + component: EncryptionCard, props: { block }, }); } @@ -128,7 +128,7 @@ function monitor_mtime(path) { return self; } -const EncryptionContainer = ({ container, block }) => { +const EncryptionCard = ({ card, block }) => { const luks_info = useObject(() => monitor_luks(block), m => m.stop(), [block]); @@ -220,7 +220,7 @@ const EncryptionContainer = ({ container, block }) => { const options = option_parts.join(", "); return ( - + @@ -240,5 +240,5 @@ const EncryptionContainer = ({ container, block }) => { - ); + ); }; diff --git a/pkg/storaged/crypto/locked-encrypted-data.jsx b/pkg/storaged/crypto/locked-encrypted-data.jsx index a233b5924393..5bb0bf0d8473 100644 --- a/pkg/storaged/crypto/locked-encrypted-data.jsx +++ b/pkg/storaged/crypto/locked-encrypted-data.jsx @@ -18,46 +18,26 @@ */ import cockpit from "cockpit"; -import React from "react"; import client from "../client"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; - -import { SCard } from "../utils/card.jsx"; -import { PageContainerStackItems, new_page, block_location, ActionButtons } from "../pages.jsx"; +import { StorageCard, new_page, new_card } from "../pages.jsx"; import { format_dialog } from "../block/format-dialog.jsx"; -import { block_name } from "../utils.js"; import { unlock } from "./actions.jsx"; -import { StorageSize } from "../storage-controls.jsx"; const _ = cockpit.gettext; -export function make_locked_encrypted_data_page(parent, block, container) { - new_page({ - location: [block_location(block)], - parent, - container, - name: block_name(block), - columns: [ - _("Locked encrypted data"), - null, - , - ], - component: LockedEncryptedDataPage, +export function make_locked_encrypted_data_page(parent, block, next_card) { + const locked_card = new_card({ + title: _("Locked data"), + next: next_card, + page_block: block, + component: StorageCard, props: { block }, actions: [ { title: _("Unlock"), action: () => unlock(block) }, { title: _("Format"), action: () => format_dialog(client, block.path), danger: true }, ] }); -} -export const LockedEncryptedDataPage = ({ page, block }) => { - return ( - - - } /> - - - ); -}; + new_page(parent, locked_card); +} diff --git a/pkg/storaged/dialog.jsx b/pkg/storaged/dialog.jsx index 6913121d270e..c1a262adf156 100644 --- a/pkg/storaged/dialog.jsx +++ b/pkg/storaged/dialog.jsx @@ -1106,8 +1106,6 @@ export const BlockingMessage = (usage) => { "stratis-pool-member": _("member of Stratis pool") }; - console.log("U", usage); - const rows = []; usage.forEach(use => { if (use.blocking && use.block) { diff --git a/pkg/storaged/drive/drive.jsx b/pkg/storaged/drive/drive.jsx index 4079cafcd255..35b6e8afa373 100644 --- a/pkg/storaged/drive/drive.jsx +++ b/pkg/storaged/drive/drive.jsx @@ -25,13 +25,12 @@ import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index. import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; -import { ActionButtons, block_location, new_container, new_page } from "../pages.jsx"; +import { StorageCard, block_location, new_card, new_page } from "../pages.jsx"; import { block_name, drive_name, format_temperature, fmt_size_long } from "../utils.js"; import { format_disk } from "../partitions/format-dialog.jsx"; -import { make_block_pages, make_block_page } from "../block/create-pages.jsx"; +import { make_block_page } from "../block/create-pages.jsx"; const _ = cockpit.gettext; @@ -52,13 +51,6 @@ export function partitionable_block_actions(block, tag) { ]; } -export function make_partitionable_block_pages(parent, block) { - const is_formatted = !client.blocks_available[block.path]; - - if (is_formatted) - make_block_pages(parent, block, null); -} - export function make_drive_page(parent, drive) { let block = client.drives_block[drive.path]; @@ -73,34 +65,26 @@ export function make_drive_page(parent, drive) { if (!block) return; - if (block.Size > 0) { - const container = new_container({ - parent: null, - page_location: ["drive", block_location(block)], - id_extra: drive_name(drive), - component: DriveContainer, - props: { drive }, - actions: partitionable_block_actions(block), - }); + const card = new_card({ + title: _("Drive"), + next: null, + page_location: ["drive", block_location(block)], + page_name: block_name(block), + for_summary: true, + id_extra: drive_name(drive), + component: DriveCard, + props: { drive }, + actions: block.Size > 0 ? partitionable_block_actions(block) : [], + }); - make_block_page(parent, block, container); + if (block.Size > 0) { + make_block_page(parent, block, card); } else { - new_page({ - parent, - name: block_name(block) + " - " + drive_name(drive), - location: ["drive", block_location(block)], - columns: [ - null, - null, - null, - ], - component: DriveContainer, - props: { drive } - }); + new_page(parent, card); } } -const DriveContainer = ({ container, page, drive }) => { +const DriveCard = ({ card, page, drive }) => { const block = client.drives_block[drive.path]; const drive_ata = client.drives_ata[drive.path]; const multipath_blocks = client.drives_multipath_blocks[drive.path]; @@ -123,7 +107,7 @@ const DriveContainer = ({ container, page, drive }) => { } return ( - }> + @@ -145,6 +129,6 @@ const DriveContainer = ({ container, page, drive }) => { } - + ); }; diff --git a/pkg/storaged/filesystem/filesystem.jsx b/pkg/storaged/filesystem/filesystem.jsx index 0c5a5ddbde6a..a2553cb46cd4 100644 --- a/pkg/storaged/filesystem/filesystem.jsx +++ b/pkg/storaged/filesystem/filesystem.jsx @@ -22,11 +22,9 @@ import React from "react"; import client from "../client"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; import { useEvent } from "hooks"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; import { block_name, fmt_size, validate_fsys_label @@ -36,8 +34,7 @@ import { } from "../dialog.jsx"; import { StorageLink, StorageUsageBar, StorageSize } from "../storage-controls.jsx"; import { - PageContainerStackItems, - new_page, block_location, ActionButtons, page_type, + StorageCard, new_page, new_card, block_location, } from "../pages.jsx"; import { format_dialog } from "../block/format-dialog.jsx"; @@ -82,7 +79,7 @@ const MountPointUsageBar = ({ mount_point, block, short }) => { return fmt_size(block.Size); }; -export function make_filesystem_page(parent, backing_block, content_block, fstab_config, container) { +export function make_filesystem_page(parent, backing_block, content_block, fstab_config, next_card) { const [, mount_point] = fstab_config; const name = block_name(backing_block); const mismount_warning = check_mismounted_fsys(backing_block, content_block, fstab_config); @@ -96,18 +93,15 @@ export function make_filesystem_page(parent, backing_block, content_block, fstab else mp_text = _("(not mounted)"); - new_page({ - location: [block_location(backing_block)], - parent, - container, - name, - columns: [ - content_block ? cockpit.format(_("$0 filesystem"), content_block.IdType) : _("Filesystem"), - mp_text, - , - ], + const filesystem_card = new_card({ + title: content_block ? cockpit.format(_("$0 filesystem"), content_block.IdType) : _("Filesystem"), + location: mp_text, + next: next_card, + page_location: [block_location(backing_block)], + page_name: name, + page_size: , has_warning: !!mismount_warning, - component: FilesystemPage, + component: FilesystemCard, props: { backing_block, content_block, fstab_config, mismount_warning }, actions: [ content_block && mounted @@ -116,10 +110,12 @@ export function make_filesystem_page(parent, backing_block, content_block, fstab { title: _("Format"), action: () => format_dialog(client, backing_block.path), danger: true }, ] }); + + new_page(parent, filesystem_card); } -export const FilesystemPage = ({ - page, backing_block, content_block, fstab_config, mismount_warning +export const FilesystemCard = ({ + card, backing_block, content_block, fstab_config, mismount_warning }) => { function rename_dialog() { // assert(content_block) @@ -147,37 +143,32 @@ export const FilesystemPage = ({ const mounted = content_block && is_mounted(client, content_block); return ( - - - }> - - - - {_("edit")} - } /> - - - - { mounted && - - - - } - - - { mismount_warning && - - - + + + + + {_("edit")} + } /> + + + + { mounted && + + + } - - - - ); + + + { mismount_warning && + + + + } + ); }; diff --git a/pkg/storaged/filesystem/mismounting.jsx b/pkg/storaged/filesystem/mismounting.jsx index 2f7001f1e111..4a78dc3551ee 100644 --- a/pkg/storaged/filesystem/mismounting.jsx +++ b/pkg/storaged/filesystem/mismounting.jsx @@ -208,7 +208,7 @@ export const MismountAlert = ({ warning, fstab_config, forced_options, backing_b fix_config_text = cockpit.format(_("Mount automatically on $0 on boot"), other); fix_mount_text = _("Unmount now"); } else if (type == "locked-on-boot-mount") { - text = _("The filesystem is configured to be automatically mounted on boot but its encryption container will not be unlocked at that time."); + text = _("The filesystem is configured to be automatically mounted on boot but its encryption card will not be unlocked at that time."); fix_config_text = _("Do not mount automatically on boot"); fix_mount_text = _("Unlock automatically on boot"); } diff --git a/pkg/storaged/filesystem/mounting-dialog.jsx b/pkg/storaged/filesystem/mounting-dialog.jsx index 8e8aeffc1211..db4f03f2b49d 100644 --- a/pkg/storaged/filesystem/mounting-dialog.jsx +++ b/pkg/storaged/filesystem/mounting-dialog.jsx @@ -568,7 +568,7 @@ export class FilesystemTab extends React.Component { fix_config_text = cockpit.format(_("Mount automatically on $0 on boot"), other); fix_mount_text = _("Unmount now"); } else if (type == "locked-on-boot-mount") { - text = _("The filesystem is configured to be automatically mounted on boot but its encryption container will not be unlocked at that time."); + text = _("The filesystem is configured to be automatically mounted on boot but its encryption card will not be unlocked at that time."); fix_config_text = _("Do not mount automatically on boot"); fix_mount_text = _("Unlock automatically on boot"); } diff --git a/pkg/storaged/iscsi/session.jsx b/pkg/storaged/iscsi/session.jsx index 906e18ad3d53..754fbd0d7491 100644 --- a/pkg/storaged/iscsi/session.jsx +++ b/pkg/storaged/iscsi/session.jsx @@ -22,12 +22,10 @@ import React from "react"; import client from "../client"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; -import { new_page, new_container, PageTable, PageContainerStackItems, page_type, ActionButtons } from "../pages.jsx"; +import { new_page, new_card, PageTable, StorageCard } from "../pages.jsx"; import { make_drive_page } from "../drive/drive.jsx"; @@ -40,9 +38,12 @@ async function disconnect(session, goto_page) { } export function make_iscsi_session_page(parent, session) { - const c = new_container({ - parent: null, - component: ISCSISessionContainer, + const session_card = new_card({ + title: _("iSCSI portal"), + next: null, + page_location: ["iscsi", session.data.target_name], + page_name: session.data.target_name, + component: ISCSISessionCard, props: { session }, actions: [ { @@ -53,50 +54,40 @@ export function make_iscsi_session_page(parent, session) { ] }); - const p = new_page({ - location: ["iscsi", session.data.target_name], - parent, - container: c, - name: session.data.target_name, - columns: [ - _("iSCSI drives"), - null, - null, - ], - component: ISCSISessionPage, + const drives_card = new_card({ + title: _("iSCSI drives"), + next: session_card, + component: ISCSIDrivesCard, props: { session }, }); + const p = new_page(parent, drives_card); + if (client.iscsi_sessions_drives[session.path]) client.iscsi_sessions_drives[session.path].forEach(d => make_drive_page(p, d)); } -const ISCSISessionPage = ({ page, session }) => { +const ISCSIDrivesCard = ({ card, session }) => { return ( - - - - - - - - - - + + + + + ); }; -const ISCSISessionContainer = ({ container, session }) => { +const ISCSISessionCard = ({ card, session }) => { return ( - }> + - + ); }; diff --git a/pkg/storaged/legacy-vdo/legacy-vdo.jsx b/pkg/storaged/legacy-vdo/legacy-vdo.jsx index c7613f7bc9f9..1a6ad5313d3b 100644 --- a/pkg/storaged/legacy-vdo/legacy-vdo.jsx +++ b/pkg/storaged/legacy-vdo/legacy-vdo.jsx @@ -32,8 +32,7 @@ import { } from "../dialog.jsx"; import { StorageButton, StorageOnOff, StorageSize } from "../storage-controls.jsx"; -import { SCard } from "../utils/card.jsx"; -import { new_page, new_container, PageContainerStackItems } from "../pages.jsx"; +import { StorageCard, new_page, new_card } from "../pages.jsx"; import { make_block_page } from "../block/create-pages.jsx"; import inotify_py from "inotify.py"; @@ -41,31 +40,123 @@ import vdo_monitor_py from "./vdo-monitor.py"; const _ = cockpit.gettext; -export function make_legacy_vdo_page(parent, vdo, backing_block, container) { +export function make_legacy_vdo_page(parent, vdo, backing_block, next_card) { const block = client.slashdevs_block[vdo.dev]; - if (block) { - const vdo_container = new_container({ - parent: container, - component: VDODetails, - props: { client, vdo } + function stop() { + const usage = get_active_usage(client, block ? block.path : "/", _("stop")); + + if (usage.Blocking) { + dialog_open({ + Title: cockpit.format(_("$0 is in use"), vdo.name), + Body: BlockingMessage(usage), + }); + return; + } + + if (usage.Teardown) { + dialog_open({ + Title: cockpit.format(_("Confirm stopping of $0"), + vdo.name), + Teardown: TeardownMessage(usage), + Action: { + Title: _("Stop"), + action: function () { + return teardown_active_usage(client, usage) + .then(function () { + return vdo.stop(); + }); + } + }, + Inits: [ + init_active_usage_processes(client, usage) + ] + }); + } else { + return vdo.stop(); + } + } + + function delete_() { + const usage = get_active_usage(client, block ? block.path : "/", _("delete")); + + if (usage.Blocking) { + dialog_open({ + Title: cockpit.format(_("$0 is in use"), vdo.name), + Body: BlockingMessage(usage), + }); + return; + } + + function wipe_with_teardown(block) { + return block.Format("empty", { 'tear-down': { t: 'b', v: true } }).then(reload_systemd); + } + + function teardown_configs() { + if (block) { + return wipe_with_teardown(block); + } else { + return vdo.start() + .then(function () { + return client.wait_for(() => client.slashdevs_block[vdo.dev]) + .then(function (block) { + return wipe_with_teardown(block) + .catch(error => { + // systemd might have mounted it, let's try unmounting + const block_fsys = client.blocks_fsys[block.path]; + if (block_fsys) { + return block_fsys.Unmount({}) + .then(() => wipe_with_teardown(block)); + } else { + return Promise.reject(error); + } + }); + }); + }); + } + } + + dialog_open({ + Title: cockpit.format(_("Permanently delete $0?"), vdo.name), + Body: TeardownMessage(usage), + Action: { + Title: _("Delete"), + Danger: _("Deleting erases all data on a VDO device."), + action: function () { + return (teardown_active_usage(client, usage) + .then(teardown_configs) + .then(function () { + return vdo.remove(); + })); + } + }, + Inits: [ + init_active_usage_processes(client, usage) + ] }); + } + + const vdo_card = new_card({ + title: cockpit.format(_("VDO device $0"), vdo.name), + next: next_card, + page_location: ["vdo", vdo.name], + page_name: block_name(backing_block), + page_size: , + component: VDODetails, + props: { client, vdo }, + actions: [ + (block + ? { title: _("Stop"), action: stop } + : { title: _("Start"), action: () => vdo.start() } + ), + { title: _("Delete"), action: delete_, danger: true } + ], + }); - make_block_page(parent, block, vdo_container); + if (block) { + make_block_page(parent, block, vdo_card); } else { - new_page({ - location: ["vdo", vdo.name], - parent, - container, - name: block_name(backing_block), - columns: [ - _("VDO device"), - null, - , - ], - component: VDODetails, - props: { client, vdo } - }); + new_page(parent, vdo_card); } } @@ -130,18 +221,13 @@ class VDODetails extends React.Component { if (vdo.broken) { return ( - - - - - {_("Remove device")} - } /> - - - { this.props.page && } - + + + {_("Remove device")} + } /> + ); } @@ -159,99 +245,6 @@ class VDODetails extends React.Component { ); - function stop() { - const usage = get_active_usage(client, block ? block.path : "/", _("stop")); - - if (usage.Blocking) { - dialog_open({ - Title: cockpit.format(_("$0 is in use"), vdo.name), - Body: BlockingMessage(usage), - }); - return; - } - - if (usage.Teardown) { - dialog_open({ - Title: cockpit.format(_("Confirm stopping of $0"), - vdo.name), - Teardown: TeardownMessage(usage), - Action: { - Title: _("Stop"), - action: function () { - return teardown_active_usage(client, usage) - .then(function () { - return vdo.stop(); - }); - } - }, - Inits: [ - init_active_usage_processes(client, usage) - ] - }); - } else { - return vdo.stop(); - } - } - - function delete_() { - const usage = get_active_usage(client, block ? block.path : "/", _("delete")); - - if (usage.Blocking) { - dialog_open({ - Title: cockpit.format(_("$0 is in use"), vdo.name), - Body: BlockingMessage(usage), - }); - return; - } - - function wipe_with_teardown(block) { - return block.Format("empty", { 'tear-down': { t: 'b', v: true } }).then(reload_systemd); - } - - function teardown_configs() { - if (block) { - return wipe_with_teardown(block); - } else { - return vdo.start() - .then(function () { - return client.wait_for(() => client.slashdevs_block[vdo.dev]) - .then(function (block) { - return wipe_with_teardown(block) - .catch(error => { - // systemd might have mounted it, let's try unmounting - const block_fsys = client.blocks_fsys[block.path]; - if (block_fsys) { - return block_fsys.Unmount({}) - .then(() => wipe_with_teardown(block)); - } else { - return Promise.reject(error); - } - }); - }); - }); - } - } - - dialog_open({ - Title: cockpit.format(_("Permanently delete $0?"), vdo.name), - Body: TeardownMessage(usage), - Action: { - Title: _("Delete"), - Danger: _("Deleting erases all data on a VDO device."), - action: function () { - return (teardown_active_usage(client, usage) - .then(teardown_configs) - .then(function () { - return vdo.remove(); - })); - } - }, - Inits: [ - init_active_usage_processes(client, usage) - ] - }); - } - function grow_logical() { dialog_open({ Title: cockpit.format(_("Grow logical size of $0"), vdo.name), @@ -293,15 +286,7 @@ class VDODetails extends React.Component { {alert} - - { block - ? {_("Stop")} - : {_("Start")} - } - { "\n" } - {_("Delete")} - }> + @@ -361,9 +346,8 @@ class VDODetails extends React.Component { - + - { this.props.page && } ); diff --git a/pkg/storaged/lvm2/block-logical-volume.jsx b/pkg/storaged/lvm2/block-logical-volume.jsx index e35480556466..0db3c87330a0 100644 --- a/pkg/storaged/lvm2/block-logical-volume.jsx +++ b/pkg/storaged/lvm2/block-logical-volume.jsx @@ -29,10 +29,9 @@ import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/ind import { StorageButton, StorageLink } from "../storage-controls.jsx"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; import { check_unused_space, get_resize_info, grow_dialog, shrink_dialog } from "../block/resize.jsx"; -import { new_container, navigate_to_new_page_location, ActionButtons, navigate_away_from_page } from "../pages.jsx"; +import { StorageCard, new_card, navigate_to_new_page_location, navigate_away_from_page } from "../pages.jsx"; import { block_name, fmt_size, get_active_usage, teardown_active_usage, reload_systemd } from "../utils.js"; import { dialog_open, TextInput, SelectSpaces, BlockingMessage, TeardownMessage, @@ -173,7 +172,7 @@ function deactivate(lvol, block) { }); } -export function make_lvm2_logical_volume_container(parent, vgroup, lvol, block) { +export function make_lvm2_logical_volume_card(next, vgroup, lvol, block) { const unused_space_warning = check_unused_space(block.path); const unused_space = !!unused_space_warning; const status_code = client.lvols_status[lvol.path]; @@ -195,14 +194,15 @@ export function make_lvm2_logical_volume_container(parent, vgroup, lvol, block) if (status_code == "degraded" || status_code == "degraded-maybe-partial") repair_action = { title: _("Repair"), action: () => repair(lvol) }; - const cont = new_container({ - parent, - page_name: lvol.Name, + const cont = new_card({ + title: _("LVM2 logical volume"), + next, page_location: ["vg", vgroup.Name, lvol.Name], - stored_on_format: _("LVM2 logical volume in $0"), + page_name: lvol.Name, + for_summary: true, has_warning: !!unused_space_warning || !!repair_action, has_danger: status_code == "partial", - component: LVM2LogicalVolumeContainer, + component: LVM2LogicalVolumeCard, props: { vgroup, lvol, block, unused_space_warning, resize_info: info }, actions: [ (!unused_space && @@ -233,7 +233,7 @@ export function make_lvm2_logical_volume_container(parent, vgroup, lvol, block) return cont; } -const LVM2LogicalVolumeContainer = ({ container, vgroup, lvol, block, unused_space_warning, resize_info }) => { +const LVM2LogicalVolumeCard = ({ card, vgroup, lvol, block, unused_space_warning, resize_info }) => { const unused_space = !!unused_space_warning; function rename() { @@ -247,7 +247,7 @@ const LVM2LogicalVolumeContainer = ({ container, vgroup, lvol, block, unused_spa Title: _("Rename"), action: async function (vals) { await lvol.Rename(vals.name, { }); - navigate_to_new_page_location(container.page, ["vg", vgroup.Name, vals.name]); + navigate_to_new_page_location(card.page, ["vg", vgroup.Name, vals.name]); } } }); @@ -273,7 +273,7 @@ const LVM2LogicalVolumeContainer = ({ container, vgroup, lvol, block, unused_spa const layout = lvol.Layout; return ( - }> + } - ); + ); }; export const StructureDescription = ({ client, lvol }) => { diff --git a/pkg/storaged/lvm2/inactive-logical-volume.jsx b/pkg/storaged/lvm2/inactive-logical-volume.jsx index ed8b5b4f4a7f..a24e6bbd5313 100644 --- a/pkg/storaged/lvm2/inactive-logical-volume.jsx +++ b/pkg/storaged/lvm2/inactive-logical-volume.jsx @@ -19,12 +19,10 @@ import cockpit from "cockpit"; import React from "react"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { - new_page, ActionButtons, page_type, PageContainerStackItems + StorageCard, new_page, new_card } from "../pages.jsx"; -import { SCard } from "../utils/card.jsx"; import { StorageSize } from "../storage-controls.jsx"; import { lvol_delete } from "./block-logical-volume.jsx"; @@ -32,33 +30,20 @@ import { lvm2_create_snapshot_action } from "./volume-group.jsx"; const _ = cockpit.gettext; -export function make_lvm2_inactive_logical_volume_page(parent, vgroup, lvol, container) { - const page = new_page({ - location: ["vg", vgroup.Name, lvol.Name], - parent, - container, - name: lvol.Name, - columns: [ - _("Inactive logical volume"), - null, - , - ], - component: LVM2InactiveLogicalVolumePage, - props: { vgroup, lvol }, +export function make_lvm2_inactive_logical_volume_page(parent, vgroup, lvol, next_card) { + const inactive_card = new_card({ + title: _("Inactive logical volume"), + next: next_card, + page_location: ["vg", vgroup.Name, lvol.Name], + page_name: lvol.Name, + page_size: , + component: StorageCard, actions: [ { title: _("Activate"), action: () => lvol.Activate({}) }, lvm2_create_snapshot_action(lvol), { title: _("Delete"), action: () => lvol_delete(lvol, page), danger: true }, ] }); -} -export const LVM2InactiveLogicalVolumePage = ({ page, vgroup, lvol }) => { - return ( - - - } /> - - - ); -}; + const page = new_page(parent, inactive_card); +} diff --git a/pkg/storaged/lvm2/physical-volume.jsx b/pkg/storaged/lvm2/physical-volume.jsx index 33f4709b402e..9f885bd67e2e 100644 --- a/pkg/storaged/lvm2/physical-volume.jsx +++ b/pkg/storaged/lvm2/physical-volume.jsx @@ -23,14 +23,12 @@ import client from "../client"; import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; import { - PageContainerStackItems, - new_page, block_location, ActionButtons, page_type, + StorageCard, + new_page, new_card, block_location, register_crossref, } from "../pages.jsx"; import { format_dialog } from "../block/format-dialog.jsx"; @@ -40,26 +38,20 @@ import { StorageSize, StorageUsageBar } from "../storage-controls.jsx"; const _ = cockpit.gettext; -/* XXX - Unlike for make_filesystem_page, "content_block" is never null. - */ - -export function make_lvm2_physical_volume_page(parent, backing_block, content_block, container) { +export function make_lvm2_physical_volume_page(parent, backing_block, content_block, next_card) { const block_pvol = client.blocks_pvol[content_block.path]; const vgroup = block_pvol && client.vgroups[block_pvol.VolumeGroup]; - const p = new_page({ - location: [block_location(backing_block)], - parent, - container, - name: block_name(backing_block), - columns: [ - _("LVM2 physical volume"), - vgroup ? vgroup.Name : null, - (block_pvol - ? - : ), - ], - component: LVM2PhysicalVolumePage, + const pv_card = new_card({ + title: _("LVM2 physical volume"), + location: vgroup ? vgroup.Name : null, + next: next_card, + page_location: [block_location(backing_block)], + page_name: block_name(backing_block), + page_size: (block_pvol + ? + : ), + component: LVM2PhysicalVolumeCard, props: { backing_block, content_block }, actions: [ std_lock_action(backing_block, content_block), @@ -67,6 +59,8 @@ export function make_lvm2_physical_volume_page(parent, backing_block, content_bl ] }); + const p = new_page(parent, pv_card); + function pvol_remove() { return vgroup.RemoveDevice(block_pvol.path, true, {}); } @@ -110,42 +104,36 @@ export function make_lvm2_physical_volume_page(parent, backing_block, content_bl }, ], size: , - extra: content_block.IdUUID, }); } } -export const LVM2PhysicalVolumePage = ({ page, backing_block, content_block }) => { +export const LVM2PhysicalVolumeCard = ({ card, backing_block, content_block }) => { const block_pvol = client.blocks_pvol[content_block.path]; const vgroup = block_pvol && client.vgroups[block_pvol.VolumeGroup]; return ( - - - }> - - - - {vgroup - ? - : "-" - } - - - { block_pvol && - - - - } - - - - - - ); + + + + + {vgroup + ? + : "-" + } + + + { block_pvol && + + + + } + + + ); }; diff --git a/pkg/storaged/lvm2/thin-pool-logical-volume.jsx b/pkg/storaged/lvm2/thin-pool-logical-volume.jsx index e5a8091cc075..0243f8a97ac4 100644 --- a/pkg/storaged/lvm2/thin-pool-logical-volume.jsx +++ b/pkg/storaged/lvm2/thin-pool-logical-volume.jsx @@ -23,18 +23,15 @@ 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 { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { - PageContainerStackItems, PageTable, - new_page, ActionButtons, page_type, new_container, + StorageCard, PageTable, new_page, new_card, } from "../pages.jsx"; import { fmt_size, validate_lvm2_name } from "../utils.js"; import { dialog_open, TextInput, SizeSlider, } from "../dialog.jsx"; import { StorageLink, StorageSize } from "../storage-controls.jsx"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; import { grow_dialog } from "../block/resize.jsx"; @@ -71,19 +68,15 @@ export function make_lvm2_thin_pool_logical_volume_page(parent, vgroup, lvol) { }); } - const pool_container = make_lvm2_thin_pool_container(null, vgroup, lvol); + const pool_card = make_lvm2_thin_pool_card(null, vgroup, lvol); - const p = new_page({ - location: ["vg", vgroup.Name, lvol.Name], - parent, - container: pool_container, - name: lvol.Name, - columns: [ - _("Thinly provisioned LVM2 logical volumes"), - null, - , - ], - component: LVM2ThinPoolLogicalVolumePage, + const thin_vols_card = new_card({ + title: _("Thinly provisioned LVM2 logical volumes"), + next: pool_card, + page_location: ["vg", vgroup.Name, lvol.Name], + page_name: lvol.Name, + page_size: , + component: LVM2ThinPoolLogicalVolumeCard, props: { vgroup, lvol }, actions: [ { @@ -94,12 +87,14 @@ export function make_lvm2_thin_pool_logical_volume_page(parent, vgroup, lvol) { ] }); + const p = new_page(parent, thin_vols_card); + client.lvols_pool_members[lvol.path].forEach(member_lvol => { make_lvm2_logical_volume_page(p, vgroup, member_lvol); }); } -function make_lvm2_thin_pool_container(parent, vgroup, lvol) { +function make_lvm2_thin_pool_card(next, vgroup, lvol) { let grow_excuse = null; if (vgroup.FreeSize == 0) { grow_excuse = ( @@ -111,9 +106,10 @@ function make_lvm2_thin_pool_container(parent, vgroup, lvol) { ); } - const c = new_container({ - parent, - component: LVM2ThinPoolContainer, + const c = new_card({ + title: _("Pool for thinly provisioned LVM2 logical volumes"), + next, + component: LVM2ThinPoolCard, props: { vgroup, lvol }, actions: [ { @@ -135,26 +131,20 @@ function perc(ratio) { return (ratio * 100).toFixed(0) + "%"; } -export const LVM2ThinPoolLogicalVolumePage = ({ page, vgroup, lvol }) => { +export const LVM2ThinPoolLogicalVolumeCard = ({ card, vgroup, lvol }) => { return ( - - - }> - - - - - - - ); + + + + + ); }; -export const LVM2ThinPoolContainer = ({ container, vgroup, lvol }) => { +export const LVM2ThinPoolCard = ({ card, vgroup, lvol }) => { return ( - }> + { - ); + ); }; diff --git a/pkg/storaged/lvm2/unsupported-logical-volume.jsx b/pkg/storaged/lvm2/unsupported-logical-volume.jsx index 785ed4d85039..185a3bc773d8 100644 --- a/pkg/storaged/lvm2/unsupported-logical-volume.jsx +++ b/pkg/storaged/lvm2/unsupported-logical-volume.jsx @@ -21,48 +21,39 @@ import cockpit from "cockpit"; import React from "react"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { - new_page, ActionButtons, page_type, PageContainerStackItems + StorageCard, new_page, new_card } from "../pages.jsx"; -import { SCard } from "../utils/card.jsx"; import { StorageSize } from "../storage-controls.jsx"; import { lvol_delete } from "./block-logical-volume.jsx"; const _ = cockpit.gettext; -export function make_lvm2_unsupported_logical_volume_page(parent, vgroup, lvol, container) { - const page = new_page({ - location: ["vg", vgroup.Name, lvol.Name], - parent, - container, - name: lvol.Name, - columns: [ - _("Unsupported logical volume"), - null, - , - ], - component: LVM2UnsupportedLogicalVolumePage, +export function make_lvm2_unsupported_logical_volume_page(parent, vgroup, lvol, next_card) { + const unsupported_card = new_card({ + title: _("Unsupported logical volume"), + next: next_card, + page_location: ["vg", vgroup.Name, lvol.Name], + page_name: lvol.Name, + page_size: , + component: LVM2UnsupportedLogicalVolumeCard, props: { vgroup, lvol }, actions: [ { title: _("Deactivate"), action: () => lvol.Deactivate({}) }, { title: _("Delete"), action: () => lvol_delete(lvol, page), danger: true }, ] }); + + const page = new_page(parent, unsupported_card); } -const LVM2UnsupportedLogicalVolumePage = ({ page, vgroup, lvol }) => { +const LVM2UnsupportedLogicalVolumeCard = ({ card, vgroup, lvol }) => { return ( - - - }> - -

{_("INTERNAL ERROR - This logical volume is marked as active and should have an associated block device. However, no such block device could be found.")}

-
-
-
- -
); + + +

{_("INTERNAL ERROR - This logical volume is marked as active and should have an associated block device. However, no such block device could be found.")}

+
+
); }; diff --git a/pkg/storaged/lvm2/vdo-pool.jsx b/pkg/storaged/lvm2/vdo-pool.jsx index 882d5b711d6d..592590f898cf 100644 --- a/pkg/storaged/lvm2/vdo-pool.jsx +++ b/pkg/storaged/lvm2/vdo-pool.jsx @@ -25,25 +25,24 @@ import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index. import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; import { StorageOnOff } from "../storage-controls.jsx"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; import { grow_dialog } from "../block/resize.jsx"; -import { new_container, ActionButtons } from "../pages.jsx"; +import { StorageCard, new_card } from "../pages.jsx"; import { fmt_size } from "../utils.js"; const _ = cockpit.gettext; -export function make_lvm2_vdo_pool_container(parent, vgroup, lvol) { +export function make_lvm2_vdo_pool_card(next, vgroup, lvol) { const vdo_iface = client.vdo_vols[lvol.path]; const vdo_pool_vol = client.lvols[vdo_iface.VDOPool]; if (!vdo_pool_vol) return null; - const cont = new_container({ - parent, - stored_on_format: _("VDO pool in $0"), - component: LVM2VDOPoolContainer, + return new_card({ + title: _("LVM2 VDO pool"), + next, + component: LVM2VDOPoolCard, props: { vgroup, lvol, vdo_iface, vdo_pool_vol }, actions: [ { @@ -52,10 +51,9 @@ export function make_lvm2_vdo_pool_container(parent, vgroup, lvol) { } ], }); - return cont; } -const LVM2VDOPoolContainer = ({ container, vgroup, lvol, vdo_iface, vdo_pool_vol }) => { +const LVM2VDOPoolCard = ({ card, vgroup, lvol, vdo_iface, vdo_pool_vol }) => { function toggle_compression() { const new_state = !vdo_iface.Compression; return vdo_iface.EnableCompression(new_state, {}) @@ -75,7 +73,7 @@ const LVM2VDOPoolContainer = ({ container, vgroup, lvol, vdo_iface, vdo_pool_vol const used_pct = perc(vdo_iface.UsedSize / vdo_pool_vol.Size); return ( - }> + @@ -94,5 +92,5 @@ const LVM2VDOPoolContainer = ({ container, vgroup, lvol, vdo_iface, vdo_pool_vol - ); + ); }; diff --git a/pkg/storaged/lvm2/volume-group.jsx b/pkg/storaged/lvm2/volume-group.jsx index d03d4e63a519..c768fef2bf17 100644 --- a/pkg/storaged/lvm2/volume-group.jsx +++ b/pkg/storaged/lvm2/volume-group.jsx @@ -27,11 +27,10 @@ import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/ import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; import { useObject } from "hooks"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; import { StorageButton, StorageLink, StorageSize } from "../storage-controls.jsx"; import { - PageContainerStackItems, PageTable, ActionButtons, new_page, new_container, page_type, get_crossrefs, + StorageCard, PageTable, new_page, new_card, get_crossrefs, } from "../pages.jsx"; import { fmt_size_long, get_active_usage, teardown_active_usage, for_each_async, @@ -48,8 +47,8 @@ import { import { create_logical_volume } from "./create-logical-volume-dialog.jsx"; -import { make_lvm2_logical_volume_container } from "./block-logical-volume.jsx"; -import { make_lvm2_vdo_pool_container } from "./vdo-pool.jsx"; +import { make_lvm2_logical_volume_card } from "./block-logical-volume.jsx"; +import { make_lvm2_vdo_pool_card } from "./vdo-pool.jsx"; import { make_lvm2_thin_pool_logical_volume_page } from "./thin-pool-logical-volume.jsx"; import { make_lvm2_inactive_logical_volume_page } from "./inactive-logical-volume.jsx"; import { make_lvm2_unsupported_logical_volume_page } from "./unsupported-logical-volume.jsx"; @@ -142,32 +141,32 @@ export function lvm2_create_snapshot_action(lvol) { return { title: _("Create snapshot"), action: () => create_snapshot(lvol) }; } -function make_lvm2_generic_logical_volume_container(parent, vgroup, lvol) { +function make_lvm2_generic_logical_volume_card(parent, vgroup, lvol) { let result = parent; if (client.vdo_vols[lvol.path]) - result = make_lvm2_vdo_pool_container(result, vgroup, lvol); + result = make_lvm2_vdo_pool_card(result, vgroup, lvol); return result; } export function make_lvm2_logical_volume_page(parent, vgroup, lvol) { - const generic_container = make_lvm2_generic_logical_volume_container(null, vgroup, lvol); + const generic_card = make_lvm2_generic_logical_volume_card(null, vgroup, lvol); if (lvol.Type == "pool") { make_lvm2_thin_pool_logical_volume_page(parent, vgroup, lvol); } else { const block = client.lvols_block[lvol.path]; if (block) { - const container = make_lvm2_logical_volume_container(generic_container, vgroup, lvol, block); - make_block_page(parent, block, container); + const card = make_lvm2_logical_volume_card(generic_card, vgroup, lvol, block); + make_block_page(parent, block, card); } else { // If we can't find the block for a active // volume, Storaged or something below is // probably misbehaving, and we show it as // "unsupported". if (lvol.Active) { - make_lvm2_unsupported_logical_volume_page(parent, vgroup, lvol, generic_container); + make_lvm2_unsupported_logical_volume_page(parent, vgroup, lvol, generic_card); } else { - make_lvm2_inactive_logical_volume_page(parent, vgroup, lvol, generic_container); + make_lvm2_inactive_logical_volume_page(parent, vgroup, lvol, generic_card); } } } @@ -184,7 +183,7 @@ function make_logical_volume_pages(parent, vgroup) { // treat them specially, and we haven't bothered to write the // code for that. // - // We ignore vdo pools; they appear as a container for their + // We ignore vdo pools; they appear as a card for their // single contained logical volume. // if (lvol.ThinPool == "/" && lvol.Origin == "/" && !isVDOPool(lvol)) @@ -235,9 +234,10 @@ export function make_lvm2_volume_group_page(parent, vgroup) { else if (vgroup.FreeSize == 0) lvol_excuse = _("No free space"); - const pvols_container = new_container({ - parent: null, - component: LVM2PVolsContainer, + const pvols_card = new_card({ + title: _("LVM2 physical volumes"), + next: null, + component: LVM2PVolsCard, props: { vgroup }, actions: [ { @@ -248,9 +248,13 @@ export function make_lvm2_volume_group_page(parent, vgroup) { ], }); - const vgroup_container = new_container({ - parent: pvols_container, - component: LVM2VolumeGroupContainer, + const vgroup_card = new_card({ + title: _("LVM2 volume group"), + next: pvols_card, + page_location: ["vg", vgroup.Name], + page_name: vgroup.Name, + page_size: , + component: LVM2VolumeGroupCard, props: { vgroup }, actions: [ { @@ -262,18 +266,11 @@ export function make_lvm2_volume_group_page(parent, vgroup) { ], }); - const vgroup_page = new_page({ - location: ["vg", vgroup.Name], - parent, - container: vgroup_container, - name: vgroup.Name, - columns: [ - _("LVM2 logical volumes"), - null, - , - ], + const lvols_card = new_card({ + title: _("LVM2 logical volumes"), + next: vgroup_card, has_warning: has_missing_pvs, - component: LVM2VolumeGroupPage, + component: LVM2LogicalVolumesCard, props: { vgroup }, actions: [ { @@ -285,6 +282,7 @@ export function make_lvm2_volume_group_page(parent, vgroup) { ], }); + const vgroup_page = new_page(parent, lvols_card); make_logical_volume_pages(vgroup_page, vgroup); } @@ -305,24 +303,19 @@ function vgroup_poller(vgroup) { }; } -const LVM2VolumeGroupPage = ({ page, vgroup }) => { +const LVM2LogicalVolumesCard = ({ card, vgroup }) => { return ( - - - }> - - - - - - - + + + + + ); }; -const LVM2VolumeGroupContainer = ({ container, vgroup }) => { +const LVM2VolumeGroupCard = ({ card, vgroup }) => { const has_missing_pvs = vgroup.MissingPhysicalVolumes && vgroup.MissingPhysicalVolumes.length > 0; useObject(() => vgroup_poller(vgroup), @@ -399,7 +392,7 @@ const LVM2VolumeGroupContainer = ({ container, vgroup }) => { {alert} - }> + { - + ); }; -const LVM2PVolsContainer = ({ container, vgroup }) => { +const LVM2PVolsCard = ({ card, vgroup }) => { return ( - }> + - ); + ); }; diff --git a/pkg/storaged/mdraid/mdraid-disk.jsx b/pkg/storaged/mdraid/mdraid-disk.jsx index 44d4b4e93f09..fa4359bcf334 100644 --- a/pkg/storaged/mdraid/mdraid-disk.jsx +++ b/pkg/storaged/mdraid/mdraid-disk.jsx @@ -23,37 +23,26 @@ import client from "../client"; import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; -import { - PageContainerStackItems, - new_page, block_location, ActionButtons, page_type, - register_crossref, -} from "../pages.jsx"; +import { StorageCard, new_page, new_card, register_crossref } from "../pages.jsx"; import { format_dialog } from "../block/format-dialog.jsx"; import { block_name, fmt_size, mdraid_name } from "../utils.js"; import { std_lock_action } from "../crypto/actions.jsx"; -import { StorageSize } from "../storage-controls.jsx"; const _ = cockpit.gettext; -export function make_mdraid_disk_page(parent, backing_block, content_block, container) { +export function make_mdraid_disk_page(parent, backing_block, content_block, next_card) { const mdraid = client.mdraids[content_block.MDRaidMember]; - - const p = new_page({ - location: [block_location(backing_block)], - parent, - container, - name: block_name(backing_block), - columns: [ - _("MDRAID disk"), - mdraid ? mdraid_name(mdraid) : null, - , - ], - component: MDRaidDiskPage, + const mdraid_block = client.mdraids_block[mdraid.path]; + + const disk_card = new_card({ + title: _("MDRAID disk"), + next: next_card, + location: mdraid_block ? block_name(mdraid_block) : (mdraid ? mdraid_name(mdraid) : null), + page_block: backing_block, + component: MDRaidDiskCard, props: { backing_block, content_block, mdraid }, actions: [ std_lock_action(backing_block, content_block), @@ -61,6 +50,8 @@ export function make_mdraid_disk_page(parent, backing_block, content_block, cont ] }); + const p = new_page(parent, disk_card); + if (mdraid) { const members = client.mdraids_members[mdraid.path] || []; let n_spares = 0; @@ -130,26 +121,22 @@ export function make_mdraid_disk_page(parent, backing_block, content_block, cont } } -export const MDRaidDiskPage = ({ page, backing_block, content_block, mdraid }) => { +export const MDRaidDiskCard = ({ card, backing_block, content_block, mdraid }) => { return ( - - - }> - - - - {mdraid - ? - : "-" - } - - - - - - - ); + + + + + {mdraid + ? + : "-" + } + + + + + ); }; diff --git a/pkg/storaged/mdraid/mdraid.jsx b/pkg/storaged/mdraid/mdraid.jsx index 0059b08f2604..2b575538c4a0 100644 --- a/pkg/storaged/mdraid/mdraid.jsx +++ b/pkg/storaged/mdraid/mdraid.jsx @@ -26,13 +26,12 @@ import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index. import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; import { StorageButton, StorageSize } from "../storage-controls.jsx"; -import { PageContainerStackItems, PageTable, ActionButtons, new_page, new_container, get_crossrefs } from "../pages.jsx"; +import { StorageCard, PageTable, new_page, new_card, get_crossrefs } from "../pages.jsx"; import { make_block_page } from "../block/create-pages.jsx"; import { - mdraid_name, encode_filename, decode_filename, + block_name, mdraid_name, encode_filename, decode_filename, fmt_size, fmt_size_long, get_active_usage, teardown_active_usage, get_available_spaces, prepare_available_spaces, reload_systemd, @@ -193,10 +192,11 @@ export function make_mdraid_page(parent, mdraid) { if (!block) add_excuse = _("The MDRAID device must be running in order to add spare disks."); - const disks_container = new_container({ - parent: null, - component: MDRaidDisksContainer, - props: { mdraid, running: !!block }, + const disks_card = new_card({ + title: _("MDRAID disks"), + next: null, + component: MDRaidDisksCard, + props: { mdraid }, actions: [ (mdraid.Level != "raid0" && { @@ -208,59 +208,35 @@ export function make_mdraid_page(parent, mdraid) { ], }); - if (!block) { - new_page({ - location: ["mdraid", mdraid.UUID], - parent, - container: disks_container, - name: mdraid_name(mdraid), - columns: [ - _("MDRAID device (stopped)"), - null, - , - ], - component: MDRaidContainer, - props: { mdraid, running: false }, - actions: [ - start_stop_action(mdraid), - { - title: _("Delete"), - action: () => mdraid_delete(mdraid, null), - danger: true, - tag: "device", - }, - ], - }); - return; - } - - const container = make_mdraid_container(disks_container, mdraid, block); - make_block_page(parent, block, container); -} - -export function make_mdraid_container(parent, mdraid, block) { // XXX - set has_warning appropriately - const device_cont = new_container({ - parent, + const mdraid_card = new_card({ + title: _("MDRAID device"), + next: disks_card, page_location: ["mdraid", mdraid.UUID], - id_extra: cockpit.format(_("MDRAID Device $0"), mdraid_name(mdraid)), - component: MDRaidContainer, - props: { mdraid, block, running: true }, - actions: partitionable_block_actions(block, "device").concat([ + page_name: block ? block_name(block) : mdraid_name(mdraid), + page_size: , + type_extra: !block && _("stopped"), + id_extra: block && _("MDRAID device"), + for_summary: true, + component: MDRaidCard, + props: { mdraid, block }, + actions: (block ? partitionable_block_actions(block, "device") : []).concat([ start_stop_action(mdraid), { title: _("Delete"), action: () => mdraid_delete(mdraid, block), danger: true, - tag: "device", }, ]), }); - return device_cont; + if (!block) { + new_page(parent, mdraid_card); + } else + make_block_page(parent, block, mdraid_card); } -const MDRaidContainer = ({ page, container, mdraid, block, running }) => { +const MDRaidCard = ({ card, mdraid, block }) => { function format_level(str) { return { raid0: _("RAID 0"), @@ -318,32 +294,31 @@ const MDRaidContainer = ({ page, container, mdraid, block, running }) => { {bitmap_message} {degraded_message} - }> + + - + - + - { page && } ); }; -const MDRaidDisksContainer = ({ container, mdraid, block, running }) => { +const MDRaidDisksCard = ({ card, mdraid }) => { return ( - }> + - + ); }; diff --git a/pkg/storaged/nfs/nfs.jsx b/pkg/storaged/nfs/nfs.jsx index ec2388375fcb..7b1689156898 100644 --- a/pkg/storaged/nfs/nfs.jsx +++ b/pkg/storaged/nfs/nfs.jsx @@ -23,7 +23,6 @@ import client from "../client"; import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; import { @@ -31,10 +30,9 @@ import { StopProcessesMessage, stop_processes_danger_message } from "../dialog.jsx"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; import { StorageUsageBar } from "../storage-controls.jsx"; -import { new_page, page_type, ActionButtons } from "../pages.jsx"; +import { StorageCard, new_page, new_card } from "../pages.jsx"; import { parse_options, unparse_options, extract_option } from "../utils.js"; const _ = cockpit.gettext; @@ -294,16 +292,14 @@ export function make_nfs_page(parent, entry) { if (!entry.mounted) mount_point += " " + _("(not mounted)"); - new_page({ - location: ["nfs", remote, local], - parent, - name: remote, - columns: [ - _("NFS mount"), - mount_point, - , - ], - component: NfsPage, + const nfs_card = new_card({ + title: _("NFS mount"), + location: mount_point, + next: null, + page_location: ["nfs", remote, local], + page_name: remote, + page_size: , + component: NfsCard, props: { entry }, actions: [ (entry.mounted @@ -317,24 +313,22 @@ export function make_nfs_page(parent, entry) { : null), ] }); + + new_page(parent, nfs_card); } -const NfsPage = ({ page, entry }) => { +const NfsCard = ({ card, entry }) => { return ( - - - }> - - - - - - - - - - - - + + + + + + + + + + + ); }; diff --git a/pkg/storaged/overview/overview.jsx b/pkg/storaged/overview/overview.jsx index ce6dea3e3e05..56a64e07e516 100644 --- a/pkg/storaged/overview/overview.jsx +++ b/pkg/storaged/overview/overview.jsx @@ -26,7 +26,6 @@ import { install_dialog } from "cockpit-components-install-dialog.jsx"; import { Card, CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; -import { SCard } from "../utils/card.jsx"; import { StoragePlots } from "../plot.jsx"; import { StorageMenuItem, StorageBarMenu } from "../storage-controls.jsx"; import { dialog_open } from "../dialog.jsx"; @@ -38,7 +37,7 @@ import { create_stratis_pool } from "../stratis/create-dialog.jsx"; import { iscsi_change_name, iscsi_discover } from "../iscsi/create-dialog.jsx"; import { get_other_devices } from "../utils.js"; -import { new_page, PageTable } from "../pages.jsx"; +import { new_page, new_card, StorageCard, PageTable } from "../pages.jsx"; import { make_drive_page } from "../drive/drive.jsx"; import { make_lvm2_volume_group_page } from "../lvm2/volume-group.jsx"; import { make_mdraid_page } from "../mdraid/mdraid.jsx"; @@ -51,12 +50,15 @@ import { make_other_page } from "../block/other.jsx"; const _ = cockpit.gettext; export function make_overview_page() { - const overview_page = new_page({ - location: [], - name: _("Storage"), - component: OverviewPage + const overview_card = new_card({ + title: _("Storage"), + page_location: [], + page_name: _("Storage"), + component: OverviewCard }); + const overview_page = new_page(null, overview_card); + Object.keys(client.iscsi_sessions).forEach(p => make_iscsi_session_page(overview_page, client.iscsi_sessions[p])); Object.keys(client.drives).forEach(p => { if (!client.drives_iscsi_session[p]) @@ -70,7 +72,7 @@ export function make_overview_page() { get_other_devices(client).map(p => make_other_page(overview_page, client.blocks[p])); } -const OverviewPage = ({ page, plot_state }) => { +const OverviewCard = ({ card, plot_state }) => { function menu_item(feature, title, action) { const feature_enabled = !feature || feature.is_enabled(); const required_package = feature && feature.package; @@ -157,13 +159,13 @@ const OverviewPage = ({ page, plot_state }) => { - + + pages={card.page.children} /> - + diff --git a/pkg/storaged/pages.jsx b/pkg/storaged/pages.jsx index 059edcced399..1009b5d5fbfa 100644 --- a/pkg/storaged/pages.jsx +++ b/pkg/storaged/pages.jsx @@ -21,7 +21,7 @@ import cockpit from "cockpit"; import React, { useState } from "react"; import client from "./client"; -import { Card, CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; +import { Card, CardHeader, CardTitle, CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { Bullseye } from "@patternfly/react-core/dist/esm/layouts/Bullseye/index.js"; import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js"; @@ -32,9 +32,9 @@ import { Icon } from '@patternfly/react-core'; import { Page, PageBreadcrumb, PageSection } from "@patternfly/react-core/dist/esm/components/Page/index.js"; import { Breadcrumb, BreadcrumbItem } from "@patternfly/react-core/dist/esm/components/Breadcrumb/index.js"; -import { decode_filename } from "./utils.js"; +import { decode_filename, block_name } from "./utils.js"; -import { StorageButton, StorageBarMenu, StorageMenuItem } from "./storage-controls.jsx"; +import { StorageButton, StorageBarMenu, StorageMenuItem, StorageSize } from "./storage-controls.jsx"; import { MultipathAlert } from "./multipath.jsx"; @@ -48,47 +48,48 @@ export function reset_pages() { crossrefs = new Map(); } -function name_from_container(container) { - if (!container) +function name_from_card(card) { + if (!card) return null; - if (container.page_name) - return container.page_name; - return name_from_container(container.parent); + return name_from_card(card.next) || card.page_name; } -function location_from_container(container) { - if (!container) +function location_from_card(card) { + if (!card) return null; - return location_from_container(container.parent) || container.page_location; + return location_from_card(card.next) || card.page_location; } -export function new_page({ - location, parent, container, - name, component, props, columns, has_warning, has_danger, actions -}) { - const loc = location_from_container(container) || location; +function size_from_card(card) { + if (!card) + return null; + if (card.page_size) + return card.page_size; + return size_from_card(card.next); +} + +export function new_page(parent, card) { const page = { - location: loc, - name: name_from_container(container) || name, + location: location_from_card(card), + name: name_from_card(card), parent, - component, - props: props || {}, children: [], - container, - columns: columns || [], - has_warning, - has_danger, - actions: actions ? actions.filter(a => !!a) : null, + card, }; + page.columns = [ + card.title, + card.location, + size_from_card(card), + ]; if (parent) parent.children.push(page); - while (container) { - container.page = page; - container = container.parent; + while (card) { + card.page = page; + card = card.next; } - if (loc) { - pages.set(JSON.stringify(loc), page); - if (loc.length == 0) { + if (page.location) { + pages.set(JSON.stringify(page.location), page); + if (page.location.length == 0) { // This is the Overview page. Make it the parent of the // special "not found" page (but don't make the "not // found" page a child of the Overview...) @@ -98,19 +99,30 @@ export function new_page({ return page; } -export function new_container({ - parent, - type_format, stored_on_format, page_name, page_location, +export function new_card({ + title, location, next, + type_extra, + page_name, page_location, page_size, + page_block, + for_summary, component, props, has_warning, has_danger, actions, id_extra, }) { + if (page_block) { + page_location = [block_location(page_block)]; + page_name = block_name(page_block); + page_size = ; + } return { - parent, - type_format, - stored_on_format, + title, + location, + next, + type_extra, page_name, page_location, + page_size, + for_summary, component, props, has_warning, @@ -136,14 +148,11 @@ export function get_crossrefs(key) { * no real page at the given location. */ -const NotFoundPage = ({ page }) => { +const NotFoundCard = ({ card }) => { return {_("Not found")}; }; -const not_found_page = new_page({ - name: "Not found", - component: NotFoundPage -}); +const not_found_page = new_page(null, new_card({ page_name: _("Not found"), component: NotFoundCard })); export function get_page_from_location(location) { if (!pages) @@ -196,10 +205,10 @@ function make_page_kebab(page) { } add_actions(page.actions); - let cont = page.container; - while (cont) { - add_actions(cont.actions); - cont = cont.parent; + let c = page.card; + while (c) { + add_actions(c.actions); + c = c.next; } if (items.length == 0) @@ -215,12 +224,11 @@ function make_actions_kebab(actions) { return ; } -export const ActionButtons = ({ page, container, tag }) => { - const actions = page ? page.actions : container.actions; - if (!actions) +const ActionButtons = ({ card, tag }) => { + if (!card.actions) return null; - return actions + return card.actions .filter(a => !tag || a.tag == tag) .map(a => a.action(false)} @@ -229,65 +237,91 @@ export const ActionButtons = ({ page, container, tag }) => { ); }; -export function page_type(page, for_table) { - let type = page.columns[0]; - - if (for_table) { - let cont = page.container; - while (cont) { - if (cont.type_format) - type = cockpit.format(cont.type_format, type); - cont = cont.parent; - } +function page_type_extra(page) { + const extra = []; + let c = page.card; + while (c) { + if (c.type_extra) + extra.push(c.type_extra); + c = c.next; } + return extra; +} - return type; +function page_type(page) { + const type = page.card.title; + const extra = page_type_extra(page); + if (extra.length > 0) + return type + " (" + extra.join(", ") + ")"; + else + return type; } -function page_stored_on(page) { - function apply_container_format(cont, text) { - if (cont) { - if (cont.id_extra) { - text = cont.id_extra; - } else { - text = apply_container_format(cont.parent, text); - if (cont.stored_on_format) - text = cockpit.format(cont.stored_on_format, text); - } +function page_block_summary_1(page) { + // Describe a page in a way that is useful to identify it when + // deciding which block device to format, or which block devices + // to make a volume group out of. The block device itself (such as + // /dev/sda5) should not be aprt of the description; it is in + // another table column already. + + // The first card on the page that has the "for_summary" flag set + // provides the description, and the type extra of all cards + // leading to it are added. The description is either the title + // of the card, or its id_extra. + + let description = null; + const extra = []; + for (let card = page.card; card; card = card.next) { + if (card.for_summary) { + description = card.id_extra || card.title; + break; } - return text; + if (card.type_extra) + extra.push(card.type_extra); } - return apply_container_format(page.container, page_display_name(page.parent)); + if (description && extra.length > 0) + description += " (" + extra.join(", ") + ")"; + + return description; +} + +function page_block_summary(page) { + const desc1 = page_block_summary_1(page); + const desc2 = page.parent && page.parent.parent && page_block_summary_1(page.parent); + if (desc1 && desc2) + return desc1 + " ➞ " + desc2; + else + return desc1 || desc2; } export const PageTable = ({ emptyCaption, aria_label, pages, crossrefs }) => { const [collapsed, setCollapsed] = useState(true); let rows = []; - function container_has_danger(container) { - if (container) - return container.has_danger || container_has_danger(container.parent); + function card_has_danger(card) { + if (card) + return card.has_danger || card_has_danger(card.next); else return false; } - function container_has_warning(container) { - if (container) - return container.has_warning || container_has_warning(container.parent); + function card_has_warning(card) { + if (card) + return card.has_warning || card_has_warning(card.next); else return false; } function make_row(page, crossref, level, key, border) { let info = null; - if (page.has_danger || container_has_danger(page.container)) + if (card_has_danger(page.card)) info = <>{"\n"}; - else if (page.has_warning || container_has_warning(page.container)) + else if (card_has_warning(page.card)) info = <>{"\n"}; const name = crossref ? page.name : page_display_name(page); - const type = crossref ? page_stored_on(page) : page_type(page, true); + const type = crossref ? page_block_summary(page) : page_type(page); const loc = crossref ? crossref.extra : page.columns[1]; const size = crossref ? crossref.size : page.columns[2]; const actions = crossref ? make_actions_kebab(crossref.actions) : make_page_kebab(page); @@ -382,18 +416,22 @@ export const PageTable = ({ emptyCaption, aria_label, pages, crossrefs }) => { ); }; -const Container = ({ container }) => { - return ; -}; +function page_id_extra(page) { + let extra = ""; + let c = page.card; + while (c) { + if (c.id_extra) + extra += " " + c.id_extra; + c = c.next; + } + return extra; +} -export function page_display_name(page) { +function page_display_name(page) { let name = page.name; - let cont = page.container; - while (cont) { - if (cont.id_extra) - name = name + " - " + cont.id_extra; - cont = cont.parent; - } + const extra = page_id_extra(page); + if (extra) + name = name + " - " + extra; return name; } @@ -401,28 +439,30 @@ const PageLink = ({ page }) => { return ( ); }; -export const PageContainerStackItems = ({ page }) => { +const PageCardStackItems = ({ page, plot_state }) => { const items = []; - let cont = page.container; - while (cont) { + let c = page.card; + while (c) { items.push( + { (items.length > 0) && + } - + ); - cont = cont.parent; + c = c.next; } if (page.parent && page.parent.parent) { @@ -451,6 +491,16 @@ export function block_location(block) { return decode_filename(block.PreferredDevice).replace(/^\/dev\//, ""); } +export const StorageCard = ({ card, actions, children }) => { + return ( + + }}> + {card.title} + + {children} + ); +}; + export const StoragePage = ({ location, plot_state }) => { const page = get_page_from_location(location); @@ -473,12 +523,10 @@ export const StoragePage = ({ location, plot_state }) => { {page_display_name(page)} - + - - - + diff --git a/pkg/storaged/partitions/partition-table.jsx b/pkg/storaged/partitions/partition-table.jsx index 317529ff27df..61db82963942 100644 --- a/pkg/storaged/partitions/partition-table.jsx +++ b/pkg/storaged/partitions/partition-table.jsx @@ -22,70 +22,108 @@ import React from "react"; import client from "../client"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; -import { SCard } from "../utils/card.jsx"; +import { get_partitions } from "../utils.js"; import { SDesc } from "../utils/desc.jsx"; -import { PageContainerStackItems, PageTable, new_page, page_type, new_container, block_location } from "../pages.jsx"; -import { block_name } from "../utils.js"; +import { StorageCard, PageTable, new_page, new_card } from "../pages.jsx"; +import { format_dialog } from "../block/format-dialog.jsx"; import { StorageSize } from "../storage-controls.jsx"; - -import { make_block_pages } from "../block/create-pages.jsx"; +import { make_block_page } from "../block/create-pages.jsx"; +import { make_partition_card, delete_partition } from "./partition.jsx"; const _ = cockpit.gettext; -export function make_partition_table_page(parent, block, container) { +function make_partition_pages(parent, block) { const block_ptable = client.blocks_ptable[block.path]; - const p = new_page({ - location: [block_location(block)], - parent, - container: make_partition_table_container(container, block, block_ptable), - name: block_name(block), - columns: [ - _("Partitions"), - null, - , - ], - component: PartitionTablePage, - props: { block, block_ptable }, - }); - make_block_pages(p, block); + function make_free_space_page(parent, start, size, enable_dos_extended) { + const card = new_card({ + page_name: _("Free space"), + page_size: , + actions: [ + { + title: _("Create partition"), + action: () => format_dialog(client, block.path, start, size, + enable_dos_extended), + } + ], + }); + new_page(parent, card); + } + + function make_extended_partition_page(parent, partition) { + const card = new_card({ + page_name: _("Extended partition"), + page_size: , + actions: [ + { title: _("Delete"), action: () => delete_partition(partition.block, page), danger: true }, + ] + }); + const page = new_page(parent, card); + process_partitions(page, partition.partitions, false); + } + + function process_partitions(parent, partitions, enable_dos_extended) { + let i, p; + for (i = 0; i < partitions.length; i++) { + p = partitions[i]; + if (p.type == 'free') + make_free_space_page(parent, p.start, p.size, enable_dos_extended); + else if (p.type == 'container') + make_extended_partition_page(parent, p); + else { + const card = make_partition_card(null, p.block); + make_block_page(parent, p.block, card); + } + } + } + + process_partitions(parent, get_partitions(client, block), + block_ptable.Type == 'dos'); } -function make_partition_table_container(parent, block, block_ptable) { - return new_container({ - parent, - component: PartitionTableContainer, +export function make_partition_table_page(parent, block, next_card) { + const block_ptable = client.blocks_ptable[block.path]; + + const ptable_card = new_card({ + title: _("Partition table"), + next: next_card, + page_block: block, + component: PartitionTableCard, props: { block, block_ptable }, }); + + const parts_card = new_card({ + title: _("Partitions"), + next: ptable_card, + component: PartitionsCard, + props: { }, + }); + + const p = new_page(parent, parts_card); + make_partition_pages(p, block); } -const PartitionTablePage = ({ page, block, block_ptable }) => { +const PartitionsCard = ({ card }) => { return ( - - - - - - - - - - + + + + + ); }; -const PartitionTableContainer = ({ container, block, block_ptable }) => { +const PartitionTableCard = ({ card, block, block_ptable }) => { return ( - + - ); + ); }; diff --git a/pkg/storaged/partitions/partition.jsx b/pkg/storaged/partitions/partition.jsx index a6cec4975548..d21cc8d7fbcd 100644 --- a/pkg/storaged/partitions/partition.jsx +++ b/pkg/storaged/partitions/partition.jsx @@ -25,13 +25,12 @@ import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js 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 { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; -import { dialog_open, init_active_usage_processes, BlockingMessage, TeardownMessage } from "../dialog.jsx"; import { StorageButton } from "../storage-controls.jsx"; +import { dialog_open, init_active_usage_processes, BlockingMessage, TeardownMessage } from "../dialog.jsx"; import { block_name, fmt_size, get_active_usage, teardown_active_usage, reload_systemd } from "../utils.js"; import { check_unused_space, get_resize_info, free_space_after_part, grow_dialog, shrink_dialog } from "../block/resize.jsx"; -import { new_container, navigate_away_from_page, ActionButtons } from "../pages.jsx"; +import { StorageCard, new_card, navigate_away_from_page, block_location } from "../pages.jsx"; const _ = cockpit.gettext; @@ -67,7 +66,7 @@ export function delete_partition(block, page) { }); } -export function make_partition_container(parent, block) { +export function make_partition_card(next, block) { const block_part = client.blocks_part[block.path]; const unused_space_warning = check_unused_space(block.path); const unused_space = !!unused_space_warning; @@ -77,10 +76,13 @@ export function make_partition_container(parent, block) { grow_excuse = _("No free space after this partition"); } - const cont = new_container({ - stored_on_format: _("Partition of $0"), + const cont = new_card({ + title: _("Partition"), + next, + page_location: [block_location(block)], + for_summary: true, has_warning: !!unused_space_warning, - component: PartitionContainer, + component: PartitionCard, props: { block, unused_space_warning, resize_info: info }, actions: [ (!unused_space && @@ -105,7 +107,7 @@ export function make_partition_container(parent, block) { return cont; } -const PartitionContainer = ({ container, block, unused_space_warning, resize_info }) => { +const PartitionCard = ({ card, block, unused_space_warning, resize_info }) => { const block_part = client.blocks_part[block.path]; const unused_space = !!unused_space_warning; @@ -118,7 +120,7 @@ const PartitionContainer = ({ container, block, unused_space_warning, resize_inf } return ( - }> + @@ -149,5 +151,5 @@ const PartitionContainer = ({ container, block, unused_space_warning, resize_inf } - ); + ); }; diff --git a/pkg/storaged/stratis/blockdev.jsx b/pkg/storaged/stratis/blockdev.jsx index c3efb6612d4b..d30e03ca23b7 100644 --- a/pkg/storaged/stratis/blockdev.jsx +++ b/pkg/storaged/stratis/blockdev.jsx @@ -23,39 +23,27 @@ import client from "../client"; import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; -import { - PageContainerStackItems, - new_page, block_location, ActionButtons, page_type, - register_crossref, -} from "../pages.jsx"; +import { StorageCard, new_page, new_card, register_crossref } from "../pages.jsx"; import { format_dialog } from "../block/format-dialog.jsx"; -import { block_name, fmt_size } from "../utils.js"; +import { fmt_size } from "../utils.js"; import { std_lock_action } from "../crypto/actions.jsx"; -import { StorageSize } from "../storage-controls.jsx"; const _ = cockpit.gettext; -export function make_stratis_blockdev_page(parent, backing_block, content_block, container) { +export function make_stratis_blockdev_page(parent, backing_block, content_block, next_card) { const blockdev = client.blocks_stratis_blockdev[content_block.path]; const pool = blockdev && client.stratis_pools[blockdev.Pool]; const stopped_pool = client.blocks_stratis_stopped_pool[content_block.path]; - const p = new_page({ - location: [block_location(backing_block)], - parent, - container, - name: block_name(backing_block), - columns: [ - _("Stratis block device"), - pool ? pool.Name : stopped_pool, - , - ], - component: StratisBlockdevPage, + const blockdev_card = new_card({ + title: _("Stratis block device"), + location: pool ? pool.Name : stopped_pool, + page_block: backing_block, + next: next_card, + component: StratisBlockdevCard, props: { backing_block, content_block, pool, stopped_pool }, actions: [ std_lock_action(backing_block, content_block), @@ -63,6 +51,8 @@ export function make_stratis_blockdev_page(parent, backing_block, content_block, ] }); + const p = new_page(parent, blockdev_card); + if (pool || stopped_pool) { let extra; if (blockdev && blockdev.Tier == 0) @@ -82,29 +72,25 @@ export function make_stratis_blockdev_page(parent, backing_block, content_block, } } -export const StratisBlockdevPage = ({ page, backing_block, content_block, pool, stopped_pool }) => { +export const StratisBlockdevCard = ({ card, backing_block, content_block, pool, stopped_pool }) => { const pool_name = pool ? pool.Name : stopped_pool; const pool_uuid = pool ? pool.Uuid : stopped_pool; return ( - - - }> - - - - {(pool || stopped_pool) - ? - : "-" - } - - - - - - - ); + + + + + {(pool || stopped_pool) + ? + : "-" + } + + + + + ); }; diff --git a/pkg/storaged/stratis/filesystem.jsx b/pkg/storaged/stratis/filesystem.jsx index 6cc9a306207b..41a091d761ff 100644 --- a/pkg/storaged/stratis/filesystem.jsx +++ b/pkg/storaged/stratis/filesystem.jsx @@ -22,10 +22,8 @@ import React from "react"; import client from "../client"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; import { dialog_open, TextInput, CheckBoxes, SelectOne, BlockingMessage, TeardownMessage, @@ -33,8 +31,8 @@ import { } from "../dialog.jsx"; import { StorageUsageBar, StorageLink } from "../storage-controls.jsx"; import { - PageContainerStackItems, - new_page, ActionButtons, page_type, + StorageCard, + new_page, new_card, navigate_away_from_page, navigate_to_new_page_location, } from "../pages.jsx"; import { @@ -191,21 +189,19 @@ export function make_stratis_filesystem_page(parent, pool, fsys, else mp_text = _("(not mounted)"); - const page = new_page({ - location: ["pool", pool.Name, fsys.Name], - parent, - name: fsys.Name, - columns: [ - _("Stratis filesystem"), - mp_text, - (!managed_fsys_sizes - ? - : ) - ], + const fsys_card = new_card({ + title: _("Stratis filesystem"), + location: mp_text, + next: null, + page_location: ["pool", pool.Name, fsys.Name], + page_name: fsys.Name, + page_size: (!managed_fsys_sizes + ? + : ), has_warning: !!mismount_warning, - component: StratisFilesystemPage, + component: StratisFilesystemCard, props: { pool, fsys, fstab_config, forced_options, managed_fsys_sizes, mismount_warning, offset }, actions: [ (fs_is_mounted @@ -215,10 +211,12 @@ export function make_stratis_filesystem_page(parent, pool, fsys, { title: _("Delete"), action: delete_fsys, danger: true }, ] }); + + const page = new_page(parent, fsys_card); } -const StratisFilesystemPage = ({ - page, pool, fsys, fstab_config, forced_options, managed_fsys_sizes, mismount_warning, offset, +const StratisFilesystemCard = ({ + card, pool, fsys, fstab_config, forced_options, managed_fsys_sizes, mismount_warning, offset, }) => { const filesystems = client.stratis_pool_filesystems[pool.path]; const stats = client.stratis_pool_stats[pool.path]; @@ -238,46 +236,42 @@ const StratisFilesystemPage = ({ Title: _("Rename"), action: async function (vals) { await fsys.SetName(vals.name).then(std_reply); - navigate_to_new_page_location(page, ["pool", pool.Name, vals.name]); + navigate_to_new_page_location(card.page, ["pool", pool.Name, vals.name]); } } }); } return ( - - - }> - - - + + + - {_("edit")} - } /> - - - - - {(!managed_fsys_sizes - ? - : ) - } - - - - { mismount_warning && - - - - } - - - - ); + action={ + {_("edit")} + } /> + + + + + {(!managed_fsys_sizes + ? + : ) + } + + + + { mismount_warning && + + + + } + + ); }; diff --git a/pkg/storaged/stratis/pool.jsx b/pkg/storaged/stratis/pool.jsx index e9622212c7a2..06125c210e90 100644 --- a/pkg/storaged/stratis/pool.jsx +++ b/pkg/storaged/stratis/pool.jsx @@ -30,10 +30,9 @@ import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/ind import { fmt_to_fragments } from "utils.jsx"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; import { StorageButton, StorageUsageBar, StorageLink, StorageSize } from "../storage-controls.jsx"; -import { PageContainerStackItems, PageTable, ActionButtons, new_page, new_container, page_type, get_crossrefs } from "../pages.jsx"; +import { StorageCard, PageTable, new_page, new_card, get_crossrefs } from "../pages.jsx"; import { get_active_usage, teardown_active_usage, for_each_async, get_available_spaces, prepare_available_spaces, @@ -284,9 +283,12 @@ export function make_stratis_pool_page(parent, pool) { const managed_fsys_sizes = client.features.stratis_managed_fsys_sizes && !pool.Overprovisioning; const stats = client.stratis_pool_stats[pool.path]; - const blockdevs_container = new_container({ - parent: null, - component: StratisBlockdevsContainer, + const use = pool.TotalPhysicalUsed[0] && [Number(pool.TotalPhysicalUsed[1]), Number(pool.TotalPhysicalSize)]; + + const blockdevs_card = new_card({ + title: _("Stratis block devices"), + next: null, + component: StratisBlockdevsCard, props: { pool }, actions: [ { @@ -296,9 +298,15 @@ export function make_stratis_pool_page(parent, pool) { ], }); - const pool_container = new_container({ - parent: blockdevs_container, - component: StratisPoolContainer, + const pool_card = new_card({ + title: pool.Encrypted ? _("Encrypted Stratis pool") : _("Stratis pool"), + next: blockdevs_card, + page_location: ["pool", pool.Uuid], + page_name: pool.Name, + page_size: ((!managed_fsys_sizes && use) + ? + : ), + component: StratisPoolCard, props: { pool, degraded_ops, can_grow, managed_fsys_sizes, stats }, actions: [ { @@ -309,22 +317,11 @@ export function make_stratis_pool_page(parent, pool) { ], }); - const use = pool.TotalPhysicalUsed[0] && [Number(pool.TotalPhysicalUsed[1]), Number(pool.TotalPhysicalSize)]; - - const p = new_page({ - location: ["pool", pool.Uuid], - parent, - container: pool_container, - name: pool.Name, - columns: [ - _("Stratis filesystems"), - null, - ((!managed_fsys_sizes && use) - ? - : ), - ], + const fsys_card = new_card({ + title: _("Stratis filesystems"), + next: pool_card, has_warning: degraded_ops || can_grow, - component: StratisPoolPage, + component: StratisFilesystemsCard, props: { pool, degraded_ops, can_grow, managed_fsys_sizes, stats }, actions: [ { @@ -335,12 +332,14 @@ export function make_stratis_pool_page(parent, pool) { : null), }, ], + }); + const p = new_page(parent, fsys_card); make_stratis_filesystem_pages(p, pool); } -const StratisPoolPage = ({ page, pool, degraded_ops, can_grow, managed_fsys_sizes, stats }) => { +const StratisFilesystemsCard = ({ card, pool, degraded_ops, can_grow, managed_fsys_sizes, stats }) => { const blockdevs = client.stratis_pool_blockdevs[pool.path] || []; function grow_blockdevs() { @@ -382,20 +381,19 @@ const StratisPoolPage = ({ page, pool, degraded_ops, can_grow, managed_fsys_size {alerts} - }> + + pages={card.page.children} /> - + - ); }; -const StratisPoolContainer = ({ container, pool, degraded_ops, can_grow, managed_fsys_sizes, stats }) => { +const StratisPoolCard = ({ card, pool, degraded_ops, can_grow, managed_fsys_sizes, stats }) => { const key_desc = (pool.Encrypted && pool.KeyDescription[0] && pool.KeyDescription[1][1]); @@ -542,8 +540,7 @@ const StratisPoolContainer = ({ container, pool, degraded_ops, can_grow, managed const use = pool.TotalPhysicalUsed[0] && [Number(pool.TotalPhysicalUsed[1]), Number(pool.TotalPhysicalSize)]; return ( - }> + - + ); }; -const StratisBlockdevsContainer = ({ container, pool }) => { +const StratisBlockdevsCard = ({ card, pool }) => { return ( - }> + - + ); }; diff --git a/pkg/storaged/stratis/stopped-pool.jsx b/pkg/storaged/stratis/stopped-pool.jsx index 5599378ac888..5d93b9301769 100644 --- a/pkg/storaged/stratis/stopped-pool.jsx +++ b/pkg/storaged/stratis/stopped-pool.jsx @@ -22,13 +22,11 @@ import React from "react"; import client from "../client"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; import { List, ListItem } from "@patternfly/react-core/dist/esm/components/List/index.js"; -import { SCard } from "../utils/card.jsx"; import { SDesc } from "../utils/desc.jsx"; -import { PageContainerStackItems, PageTable, ActionButtons, new_page, new_container, page_type, get_crossrefs } from "../pages.jsx"; +import { StorageCard, PageTable, new_page, new_card, get_crossrefs } from "../pages.jsx"; import { dialog_open, PassInput } from "../dialog.jsx"; import { std_reply, with_stored_passphrase } from "./utils.jsx"; @@ -89,31 +87,30 @@ function start_pool(uuid, show_devs) { } export function make_stratis_stopped_pool_page(parent, uuid) { - const blockdevs_container = new_container({ - parent: null, - component: StratisStoppedBlockdevsContainer, + const blockdevs_card = new_card({ + title: _("Stratis block devices"), + next: null, + component: StratisStoppedBlockdevsCard, props: { uuid }, }); - new_page({ - location: ["pool", uuid], - parent, - container: blockdevs_container, - name: uuid, - columns: [ - _("Stopped Stratis pool"), - null, - null, - ], - component: StoppedStratisPoolPage, + const pool_card = new_card({ + title: _("Stratis pool"), + type_extra: _("stopped"), + next: blockdevs_card, + page_location: ["pool", uuid], + page_name: uuid, + component: StoppedStratisPoolCard, props: { uuid }, actions: [ - { title: _("Start"), action: from_menu => start_pool(uuid, from_menu), }, // XXX - show_devs? + { title: _("Start"), action: from_menu => start_pool(uuid, from_menu), }, ], }); + + new_page(parent, pool_card); } -const StoppedStratisPoolPage = ({ page, uuid }) => { +const StoppedStratisPoolCard = ({ card, uuid }) => { const key_desc = client.stratis_stopped_pool_key_description[uuid]; const clevis_info = client.stratis_stopped_pool_clevis_info[uuid]; @@ -122,37 +119,32 @@ const StoppedStratisPoolPage = ({ page, uuid }) => { const tang_url = (can_tang && clevis_info) ? JSON.parse(clevis_info[1]).url : null; return ( - - - }> - - - - { encrypted && client.features.stratis_crypto_binding && - - { key_desc ? cockpit.format(_("using key description $0"), key_desc) : _("none") } - - } - { can_tang && client.features.stratis_crypto_binding && - - } - - - - - - + + + + + { encrypted && client.features.stratis_crypto_binding && + + { key_desc ? cockpit.format(_("using key description $0"), key_desc) : _("none") } + + } + { can_tang && client.features.stratis_crypto_binding && + + } + + + ); }; -const StratisStoppedBlockdevsContainer = ({ container, uuid }) => { +const StratisStoppedBlockdevsCard = ({ card, uuid }) => { return ( - }> + - + ); }; diff --git a/pkg/storaged/swap/swap.jsx b/pkg/storaged/swap/swap.jsx index 11b24029dc65..dc0f725b93c7 100644 --- a/pkg/storaged/swap/swap.jsx +++ b/pkg/storaged/swap/swap.jsx @@ -22,37 +22,25 @@ import React from "react"; import client from "../client"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; -import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; import { useEvent } from "hooks"; -import { - PageContainerStackItems, - new_page, block_location, ActionButtons, page_type, -} from "../pages.jsx"; -import { SCard } from "../utils/card.jsx"; +import { StorageCard, new_page, new_card } from "../pages.jsx"; import { SDesc } from "../utils/desc.jsx"; import { format_dialog } from "../block/format-dialog.jsx"; -import { block_name, fmt_size, decode_filename } from "../utils.js"; +import { fmt_size, decode_filename } from "../utils.js"; import { std_lock_action } from "../crypto/actions.jsx"; -import { StorageSize } from "../storage-controls.jsx"; const _ = cockpit.gettext; -export function make_swap_page(parent, backing_block, content_block, container) { +export function make_swap_page(parent, backing_block, content_block, next_card) { const block_swap = client.blocks_swap[content_block.path]; - new_page({ - location: [block_location(backing_block)], - parent, - container, - name: block_name(backing_block), - columns: [ - _("Swap"), - null, - , - ], - component: SwapPage, + const swap_card = new_card({ + title: _("Swap"), + next: next_card, + page_block: backing_block, + component: SwapCard, props: { block: content_block, block_swap }, actions: [ std_lock_action(backing_block, content_block), @@ -65,9 +53,11 @@ export function make_swap_page(parent, backing_block, content_block, container) { title: _("Format"), action: () => format_dialog(client, backing_block.path), danger: true }, ] }); + + new_page(parent, swap_card); } -export const SwapPage = ({ page, block, block_swap }) => { +export const SwapCard = ({ card, block, block_swap }) => { const is_active = block_swap && block_swap.Active; let used; @@ -84,16 +74,12 @@ export const SwapPage = ({ page, block, block_swap }) => { } return ( - - - }> - - - - - - - - - ); + + + + + + + + ); }; diff --git a/pkg/storaged/utils/card.jsx b/pkg/storaged/utils/card.jsx deleted file mode 100644 index b101664dd8de..000000000000 --- a/pkg/storaged/utils/card.jsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 React from "react"; - -import { Card, CardHeader, CardTitle } from "@patternfly/react-core/dist/esm/components/Card/index.js"; - -export const SCard = ({ title, actions, children }) => { - return ( - - - {title} - - {children} - ); -}; diff --git a/test/verify/check-storage-luks b/test/verify/check-storage-luks index 62a6bb484d5b..5a8bd18e58e1 100755 --- a/test/verify/check-storage-luks +++ b/test/verify/check-storage-luks @@ -205,7 +205,7 @@ class TestStorageLuks(storagelib.StorageCase): # Lock it b.click(self.card_button("Unformatted data", "Lock")) - b.wait_visible(self.card("Locked encrypted data")) + b.wait_visible(self.card("Locked data")) # Make it readonly b.click(self.card_desc_action("Encryption", "Options")) @@ -213,7 +213,7 @@ class TestStorageLuks(storagelib.StorageCase): self.assertNotEqual(m.execute("grep readonly /etc/crypttab"), "") # Unlock it - b.click(self.card_button("Locked encrypted data", "Unlock")) + b.click(self.card_button("Locked data", "Unlock")) self.dialog({"passphrase": "vainu-reku-toma-rolle-kaja"}) # Try to format it, just for kicks diff --git a/test/verify/check-storage-stratis b/test/verify/check-storage-stratis index dd84319d223f..6bfcd67c6e13 100755 --- a/test/verify/check-storage-stratis +++ b/test/verify/check-storage-stratis @@ -111,7 +111,7 @@ class TestStorageStratis(storagelib.StorageCase): # Stop the pool (only works with Stratis 3) pool_uuid = m.execute("stratis --unhyphenated-uuids pool list --name pool0 | grep ^UUID | cut -d' ' -f2").strip() m.execute(f"stratis pool stop {self.stop_type_opt} pool0") - b.wait_in_text(self.card_row("Storage", name=pool_uuid), "Stopped Stratis pool") + b.wait_in_text(self.card_row("Storage", name=pool_uuid), "Stratis pool (stopped)") # Start it b.clicks(self.dropdown_action(self.card_row("Storage", name=pool_uuid), "Start")) @@ -520,12 +520,12 @@ class TestStorageStratisReboot(storagelib.StorageCase): b.relogin() b.enter_page("/storage") - b.wait_visible(self.card("Stopped Stratis pool")) + b.wait_visible(self.card("Stratis pool (stopped)")) b.wait_in_text(self.card("Stratis block devices"), "DISK1") b.wait_in_text(self.card("Stratis block devices"), "DISK2") # Unlock the pool - b.click(self.card_button("Stopped Stratis pool", "Start")) + b.click(self.card_button("Stratis pool (stopped)", "Start")) self.dialog_wait_open() self.dialog_set_val('passphrase', "wrong-passphrase") self.dialog_apply() @@ -877,9 +877,9 @@ class TestStorageStratisNBDE(packagelib.PackageCase, storagelib.StorageCase): # Stop the pool and start it again. This should not ask # for the passphrase (since there isn't any) m.execute(f"stratis pool stop {self.stop_type_opt} pool0") - b.wait_visible(self.card("Stopped Stratis pool")) + b.wait_visible(self.card("Stratis pool (stopped)")) tang_m.execute("systemctl stop tangd.socket") - b.click(self.card_button("Stopped Stratis pool", "Start")) + b.click(self.card_button("Stratis pool (stopped)", "Start")) self.dialog_wait_open() # stratis' error message for unreachable tang server is very poor: # https://bugzilla.redhat.com/show_bug.cgi?id=2246920 Version < 3.6.0 said @@ -889,7 +889,7 @@ class TestStorageStratisNBDE(packagelib.PackageCase, storagelib.StorageCase): self.dialog_wait_close() tang_m.execute("systemctl start tangd.socket") - b.click(self.card_button("Stopped Stratis pool", "Start")) + b.click(self.card_button("Stratis pool (stopped)", "Start")) b.wait_visible(self.card("Encrypted Stratis pool")) # Put passphrase back and do the stopping starting again, but @@ -902,7 +902,7 @@ class TestStorageStratisNBDE(packagelib.PackageCase, storagelib.StorageCase): b.wait_visible(self.card_desc("Encrypted Stratis pool", "Passphrase") + " button:contains(Remove):not(:disabled)") m.execute(f"stratis pool stop {self.stop_type_opt} pool0") tang_m.execute("systemctl stop tangd.socket") - b.click(self.card_button("Stopped Stratis pool", "Start")) + b.click(self.card_button("Stratis pool (stopped)", "Start")) self.dialog_wait_open() self.dialog_set_val("passphrase", "foobar") self.dialog_cancel() @@ -910,7 +910,7 @@ class TestStorageStratisNBDE(packagelib.PackageCase, storagelib.StorageCase): # Finally start tang tang_m.execute("systemctl start tangd.socket") - b.click(self.card_button("Stopped Stratis pool", "Start")) + b.click(self.card_button("Stratis pool (stopped)", "Start")) b.wait_visible(self.card("Encrypted Stratis pool")) # Add a blockdevice. This requires the passphrase.