/* * Copyright (C) 2021 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ import cockpit from "cockpit"; import React, { useState } from "react"; import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js"; import { ClipboardCopy, ClipboardCopyVariant } from "@patternfly/react-core/dist/esm/components/ClipboardCopy/index.js"; 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 { ActionGroup, Form, FormGroup } from "@patternfly/react-core/dist/esm/components/Form/index.js"; import { HelperText, HelperTextItem } from "@patternfly/react-core/dist/esm/components/HelperText/index.js"; import { Modal, ModalBody, ModalFooter, ModalHeader } from '@patternfly/react-core/dist/esm/components/Modal/index.js'; import { Popover } from "@patternfly/react-core/dist/esm/components/Popover/index.js"; import { Stack } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { Switch } from "@patternfly/react-core/dist/esm/components/Switch/index.js"; import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js"; import { InfoCircleIcon } from '@patternfly/react-icons'; import * as credentials from "credentials"; import { ListingPanel } from 'cockpit-components-listing-panel.jsx'; import { ListingTable } from 'cockpit-components-table.jsx'; import { ModalError } from 'cockpit-components-inline-notification.jsx'; import { useEvent, useObject } from 'hooks'; import { WithDialogs, useDialogs, DialogResult } from "dialogs"; import { FileChooser } from "cockpit/react/FileChooser"; import "./credentials.scss"; const _ = cockpit.gettext; const KeyDetails = ({ currentKey } : { currentKey: credentials.Key }) => { return ( {_("Comment")} {currentKey.comment} {_("Type")} {currentKey.type} {_("Fingerprint")} {currentKey.fingerprint} ); }; const PublicKey = ({ currentKey } : { currentKey: credentials.Key }) => { return ( {currentKey.data.trim()} ); }; const KeyPassword = ({ currentKey, keys, setDialogError } : { currentKey: credentials.Key, keys: credentials.Keys, setDialogError: (msg: string | null) => void }) => { const [confirmPassword, setConfirmPassword] = useState(''); const [inProgress, setInProgress] = useState(undefined); const [newPassword, setNewPassword] = useState(''); const [oldPassword, setOldPassword] = useState(''); function changePassword() { if (!currentKey || !currentKey.name) return; setInProgress(true); setDialogError(null); if (oldPassword === undefined || newPassword === undefined || confirmPassword === undefined) setDialogError("Invalid password fields"); keys.change(currentKey.name, oldPassword, newPassword) .then(() => { setOldPassword(''); setNewPassword(''); setConfirmPassword(''); setInProgress(false); }) .catch(ex => { setDialogError(ex.message); setInProgress(undefined); }); } const changePasswordBtn = ( ); return (
{ e.preventDefault(); return false }} isHorizontal> {inProgress === false && {_("Password changed successfully")} } setOldPassword(value)} /> setNewPassword(value)} /> setConfirmPassword(value)} /> {changePasswordBtn}
); }; const UnlockKey = ({ keyName, keys, onClose } : { keyName: string, keys: credentials.Keys, onClose: () => void }) => { const [password, setPassword] = useState(""); const [dialogError, setDialogError] = useState(""); function load_key() { if (!keyName) return; keys.load(keyName, password) .then(onClose) .catch(ex => { setDialogError(ex.message); console.warn("loading key failed: ", ex.message); }); } return (
{ e.preventDefault(); return false }} isHorizontal> {dialogError && } setPassword(value)} />
); }; const AddKeyButton = ({ callback } : { callback: (path: string) => Promise, }) => { const Dialogs = useDialogs(); return ( ); }; export const CredentialsModal = ({ dialogResult } : { dialogResult: DialogResult }) => { const keys = useObject(() => credentials.keys_instance(), null, []); const [dialogError, setDialogError] = useState(); const [unlockKey, setUnlockKey] = useState(); useEvent(keys as unknown as cockpit.EventSource, "changed"); if (!keys) return null; function onToggleKey(id: string, enable: boolean) { const key = keys.items[id]; if (!key || !key.name) return; /* Key needs to be loaded, show load UI */ if (enable && !key.loaded) { setUnlockKey(key.name); /* Key needs to be unloaded, do that directly */ } else if (!enable && key.loaded) { keys.unload(key).catch(ex => setDialogError(ex.message)); } } const addCustomKey = async (newKeyPath: string): Promise => { try { await keys.load(newKeyPath, ""); } catch (ex) { if (!(ex instanceof credentials.KeyLoadError) || !ex.sent_password) throw ex; else setUnlockKey(newKeyPath); } }; return ( <> dialogResult.resolve()} id="credentials-modal" > {dialogError && } {_("Use the following keys to authenticate against other systems")} { const currentKey = keys.items[currentKeyId] || { name: 'test' }; const tabRenderers = [ { data: { currentKey }, name: _("Details"), renderer: KeyDetails, }, { data: { currentKey }, name: _("Public key"), renderer: PublicKey, }, { data: { currentKey, keys, setDialogError }, name: _("Password"), renderer: KeyPassword, }, ]; const expandedContent = ( ); return ({ columns: [ { title: currentKey.name || currentKey.comment, }, { title: onToggleKey(currentKeyId, value)} />, props: { isActionCell: true } } ], expandedContent, props: { key: currentKey.fingerprint, 'data-name': currentKey.name || currentKey.comment, 'data-loaded': !!currentKey.loaded }, }); })} /> {unlockKey && setUnlockKey(undefined)} />} ); };