Line data Source code
1 : /*
2 : * Copyright (C) 2023 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 113 : import cockpit from "cockpit";
7 113 : import React from "react";
8 : import client from "../client";
9 :
10 : import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
11 : import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
12 : import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
13 :
14 : import { StorageButton, StorageLink } from "../storage-controls.jsx";
15 : import {
16 : dialog_open,
17 : SelectOne, TextInput,
18 : init_teardown_usage, BlockingMessage, TeardownMessage
19 : } from "../dialog.jsx";
20 : import { block_name, fmt_size, get_active_usage, teardown_active_usage, reload_systemd } from "../utils.js";
21 : import { check_unused_space, get_resize_info, free_space_after_part, grow_dialog, shrink_dialog } from "../block/resize.jsx";
22 : import { StorageCard, StorageDescription, new_card, navigate_away_from_card } from "../pages.jsx";
23 :
24 113 : const _ = cockpit.gettext;
25 :
26 5 : export function delete_partition(block, card) {
27 5 : const block_part = client.blocks_part[block.path];
28 5 : const name = block_name(block);
29 5 : const usage = get_active_usage(client, block.path, _("delete"));
30 :
31 0 : if (usage.Blocking) {
32 0 : dialog_open({
33 0 : Title: cockpit.format(_("$0 is in use"), name),
34 0 : Body: BlockingMessage(usage)
35 0 : });
36 0 : return;
37 0 : }
38 :
39 5 : dialog_open({
40 5 : Title: cockpit.format(_("Permanently delete $0?"), name),
41 5 : Teardown: TeardownMessage(usage),
42 5 : Action: {
43 5 : Danger: _("Deleting a partition will delete all data in it."),
44 5 : Title: _("Delete"),
45 4 : action: async function () {
46 4 : await teardown_active_usage(client, usage);
47 4 : await block_part.Delete({ 'tear-down': { t: 'b', v: true } });
48 4 : await reload_systemd();
49 4 : navigate_away_from_card(card);
50 4 : }
51 5 : },
52 5 : Inits: [
53 5 : init_teardown_usage(client, usage)
54 5 : ]
55 5 : });
56 5 : }
57 :
58 104 : export function make_partition_card(next, block) {
59 104 : const block_part = client.blocks_part[block.path];
60 104 : const unused_space_warning = check_unused_space(block.path);
61 104 : const unused_space = !!unused_space_warning;
62 104 : let { info, shrink_excuse, grow_excuse } = get_resize_info(client, block, unused_space);
63 :
64 104 : if (!unused_space_warning && !grow_excuse && free_space_after_part(client, block_part) == 0) {
65 104 : grow_excuse = _("No free space after this partition");
66 104 : }
67 :
68 104 : const type = block_part.Type.replace(/^0x/, "").toLowerCase();
69 104 : let type_extra;
70 104 : if (type == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" || type == "ef")
71 102 : type_extra = _("EFI system partition");
72 104 : else if (type == "21686148-6449-6e6f-744e-656564454649")
73 102 : type_extra = _("BIOS boot partition");
74 :
75 104 : const card = new_card({
76 104 : title: _("Partition"),
77 104 : type_extra,
78 104 : next,
79 104 : page_block: block,
80 104 : for_summary: true,
81 104 : has_warning: !!unused_space_warning,
82 104 : component: PartitionCard,
83 104 : props: { block, unused_space_warning, resize_info: info },
84 104 : actions: [
85 104 : (!unused_space &&
86 104 : {
87 104 : title: _("Shrink"),
88 1 : action: () => shrink_dialog(client, block_part, info),
89 104 : excuse: shrink_excuse,
90 104 : }),
91 104 : (!unused_space &&
92 104 : {
93 104 : title: _("Grow"),
94 1 : action: () => grow_dialog(client, block_part, info),
95 104 : excuse: grow_excuse,
96 104 : }),
97 104 : {
98 104 : title: _("Delete"),
99 4 : action: () => delete_partition(block, card),
100 104 : danger: true,
101 104 : },
102 104 : ],
103 104 : });
104 104 : return card;
105 104 : }
106 :
107 113 : const gpt_type_names = {
108 113 : "21686148-6449-6e6f-744e-656564454649": _("BIOS boot partition"),
109 113 : "c12a7328-f81f-11d2-ba4b-00a0c93ec93b": _("EFI system partition"),
110 113 : "9e1a2d38-c612-4316-aa26-8b49521e5a8b": _("PowerPC PReP boot partition"),
111 113 : "0657fd6d-a4ab-43c4-84e5-0933c84b4f4f": _("Linux swap space"),
112 113 : "0fc63daf-8483-4772-8e79-3d69d8477de4": _("Linux filesystem data"),
113 113 : "e6d6d379-f507-44c2-a23c-238f2a3df928": _("Logical Volume Manager partition"),
114 113 : };
115 :
116 113 : const mbr_type_names = {
117 113 : ef: _("EFI system partition"),
118 113 : 82: _("Linux swap space"),
119 113 : 83: _("Linux filesystem data"),
120 113 : "8e": _("Logical Volume Manager partition"),
121 113 : };
122 :
123 9 : const PartitionCard = ({ card, block, unused_space_warning, resize_info }) => {
124 9 : const block_part = client.blocks_part[block.path];
125 9 : const unused_space = !!unused_space_warning;
126 9 : const block_ptable = client.blocks_ptable[block_part.Table];
127 1 : const type_names = block_ptable?.Type == "dos" ? mbr_type_names : gpt_type_names;
128 9 : const type = block_part.Type.replace(/^0x/, "").toLowerCase();
129 :
130 1 : function shrink_to_fit() {
131 1 : return shrink_dialog(client, block_part, resize_info, true);
132 1 : }
133 :
134 1 : function grow_to_fit() {
135 1 : return grow_dialog(client, block_part, resize_info, true);
136 1 : }
137 :
138 4 : function set_type_dialog() {
139 4 : const choices = Object.keys(type_names).map(k => ({ value: k, title: type_names[k] }));
140 4 : choices.push({ title: _("Custom"), value: "custom" });
141 :
142 1 : function validate_type(val) {
143 1 : if (block_ptable.Type == "dos") {
144 1 : const hex_rx = /^[a-fA-F0-9]{2}$/;
145 1 : if (!hex_rx.test(val))
146 1 : return _("Type must contain exactly two hexadecimal characters (0 to 9, A to F).");
147 1 : } else {
148 : /* We let people use any 128 bit value as a UUID, even
149 : those that are not defined by RFC 4122. This is
150 : what the rest of the storage stack does as well,
151 : and the "BIOS boot" UUID is in fact invalid
152 : according to RFC 4122.
153 :
154 : But we do insist that the dashes are in the right
155 : place, because UDisks2 does as well.
156 : */
157 1 : const uuid_rx_1 = /^[a-fA-F0-9-]*$/;
158 1 : if (!uuid_rx_1.test(val))
159 1 : return _("Type can only contain the characters 0 to 9, A to F, and \"-\".");
160 1 : const uuid_rx_2 = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;
161 1 : if (!uuid_rx_2.test(val))
162 1 : return _("Type must be of the form NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN.");
163 1 : }
164 1 : }
165 :
166 4 : dialog_open({
167 4 : Title: cockpit.format(_("Set partition type of $0"), block_name(block)),
168 4 : Fields: [
169 4 : SelectOne("type", _("Type"),
170 4 : {
171 0 : value: (type_names[type] ? type : "custom"),
172 4 : choices,
173 4 : }),
174 4 : TextInput("custom", _("Custom type"),
175 4 : {
176 4 : value: type,
177 4 : validate: validate_type,
178 4 : visible: vals => vals.type == "custom",
179 4 : }),
180 4 : ],
181 4 : Action: {
182 2 : Danger: !client.in_anaconda_mode() && _("Changing partition types might prevent the system from booting."),
183 4 : Title: _("Save"),
184 4 : action: async function (vals) {
185 1 : let t = vals.type == "custom" ? vals.custom : vals.type;
186 4 : if (block_ptable?.Type == "dos")
187 1 : t = "0x" + t;
188 4 : await block_part.SetType(t, { });
189 4 : }
190 4 : },
191 4 : });
192 4 : }
193 :
194 9 : return (
195 9 : <StorageCard card={card}
196 9 : alert={unused_space &&
197 1 : <Alert variant="warning" isInline
198 1 : title={_("This partition is not completely used by its content.")}>
199 1 : {cockpit.format(_("Partition size is $0. Content size is $1."),
200 1 : fmt_size(unused_space_warning.volume_size),
201 1 : fmt_size(unused_space_warning.content_size))}
202 1 : <div className='storage-alert-actions'>
203 1 : <StorageButton onClick={shrink_to_fit}>
204 1 : {_("Shrink partition")}
205 1 : </StorageButton>
206 1 : <StorageButton onClick={grow_to_fit}>
207 1 : {_("Grow content")}
208 1 : </StorageButton>
209 1 : </div>
210 1 : </Alert>}>
211 9 : <CardBody>
212 9 : <DescriptionList className="pf-m-horizontal-on-sm">
213 8 : <StorageDescription title={_("Name")} value={block_part.Name || "-"} />
214 9 : <StorageDescription title={_("UUID")} value={block_part.UUID} />
215 9 : <StorageDescription title={_("Type")}
216 2 : value={type_names[type] || type}
217 9 : action={<StorageLink onClick={set_type_dialog}>
218 9 : {_("edit")}
219 9 : </StorageLink>} />
220 9 : { !unused_space &&
221 9 : <StorageDescription title={_("Size")} value={fmt_size(block_part.Size)} />
222 : }
223 9 : </DescriptionList>
224 9 : </CardBody>
225 9 : </StorageCard>);
226 9 : };
|