Skip to content

Commit

Permalink
Added NoVNC console page
Browse files Browse the repository at this point in the history
Fixes: oVirt#490
  • Loading branch information
bond95 committed Feb 15, 2019
1 parent b2f9ce6 commit 03c817c
Show file tree
Hide file tree
Showing 38 changed files with 1,252 additions and 406 deletions.
3 changes: 3 additions & 0 deletions scripts/start.js
Expand Up @@ -8,6 +8,8 @@ var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var historyApiFallback = require('connect-history-api-fallback');
var httpProxyMiddleware = require('http-proxy-middleware');
var http = require('http');
var httpProxy = require('http-proxy');
var execSync = require('child_process').execSync;
var opn = require('opn');
var detect = require('detect-port');
Expand All @@ -19,6 +21,7 @@ var env = require('../config/env')
var rimraf = require('rimraf')
var formatMessage = require('./utils/utils').formatMessage
var isLikelyASyntaxError = require('./utils/utils').isLikelyASyntaxError
const url = require('url');

// Tools like Cloud9 rely on this.
var DEFAULT_PORT = process.env.PORT || 3000;
Expand Down
86 changes: 57 additions & 29 deletions src/actions/console.js
@@ -1,84 +1,112 @@
import {
SET_CONSOLE_IN_USE,
CHECK_CONSOLE_IN_USE,
SET_CONSOLE_LOGON,
OPEN_CONSOLE_MODAL,
SET_CONSOLE_NOVNC_STATUS,
SET_CONSOLE_TICKETS,
DOWNLOAD_CONSOLE_VM,
SET_ACTIVE_CONSOLE, SET_CONSOLE_NVNC,
SET_ACTIVE_CONSOLE,
SET_NEW_CONSOLE_MODAL,
CLOSE_CONSOLE_MODAL,
SET_IN_USE_CONSOLE_MODAL_STATE,
SET_LOGON_CONSOLE_MODAL_STATE,
} from '../constants'

