Skip to content

Commit

Permalink
WIP - redesign
Browse files Browse the repository at this point in the history
  • Loading branch information
mvollmer committed Oct 12, 2023
1 parent 5d5b420 commit 2c0f895
Show file tree
Hide file tree
Showing 44 changed files with 5,488 additions and 149 deletions.
74 changes: 74 additions & 0 deletions pkg/storaged/actions.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

import cockpit from "cockpit";
import client from "./client";

import { get_existing_passphrase, unlock_with_type } from "./crypto-keyslots.jsx"; // XXX
import { set_crypto_auto_option } from "./utils.js";
import { dialog_open, PassInput } from "./dialog.jsx";

const _ = cockpit.gettext;

export function unlock(block) {
const crypto = client.blocks_crypto[block.path];
if (!crypto)
return;

function unlock_with_passphrase() {
const crypto = client.blocks_crypto[block.path];
if (!crypto)
return;

dialog_open({
Title: _("Unlock"),
Fields: [
PassInput("passphrase", _("Passphrase"), {})
],
Action: {
Title: _("Unlock"),
action: function (vals) {
return (crypto.Unlock(vals.passphrase, {})
.then(() => set_crypto_auto_option(block, true)));
}
}
});
}

return get_existing_passphrase(block, true).then(type => {
return (unlock_with_type(client, block, null, type)
.then(() => set_crypto_auto_option(block, true))
.catch(() => unlock_with_passphrase()));
});
}

export function lock(block) {
const crypto = client.blocks_crypto[block.path];
if (!crypto)
return;

return crypto.Lock({}).then(() => set_crypto_auto_option(block, false));
}

export function std_lock_action(backing_block, content_block) {
if (backing_block == content_block)
return null;

return { title: _("Lock"), action: () => lock(backing_block) };
}
26 changes: 24 additions & 2 deletions pkg/storaged/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import vdo_monitor_py from "./vdo-monitor.py";
import stratis2_set_key_py from "./stratis2-set-key.py";
import stratis3_set_key_py from "./stratis3-set-key.py";

import { create_pages } from "./create-pages.jsx";

/* STORAGED CLIENT
*/

Expand Down Expand Up @@ -544,6 +546,25 @@ function update_indices() {
client.blocks_partitions[path].sort(function (a, b) { return a.Offset - b.Offset });
}

client.iscsi_sessions_drives = { };
client.drives_iscsi_session = { };
for (path in client.drives) {
const block = client.drives_block[path];
if (!block)
continue;
for (const session_path in client.iscsi_sessions) {
const session = client.iscsi_sessions[session_path];
for (i = 0; i < block.Symlinks.length; i++) {
if (utils.decode_filename(block.Symlinks[i]).includes(session.data.target_name)) {
client.drives_iscsi_session[path] = session;
if (!client.iscsi_sessions_drives[session_path])
client.iscsi_sessions_drives[session_path] = [];
client.iscsi_sessions_drives[session_path].push(client.drives[path]);
}
}
}
}

