Left side menu and navbar. (#869)

Merge PR #869 from @pdonias.
This commit is contained in:
Pierre Donias
2016-04-08 18:34:20 +02:00
committed by Julien Fontanet
parent d9d91c4953
commit 82d9c53f3e
7 changed files with 361 additions and 38 deletions

View File

@@ -10,7 +10,7 @@ import {
MenuItem
} from 'react-bootstrap-4/lib'
const ActionBar = ({ actions, display = 'both' }) => (
const ActionBar = ({ actions, display = 'text' }) => (
<ButtonToolbar>
{map(actions, (action, index) =>
!action.dropdownItems

View File

@@ -18,11 +18,58 @@ import {
export const messages = {
// ----- Titles -----
homePage: {
defaultMessage: 'Home'
},
dashboardPage: {
defaultMessage: 'Dashboard'
},
selfServicePage: {
defaultMessage: 'Self service'
},
selfServiceDashboardPage: {
defaultMessage: 'Dashboard'
},
selfServiceAdminPage: {
defaultMessage: 'Administration'
},
backupPage: {
defaultMessage: 'Backup'
},
updatePage: {
defaultMessage: 'Updates'
},
settingsPage: {
defaultMessage: 'Settings'
},
settingsServersPage: {
defaultMessage: 'Servers'
},
settingsUsersPage: {
defaultMessage: 'Users'
},
settingsGroupsPage: {
defaultMessage: 'Groups'
},
settingsAclsPage: {
defaultMessage: 'ACLs'
},
settingsPluginsPage: {
defaultMessage: 'Plugins'
},
aboutPage: {
defaultMessage: 'About'
},
homePage: {
defaultMessage: 'Home'
createMenu: {
defaultMessage: 'Create'
},
// ----- Languages -----
enLang: {
defaultMessage: 'EN'
},
frLang: {
defaultMessage: 'FR'
},
// ----- Sign in -----
@@ -376,8 +423,21 @@ const localizedMessages = {}
addLocaleData(frLocaleData)
localizedMessages.fr = {
aboutPage: 'À propos',
homePage: 'Accueil',
dashboardPage: 'Tableau de bord',
selfServicePage: 'Self service',
selfServiceDashboardPage: 'Tableau de bord',
settingsServersPage: 'Serveurs',
settingsUsersPage: 'Utilisateurs',
settingsGroupsPage: 'Groupes',
settingsAclsPage: 'ACLs',
settingsPluginsPage: 'Extensions',
selfServiceAdminPage: 'Administration',
backupPage: 'Sauvegarde',
updatePage: 'Mises à jour',
settingsPage: 'Paramètres',
aboutPage: 'À propos',
createMenu: 'Créer',
usernameLabel: 'Nom :',
passwordLabel: 'Mot de passe :',
signInButton: 'Connexion',

View File

@@ -27,7 +27,7 @@ export default class Tags extends Component {
</span>
{onAdd
? !this.state.editing
? <span className='add-tag-action' onClick={() => this.setState({...this.state, editing: true})} style={{cursor: 'pointer'}}>
? <span className='add-tag-action' onClick={() => this.setState({editing: true})} style={{cursor: 'pointer'}}>
<Icon icon='add-tag' />
</span>
: <span>

View File

@@ -1,6 +1,10 @@
// http://v4-alpha.getbootstrap.com/getting-started/flexbox/#how-it-works
$enable-flex: true;
$nav-pills-border-radius: 0;
$nav-pills-active-link-color: white;
$nav-pills-active-link-bg: #373a3c;
@import "../node_modules/bootstrap/scss/bootstrap";
// -------------------------------------------------------------------
@@ -324,6 +328,82 @@ $ct-series-colors: (
&-action-row:hover {
color: black
}
// Navbar
&-user {
@extend .fa;
@extend .fa-user;
}
&-sign-out {
@extend .fa;
@extend .fa-sign-out;
}
// Menu
&-menu-collapse {
@extend .fa;
@extend .fa-bars;
}
&-menu-home {
@extend .fa;
@extend .fa-home;
}
&-menu-dashboard {
@extend .fa;
@extend .fa-dashboard;
}
&-menu-self-service {
@extend .fa;
@extend .fa-cloud;
&-dashboard {
@extend .fa;
@extend .fa-dashboard;
}
&-admin {
@extend .fa;
@extend .fa-wrench;
}
}
&-menu-backup {
@extend .fa;
@extend .fa-archive;
}
&-menu-update {
@extend .fa;
@extend .fa-refresh;
}
&-menu-settings {
@extend .fa;
@extend .fa-cog;
&-servers {
@extend .fa;
@extend .fa-cloud;
}
&-users {
@extend .fa;
@extend .fa-user;
}
&-groups {
@extend .fa;
@extend .fa-users;
}
&-acls {
@extend .fa;
@extend .fa-key;
}
&-plugins {
@extend .fa;
@extend .fa-puzzle-piece;
}
}
&-menu-about {
@extend .fa;
@extend .fa-info;
}
&-menu-create {
@extend .fa;
@extend .fa-plus;
}
}
// OJBECT TAB STYLE ============================================================
@@ -385,3 +465,85 @@ $ct-series-colors: (
.label-ip {
margin-left: 1em;
}
// MENU STYLE ==================================================================
.xo-menu, .xo-sub-menu {
background-color: $gray;
color: white;
}
.xo-menu {
a {
color: inherit;
}
button {
background-color: inherit;
color: inherit;
}
}
.xo-menu-item {
min-width: 100%;
position: relative;
width: max-content;
&:hover {
background-color: $nav-pills-active-link-bg;
color: $nav-pills-active-link-color;
}
}
.xo-sub-menu {
left: 100%;
opacity: 0;
position: absolute;
top: 0;
transition: opacity .3s;
visibility: hidden;
width: max-content;
z-index: 1000;
}
.xo-menu-item:hover > .xo-sub-menu {
opacity: 1;
visibility: visible;
}
// NAVBAR STYLE ================================================================
.xo-navbar {
background-color: $gray-dark;
color: white; // FIXME: use boostrap variables
}
.xo-brand {
color: white !important; // FIXME: use boostrap variables
font-size: 1.5em;
}
// MAIN STYLE ==================================================================
.xo-main {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.xo-body {
display: flex;
flex: 1;
flex-direction: row;
}
.xo-navbar-substitute {
height: 60px;
}
.xo-content {
min-width: 60em;
padding: 1em;
flex: 1;
}

View File

@@ -1,6 +1,3 @@
import _ from 'messages'
import IndexLink from 'react-router/lib/IndexLink'
import Link from 'react-router/lib/Link'
import React, {
Component
} from 'react'
@@ -18,6 +15,9 @@ import Home from './home'
import SignIn from './sign-in'
import Vm from './vm'
import Menu from './menu'
import Navbar from './navbar'
@routes(Home, [
{
path: 'about',
@@ -29,8 +29,7 @@ import Vm from './vm'
}
])
@connectStore([
'user',
'status'
'user'
])
@propTypes({
children: propTypes.node.isRequired
@@ -41,36 +40,22 @@ export default class XoApp extends Component {
children,
user,
signIn,
selectLang,
status
selectLang
} = this.props
return <div className='container-fluid'>
<h1>Xen Orchestra</h1>
<p>
<button
type='button'
onClick={() => selectLang('en')}
>en</button>
<button
type='button'
onClick={() => selectLang('fr')}
>fr</button>
</p>
<ul>
<li><Link to='/about'>{_('aboutPage')}</Link></li>
<li><IndexLink to='/'>{_('homePage')}</IndexLink></li>
</ul>
<p>{status}{user && ` as ${user.email}`}</p>
{
user == null
? <SignIn onSubmit={signIn} />
: children
}
return <div className='xo-main'>
<Navbar selectLang={(lang) => selectLang(lang)} />
<div className='xo-navbar-substitute'>&nbsp;</div>
<div className='xo-body'>
<Menu />
<div className='xo-content'>
{
user == null
? <SignIn onSubmit={signIn} />
: children
}
</div>
</div>
</div>
}
}

77
src/xo-app/menu/index.js Normal file
View File

@@ -0,0 +1,77 @@
import _ from 'messages'
import { Button } from 'react-bootstrap-4/lib'
import IndexLink from 'react-router/lib/IndexLink'
import Link from 'react-router/lib/Link'
import map from 'lodash/map'
import React, { Component } from 'react'
import Icon from 'icon'
export default class Menu extends Component {
componentWillMount () {
this.setState({collapsed: false})
}
render () {
const items = [
{ to: '/home', icon: 'home', label: 'homePage' },
{ to: '/dashboard', icon: 'dashboard', label: 'dashboardPage' },
{ to: '/self', icon: 'self-service', label: 'selfServicePage', subMenu: [
{ to: '/self/dashboard', icon: 'self-service-dashboard', label: 'selfServiceDashboardPage' },
{ to: '/self/admin', icon: 'self-service-admin', label: 'selfServiceAdminPage' }
]},
{ to: '/backup', icon: 'backup', label: 'backupPage' },
{ to: '/update', icon: 'update', label: 'updatePage' },
{ to: '/settings', icon: 'settings', label: 'settingsPage', subMenu: [
{ to: '/settings/servers', icon: 'settings-servers', label: 'settingsServersPage' },
{ to: '/settings/users', icon: 'settings-users', label: 'settingsUsersPage' },
{ to: '/settings/groups', icon: 'settings-groups', label: 'settingsGroupsPage' },
{ to: '/settings/acls', icon: 'settings-acls', label: 'settingsAclsPage' },
{ to: '/settings/plugins', icon: 'settings-plugins', label: 'settingsPluginsPage' }
]},
{ to: '/about', icon: 'about', label: 'aboutPage' },
{ to: '/create', icon: 'create', label: 'createMenu' }
]
return <div className='xo-menu'>
<ul className='nav nav-sidebar nav-pills nav-stacked'>
<Button onClick={() => this.setState({collapsed: !this.state.collapsed})}>
<Icon icon='menu-collapse' size='lg' fixedWidth />
</Button>
{map(items, (item, index) =>
<MenuLinkItem key={index} item={item} collapsed={this.state.collapsed}/>
)}
</ul>
</div>
}
}
const MenuLinkItem = (props) => {
const { item, collapsed } = props
const { to, icon, label, subMenu } = item
const [ LinkComponent, path ] = to === '/home'
? [ IndexLink, '/' ] : [ Link, to ]
return <li className='nav-item xo-menu-item'>
<LinkComponent activeClassName='active' className='nav-link' to={path}>
<Icon icon={`menu-${icon}`} size='lg' fixedWidth/>
{!collapsed && <span>&nbsp;&nbsp;&nbsp;</span>}
{!collapsed && _(label)}
</LinkComponent>
{subMenu && <SubMenu items={subMenu}/>}
</li>
}
const SubMenu = (props) => {
return <ul className='nav nav-pills nav-stacked xo-sub-menu'>
{map(props.items, (item, index) => {
const [ LinkComponent, path ] = item.to === 'home'
? [ IndexLink, '/' ] : [ Link, item.to ]
return <li key={index} className='nav-item xo-menu-item'>
<LinkComponent className='nav-link' to={path}>
<Icon icon={`menu-${item.icon}`} size='lg' fixedWidth/>&nbsp;&nbsp;&nbsp;
{_(item.label)}
</LinkComponent>
</li>
})}
</ul>
}

View File

@@ -0,0 +1,39 @@
import Icon from 'icon'
import React, { Component } from 'react'
import {
connectStore,
propTypes,
autobind
} from 'utils'
@connectStore([
'user'
])
@propTypes({
selectLang: propTypes.func.isRequired
})
export default class Navbar extends Component {
@autobind
handleSelectLang (event) {
// FIXME: find a way to reach selectLang to set the app language
this.props.selectLang(event.target.value)
}
render () {
const {
user
} = this.props
return <nav className='navbar navbar-full navbar-fixed-top navbar-light bg-faded xo-navbar'>
<a className='navbar-brand xo-brand' href='#'>Xen Orchestra</a>
<div className='pull-xs-right'>
<Icon icon='user' fixedWidth/>&nbsp;{user && `${user.email}`}&nbsp;
<Icon icon='sign-out' fixedWidth/>
</div>
<div className='pull-xs-right' style={{marginRight: '1em'}}>
<select className='form-control' onChange={this.handleSelectLang} defaultValue={'en'} >
<option value='en'>English</option>
<option value='fr'>Français</option>
</select>
</div>
</nav>
}
}