export function setConsoleInUse ({ vmId, consoleInUse }) {
export function openConsoleModal ({ vmId, usbFilter, userId, consoleId, hasGuestAgent, openInPage, isNoVNC, modalId }) {
return {
type: SET_CONSOLE_IN_USE,
type: OPEN_CONSOLE_MODAL,
payload: {
vmId,
consoleInUse,
usbFilter,
userId,
consoleId,
hasGuestAgent,
openInPage,
isNoVNC,
modalId,
},
}
}

export function setConsoleLogon ({ vmId, isLogon }) {
export function setActiveConsole ({ vmId, consoleId }) {
return {
type: SET_CONSOLE_LOGON,
type: SET_ACTIVE_CONSOLE,
payload: {
vmId,
isLogon,
consoleId,
},
}
}

export function checkConsoleInUse ({ vmId, usbFilter, userId, hasGuestAgent }) {
export function downloadConsole ({ vmId, consoleId, usbFilter, hasGuestAgent, skipSSO, openInPage, isNoVNC, modalId }) {
return {
type: CHECK_CONSOLE_IN_USE,
type: DOWNLOAD_CONSOLE_VM,
payload: {
vmId,
consoleId,
usbFilter,
userId,
hasGuestAgent,
skipSSO,
openInPage,
isNoVNC,
modalId,
},
}
}

export function setActiveConsole ({ vmId, consoleId }) {
export function setConsoleTickets ({ vmId, proxyTicket, ticket }) {
return {
type: SET_ACTIVE_CONSOLE,
type: SET_CONSOLE_TICKETS,
payload: {
vmId,
consoleId,
proxyTicket,
ticket,
},
}
}

export function setConsoleNoVNC ({ vmId, isRunning }) {
export function setConsoleStatus ({ vmId, status }) {
return {
type: SET_CONSOLE_NVNC,
type: SET_CONSOLE_NOVNC_STATUS,
payload: {
vmId,
isRunning,
status,
},
}
}

export function downloadConsole ({ vmId, consoleId, usbFilter, hasGuestAgent, skipSSO }) {
export function setNewConsoleModal ({ modalId, vmId, consoleId }) {
return {
type: DOWNLOAD_CONSOLE_VM,
type: SET_NEW_CONSOLE_MODAL,
payload: {
modalId,
vmId,
consoleId,
usbFilter,
hasGuestAgent,
skipSSO,
},
}
}

export function setConsoleTickets ({ vmId, proxyTicket, ticket }) {
export function closeConsoleModal ({ modalId }) {
return {
type: SET_CONSOLE_TICKETS,
type: CLOSE_CONSOLE_MODAL,
payload: {
vmId,
proxyTicket,
ticket,
modalId,
},
}
}

export function setInUseConsoleModalState ({ modalId }) {
return {
type: SET_IN_USE_CONSOLE_MODAL_STATE,
payload: {
modalId,
},
}
}

export function setLogonConsoleModalState ({ modalId }) {
return {
type: SET_LOGON_CONSOLE_MODAL_STATE,
payload: {
modalId,
},
}
}
10 changes: 10 additions & 0 deletions src/actions/index.js
Expand Up @@ -13,6 +13,7 @@ import {
SET_USB_FILTER,
SET_USER_FILTER_PERMISSION,
SET_USER_GROUPS,
SET_WEBSOCKET,
SHOW_TOKEN_EXPIRED_MSG,
START_SCHEDULER_FIXED_DELAY,
STOP_SCHEDULER_FIXED_DELAY,
Expand Down Expand Up @@ -79,6 +80,15 @@ export function setUserFilterPermission (filter) {
}
}

export function setWebsocket (websocket) {
return {
type: SET_WEBSOCKET,
payload: {
websocket,
},
}
}

export function setAdministrator (administrator) {
return {
type: SET_ADMINISTRATOR,
Expand Down
32 changes: 32 additions & 0 deletions src/components/Loader.js
@@ -0,0 +1,32 @@
import React from 'react'
import PropTypes from 'prop-types'

import style from './sharedStyle.css'

export const SIZES = {
SMALL: 'SMALL',
LARGE: 'LARGE',
}

const Loader = ({ loaderText, size }) => {
let wh = 0
switch (size) {
case SIZES.SMALL:
wh = 30
break
case SIZES.LARGE:
wh = 100
break
}
return <div key='infinite-scroll-loader' className={style['loaderBox']}>
<div style={{ height: wh, width: wh }} className={style['loader']} />
<div>{loaderText}</div>
</div>
}

Loader.propTypes = {
loaderText: PropTypes.string,
size: PropTypes.oneOf(Object.values(SIZES)),
}

export default Loader
2 changes: 1 addition & 1 deletion src/components/PageRouter.js
Expand Up @@ -77,7 +77,7 @@ class PageRouter extends React.Component {
return (
<div id='page-router'>
<Breadcrumb branches={branches} />
<Toolbar>
<Toolbar isFullWidth={branch.route.isToolbarFullWidth}>
{tools}
</Toolbar>
<div id='page-router-render-component'>
Expand Down
90 changes: 9 additions & 81 deletions src/components/Pages/index.js
Expand Up @@ -11,14 +11,10 @@ import { canUserUseAnyClusters } from '_/utils'
import VmDialog from '../VmDialog'
import VmsList from '../VmsList'
import VmDetails from '../VmDetails'
import VmConsole from '../VmConsole'
import { default as LegacyVmDetails } from '../VmDetail'
import { SplitButton, MenuItem } from 'patternfly-react'

import { addUserMessage } from '_/actions'
import { VncConsole } from '@patternfly/react-console'
import ConsoleConfirmationModal from '../VmActions/ConsoleConfirmationModal'
import Action from '../VmActions/Action'
import { fromJS } from 'immutable'

/**
* Route component (for PageRouter) to view the list of VMs and Pools
Expand Down Expand Up @@ -155,100 +151,32 @@ const VmCreatePageConnected = connect(
})
)(VmCreatePage)

class VmConsoleSelector extends React.Component {
render () {
const { vmId, id, vms, consoleId } = this.props
let actions = vms.getIn(['vms', vmId, 'consoles'])
actions = [...actions]
if (actions.length === 0) {
return <div />
}
return <SplitButton title='Spice Console' id={id}>
{ actions.map(action => {
let selected = {}
selected['active'] = action.get('id') === consoleId
return <Action
confirmation={<div><ConsoleConfirmationModal vm={fromJS({ id: vmId })} consoleId={action.get('id')}
/></div>}
>
<MenuItem key={action.id} id={action.id}
onClick={action.onClick} {...selected}>{action.get('protocol')}</MenuItem>
</Action>
}) }
</SplitButton>
}
}

VmConsoleSelector.propTypes = {
vmId: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
vms: PropTypes.object.isRequired,
consoleId: PropTypes.string.isRequired,
}

const VmConsoleSelectorConnected = connect(
(state) => ({
vms: state.vms,
consoles: state.consoles,
})
)(VmConsoleSelector)

class VmConsolePage extends React.Component {
constructor (props) {
super(props)
this.state = {
vmId: undefined,
consoleId: undefined,
disconnected: false,
}
}

static getDerivedStateFromProps (props, state) {
if (state.vmId !== props.match.params.id) {
const vmId = props.match.params.id
return { vmId }
}
}

onDisconnected (e) {
this.state({ disconnected: true })
}

render () {
const host = window.location.hostname
const confirmation = <ConsoleConfirmationModal vm={fromJS({ vm: this.state.vmId })} consoleId={this.props.match.console_id}
onClose={() => {}} />
if (this.props.consoles.getIn(['vms', this.state.vmId]) === undefined) {
return <div>{confirmation}</div>
}
const proxyTicket = this.props.consoles.getIn(['vms', this.state.vmId, 'proxyTicket'])
const ticket = this.props.consoles.getIn(['vms', this.state.vmId, 'ticket'])
if (ticket !== undefined) {
return <div><VncConsole encrypt credentials={{ password: ticket.value }}
path={proxyTicket}
host={host} port='6100' onDisconnected={this.onDisconnected} /></div>
} else {
return <div>Downloading vv file</div>
const { vms, match } = this.props
if (match.params.id && vms.getIn(['vms', match.params.id])) {
return <VmConsole consoleId={match.params.console_id} vmId={match.params.id} />
}
return null
}
}

VmConsolePage.propTypes = {
consoles: PropTypes.object.isRequired,
match: RouterPropTypeShapes.match.isRequired,
vms: PropTypes.object.isRequired,
}

const VmConsolePageConnected = connect(
(state) => ({
consoles: state.consoles,
})
vms: state.vms,
}),
(dispatch) => ({})
)(VmConsolePage)

export {
PoolDetailsPageConnected as PoolDetailsPage,
VmDetailsPageConnected as VmDetailsPage,
VmCreatePageConnected as VmCreatePage,
VmConsolePageConnected as VmConsolePage,
VmConsoleSelectorConnected as VmConsoleSelector,
VmsPage,
}
7 changes: 4 additions & 3 deletions src/components/Toolbar/Toolbar.js
Expand Up @@ -2,17 +2,17 @@ import React from 'react'
import PropTypes from 'prop-types'
import style from './style.css'

const Toolbar = ({ children }) => {
const Toolbar = ({ children, isFullWidth }) => {
let i = 0
const actions = children.map((c) => {
return (<div key={'toolbar' + i++} className={`form-group toolbar-pf-view-selector ${style['actions-padding']}`}>{c}</div>)
return (<div key={'toolbar' + i++} className={`form-group toolbar-pf-view-selector ${style['actions-padding']} ${isFullWidth && style['full-width']}`}>{c}</div>)
})

return (<div className={`container-fluid ${style['toolbar']}`}>
<div className='row toolbar-pf'>
<div className='col-sm-12'>
<div className='toolbar-pf-actions'>
<div className='toolbar-pf-action-right toolbar-pf-height-shim'>
<div className={`toolbar-pf-action-right toolbar-pf-height-shim ${isFullWidth && style['full-width']}`}>
{actions}
</div>
</div>
Expand All @@ -23,6 +23,7 @@ const Toolbar = ({ children }) => {

Toolbar.propTypes = {
children: PropTypes.node.isRequired,
isFullWidth: PropTypes.bool,
}

export default Toolbar

0 comments on commit 03c817c

Please sign in to comment.