-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
shell: More types than ever #21426
base: main
Are you sure you want to change the base?
shell: More types than ever #21426
Changes from all commits
833ebfb
101f775
74bcf13
e934440
e834f95
6e65422
b3f8c0f
c29bed6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,120 +39,28 @@ 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 { DialogResult } from "dialogs"; | ||
|
||
import "./credentials.scss"; | ||
|
||
const _ = cockpit.gettext; | ||
|
||
export const CredentialsModal = ({ dialogResult }) => { | ||
const keys = useObject(() => credentials.keys_instance(), null, []); | ||
const [addNewKey, setAddNewKey] = useState(false); | ||
const [dialogError, setDialogError] = useState(); | ||
const [unlockKey, setUnlockKey] = useState(); | ||
|
||
useEvent(keys, "changed"); | ||
|
||
if (!keys) | ||
return null; | ||
|
||
function onToggleKey(id, enable) { | ||
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)); | ||
} | ||
} | ||
|
||
return ( | ||
<> | ||
<Modal isOpen position="top" variant="medium" | ||
onClose={() => dialogResult.resolve()} | ||
title={_("SSH keys")} | ||
id="credentials-modal" | ||
footer={<Button variant='secondary' onClick={() => dialogResult.resolve()}>{_("Close")}</Button>} | ||
> | ||
<Stack hasGutter> | ||
{dialogError && <ModalError dialogError={dialogError} />} | ||
<Flex justifyContent={{ default: 'justifyContentSpaceBetween' }}> | ||
<FlexItem>{_("Use the following keys to authenticate against other systems")}</FlexItem> | ||
<Button variant='secondary' | ||
id="ssh-file-add-custom" | ||
onClick={() => setAddNewKey(true)}> | ||
{_("Add key")} | ||
</Button> | ||
</Flex> | ||
{addNewKey && <AddNewKey keys={keys} unlockKey={setUnlockKey} onClose={() => setAddNewKey(false)} />} | ||
<ListingTable | ||
aria-label={ _("SSH keys") } | ||
gridBreakPoint='' | ||
id="credential-keys" | ||
showHeader={false} | ||
variant="compact" | ||
columns={ [ | ||
{ title: _("Name"), header: true }, | ||
{ title: _("Toggle") }, | ||
] } | ||
rows={ Object.keys(keys.items).map((currentKeyId, index) => { | ||
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 = ( | ||
<ListingPanel tabRenderers={tabRenderers} /> | ||
); | ||
|
||
return ({ | ||
columns: [ | ||
{ | ||
title: currentKey.name || currentKey.comment, | ||
}, | ||
{ | ||
title: <Switch aria-label={_("Use key")} | ||
isChecked={!!currentKey.loaded} | ||
key={"switch-" + index} | ||
onChange={(_event, value) => onToggleKey(currentKeyId, value)} />, | ||
} | ||
], | ||
expandedContent, | ||
props: { key: currentKey.fingerprint, 'data-name': currentKey.name || currentKey.comment, 'data-loaded': !!currentKey.loaded }, | ||
}); | ||
})} /> | ||
</Stack> | ||
</Modal> | ||
{unlockKey && <UnlockKey keyName={unlockKey} keys={keys} onClose={() => { setUnlockKey(undefined); setAddNewKey(false) }} />} | ||
</> | ||
); | ||
}; | ||
|
||
const AddNewKey = ({ keys, unlockKey, onClose }) => { | ||
const AddNewKey = ({ | ||
keys, | ||
unlockKey, | ||
onClose | ||
} : { | ||
keys: credentials.Keys, | ||
unlockKey: (name: string) => void, | ||
onClose: () => void | ||
}) => { | ||
const [addNewKeyLoading, setAddNewKeyLoading] = useState(false); | ||
const [newKeyPath, setNewKeyPath] = useState(""); | ||
const [newKeyPathError, setNewKeyPathError] = useState(); | ||
const [newKeyPathError, setNewKeyPathError] = useState(""); | ||
|
||
const addCustomKey = () => { | ||
setAddNewKeyLoading(true); | ||
keys.load(newKeyPath) | ||
keys.load(newKeyPath, "") | ||
.then(onClose) | ||
.catch(ex => { | ||
if (!(ex instanceof credentials.KeyLoadError) || !ex.sent_password) | ||
|
@@ -186,7 +94,7 @@ const AddNewKey = ({ keys, unlockKey, onClose }) => { | |
); | ||
}; | ||
|
||
const KeyDetails = ({ currentKey }) => { | ||
const KeyDetails = ({ currentKey } : { currentKey: credentials.Key }) => { | ||
return ( | ||
<DescriptionList className="pf-m-horizontal-on-sm"> | ||
<DescriptionListGroup> | ||
|
@@ -205,17 +113,25 @@ const KeyDetails = ({ currentKey }) => { | |
); | ||
}; | ||
|
||
const PublicKey = ({ currentKey }) => { | ||
const PublicKey = ({ currentKey } : { currentKey: credentials.Key }) => { | ||
return ( | ||
<ClipboardCopy isReadOnly hoverTip={_("Copy")} clickTip={_("Copied")} variant={ClipboardCopyVariant.expansion}> | ||
{currentKey.data.trim()} | ||
</ClipboardCopy> | ||
); | ||
}; | ||
|
||
const KeyPassword = ({ currentKey, keys, setDialogError }) => { | ||
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 [inProgress, setInProgress] = useState<boolean | undefined>(undefined); | ||
const [newPassword, setNewPassword] = useState(''); | ||
const [oldPassword, setOldPassword] = useState(''); | ||
|
||
|
@@ -224,7 +140,7 @@ const KeyPassword = ({ currentKey, keys, setDialogError }) => { | |
return; | ||
|
||
setInProgress(true); | ||
setDialogError(); | ||
setDialogError(null); | ||
|
||
if (oldPassword === undefined || newPassword === undefined || confirmPassword === undefined) | ||
setDialogError("Invalid password fields"); | ||
|
@@ -245,8 +161,7 @@ const KeyPassword = ({ currentKey, keys, setDialogError }) => { | |
const changePasswordBtn = ( | ||
<Button variant="primary" | ||
id={(currentKey.name || currentKey.comment) + "-change-password"} | ||
isDisabled={inProgress} | ||
isLoading={inProgress} | ||
{... inProgress !== undefined ? { isDisabled: inProgress, isLoading: inProgress } : {} } | ||
onClick={() => changePassword()}>{_("Change password")}</Button> | ||
); | ||
|
||
|
@@ -285,9 +200,17 @@ const KeyPassword = ({ currentKey, keys, setDialogError }) => { | |
); | ||
}; | ||
|
||
const UnlockKey = ({ keyName, keys, onClose }) => { | ||
const [password, setPassword] = useState(); | ||
const [dialogError, setDialogError] = useState(); | ||
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) | ||
|
@@ -320,3 +243,108 @@ const UnlockKey = ({ keyName, keys, onClose }) => { | |
</Modal> | ||
); | ||
}; | ||
|
||
export const CredentialsModal = ({ | ||
dialogResult | ||
} : { | ||
dialogResult: DialogResult<void> | ||
}) => { | ||
const keys = useObject(() => credentials.keys_instance(), null, []); | ||
const [addNewKey, setAddNewKey] = useState(false); | ||
const [dialogError, setDialogError] = useState(); | ||
const [unlockKey, setUnlockKey] = useState<string | undefined>(); | ||
|
||
useEvent(keys as unknown as cockpit.EventSource<cockpit.EventMap>, "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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This added line is not executed by any test. |
||
} | ||
} | ||
|
||
return ( | ||
<> | ||
<Modal isOpen position="top" variant="medium" | ||
onClose={() => dialogResult.resolve()} | ||
title={_("SSH keys")} | ||
id="credentials-modal" | ||
footer={<Button variant='secondary' onClick={() => dialogResult.resolve()}>{_("Close")}</Button>} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This added line is not executed by any test. |
||
> | ||
<Stack hasGutter> | ||
{dialogError && <ModalError dialogError={dialogError} />} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This added line is not executed by any test. |
||
<Flex justifyContent={{ default: 'justifyContentSpaceBetween' }}> | ||
<FlexItem>{_("Use the following keys to authenticate against other systems")}</FlexItem> | ||
<Button variant='secondary' | ||
id="ssh-file-add-custom" | ||
onClick={() => setAddNewKey(true)}> | ||
{_("Add key")} | ||
</Button> | ||
</Flex> | ||
{addNewKey && <AddNewKey keys={keys} unlockKey={setUnlockKey} onClose={() => setAddNewKey(false)} />} | ||
<ListingTable | ||
aria-label={ _("SSH keys") } | ||
gridBreakPoint='' | ||
id="credential-keys" | ||
showHeader={false} | ||
variant="compact" | ||
columns={ [ | ||
{ title: _("Name"), header: true }, | ||
{ title: _("Toggle") }, | ||
] } | ||
rows={ Object.keys(keys.items).map((currentKeyId, index) => { | ||
const currentKey = keys.items[currentKeyId] || { name: 'test' }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This added line is not executed by any 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 = ( | ||
<ListingPanel tabRenderers={tabRenderers} /> | ||
); | ||
|
||
return ({ | ||
columns: [ | ||
{ | ||
title: currentKey.name || currentKey.comment, | ||
}, | ||
{ | ||
title: <Switch aria-label={_("Use key")} | ||
isChecked={!!currentKey.loaded} | ||
key={"switch-" + index} | ||
onChange={(_event, value) => onToggleKey(currentKeyId, value)} />, | ||
} | ||
], | ||
expandedContent, | ||
props: { key: currentKey.fingerprint, 'data-name': currentKey.name || currentKey.comment, 'data-loaded': !!currentKey.loaded }, | ||
}); | ||
})} /> | ||
</Stack> | ||
</Modal> | ||
{unlockKey && <UnlockKey keyName={unlockKey} keys={keys} onClose={() => { setUnlockKey(undefined); setAddNewKey(false) }} />} | ||
</> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test.