Skip to content

Commit

Permalink
Bug 2104859: Add "Copy SSH command" to VM actions
Browse files Browse the repository at this point in the history
Add "Copy SSH command" option to kebab menu for the actions in the VM
list, also to Actions button in the VM details view/tabs (SSH using
virtctl).

Make editing SSH access field available in the VM Details tab,
even when the VM is stopped.

Add missing popovers for "SSH over NodePort" and "SSH using virtctl"
in the VM Details tab according to the design doc.

Fixes https://bugzilla.redhat.com/show_bug.cgi?id=2104859
Fixes https://bugzilla.redhat.com/show_bug.cgi?id=2117803
  • Loading branch information
hstastna committed Aug 25, 2022
1 parent 69a88d7 commit a826ada
Show file tree
Hide file tree
Showing 12 changed files with 64 additions and 20 deletions.
4 changes: 3 additions & 1 deletion locales/en/plugin__kubevirt-plugin.json
Expand Up @@ -202,7 +202,7 @@
"Container disk": "Container disk",
"Container Image": "Container Image",
"Copied": "Copied",
"Copy SSH Command": "Copy SSH Command",
"Copy SSH command": "Copy SSH command",
"Copy template's boot source disk": "Copy template's boot source disk",
"Copy to clipboard": "Copy to clipboard",
"Cores": "Cores",
Expand Down Expand Up @@ -823,7 +823,9 @@
"The VirtualMachine {{vmName}} is still running. It will be powered off while cloning.": "The VirtualMachine {{vmName}} is still running. It will be powered off while cloning.",
"The VirtualMachine will be bridged to the selected network, ideal for L2 devices": "The VirtualMachine will be bridged to the selected network, ideal for L2 devices",
"The VirtualMachine you are creating does not have an available boot source. We recommended that you select a boot source to create the VirtualMachine.": "The VirtualMachine you are creating does not have an available boot source. We recommended that you select a boot source to create the VirtualMachine.",
"This command is available for virtctl access, whenever the API server is reachable.": "This command is available for virtctl access, whenever the API server is reachable.",
"This field is required": "This field is required",
"This option allows access through any SSH client via a NodePort Service. Additional network ports will be allocated. The node must be accessible from the outside network.": "This option allows access through any SSH client via a NodePort Service. Additional network ports will be allocated. The node must be accessible from the outside network.",
"This Template requires some additional parameters. Click the Customize VirtualMachine button to complete the creation flow.": "This Template requires some additional parameters. Click the Customize VirtualMachine button to complete the creation flow.",
"This Template supports quick create VirtualMachine": "This Template supports quick create VirtualMachine",
"This user is not allowed to edit this boot source": "This user is not allowed to edit this boot source",
Expand Down
Expand Up @@ -7,6 +7,7 @@ import { Button, ButtonVariant } from '@patternfly/react-core';
import RDPServiceModal from './RDPServiceModal';
import { useModal } from '@kubevirt-utils/components/ModalProvider/ModalProvider';
import './rdp-service.scss';

