diff --git a/locales/en/plugin__kubevirt-plugin.json b/locales/en/plugin__kubevirt-plugin.json index 24aae0b6a3..54701dc56f 100644 --- a/locales/en/plugin__kubevirt-plugin.json +++ b/locales/en/plugin__kubevirt-plugin.json @@ -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", @@ -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", diff --git a/src/utils/components/Consoles/components/DesktopViewer/Components/RDPServiceNotConfigured.tsx b/src/utils/components/Consoles/components/DesktopViewer/Components/RDPServiceNotConfigured.tsx index 4249709086..5f47acd302 100644 --- a/src/utils/components/Consoles/components/DesktopViewer/Components/RDPServiceNotConfigured.tsx +++ b/src/utils/components/Consoles/components/DesktopViewer/Components/RDPServiceNotConfigured.tsx @@ -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 = ({ vm, vmi }) => { const { t } = useKubevirtTranslation(); const { createModal } = useModal(); diff --git a/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx b/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx index 2d62b2d67a..67647db8bb 100644 --- a/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx +++ b/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx @@ -6,7 +6,9 @@ import { DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, + Popover, } from '@patternfly/react-core'; +import { HelpIcon } from '@patternfly/react-icons'; import { getConsoleVirtctlCommand } from '../utils'; @@ -19,7 +21,16 @@ const ConsoleOverVirtctl: React.FC = ({ vmName, vmNames return ( - {t('SSH using virtctl')} + {t('SSH using virtctl')}{' '} + + t('This command is available for virtctl access, whenever the API server is reachable.') + } + > + + diff --git a/src/utils/components/SSHAccess/components/SSHCommand.tsx b/src/utils/components/SSHAccess/components/SSHCommand.tsx index bb8d1629a0..2fc32c9e1e 100644 --- a/src/utils/components/SSHAccess/components/SSHCommand.tsx +++ b/src/utils/components/SSHAccess/components/SSHCommand.tsx @@ -4,7 +4,7 @@ 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, @@ -12,6 +12,7 @@ import { Stack, StackItem, } from '@patternfly/react-core'; +import { HelpIcon } from '@patternfly/react-icons'; import useSSHCommand from '../useSSHCommand'; import { createSSHService, deleteSSHService } from '../utils'; @@ -55,7 +56,18 @@ const SSHCommand: React.FC = ({ return ( - {t('SSH over NodePort')} + {t('SSH over NodePort')}{' '} + + 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.', + ) + } + > + + diff --git a/src/utils/components/SSHAccess/useSSHCommand.ts b/src/utils/components/SSHAccess/useSSHCommand.ts index ddfbf509a5..d78ed1b1b6 100644 --- a/src/utils/components/SSHAccess/useSSHCommand.ts +++ b/src/utils/components/SSHAccess/useSSHCommand.ts @@ -9,6 +9,7 @@ export type useSSHCommandResult = { sshServiceRunning: boolean; }; +// SSH over NodePort const useSSHCommand = ( vmi: V1VirtualMachineInstance, sshService: IoK8sApiCoreV1Service, diff --git a/src/views/virtualmachines/actions/VirtualMachineActionFactory.tsx b/src/views/virtualmachines/actions/VirtualMachineActionFactory.tsx index 374d39dcfe..3111a316c1 100644 --- a/src/views/virtualmachines/actions/VirtualMachineActionFactory.tsx +++ b/src/views/virtualmachines/actions/VirtualMachineActionFactory.tsx @@ -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'; @@ -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: , + description: t('SSH using virtctl'), + cta: () => command && navigator.clipboard.writeText(command), + }; + }, editLabels: ( vm: V1VirtualMachine, createModal: (modal: ModalComponent) => void, diff --git a/src/views/virtualmachines/list/components/VirtualMachineActions/VirtualMachineActions.tsx b/src/views/virtualmachines/actions/components/VirtualMachineActions/VirtualMachineActions.tsx similarity index 87% rename from src/views/virtualmachines/list/components/VirtualMachineActions/VirtualMachineActions.tsx rename to src/views/virtualmachines/actions/components/VirtualMachineActions/VirtualMachineActions.tsx index 64a36773cd..a068a543f8 100644 --- a/src/views/virtualmachines/list/components/VirtualMachineActions/VirtualMachineActions.tsx +++ b/src/views/virtualmachines/actions/components/VirtualMachineActions/VirtualMachineActions.tsx @@ -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 }; @@ -63,7 +62,12 @@ const VirtualMachineActions: React.FC = ({ description={action?.description} > {action?.label} - + {action?.icon && + <> + {' '}< span className="text-muted">{action.icon} + + } + ))} /> ); diff --git a/src/views/virtualmachines/actions/hooks/useVirtualMachineActionsProvider.ts b/src/views/virtualmachines/actions/hooks/useVirtualMachineActionsProvider.ts index 0fad3ccb6e..6cd984e875 100644 --- a/src/views/virtualmachines/actions/hooks/useVirtualMachineActionsProvider.ts +++ b/src/views/virtualmachines/actions/hooks/useVirtualMachineActionsProvider.ts @@ -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'; @@ -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(() => { @@ -39,6 +41,7 @@ const useVirtualMachineActionsProvider: UseVirtualMachineActionsProvider = (vm) printableStatus === Paused ? VirtualMachineActionFactory.unpause(vm, t) : VirtualMachineActionFactory.pause(vm, t); + return [ startOrStop, VirtualMachineActionFactory.restart(vm, t), @@ -46,11 +49,12 @@ const useVirtualMachineActionsProvider: UseVirtualMachineActionsProvider = (vm) 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]); }; diff --git a/src/views/virtualmachines/actions/tests/useVirtualMachineActionsProvider.test.tsx b/src/views/virtualmachines/actions/tests/useVirtualMachineActionsProvider.test.tsx index cb5ed6c71f..340c5a85ad 100644 --- a/src/views/virtualmachines/actions/tests/useVirtualMachineActionsProvider.test.tsx +++ b/src/views/virtualmachines/actions/tests/useVirtualMachineActionsProvider.test.tsx @@ -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', @@ -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', @@ -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', @@ -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', diff --git a/src/views/virtualmachines/details/VirtualMachineNavPageTitle.tsx b/src/views/virtualmachines/details/VirtualMachineNavPageTitle.tsx index 582db1ca27..ecd4370f29 100644 --- a/src/views/virtualmachines/details/VirtualMachineNavPageTitle.tsx +++ b/src/views/virtualmachines/details/VirtualMachineNavPageTitle.tsx @@ -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'; diff --git a/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabDetails/components/VirtualMachinesOverviewTabDetailsTitle.tsx b/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabDetails/components/VirtualMachinesOverviewTabDetailsTitle.tsx index 320c9bef06..03e8637c37 100644 --- a/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabDetails/components/VirtualMachinesOverviewTabDetailsTitle.tsx +++ b/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabDetails/components/VirtualMachinesOverviewTabDetailsTitle.tsx @@ -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'; @@ -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 ( @@ -40,11 +35,11 @@ const VirtualMachinesOverviewTabDetailsTitle: React.FC< isPlain dropdownItems={[ 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')}{' '} diff --git a/src/views/virtualmachines/list/components/VirtualMachineRow/VirtualMachineRowLayout.tsx b/src/views/virtualmachines/list/components/VirtualMachineRow/VirtualMachineRowLayout.tsx index 0f03d6a2ba..ea569ff446 100644 --- a/src/views/virtualmachines/list/components/VirtualMachineRow/VirtualMachineRowLayout.tsx +++ b/src/views/virtualmachines/list/components/VirtualMachineRow/VirtualMachineRowLayout.tsx @@ -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';