committed by
Julien Fontanet
parent
d9d91c4953
commit
82d9c53f3e
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
162
src/index.scss
162
src/index.scss
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'> </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
77
src/xo-app/menu/index.js
Normal 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> </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/>
|
||||
{_(item.label)}
|
||||
</LinkComponent>
|
||||
</li>
|
||||
})}
|
||||
</ul>
|
||||
}
|
||||
39
src/xo-app/navbar/index.js
Normal file
39
src/xo-app/navbar/index.js
Normal 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/> {user && `${user.email}`}
|
||||
<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>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user