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 { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
11 : import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
12 :
13 : import {
14 : dialog_open, TextInput, CheckBoxes, SizeSlider, BlockingMessage, TeardownMessage,
15 : init_teardown_usage,
16 : } from "../dialog.jsx";
17 : import { StorageUsageBar, StorageLink } from "../storage-controls.jsx";
18 : import {
19 : StorageCard, StorageDescription,
20 : new_page, new_card,
21 : navigate_away_from_card, navigate_to_new_card_location,
22 : } from "../pages.jsx";
23 : import {
24 : MountPoint, edit_mount_point,
25 : is_valid_mount_point, is_mounted,
26 : get_fstab_config, mount_point_text,
27 : } from "../filesystem/utils.jsx";
28 : import { MismountAlert, check_mismounted_fsys } from "../filesystem/mismounting.jsx";
29 : import { mounting_dialog, at_boot_input, update_at_boot_input, mount_options } from "../filesystem/mounting-dialog.jsx";
30 : import { fmt_size, get_active_usage, teardown_active_usage } from "../utils.js";
31 : import { std_reply, validate_fs_name, set_mount_options, destroy_filesystem } from "./utils.jsx";
32 :
33 113 : const _ = cockpit.gettext;
34 :
35 13 : export function make_stratis_filesystem_page(parent, pool, fsys,
36 13 : offset, forced_options) {
37 13 : const filesystems = client.stratis_pool_filesystems[pool.path];
38 13 : const stats = client.stratis_pool_stats[pool.path];
39 13 : const block = client.slashdevs_block[fsys.Devnode];
40 :
41 13 : if (!block)
42 13 : return;
43 :
44 13 : const fstab_config = get_fstab_config(block);
45 13 : const [, mount_point] = fstab_config;
46 13 : const fs_is_mounted = is_mounted(client, block);
47 :
48 13 : const mismount_warning = check_mismounted_fsys(block, block, fstab_config);
49 :
50 2 : function mount() {
51 2 : return mounting_dialog(client, block, "mount", forced_options);
52 2 : }
53 :
54 3 : function unmount() {
55 3 : return mounting_dialog(client, block, "unmount", forced_options);
56 3 : }
57 :
58 4 : function snapshot_fsys() {
59 4 : dialog_open({
60 4 : Title: cockpit.format(_("Create a snapshot of filesystem $0"), fsys.Name),
61 4 : Fields: [
62 4 : TextInput("name", _("Name"),
63 4 : {
64 4 : value: "",
65 4 : validate: name => validate_fs_name(null, name, filesystems)
66 4 : }),
67 4 : TextInput("mount_point", _("Mount point"),
68 4 : {
69 4 : validate: (val, values, variant) => {
70 4 : return is_valid_mount_point(client,
71 4 : null,
72 4 : client.add_mount_point_prefix(val),
73 4 : variant == "nomount");
74 4 : }
75 4 : }),
76 4 : mount_options(false, false),
77 4 : at_boot_input(),
78 4 : ],
79 4 : update: update_at_boot_input,
80 4 : Action: {
81 4 : Title: _("Create snapshot and mount"),
82 4 : Variants: [{ tag: "nomount", Title: _("Create snapshot only") }],
83 4 : action: function (vals) {
84 4 : return pool.SnapshotFilesystem(fsys.path, vals.name)
85 4 : .then(std_reply)
86 4 : .then(result => {
87 4 : if (result[0])
88 0 : return set_mount_options(result[1], vals, forced_options);
89 : else
90 0 : return Promise.resolve();
91 4 : });
92 4 : }
93 4 : }
94 4 : });
95 4 : }
96 :
97 5 : function delete_fsys() {
98 5 : const usage = get_active_usage(client, block.path, _("delete"));
99 :
100 0 : if (usage.Blocking) {
101 0 : dialog_open({
102 0 : Title: cockpit.format(_("$0 is in use"),
103 0 : fsys.Name),
104 0 : Body: BlockingMessage(usage)
105 0 : });
106 0 : return;
107 0 : }
108 :
109 5 : dialog_open({
110 5 : Title: cockpit.format(_("Confirm deletion of $0"), fsys.Name),
111 5 : Teardown: TeardownMessage(usage),
112 5 : Action: {
113 5 : Danger: _("Deleting a filesystem will delete all data in it."),
114 5 : Title: _("Delete"),
115 5 : action: async function () {
116 5 : await teardown_active_usage(client, usage);
117 5 : await destroy_filesystem(fsys);
118 5 : navigate_away_from_card(fsys_card);
119 5 : }
120 5 : },
121 5 : Inits: [
122 5 : init_teardown_usage(client, usage)
123 5 : ]
124 5 : });
125 5 : }
126 :
127 13 : const mp_text = mount_point_text(mount_point, fs_is_mounted);
128 13 : if (mp_text == null)
129 4 : return null;
130 :
131 13 : const fsys_card = new_card({
132 13 : title: _("Stratis filesystem"),
133 13 : location: mp_text,
134 13 : next: null,
135 13 : page_location: ["pool", pool.Name, fsys.Name],
136 13 : page_name: fsys.Name,
137 13 : page_size: <StorageUsageBar stats={[Number(fsys.Used[0] && Number(fsys.Used[1])), stats.pool_total]}
138 13 : critical={1} total={stats.fsys_total_used} offset={offset} short />,
139 13 : has_warning: !!mismount_warning,
140 13 : component: StratisFilesystemCard,
141 13 : props: { pool, fsys, fstab_config, forced_options, mismount_warning, offset },
142 13 : actions: [
143 13 : client.in_anaconda_mode() &&
144 1 : { title: _("Edit mount point"), action: () => edit_mount_point(block, forced_options) },
145 13 : (fs_is_mounted
146 9 : ? { title: _("Unmount"), action: unmount }
147 13 : : null),
148 13 : (!fs_is_mounted && !client.in_anaconda_mode()
149 12 : ? { title: _("Mount"), action: mount }
150 9 : : null),
151 13 : {
152 13 : title: _("Snapshot"),
153 13 : action: snapshot_fsys,
154 6 : excuse: ((!pool.Overprovisioning && stats.pool_free < Number(fsys.Size))
155 6 : ? _("Not enough free space")
156 13 : : null),
157 13 : },
158 13 : { title: _("Delete"), action: delete_fsys, danger: true },
159 13 : ]
160 13 : });
161 :
162 13 : new_page(parent, fsys_card);
163 13 : }
164 :
165 7 : const StratisFilesystemCard = ({
166 7 : card, pool, fsys, fstab_config, forced_options, mismount_warning, offset,
167 7 : }) => {
168 7 : const filesystems = client.stratis_pool_filesystems[pool.path];
169 7 : const stats = client.stratis_pool_stats[pool.path];
170 7 : const block = client.slashdevs_block[fsys.Devnode];
171 :
172 2 : function rename_fsys() {
173 2 : dialog_open({
174 2 : Title: _("Rename filesystem"),
175 2 : Fields: [
176 2 : TextInput("name", _("Name"),
177 2 : {
178 2 : value: fsys.Name,
179 2 : validate: name => validate_fs_name(fsys, name, filesystems)
180 2 : })
181 2 : ],
182 2 : Action: {
183 2 : Title: _("Rename"),
184 2 : action: async function (vals) {
185 2 : await fsys.SetName(vals.name).then(std_reply);
186 2 : navigate_to_new_card_location(card, ["pool", pool.Name, vals.name]);
187 2 : }
188 2 : }
189 2 : });
190 2 : }
191 :
192 2 : function set_limit() {
193 2 : dialog_open({
194 2 : Title: _("Set limit of virtual filesystem size"),
195 2 : Fields: [
196 2 : CheckBoxes("size_options", _("Options"),
197 2 : {
198 2 : value: {
199 2 : custom_limit: fsys.SizeLimit[0],
200 2 : },
201 2 : fields: [
202 2 : { tag: "custom_limit", title: _("Limit virtual filesystem size") },
203 2 : ]
204 2 : }),
205 2 : SizeSlider("limit", _("Virtual size limit"),
206 2 : {
207 2 : visible: vals => vals.size_options.custom_limit,
208 2 : value: fsys.SizeLimit[0] && Number(fsys.SizeLimit[1]),
209 2 : min: Number(fsys.Size),
210 0 : max: pool.Overprovisioning ? stats.pool_total : stats.pool_free + Number(fsys.Size),
211 2 : allow_infinite: true,
212 2 : round: 512
213 2 : }),
214 2 : ],
215 2 : Action: {
216 2 : Title: _("Set"),
217 2 : action: async function (vals) {
218 2 : await client.stratis_set_property(fsys,
219 2 : "SizeLimit",
220 2 : "(bs)", (vals.size_options.custom_limit
221 2 : ? [true, vals.limit.toString()]
222 2 : : [false, ""]));
223 2 : }
224 2 : }
225 2 : });
226 2 : }
227 :
228 7 : return (
229 7 : <StorageCard card={card}
230 7 : alert={mismount_warning &&
231 1 : <MismountAlert warning={mismount_warning}
232 1 : fstab_config={fstab_config} forced_options={forced_options}
233 1 : backing_block={block} content_block={block} />}>
234 7 : <CardBody>
235 7 : <DescriptionList className="pf-m-horizontal-on-sm">
236 7 : <StorageDescription title={_("Name")}
237 7 : value={fsys.Name}
238 7 : action={<StorageLink onClick={rename_fsys}>
239 7 : {_("edit")}
240 7 : </StorageLink>} />
241 7 : <StorageDescription title={_("Mount point")}>
242 7 : <MountPoint fstab_config={fstab_config} forced_options={forced_options}
243 7 : backing_block={block} content_block={block} />
244 7 : </StorageDescription>
245 7 : <StorageDescription title={_("Usage")}>
246 7 : <StorageUsageBar stats={[Number(fsys.Used[0] && Number(fsys.Used[1])), stats.pool_total]}
247 7 : critical={1} total={stats.fsys_total_used} offset={offset} />
248 7 : </StorageDescription>
249 7 : <StorageDescription title={_("Virtual size")}
250 7 : value={fmt_size(Number(fsys.Size))} />
251 7 : <StorageDescription title={_("Virtual size limit")}
252 2 : value={fsys.SizeLimit[0] ? fmt_size(Number(fsys.SizeLimit[1])) : _("none")}
253 7 : action={<StorageLink onClick={set_limit}>
254 7 : {_("edit")}
255 7 : </StorageLink>} />
256 7 : </DescriptionList>
257 7 : </CardBody>
258 7 : </StorageCard>
259 : );
260 7 : };
|