client.path_jobs = { };
function enter_job(job) {
if (!job.Objects || !job.Objects.length)
Expand All @@ -566,6 +587,7 @@ function update_indices() {
client.update = () => {
update_indices();
client.path_warnings = find_warnings(client);
create_pages();
client.dispatchEvent("changed");
};

Expand Down Expand Up @@ -844,11 +866,11 @@ function nfs_mounts() {
.then(function (output) {
const data = JSON.parse(output);
self.fsys_sizes[path] = [(data[2] - data[1]) * data[0], data[2] * data[0]];
client.dispatchEvent('changed');
client.update();
})
.catch(function () {
self.fsys_sizes[path] = [0, 0];
client.dispatchEvent('changed');
client.update();
});

return null;
Expand Down
265 changes: 265 additions & 0 deletions pkg/storaged/containers/encryption.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

import cockpit from "cockpit";
import React from "react";
import client from "../client";

import { DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
import { Card, CardHeader, CardTitle, CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
import { useObject, useEvent } from "hooks";
import * as python from "python.js";
import * as timeformat from "timeformat.js";

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 luksmeta_monitor_hack_py from "../luksmeta-monitor-hack.py";
import { is_mounted } from "../fsys-tab.jsx"; // XXX
import { StorageLink } from "../storage-controls.jsx";
import { CryptoKeyslots } from "../crypto-keyslots.jsx";

const _ = cockpit.gettext;

export function make_encryption_container(parent, block) {
const cleartext = client.blocks_cleartext[block.path];
return new_container({
parent,
type_format: cleartext ? _("$0 (encrypted)") : _("$0 (locked)"), // XXX - icons?
component: EncryptionContainer,
props: { block },
});
}

function monitor_luks(block) {
const self = {
stop,

luks_version: null,
slots: null,
slot_error: null,
max_slots: null,
};

cockpit.event_target(self);

const dev = decode_filename(block.Device);
const channel = python.spawn(luksmeta_monitor_hack_py, [dev], { superuser: true });
let buf = "";

channel.stream(output => {
buf += output;
const lines = buf.split("\n");
buf = lines[lines.length - 1];
if (lines.length >= 2) {
const data = JSON.parse(lines[lines.length - 2]);
self.slots = data.slots;
self.luks_version = data.version;
self.max_slots = data.max_slots;
self.dispatchEvent("changed");
}
});

channel.catch(err => {
self.slots = [];
self.slot_error = err;
self.dispatchEvent("changed");
});

function stop() {
channel.close();
}

return self;
}

function parse_tag_mtime(tag) {
if (tag && tag.indexOf("1:") == 0) {
try {
const parts = tag.split("-")[1].split(".");
// s:ns → ms
const mtime = parseInt(parts[0]) * 1000 + parseInt(parts[1]) * 1e-6;
return cockpit.format(_("Last modified: $0"), timeformat.dateTime(mtime));
} catch {
return null;
}
} else
return null;
}

function monitor_mtime(path) {
const self = {
stop,

mtime: 0
};

cockpit.event_target(self);

let file = null;
if (path) {
file = cockpit.file(path, { superuser: true });
file.watch((_, tag) => { self.mtime = parse_tag_mtime(tag); self.dispatchEvent("changed") },
{ read: false });
}

function stop() {
if (file)
file.close();
}

return self;
}

const EncryptionContainer = ({ container, block }) => {
const luks_info = useObject(() => monitor_luks(block),
m => m.stop(),
[block]);
useEvent(luks_info, "changed");

let old_options, passphrase_path;
const old_config = block.Configuration.find(c => c[0] == "crypttab");
if (old_config) {
old_options = (decode_filename(old_config[1].options.v)
.split(",")
.filter(function (s) { return s.indexOf("x-parent") !== 0 })
.join(","));
passphrase_path = decode_filename(old_config[1]["passphrase-path"].v);
}

const stored_passphrase_info = useObject(() => monitor_mtime(passphrase_path),
m => m.stop(),
[passphrase_path]);
useEvent(stored_passphrase_info, "changed");

const split_options = parse_options(old_options);
let opt_noauto = extract_option(split_options, "noauto");
const extra_options = unparse_options(split_options);

function edit_stored_passphrase() {
edit_crypto_config(block, function (config, commit) {
dialog_open({
Title: _("Stored passphrase"),
Fields: [
PassInput("passphrase", _("Stored passphrase"),
{
value: (config && config['passphrase-contents']
? decode_filename(config['passphrase-contents'].v)
: "")
})
],
Action: {
Title: _("Save"),
action: function (vals) {
config["passphrase-contents"] = {
t: 'ay',
v: encode_filename(vals.passphrase)
};
delete config["passphrase-path"];
return commit();
}
}
});
});
}

function edit_options() {
const fsys_config = client.blocks_crypto[block.path]?.ChildConfiguration.find(c => c[0] == "fstab");
const content_block = client.blocks_cleartext[block.path];
const is_fsys = fsys_config || (content_block && content_block.IdUsage == "filesystem");

edit_crypto_config(block, function (config, commit) {
dialog_open({
Title: _("Encryption options"),
Fields: [
TextInput("options", "", { value: extra_options }),
],
isFormHorizontal: false,
Action: {
Title: _("Save"),
action: function (vals) {
let opts = [];
if (is_fsys && content_block)
opt_noauto = !is_mounted(client, content_block);
if (opt_noauto)
opts.push("noauto");
opts = opts.concat(parse_options(vals.options));
config.options = {
t: 'ay',
v: encode_filename(unparse_options(opts))
};
return commit();
}
}
});
});
}

const cleartext = client.blocks_cleartext[block.path];

const option_parts = [];
if (extra_options)
option_parts.push(extra_options);
const options = option_parts.join(", ");

return (
<Card>
<CardHeader>
<CardTitle component="h2">{_("Encryption")}</CardTitle>
</CardHeader>
<CardBody>
<DescriptionList className="pf-m-horizontal-on-sm ct-wide-labels">
<DescriptionListGroup>
<DescriptionListTerm>{_("Encryption type")}</DescriptionListTerm>
<DescriptionListDescription>
{ luks_info.luks_version ? "LUKS" + luks_info.luks_version : "-" }
</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>{_("Cleartext device")}</DescriptionListTerm>
<DescriptionListDescription>
{cleartext ? block_name(cleartext) : "-"}
</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>{_("Stored passphrase")}</DescriptionListTerm>
<DescriptionListDescription>
<Flex>
<FlexItem>{ passphrase_path ? stored_passphrase_info.mtime || _("yes") : _("none") }</FlexItem>
<FlexItem><StorageLink onClick={edit_stored_passphrase}>{_("edit")}</StorageLink></FlexItem>
</Flex>
</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>{_("Options")}</DescriptionListTerm>
<DescriptionListDescription>
<Flex>
<FlexItem>{ options || _("none") }</FlexItem>
<FlexItem><StorageLink onClick={edit_options}>{_("edit")}</StorageLink></FlexItem>
</Flex>
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</CardBody>
<CryptoKeyslots client={client} block={block}
slots={luks_info.slots} slot_error={luks_info.slot_error}
max_slots={luks_info.max_slots} />
</Card>);
};
Loading

0 comments on commit 2c0f895

Please sign in to comment.