Skip to content

consoles: Redesign #2008

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion node_modules
Submodule node_modules updated 1126 files
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
},
"dependencies": {
"@patternfly/patternfly": "6.2.3",
"@patternfly/react-console": "6.0.0",
"@patternfly/react-core": "6.2.2",
"@patternfly/react-icons": "6.2.2",
"@patternfly/react-styles": "6.2.2",
Expand Down
57 changes: 55 additions & 2 deletions src/components/common/needsShutdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { Popover } from "@patternfly/react-core/dist/esm/components/Popover";
import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip";
import { PendingIcon } from "@patternfly/react-icons";

import type { VM, VMDisk, VMInterface } from '../../types';
import type { VM, VMXML, VMGraphics, VMDisk, VMInterface } from '../../types';

import {
getIfaceSourceName,
Expand Down Expand Up @@ -111,6 +111,51 @@ export function needsShutdownSpice(vm: VM) {
return vm.hasSpice !== vm.inactiveXML.hasSpice;
}

export function needsShutdownVnc(vm: VM) {
function find_vnc(v: VMXML): VMGraphics | undefined {
if (v.displays) {
for (const d of v.displays)
if (d.type == "vnc")
return d;
}
}

const active_vnc = find_vnc(vm);
const inactive_vnc = find_vnc(vm.inactiveXML);

if (inactive_vnc) {
if (!active_vnc)
return true;

// The active_vnc.port value is the actual port allocated at
// machine start, it is never -1. Thus, we can't just compare
// inactive_vnc.port with active_vnc.port here when
// inactive_vnc.port is -1. Also, when inactive_vnc.port _is_
// -1, we can't tell whether active_vnc.port has been
// allocated based on some old fixed port in inactive_vnc.port
// (in which case we might want to shutdown and restart), or
// whether it was allocated dynamically (in which case we
// don't want to). But luckily that doesn't really matter and
// a shutdown would not have any useful effect anyway, so we
// don't have to worry that we are missing a notification for
// a pending shutdown.
//
if (inactive_vnc.port != "-1" && active_vnc.port != inactive_vnc.port)
return true;
Copy link
Contributor

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.


if (active_vnc.password != inactive_vnc.password)
return true;
Copy link
Contributor

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.

}

return false;
}

export function needsShutdownSerialConsole(vm: VM) {
const serials = vm.displays && vm.displays.filter(display => display.type == 'pty');
const inactive_serials = vm.inactiveXML.displays && vm.inactiveXML.displays.filter(display => display.type == 'pty');
return serials.length != inactive_serials.length;
}

export function getDevicesRequiringShutdown(vm: VM) {
if (!vm.persistent)
return [];
Expand Down Expand Up @@ -152,6 +197,14 @@ export function getDevicesRequiringShutdown(vm: VM) {
if (needsShutdownSpice(vm))
devices.push(_("SPICE"));

// VNC
if (needsShutdownVnc(vm))
devices.push(_("VNC"));

// Serial console
if (needsShutdownSerialConsole(vm))
devices.push(_("Text console"));

// TPM
if (needsShutdownTpm(vm))
devices.push(_("TPM"));
Expand Down Expand Up @@ -207,7 +260,7 @@ export const VmNeedsShutdown = ({ vm } : { vm: VM }) => {
position="bottom"
hasAutoWidth
bodyContent={body}>
<Label className="resource-state-text" color="teal" id={`vm-${vm.name}-needs-shutdown`}
<Label className="resource-state-text" status="custom" id={`vm-${vm.name}-needs-shutdown`}
icon={<PendingIcon />} onClick={() => null}>
{_("Changes pending")}
</Label>
Expand Down
153 changes: 153 additions & 0 deletions src/components/vm/consoles/VncConsole.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*

MIT License

Copyright (c) 2025 Red Hat, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

import React from 'react';

import { initLogging } from '@novnc/novnc/lib/util/logging';
import RFB_module from '@novnc/novnc/lib/rfb';
const RFB = RFB_module.default;

export const VncConsole = ({
children,
host,
port = '80',
path = '',
encrypt = false,
resizeSession = true,
clipViewport = false,
dragViewport = false,
scaleViewport = false,
viewOnly = false,
shared = false,
credentials,
repeaterID = '',
vncLogging = 'warn',
consoleContainerId,
onDisconnected = () => {},
Copy link
Contributor

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.

onInitFailed,
onSecurityFailure,
}) => {
const rfb = React.useRef();

const novncElem = React.useRef(null);

const onConnected = () => {
};

const _onDisconnected = React.useCallback(
(e) => {
onDisconnected(e);
},
[onDisconnected]
);

const _onSecurityFailure = React.useCallback(
(e) => {
onSecurityFailure(e);
Comment on lines +68 to +69
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 added lines are not executed by any test.

},
[onSecurityFailure]
);

const addEventListeners = React.useCallback(() => {
if (rfb.current) {
rfb.current?.addEventListener('connect', onConnected);
rfb.current?.addEventListener('disconnect', _onDisconnected);
rfb.current?.addEventListener('securityfailure', _onSecurityFailure);
}
}, [rfb, _onDisconnected, _onSecurityFailure]);

const removeEventListeners = React.useCallback(() => {
if (rfb.current) {
rfb.current.removeEventListener('connect', onConnected);
rfb.current.removeEventListener('disconnect', _onDisconnected);
rfb.current.removeEventListener('securityfailure', _onSecurityFailure);
}
}, [rfb, _onDisconnected, _onSecurityFailure]);

const connect = React.useCallback(() => {
const protocol = encrypt ? 'wss' : 'ws';
Copy link
Contributor

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.

const url = `${protocol}://${host}:${port}/${path}`;

const options = {
repeaterID,
shared,
credentials
};
rfb.current = new RFB(novncElem.current, url, options);
addEventListeners();
rfb.current.viewOnly = viewOnly;
rfb.current.clipViewport = clipViewport;
rfb.current.dragViewport = dragViewport;
rfb.current.scaleViewport = scaleViewport;
rfb.current.resizeSession = resizeSession;
}, [
addEventListeners,
host,
path,
port,
resizeSession,
clipViewport,
dragViewport,
scaleViewport,
viewOnly,
encrypt,
rfb,
repeaterID,
shared,
credentials
]);

React.useEffect(() => {
initLogging(vncLogging);
try {
connect();
} catch (e) {
onInitFailed && onInitFailed(e);
rfb.current = undefined;
Comment on lines +127 to +129
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 3 added lines are not executed by any test.

}

return () => {
disconnect();
removeEventListeners();
rfb.current = undefined;
};
}, [connect, onInitFailed, removeEventListeners, vncLogging]);

const disconnect = () => {
if (!rfb.current) {
return;
Comment on lines +140 to +141
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 added lines are not executed by any test.

}
rfb.current.disconnect();
};

return (
<div className="vm-console-vnc">
<div id={consoleContainerId} ref={novncElem} />
</div>
);
};

VncConsole.displayName = 'VncConsole';
Loading
Loading