Skip to content
This repository was archived by the owner on Apr 28, 2020. It is now read-only.

Commit 7dc7230

Browse files
mareklibrarawagner
authored andcommitted
Introduce VmConsoles component (#115)
Introduce VmConsoles component - Add "@patternfly/react-console" peer dependency - Upgrade patternfly-react to latest 2.24.6
1 parent 9aa3d23 commit 7dc7230

18 files changed

+598
-12
lines changed

config/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ module.exports = {
2323
transform: {
2424
'\\.js$': `${paths.config}/jest.transform.babel.js`,
2525
},
26+
transformIgnorePatterns: ['node_modules/(?!(@novnc))'],
2627
setupFiles: [`${paths.src}/jest/setupTest.js`],
2728
snapshotSerializers: ['enzyme-to-json/serializer'],
2829
},

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"coveralls": "shx cat coverage/lcov.info | coveralls"
3232
},
3333
"peerDependencies": {
34+
"@patternfly/react-console": "1.8.1",
3435
"patternfly-react": "2.x",
3536
"prop-types": "15.x",
3637
"react": "16.3.x",
@@ -44,6 +45,7 @@
4445
"@babel/polyfill": "7.x",
4546
"@babel/preset-env": "7.x",
4647
"@babel/preset-react": "7.x",
48+
"@patternfly/react-console": "1.8.1",
4749
"babel-core": "7.0.0-bridge.0",
4850
"babel-eslint": "9.x",
4951
"babel-jest": "23.x",

sass/_components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
@import './components/NetworksTab';
1010
@import './components/StorageTab';
1111
@import './components/VmStatus';
12+
@import './components/VmConsoles';

sass/components/_VmConsoles.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.vnc-console .toolbar-pf-action-right { /* TODO: This fix should go to patternfly-react */
2+
padding-bottom: 12px !important;
3+
margin-top: -14px !important;
4+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import { AccessConsoles, SerialConsole } from '@patternfly/react-console';
5+
6+
const { CONNECTED, DISCONNECTED, LOADING } = AccessConsoles.constants;
7+
8+
const { debug, info, error } = console;
9+
10+
/**
11+
* Kubevirt serial console is accessed via WebSocket proxy in k8s API.
12+
* Protocol used is "plain.kubevirt.io", means binary and single channel - forwarding of unix socket only (see subresource.go in kubevirt).
13+
*/
14+
class SerialConsoleConnector extends React.Component {
15+
state = {
16+
status: LOADING,
17+
passKeys: false,
18+
};
19+
20+
onBackendDisconnected = event => {
21+
debug('Backend has disconnected');
22+
if (this.childSerialconsole) {
23+
this.childSerialconsole.onConnectionClosed('Reason for disconnect provided by backend.');
24+
}
25+
26+
if (event) {
27+
info('Serial console connection closed, reason: ', event.reason);
28+
}
29+
30+
this.ws && this.ws.destroy && this.ws.destroy();
31+
32+
this.setState({
33+
passKeys: false,
34+
status: DISCONNECTED, // will close the terminal window
35+
});
36+
};
37+
38+
onConnect = () => {
39+
debug('SerialConsoleConnector.onConnect(), ', this.state);
40+
const { vmi, host, path, WSFactory } = this.props;
41+
42+
if (this.ws) {
43+
this.ws.destroy();
44+
this.setState({
45+
status: LOADING,
46+
});
47+
}
48+
49+
const options = {
50+
host,
51+
path,
52+
reconnect: false,
53+
jsonParse: false,
54+
subprotocols: ['plain.kubevirt.io'],
55+
};
56+
57+
this.ws = new WSFactory(`${vmi.metadata.name}-serial`, options)
58+
.onmessage(this.onDataFromBackend)
59+
.onopen(this.setConnected)
60+
.onclose(this.onBackendDisconnected)
61+
.onerror(event => {
62+
error('WS error received: ', event);
63+
});
64+
};
65+
66+
onData = data => {
67+
debug('UI terminal component produced data, i.e. a key was pressed, pass it to backend. [', data, ']');
68+
this.ws && this.ws.send(new Blob([data]));
69+
// data are resent back from backend so _will_ pass through onDataFromBackend
70+
};
71+
72+
onDataFromBackend = data => {
73+
// plain.kubevirt.io is binary and single-channel protocol
74+
debug('Backend sent data, pass them to the UI component. [', data, ']');
75+
if (this.childSerialconsole) {
76+
const reader = new FileReader();
77+
reader.addEventListener('loadend', e => {
78+
// Blob to text transformation ...
79+
const target = e.target || e.srcElement;
80+
const text = target.result;
81+
this.childSerialconsole.onDataReceived(text);
82+
});
83+
reader.readAsText(data);
84+
}
85+
};
86+
87+
onDisconnect = () => {
88+
// disconnection initiated by UI component
89+
this.onBackendDisconnected();
90+
};
91+
92+
onResize = (rows, cols) => {
93+
debug(
94+
'UI has been resized, pass this info to backend. [',
95+
rows,
96+
', ',
97+
cols,
98+
']. Ignoring since recently not supported by backend.',
99+
this
100+
);
101+
};
102+
103+
setConnected = () => {
104+
this.setState({
105+
status: CONNECTED,
106+
passKeys: true,
107+
});
108+
};
109+
110+
/*
111+
autoFit={this.props.autoFit}
112+
cols={this.props.cols}
113+
rows={this.props.rows}
114+
*/
115+
render() {
116+
return (
117+
<SerialConsole
118+
onConnect={this.onConnect}
119+
onDisconnect={this.onDisconnect}
120+
onResize={this.onResize}
121+
onData={this.onData}
122+
id="serial-console-todo"
123+
status={this.state.status}
124+
ref={c => {
125+
this.childSerialconsole = c;
126+
}}
127+
/>
128+
);
129+
}
130+
}
131+
SerialConsoleConnector.propTypes = {
132+
vmi: PropTypes.object.isRequired,
133+
host: PropTypes.string.isRequired,
134+
path: PropTypes.string.isRequired,
135+
136+
WSFactory: PropTypes.func.isRequired, // reference to OKD utility
137+
};
138+
139+
export default SerialConsoleConnector;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import { AccessConsoles, VncConsole } from '@patternfly/react-console';
5+
import { Button } from 'patternfly-react';
6+
7+
import { isVmiRunning, isVmStarting } from '../VmStatus';
8+
import SerialConsoleConnector from './SerialConsoleConnector';
9+
10+
const { VNC_CONSOLE_TYPE, SERIAL_CONSOLE_TYPE } = AccessConsoles.constants;
11+
12+
const VmIsDown = ({ vm, onStartVm }) => {
13+
const action = (
14+
<Button bsStyle="link" onClick={onStartVm}>
15+
start
16+
</Button>
17+
);
18+
19+
return (
20+
<div className="co-m-pane__body">
21+
<div className="vm-consoles-loading">This Virtual Machine is down. Please {action} it to access its console.</div>
22+
</div>
23+
);
24+
};
25+
VmIsDown.propTypes = {
26+
vm: PropTypes.object.isRequired,
27+
onStartVm: PropTypes.func.isRequired,
28+
};
29+
30+
const VmIsStarting = ({ LoadingComponent }) => (
31+
<div className="co-m-pane__body">
32+
<div className="vm-consoles-loading">
33+
<LoadingComponent />
34+
This Virtual Machine is still starting up. The console will be available soon.
35+
</div>
36+
</div>
37+
);
38+
VmIsStarting.propTypes = {
39+
LoadingComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
40+
};
41+
42+
/**
43+
* Actual component for consoles.
44+
*/
45+
export const VmConsoles = ({
46+
vm,
47+
vmi,
48+
onStartVm,
49+
getVncConnectionDetails,
50+
getSerialConsoleConnectionDetails,
51+
WSFactory,
52+
LoadingComponent,
53+
}) => {
54+
if (!isVmiRunning(vmi)) {
55+
return isVmStarting(vm, vmi) ? (
56+
<VmIsStarting LoadingComponent={LoadingComponent} />
57+
) : (
58+
<VmIsDown vm={vm} onStartVm={onStartVm} />
59+
);
60+
}
61+
62+
const vncConDetails = getVncConnectionDetails(vmi);
63+
const serialConDetails = getSerialConsoleConnectionDetails(vmi);
64+
return (
65+
<div className="co-m-pane__body">
66+
<AccessConsoles preselectedType={VNC_CONSOLE_TYPE}>
67+
<SerialConsoleConnector type={SERIAL_CONSOLE_TYPE} WSFactory={WSFactory} {...serialConDetails} />
68+
<VncConsole {...vncConDetails} />
69+
</AccessConsoles>
70+
</div>
71+
);
72+
};
73+
VmConsoles.propTypes = {
74+
vm: PropTypes.object.isRequired,
75+
vmi: PropTypes.object,
76+
77+
getVncConnectionDetails: PropTypes.func.isRequired,
78+
getSerialConsoleConnectionDetails: PropTypes.func.isRequired,
79+
onStartVm: PropTypes.func.isRequired,
80+
81+
LoadingComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
82+
WSFactory: PropTypes.func.isRequired,
83+
};
84+
VmConsoles.defaultProps = {
85+
vmi: undefined,
86+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
3+
export const dummyFixture = {
4+
vmi: {},
5+
host: 'dummy.host',
6+
path: 'dummy.path',
7+
WSFactory: () => {},
8+
};
9+
10+
export default {
11+
component: ({ text }) => <div>{text}</div> /* eslint-disable-line react/prop-types */,
12+
props: {
13+
text: 'SerialConsoleConnector skipped due to complexity',
14+
},
15+
};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import React from 'react';
2+
3+
import { helpers } from 'patternfly-react';
4+
import { VmConsoles } from '../index';
5+
6+
const LoadingComponent = () => <div className="dummy-loading-component" />;
7+
8+
const vmiStarting = {
9+
metadata: {
10+
name: 'my-vm',
11+
},
12+
status: {
13+
phase: 'Scheduling',
14+
},
15+
};
16+
17+
const vmiRunning = {
18+
metadata: {
19+
name: 'my-vm',
20+
},
21+
status: {
22+
phase: 'Running',
23+
},
24+
};
25+
26+
const vmOff = {
27+
metadata: {
28+
name: 'my-vm',
29+
},
30+
spec: {
31+
running: false,
32+
},
33+
};
34+
35+
const vmRunning = {
36+
metadata: {
37+
name: 'my-vm',
38+
},
39+
spec: {
40+
running: true,
41+
},
42+
};
43+
44+
export const downVmProps = {
45+
vm: vmOff,
46+
vmi: undefined,
47+
48+
getVncConnectionDetails: helpers.noop,
49+
getSerialConsoleConnectionDetails: helpers.noop,
50+
onStartVm: helpers.noop,
51+
WSFactory: helpers.noop,
52+
LoadingComponent,
53+
};
54+
55+
export const startingVmProps = {
56+
vm: vmRunning,
57+
vmi: vmiStarting,
58+
59+
getVncConnectionDetails: helpers.noop,
60+
getSerialConsoleConnectionDetails: helpers.noop,
61+
onStartVm: helpers.noop,
62+
WSFactory: helpers.noop,
63+
LoadingComponent,
64+
};
65+
66+
export const runningVmProps = {
67+
vm: vmRunning,
68+
vmi: vmiRunning,
69+
70+
onStartVm: helpers.noop,
71+
WSFactory: helpers.noop,
72+
LoadingComponent,
73+
};
74+
75+
export default [
76+
{
77+
component: VmConsoles,
78+
props: downVmProps,
79+
},
80+
{
81+
component: VmConsoles,
82+
props: startingVmProps,
83+
},
84+
];

src/components/VmConsoles/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './VmConsoles';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
4+
import SerialConsoleConnector from '../SerialConsoleConnector';
5+
import { dummyFixture } from '../fixtures/SerialConsoleConnector.fixture';
6+
7+
describe('<SerialConsoleConnector />', () => {
8+
it('renders correctly - dummy shallow test only', () => {
9+
const component = shallow(<SerialConsoleConnector {...dummyFixture} />);
10+
expect(component).toMatchSnapshot();
11+
});
12+
});

0 commit comments

Comments
 (0)