committed by
Julien Fontanet
parent
c548e08aea
commit
2f3e463aca
16
src/common/nav.js
Normal file
16
src/common/nav.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import Link from 'react-router/lib/Link'
|
||||
import React from 'react'
|
||||
|
||||
export const NavLink = ({ children, to }) => (
|
||||
<li className='nav-item' role='tab'>
|
||||
<Link className='nav-link' activeClassName='active' to={to}>
|
||||
{children}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
|
||||
export const NavTabs = ({ children }) => (
|
||||
<ul className='nav nav-tabs' role='tablist'>
|
||||
{children}
|
||||
</ul>
|
||||
)
|
||||
@@ -201,7 +201,6 @@ $select-input-height: 40px; // Bootstrap input height
|
||||
// OJBECT TAB STYLE ============================================================
|
||||
|
||||
.nav-tabs {
|
||||
margin-bottom: 1em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
@@ -297,43 +296,6 @@ $select-input-height: 40px; // Bootstrap input height
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
// HEADER STYLE ================================================================
|
||||
|
||||
.xo-header {
|
||||
background-color: #eee;
|
||||
padding: 0.6em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// MAIN STYLE ==================================================================
|
||||
|
||||
.xo-main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 100vh;
|
||||
|
||||
/* FIXME: The size of `xo-main` matches the size of the window thanks to the
|
||||
* flex growing feature.
|
||||
* Therefore, when there is a scrollbar on the right side,
|
||||
* `xo-main` is too large (since the scrollbar uses a few
|
||||
* pixels) which makes an almost useless horizontal scrollbar appear.
|
||||
*/
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xo-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.xo-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
// DASHBOARD STYLE =============================================================
|
||||
|
||||
.card-dashboard {
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import Icon from 'icon'
|
||||
import React, { Component } from 'react'
|
||||
import { editHost } from 'xo'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { Text } from 'editable'
|
||||
import {
|
||||
connectStore
|
||||
} from 'utils'
|
||||
import {
|
||||
createGetObject
|
||||
} from 'selectors'
|
||||
|
||||
import HostActionBar from './action-bar'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@connectStore(() => {
|
||||
const getHost = createGetObject()
|
||||
|
||||
const getPool = createGetObject(
|
||||
(...args) => getHost(...args).$pool
|
||||
)
|
||||
|
||||
return (state, props) => {
|
||||
const host = getHost(state, props)
|
||||
if (!host) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
host: getHost(state, props),
|
||||
pool: getPool(state, props)
|
||||
}
|
||||
}
|
||||
})
|
||||
export default class Header extends Component {
|
||||
render () {
|
||||
const { host, pool } = this.props
|
||||
if (!host) {
|
||||
return <Icon icon='loading' />
|
||||
}
|
||||
return <Container>
|
||||
<Row>
|
||||
<Col smallSize={6}>
|
||||
<h2>
|
||||
<Icon icon={`host-${host.power_state.toLowerCase()}`} />
|
||||
<Text
|
||||
onChange={nameLabel => editHost(host, { nameLabel })}
|
||||
>{host.name_label}</Text>
|
||||
</h2>
|
||||
<span>
|
||||
<Text
|
||||
onChange={nameDescription => editHost(host, { nameDescription })}
|
||||
>{host.name_description}</Text>
|
||||
<span className='text-muted'> - {pool.name_label}</span>
|
||||
</span>
|
||||
</Col>
|
||||
<Col smallSize={6}>
|
||||
<div className='pull-xs-right'>
|
||||
<HostActionBar host={host} handlers={this.props} />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
import _ from 'messages'
|
||||
import assign from 'lodash/assign'
|
||||
import HostActionBar from './action-bar'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import Link from 'react-router/lib/Link'
|
||||
import map from 'lodash/map'
|
||||
import { NavLink, NavTabs } from 'nav'
|
||||
import Page from '../page'
|
||||
import pick from 'lodash/pick'
|
||||
import sortBy from 'lodash/sortBy'
|
||||
import React, { cloneElement, Component } from 'react'
|
||||
import { fetchHostStats, getHostMissingPatches } from 'xo'
|
||||
import { Row, Col } from 'grid'
|
||||
import sortBy from 'lodash/sortBy'
|
||||
import { Text } from 'editable'
|
||||
import { editHost, fetchHostStats, getHostMissingPatches } from 'xo'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
autobind,
|
||||
connectStore,
|
||||
@@ -37,22 +41,6 @@ const isRunning = host => host && host.power_state === 'Running'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const NavLink = ({ children, to }) => (
|
||||
<li className='nav-item' role='tab'>
|
||||
<Link className='nav-link' activeClassName='active' to={to}>
|
||||
{children}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
|
||||
const NavTabs = ({ children }) => (
|
||||
<ul className='nav nav-tabs' role='tablist'>
|
||||
{children}
|
||||
</ul>
|
||||
)
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@routes('general', {
|
||||
advanced: TabAdvanced,
|
||||
console: TabConsole,
|
||||
@@ -211,9 +199,54 @@ export default class Host extends Component {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
header () {
|
||||
const { host, pool } = this.props
|
||||
const { missingPatches } = this.state || {}
|
||||
if (!host) {
|
||||
return <Icon icon='loading' />
|
||||
}
|
||||
return <Container>
|
||||
<Row>
|
||||
<Col smallSize={6}>
|
||||
<h2>
|
||||
<Icon icon={`host-${host.power_state.toLowerCase()}`} />
|
||||
<Text
|
||||
onChange={nameLabel => editHost(host, { nameLabel })}
|
||||
>{host.name_label}</Text>
|
||||
</h2>
|
||||
<span>
|
||||
<Text
|
||||
onChange={nameDescription => editHost(host, { nameDescription })}
|
||||
>{host.name_description}</Text>
|
||||
<span className='text-muted'> - {pool.name_label}</span>
|
||||
</span>
|
||||
</Col>
|
||||
<Col smallSize={6}>
|
||||
<div className='pull-xs-right'>
|
||||
<HostActionBar host={host} handlers={this.props} />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col size={12}>
|
||||
<NavTabs>
|
||||
<NavLink to={`/hosts/${host.id}/general`}>{_('generalTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/stats`}>{_('statsTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/console`}>{_('consoleTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/network`}>{_('networkTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/storage`}>{_('storageTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/patches`}>{_('patchesTabName')} {isEmpty(missingPatches) ? null : <span className='label label-pill label-danger'>{missingPatches.length}</span>}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/logs`}>{_('logsTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/advanced`}>{_('advancedTabName')}</NavLink>
|
||||
</NavTabs>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
}
|
||||
|
||||
render () {
|
||||
const { host } = this.props
|
||||
const { missingPatches } = this.state || {}
|
||||
if (!host) {
|
||||
return <h1>Loading…</h1>
|
||||
}
|
||||
@@ -231,22 +264,8 @@ export default class Host extends Component {
|
||||
'statsOverview'
|
||||
])
|
||||
)
|
||||
return <div>
|
||||
<Row>
|
||||
<Col size={12}>
|
||||
<NavTabs>
|
||||
<NavLink to={`/hosts/${host.id}/general`}>{_('generalTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/stats`}>{_('statsTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/console`}>{_('consoleTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/network`}>{_('networkTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/storage`}>{_('storageTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/patches`}>{_('patchesTabName')} {isEmpty(missingPatches) ? null : <span className='label label-pill label-danger'>{missingPatches.length}</span>}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/logs`}>{_('logsTabName')}</NavLink>
|
||||
<NavLink to={`/hosts/${host.id}/advanced`}>{_('advancedTabName')}</NavLink>
|
||||
</NavTabs>
|
||||
</Col>
|
||||
</Row>
|
||||
return <Page header={this.header()}>
|
||||
{cloneElement(this.props.children, childProps)}
|
||||
</div>
|
||||
</Page>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, {
|
||||
Component
|
||||
} from 'react'
|
||||
import { IntlProvider } from 'messages'
|
||||
import { Notification } from 'notification'
|
||||
// import {
|
||||
// keyHandler
|
||||
// } from 'react-key-handler'
|
||||
@@ -14,71 +15,53 @@ import {
|
||||
import About from './about'
|
||||
import Backup from './backup'
|
||||
import Dashboard from './dashboard'
|
||||
import Header from './header'
|
||||
import Home from './home'
|
||||
import Host from './host'
|
||||
import HostHeader from './host/header'
|
||||
import Menu from './menu'
|
||||
import Modal from 'modal'
|
||||
import New from './new'
|
||||
import { Notification } from 'notification'
|
||||
import Settings from './settings'
|
||||
import Vm from './vm'
|
||||
import VmHeader from './vm/header'
|
||||
|
||||
const makeHeaderRoutes = (content, header) => ({
|
||||
...content.route,
|
||||
components: { content, header }
|
||||
})
|
||||
|
||||
@routes('home', {
|
||||
about: About,
|
||||
backup: Backup,
|
||||
dashboard: Dashboard,
|
||||
home: Home,
|
||||
'hosts/:id': makeHeaderRoutes(Host, HostHeader),
|
||||
'hosts/:id': Host,
|
||||
new: New,
|
||||
settings: Settings,
|
||||
'vms/:id': makeHeaderRoutes(Vm, VmHeader)
|
||||
'vms/:id': Vm
|
||||
})
|
||||
@connectStore([
|
||||
'user'
|
||||
])
|
||||
@propTypes({
|
||||
children: propTypes.node,
|
||||
header: propTypes.node,
|
||||
content: propTypes.node
|
||||
children: propTypes.node
|
||||
})
|
||||
export default class XoApp extends Component {
|
||||
componentDidMount () {
|
||||
this.refs.body.style.minHeight = this.refs.menu.getWrappedInstance().height + 'px'
|
||||
this.refs.bodyWrapper.style.minHeight = this.refs.menu.getWrappedInstance().height + 'px'
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
children,
|
||||
header,
|
||||
content
|
||||
} = this.props
|
||||
|
||||
return <IntlProvider>
|
||||
<div className='xo-main'>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
minHeight: '100vh',
|
||||
/* FIXME: 'The size of `xo-main` matches the size of the window thanks to the',
|
||||
* flex growing feature.,
|
||||
* Therefore, when there is a scrollbar on the right side,,
|
||||
* `xo-main` is too large (since the scrollbar uses a few,
|
||||
* pixels) which makes an almost useless horizontal scrollbar appear.,
|
||||
*/
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<Modal />
|
||||
<Notification />
|
||||
<Menu ref='menu' />
|
||||
<div className='xo-body' ref='body'>
|
||||
{children
|
||||
? <div className='xo-content'>
|
||||
{children}
|
||||
</div>
|
||||
: [
|
||||
<Header key='header'>
|
||||
{header}
|
||||
</Header>,
|
||||
<div key='content' className='xo-content'>
|
||||
{content}
|
||||
</div>
|
||||
]
|
||||
}
|
||||
<div ref='bodyWrapper' style={{flex: '1', padding: '1em'}}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
</IntlProvider>
|
||||
|
||||
31
src/xo-app/page/index.js
Normal file
31
src/xo-app/page/index.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import Header from '../header'
|
||||
import React from 'react'
|
||||
|
||||
const Page = ({ children, header }) => {
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: '1',
|
||||
maxHeight: '100vh',
|
||||
margin: '-1em' /* To offset the padding applied to the wrapper */
|
||||
}}>
|
||||
<Header style={{
|
||||
backgroundColor: '#eee',
|
||||
padding: '0.6em',
|
||||
paddingBottom: '0',
|
||||
flexShrink: '0'
|
||||
}}>
|
||||
{header}
|
||||
</Header>
|
||||
<div style={{
|
||||
flex: '1',
|
||||
overflowY: 'auto',
|
||||
padding: '1em'
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export { Page as default }
|
||||
@@ -1,79 +0,0 @@
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import React, { Component } from 'react'
|
||||
import { editVm } from 'xo'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { Text } from 'editable'
|
||||
import {
|
||||
connectStore
|
||||
} from 'utils'
|
||||
import {
|
||||
createGetObject
|
||||
} from 'selectors'
|
||||
|
||||
import VmActionBar from './action-bar'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@connectStore(() => {
|
||||
const getVm = createGetObject()
|
||||
|
||||
const getContainer = createGetObject(
|
||||
(...args) => getVm(...args).$container
|
||||
)
|
||||
|
||||
const getPool = createGetObject(
|
||||
(...args) => getVm(...args).$pool
|
||||
)
|
||||
|
||||
return (state, props) => {
|
||||
const vm = getVm(state, props)
|
||||
if (!vm) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
container: getContainer(state, props),
|
||||
pool: getPool(state, props),
|
||||
vm
|
||||
}
|
||||
}
|
||||
})
|
||||
export default class Header extends Component {
|
||||
render () {
|
||||
const { vm, container, pool } = this.props
|
||||
if (!vm || !pool) {
|
||||
return <Icon icon='loading' />
|
||||
}
|
||||
return <Container>
|
||||
<Row>
|
||||
<Col smallSize={6}>
|
||||
<h2>
|
||||
{isEmpty(vm.current_operations)
|
||||
? <Icon icon={`vm-${vm.power_state.toLowerCase()}`} />
|
||||
: <Icon icon='vm-busy' />
|
||||
}
|
||||
|
||||
<Text
|
||||
onChange={value => editVm(vm, { name_label: value })}
|
||||
>{vm.name_label}</Text>
|
||||
</h2>
|
||||
<span>
|
||||
<Text
|
||||
onChange={value => editVm(vm, { name_description: value })}
|
||||
>{vm.name_description}</Text>
|
||||
<span className='text-muted'>
|
||||
{vm.power_state === 'Running' ? ' - ' + container.name_label : null}
|
||||
{' '}({pool.name_label})
|
||||
</span>
|
||||
</span>
|
||||
</Col>
|
||||
<Col smallSize={6}>
|
||||
<div className='pull-xs-right'>
|
||||
<VmActionBar vm={vm} handlers={this.props} />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,20 @@
|
||||
import _ from 'messages'
|
||||
import assign from 'lodash/assign'
|
||||
import forEach from 'lodash/forEach'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import Link from 'react-router/lib/Link'
|
||||
import map from 'lodash/map'
|
||||
import { NavLink, NavTabs } from 'nav'
|
||||
import Page from '../page'
|
||||
import pick from 'lodash/pick'
|
||||
import React, { cloneElement, Component } from 'react'
|
||||
import { fetchVmStats } from 'xo'
|
||||
import { Row, Col } from 'grid'
|
||||
import VmActionBar from './action-bar'
|
||||
import { Text } from 'editable'
|
||||
import {
|
||||
editVm,
|
||||
fetchVmStats
|
||||
} from 'xo'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
autobind,
|
||||
connectStore,
|
||||
@@ -36,22 +43,6 @@ const isRunning = vm => vm && vm.power_state === 'Running'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const NavLink = ({ children, to }) => (
|
||||
<li className='nav-item' role='tab'>
|
||||
<Link className='nav-link' activeClassName='active' to={to}>
|
||||
{children}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
|
||||
const NavTabs = ({ children }) => (
|
||||
<ul className='nav nav-tabs' role='tablist'>
|
||||
{children}
|
||||
</ul>
|
||||
)
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@routes('general', {
|
||||
advanced: TabAdvanced,
|
||||
console: TabConsole,
|
||||
@@ -207,8 +198,59 @@ export default class Vm extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
header () {
|
||||
const { vm, container, pool, snapshots } = this.props
|
||||
if (!vm || !pool) {
|
||||
return <Icon icon='loading' />
|
||||
}
|
||||
return <Container>
|
||||
<Row>
|
||||
<Col smallSize={6}>
|
||||
<h2>
|
||||
{isEmpty(vm.current_operations)
|
||||
? <Icon icon={`vm-${vm.power_state.toLowerCase()}`} />
|
||||
: <Icon icon='vm-busy' />
|
||||
}
|
||||
|
||||
<Text
|
||||
onChange={value => editVm(vm, { name_label: value })}
|
||||
>{vm.name_label}</Text>
|
||||
</h2>
|
||||
<span>
|
||||
<Text
|
||||
onChange={value => editVm(vm, { name_description: value })}
|
||||
>{vm.name_description}</Text>
|
||||
<span className='text-muted'>
|
||||
{vm.power_state === 'Running' ? ' - ' + container.name_label : null}
|
||||
{' '}({pool.name_label})
|
||||
</span>
|
||||
</span>
|
||||
</Col>
|
||||
<Col smallSize={6}>
|
||||
<div className='pull-xs-right'>
|
||||
<VmActionBar vm={vm} handlers={this.props} />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col size={12}>
|
||||
<NavTabs>
|
||||
<NavLink to={`/vms/${vm.id}/general`}>{_('generalTabName')}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/stats`}>{_('statsTabName')}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/console`}>{_('consoleTabName')}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/network`}>{_('networkTabName')}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/disks`}>{_('disksTabName', { disks: vm.$VBDs.length })}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/snapshots`}>{_('snapshotsTabName')} {isEmpty(snapshots) ? null : <span className='label label-pill label-default'>{snapshots.length}</span>}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/logs`}>{_('logsTabName')}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/advanced`}>{_('advancedTabName')}</NavLink>
|
||||
</NavTabs>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
}
|
||||
|
||||
render () {
|
||||
const { snapshots, vm } = this.props
|
||||
const { vm } = this.props
|
||||
|
||||
if (!vm) {
|
||||
return <h1>Loading…</h1>
|
||||
@@ -230,22 +272,8 @@ export default class Vm extends Component {
|
||||
]), pick(this.state, [
|
||||
'statsOverview'
|
||||
]))
|
||||
return <div>
|
||||
<Row>
|
||||
<Col size={12}>
|
||||
<NavTabs>
|
||||
<NavLink to={`/vms/${vm.id}/general`}>{_('generalTabName')}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/stats`}>{_('statsTabName')}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/console`}>{_('consoleTabName')}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/network`}>{_('networkTabName')}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/disks`}>{_('disksTabName', { disks: vm.$VBDs.length })}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/snapshots`}>{_('snapshotsTabName')} {isEmpty(snapshots) ? null : <span className='label label-pill label-default'>{snapshots.length}</span>}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/logs`}>{_('logsTabName')}</NavLink>
|
||||
<NavLink to={`/vms/${vm.id}/advanced`}>{_('advancedTabName')}</NavLink>
|
||||
</NavTabs>
|
||||
</Col>
|
||||
</Row>
|
||||
return <Page header={this.header()}>
|
||||
{cloneElement(this.props.children, childProps)}
|
||||
</div>
|
||||
</Page>
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user