const RDPServiceNotConfigured: React.FC<RDPServiceNotConfiguredProps> = ({ vm, vmi }) => {
const { t } = useKubevirtTranslation();
const { createModal } = useModal();
Expand Down
13 changes: 12 additions & 1 deletion src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx
Expand Up @@ -6,7 +6,9 @@ import {
DescriptionListDescription,
DescriptionListGroup,
DescriptionListTerm,
Popover,
} from '@patternfly/react-core';
import { HelpIcon } from '@patternfly/react-icons';

import { getConsoleVirtctlCommand } from '../utils';

Expand All @@ -19,7 +21,16 @@ const ConsoleOverVirtctl: React.FC<ConsoleOverVirtctlProps> = ({ vmName, vmNames
return (
<DescriptionListGroup>
<DescriptionListTerm className="pf-u-font-size-xs">
{t('SSH using virtctl')}
{t('SSH using virtctl')}{' '}
<Popover
aria-label={'Help'}
position="right"
bodyContent={() =>
t('This command is available for virtctl access, whenever the API server is reachable.')
}
>
<HelpIcon />
</Popover>
</DescriptionListTerm>

<DescriptionListDescription className="sshcommand-body">
Expand Down
16 changes: 14 additions & 2 deletions src/utils/components/SSHAccess/components/SSHCommand.tsx
Expand Up @@ -4,14 +4,15 @@ import { IoK8sApiCoreV1Service } from '@kubevirt-ui/kubevirt-api/kubernetes';
import { V1VirtualMachine, V1VirtualMachineInstance } from '@kubevirt-ui/kubevirt-api/kubevirt';
import Loading from '@kubevirt-utils/components/Loading/Loading';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { Alert, AlertVariant, ClipboardCopy } from '@patternfly/react-core';
import { Alert, AlertVariant, ClipboardCopy, Popover } from '@patternfly/react-core';
import {
DescriptionListDescription,
DescriptionListGroup,
DescriptionListTerm,
Stack,
StackItem,
} from '@patternfly/react-core';
import { HelpIcon } from '@patternfly/react-icons';

import useSSHCommand from '../useSSHCommand';
import { createSSHService, deleteSSHService } from '../utils';
Expand Down Expand Up @@ -55,7 +56,18 @@ const SSHCommand: React.FC<SSHCommandProps> = ({
return (
<DescriptionListGroup>
<DescriptionListTerm className="pf-u-font-size-xs">
{t('SSH over NodePort')}
{t('SSH over NodePort')}{' '}
<Popover
aria-label={'Help'}
position="right"
bodyContent={() =>
t(
'This option allows access through any SSH client via a NodePort Service. Additional network ports will be allocated. The node must be accessible from the outside network.',
)
}
>
<HelpIcon />
</Popover>
</DescriptionListTerm>

<DescriptionListDescription>
Expand Down
1 change: 1 addition & 0 deletions src/utils/components/SSHAccess/useSSHCommand.ts
Expand Up @@ -9,6 +9,7 @@ export type useSSHCommandResult = {
sshServiceRunning: boolean;
};

// SSH over NodePort
const useSSHCommand = (
vmi: V1VirtualMachineInstance,
sshService: IoK8sApiCoreV1Service,
Expand Down
10 changes: 10 additions & 0 deletions src/views/virtualmachines/actions/VirtualMachineActionFactory.tsx
Expand Up @@ -11,6 +11,7 @@ import { LabelsModal } from '@kubevirt-utils/components/LabelsModal/LabelsModal'
import { ModalComponent } from '@kubevirt-utils/components/ModalProvider/ModalProvider';
import { asAccessReview } from '@kubevirt-utils/resources/shared';
import { Action, k8sPatch } from '@openshift-console/dynamic-plugin-sdk';
import { CopyIcon } from '@patternfly/react-icons';

import { printableVMStatus } from '../utils';

Expand Down Expand Up @@ -154,6 +155,15 @@ export const VirtualMachineActionFactory = {
// ),
// };
// },
copySSHCommand: (command: string, t: TFunction): Action => {
return {
id: 'vm-action-copy-ssh',
label: t('Copy SSH command'),
icon: <CopyIcon />,
description: t('SSH using virtctl'),
cta: () => command && navigator.clipboard.writeText(command),
};
},
editLabels: (
vm: V1VirtualMachine,
createModal: (modal: ModalComponent) => void,
Expand Down
Expand Up @@ -12,8 +12,7 @@ import {
DropdownToggle,
KebabToggle,
} from '@patternfly/react-core';

import useVirtualMachineActionsProvider from '../../../actions/hooks/useVirtualMachineActionsProvider';
import useVirtualMachineActionsProvider from '@virtualmachines/actions/hooks/useVirtualMachineActionsProvider';

type VirtualMachinesInsanceActionsProps = { vm: V1VirtualMachine; isKebabToggle?: boolean };

Expand Down Expand Up @@ -63,7 +62,12 @@ const VirtualMachineActions: React.FC<VirtualMachinesInsanceActionsProps> = ({
description={action?.description}
>
{action?.label}
</DropdownItem>
{action?.icon &&
<>
{' '}< span className="text-muted">{action.icon}</span>
</>
}
</DropdownItem >
))}
/>
);
Expand Down
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';

import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt';
import { useModal } from '@kubevirt-utils/components/ModalProvider/ModalProvider';
import { getConsoleVirtctlCommand } from '@kubevirt-utils/components/SSHAccess/utils';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { VirtualMachineModelRef } from '@kubevirt-utils/models';
import { vmimStatuses } from '@kubevirt-utils/resources/vmim/statuses';
Expand All @@ -18,6 +19,7 @@ const useVirtualMachineActionsProvider: UseVirtualMachineActionsProvider = (vm)
const { t } = useKubevirtTranslation();
const { createModal } = useModal();
const vmim = useVirtualMachineInstanceMigration(vm);
const virtctlCommand = getConsoleVirtctlCommand(vm?.metadata?.name, vm?.metadata?.namespace);

const [, inFlight] = useK8sModel(VirtualMachineModelRef);
const actions: Action[] = React.useMemo(() => {
Expand All @@ -39,18 +41,20 @@ const useVirtualMachineActionsProvider: UseVirtualMachineActionsProvider = (vm)
printableStatus === Paused
? VirtualMachineActionFactory.unpause(vm, t)
: VirtualMachineActionFactory.pause(vm, t);

return [
startOrStop,
VirtualMachineActionFactory.restart(vm, t),
pauseOrUnpause,
VirtualMachineActionFactory.clone(vm, createModal, t),
migrateOrCancelMigration,
// VirtualMachineActionFactory.openConsole(vm),
VirtualMachineActionFactory.copySSHCommand(virtctlCommand, t),
VirtualMachineActionFactory.editLabels(vm, createModal, t),
VirtualMachineActionFactory.editAnnotations(vm, createModal, t),
VirtualMachineActionFactory.delete(vm, createModal, t),
];
}, [vm, vmim, createModal, t]);
}, [vm, vmim, virtctlCommand, createModal, t]);

return React.useMemo(() => [actions, !inFlight, undefined], [actions, inFlight]);
};
Expand Down
Expand Up @@ -29,6 +29,7 @@ describe('useVirtualMachineActionsProvider tests', () => {
'vm-action-pause',
'vm-action-clone',
'vm-action-migrate',
'vm-action-copy-ssh',
'vm-action-edit-labels',
'vm-action-edit-annotations',
'vm-action-delete',
Expand All @@ -52,6 +53,7 @@ describe('useVirtualMachineActionsProvider tests', () => {
'vm-action-pause',
'vm-action-clone',
'vm-action-migrate',
'vm-action-copy-ssh',
'vm-action-edit-labels',
'vm-action-edit-annotations',
'vm-action-delete',
Expand All @@ -75,6 +77,7 @@ describe('useVirtualMachineActionsProvider tests', () => {
'vm-action-unpause',
'vm-action-clone',
'vm-action-migrate',
'vm-action-copy-ssh',
'vm-action-edit-labels',
'vm-action-edit-annotations',
'vm-action-delete',
Expand All @@ -98,6 +101,7 @@ describe('useVirtualMachineActionsProvider tests', () => {
'vm-action-pause',
'vm-action-clone',
'vm-action-cancel-migrate',
'vm-action-copy-ssh',
'vm-action-edit-labels',
'vm-action-edit-annotations',
'vm-action-delete',
Expand Down
Expand Up @@ -5,8 +5,8 @@ import { V1VirtualMachine, V1VirtualMachineInstance } from '@kubevirt-ui/kubevir
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk';
import { Label } from '@patternfly/react-core';
import VirtualMachineActions from '@virtualmachines/actions/components/VirtualMachineActions/VirtualMachineActions';

import VirtualMachineActions from '../list/components/VirtualMachineActions/VirtualMachineActions';
import VirtualMachineBreadcrumb from '../list/components/VirtualMachineBreadcrumb/VirtualMachineBreadcrumb';
import { getVMStatusIcon } from '../utils';

Expand Down
Expand Up @@ -2,10 +2,8 @@ import * as React from 'react';
import { Link } from 'react-router-dom';

import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt';
import useSSHCommand from '@kubevirt-utils/components/SSHAccess/useSSHCommand';
import useSSHService from '@kubevirt-utils/components/SSHAccess/useSSHService';
import { getConsoleVirtctlCommand } from '@kubevirt-utils/components/SSHAccess/utils';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { useVMIAndPodsForVM } from '@kubevirt-utils/resources/vm/hooks';
import { CardTitle, Dropdown, DropdownItem, KebabToggle } from '@patternfly/react-core';
import { CopyIcon } from '@patternfly/react-icons';

Expand All @@ -22,13 +20,10 @@ const VirtualMachinesOverviewTabDetailsTitle: React.FC<
> = ({ vm }) => {
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
const { t } = useKubevirtTranslation();
const { vmi } = useVMIAndPodsForVM(vm?.metadata?.name, vm?.metadata?.namespace);
const [sshService] = useSSHService(vm);
const { command } = useSSHCommand(vmi, sshService);
const virtctlCommand = getConsoleVirtctlCommand(vm?.metadata?.name, vm?.metadata?.namespace);

const isMachinePaused = vm?.status?.printableStatus === printableVMStatus.Paused;
const isMachineStopped = vm?.status?.printableStatus === printableVMStatus.Stopped;
const isMachineRunning = vm?.status?.printableStatus === printableVMStatus.Running;

return (
<CardTitle className="text-muted card-title">
Expand All @@ -40,11 +35,11 @@ const VirtualMachinesOverviewTabDetailsTitle: React.FC<
isPlain
dropdownItems={[
<DropdownItem
onClick={() => command && navigator.clipboard.writeText(command)}
onClick={() => virtctlCommand && navigator.clipboard.writeText(virtctlCommand)}
key="copy"
isDisabled={!isMachineRunning || !sshService}
description={t('SSH using virtctl')}
>
{t('Copy SSH Command')}{' '}
{t('Copy SSH command')}{' '}
<span className="text-muted">
<CopyIcon />
</span>
Expand Down
Expand Up @@ -3,8 +3,8 @@ import * as React from 'react';
import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt';
import Timestamp from '@kubevirt-utils/components/Timestamp/Timestamp';
import { ResourceLink, RowProps, TableData } from '@openshift-console/dynamic-plugin-sdk';
import VirtualMachineActions from '@virtualmachines/actions/components/VirtualMachineActions/VirtualMachineActions';

import VirtualMachineActions from '../VirtualMachineActions/VirtualMachineActions';
import VirtualMachineStatus from '../VirtualMachineStatus/VirtualMachineStatus';
import { VMStatusConditionLabelList } from '../VMStatusConditionLabel';

Expand Down

0 comments on commit a826ada

Please sign in to comment.