diff --git a/src/components/vm/consoles/consoles.jsx b/src/components/vm/consoles/consoles.jsx index 46e649729..ee579d7dd 100644 --- a/src/components/vm/consoles/consoles.jsx +++ b/src/components/vm/consoles/consoles.jsx @@ -58,10 +58,10 @@ class Consoles extends React.Component { const { vm } = this.props; if (vm.displays) { - if (vm.displays.vnc) { + if (vm.displays.find(display => display.type == "vnc")) { return 'VncConsole'; } - if (vm.displays.spice) { + if (vm.displays.find(display => display.type == "spice")) { return 'DesktopViewer'; } } @@ -78,20 +78,21 @@ class Consoles extends React.Component { onDesktopConsoleDownload (type) { const { vm } = this.props; // fire download of the .vv file - domainDesktopConsole({ name: vm.name, id: vm.id, connectionName: vm.connectionName, consoleDetail: vm.displays[type] }); + domainDesktopConsole({ name: vm.name, id: vm.id, connectionName: vm.connectionName, consoleDetail: vm.displays.find(display => display.type == type) }); } render () { const { vm, onAddErrorNotification } = this.props; + const spice = vm.displays && vm.displays.find(display => display.type == 'spice'); + const serial = vm.displays && vm.displays.filter(display => display.type == 'pty'); + const vnc = vm.displays && vm.displays.find(display => display.type == 'vnc'); if (!domainCanConsole || !domainCanConsole(vm.state)) { return (); } - const serialConsoleCommand = domainSerialConsoleCommand({ vm }); - const onDesktopConsole = () => { // prefer spice over vnc - this.onDesktopConsoleDownload(vm.displays.spice ? 'spice' : 'vnc'); + this.onDesktopConsoleDownload(spice ? 'spice' : 'vnc'); }; return ( @@ -100,22 +101,23 @@ class Consoles extends React.Component { textSerialConsole={_("Serial console")} textVncConsole={_("VNC console")} textDesktopViewerConsole={_("Desktop viewer")}> - {!!serialConsoleCommand && - } - {vm.displays && vm.displays.vnc && + {serial.map((pty, idx) => )} + {vnc && } - {vm.displays && (vm.displays.vnc || vm.displays.spice) && + {(vnc || spice) && } + vnc={vnc} + spice={spice} />} ); } diff --git a/src/components/vm/consoles/desktopConsole.jsx b/src/components/vm/consoles/desktopConsole.jsx index 61393c36a..454551953 100644 --- a/src/components/vm/consoles/desktopConsole.jsx +++ b/src/components/vm/consoles/desktopConsole.jsx @@ -36,10 +36,10 @@ function fmt_to_fragments(fmt) { return React.createElement.apply(null, [React.Fragment, { }].concat(fmt.split(/(\$[0-9]+)/g).map(replace))); } -const DesktopConsoleDownload = ({ displays, onDesktopConsole }) => { +const DesktopConsoleDownload = ({ vnc, spice, onDesktopConsole }) => { return ( - vmState == 'running'; export const domainCanRename = (vmState) => vmState == 'shut off'; export const domainCanResume = (vmState) => vmState == 'paused'; export const domainIsRunning = (vmState) => domainCanReset(vmState); -export const domainSerialConsoleCommand = ({ vm }) => vm.displays.pty ? ['virsh', ...VMS_CONFIG.Virsh.connections[vm.connectionName].params, 'console', vm.name] : false; +export const domainSerialConsoleCommand = ({ vm, alias }) => { + if (vm.displays.find(display => display.type == 'pty')) + return ['virsh', ...VMS_CONFIG.Virsh.connections[vm.connectionName].params, 'console', vm.name, alias || '']; + else + return false; +}; let pythonPath; diff --git a/src/libvirtUtils.js b/src/libvirtUtils.js index ad5af9ddd..4a29c0243 100644 --- a/src/libvirtUtils.js +++ b/src/libvirtUtils.js @@ -52,12 +52,13 @@ function prepareParamsFromObjOfObjs(objectData, valueTransformer) { } export function prepareDisplaysParam(displays) { - return prepareParamsFromObjOfObjs(displays, display => { + return prepareParamsFromArrOfObjs(displays.filter(display => ["vnc", "spice"].includes(display.type)), display => { return { type: display.type, listen: display.address, port: display.port, tlsport: display.tlsPort, + alias: display.alias, }; }); } diff --git a/test/check-machines-consoles b/test/check-machines-consoles index 85b5da54b..5e34a7b40 100755 --- a/test/check-machines-consoles +++ b/test/check-machines-consoles @@ -103,6 +103,7 @@ class TestMachinesConsoles(VirtualMachinesCase): @no_retry_when_changed def testSerialConsole(self): b = self.browser + m = self.machine name = "vmWithSerialConsole" self.createVm(name, graphics='vnc', ptyconsole=True) @@ -130,10 +131,21 @@ class TestMachinesConsoles(VirtualMachinesCase): b.click("button:contains(Expand)") b.assert_pixels("#vm-vmWithSerialConsole-consoles-page", "vm-details-console-serial") + # Add a second serial console + m.execute("virsh destroy vmWithSerialConsole && virt-xml --add-device vmWithSerialConsole --console pty,target_type=virtio && virsh start vmWithSerialConsole") + b.click("#pf-c-console__type-selector") + b.wait_visible("#pf-c-console__type-selector + .pf-c-select__menu") + b.click("li:contains('Serial console (console0)') button") + b.wait(lambda: m.execute("ps aux | grep 'virsh -c qemu:///system console vmWithSerialConsole console0'")) + b.click("#pf-c-console__type-selector") + b.click("li:contains('Serial console (console1)') button") + b.wait(lambda: m.execute("ps aux | grep 'virsh -c qemu:///system console vmWithSerialConsole console1'")) + # disconnecting the serial console closes the pty channel self.allow_journal_messages("connection unexpectedly closed by peer", ".*Connection reset by peer") self.allow_browser_errors("Disconnection timed out.") + self.allow_journal_messages(".* couldn't shutdown fd: Transport endpoint is not connected") def testSwitchConsoleFromSerialToGraphical(self): b = self.browser