RCUE Header

https://fedorahosted.org/freeipa/ticket/3902
This commit is contained in:
Petr Vobornik 2013-11-07 18:03:38 +01:00 committed by Martin Kosek
parent b69d30072a
commit 3b9280c974
6 changed files with 363 additions and 108 deletions

View File

@ -120,38 +120,6 @@ textarea[readonly] {
padding: 0.2em;
}
/* ---- Header ---- */
.header {
position: absolute;
top: 0;
left: 6px;
right: 6px;
height: 34px;
background: transparent;
}
.header a {
text-decoration: none;
}
.header a:link {
text-decoration: none;
color: white;
}
.header a:visited {
text-decoration: none;
color: white;
}
.header span.header-logo {
padding-left: 2em;
}
.header span.header-logo a img {
border: 0;
}
/* ---- Password expiration */
.header-passwordexpires {
@ -164,20 +132,6 @@ textarea[readonly] {
font-weight: bold;
}
/* ---- Logged-in As ---- */
.header-right {
float: right;
}
.header-loggedinas {
line-height: 34px;
color: #fff;
}
.header-loggedinas .login {
font-weight: bold;
}
/* ---- Notification area ---- */
.notification-area {
@ -231,7 +185,7 @@ textarea[readonly] {
.facet {
position: absolute;
top: 5px;
top: 110px;
left: 10px;
right: 10px;
bottom: 0;
@ -253,7 +207,7 @@ textarea[readonly] {
.facet-title {
position: absolute;
top: 15px;
top: 10px;
left: 0;
color: gray;
display: block;
@ -261,6 +215,7 @@ textarea[readonly] {
.facet-title h3 {
margin: 0;
line-height: 1.8em;
}
.facet-title span {

View File

@ -0,0 +1,32 @@
/* Authors:
* Petr Vobornik <pvoborni@redhat.com>
*
* Copyright (C) 2013 Red Hat
* see file 'COPYING' for use and warranty information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file contains overrides of reference RCUE implementation to comply
// with IPA design
.header.rcue {
// Use blue instead of red
border-top: 3px solid #1d85d9;
.brand {
// Lower vertical padding by 5px
// FreeIPA uses logo with height: 20px instead of 10px.
padding: 2px 0;
}
}

View File

@ -4,3 +4,4 @@
@import "rcue/navbar";
@import "rcue/buttons";
@import "rcue/forms";
@import "brand";

View File

@ -31,10 +31,11 @@ define(['dojo/_base/declare',
'dojo/Evented',
'dojo/Stateful',
'./Menu',
'./DropdownWidget',
'dojo/NodeList-dom'
],
function(declare, lang, array, dom, construct, prop, dom_class,
dom_style, query, on, Stateful, Evented, Menu) {
dom_style, query, on, Stateful, Evented, Menu, DropdownWidget) {
/**
* Main application widget
@ -59,14 +60,8 @@ define(['dojo/_base/declare',
password_expires_node: null,
logged_nodes: null,
logged_user_node: null,
logged_user_link_node: null,
logout_link_node: null,
menu_node: null,
content_node: null,
@ -77,9 +72,7 @@ define(['dojo/_base/declare',
_loggedSetter: function(value) {
this.logged = value;
if (this.logged_nodes) {
this.logged_nodes.style('visibility', value ? 'visible' : 'hidden');
}
//TODO show/hide menu
},
fullname: '',
@ -92,8 +85,6 @@ define(['dojo/_base/declare',
},
render: function() {
// TODO: this method may be split into several components
this.domNode = construct.create('div', {
id: this.app_id,
@ -106,9 +97,6 @@ define(['dojo/_base/declare',
this._render_header();
this.menu_node = this.menu_widget.render();
construct.place(this.menu_node, this.header_node);
this.content_node = construct.create('div', {
'class': 'content'
}, this.domNode);
@ -117,57 +105,116 @@ define(['dojo/_base/declare',
_render_header: function() {
this.header_node = construct.create('div', {
'class': 'header rcue'
}, this.domNode);
});
// logo
construct.place(''+
'<span class="header-logo">'+
'<a href="#"><img src="images/ipa-logo.png" />'+
'<img src="images/ipa-banner.png" /></a>'+
'</span>', this.header_node);
this._render_nav_util();
construct.place(this.nav_util_node, this.header_node);
// right part
construct.place(''+
'<span class="header-right">'+
'<span class="header-passwordexpires"></span>'+
'<span class="loggedinas header-loggedinas" style="visibility:hidden;">'+
'<a href="#"><span class="login_header">Logged in as</span>: <span class="login"></span></a>'+
'</span>'+
'<span class="header-loggedinas" style="visibility:hidden;">'+
' | <a href="#logout" class="logout">Logout</a>'+
'</span>'+
'<span class="header-network-activity-indicator network-activity-indicator">'+
'<img src="images/spinner-header.gif" />'+
'</span>'+
'</span>', this.header_node);
this.password_expires_node = query('.header-passwordexpires', this.header_node)[0];
this.logged_nodes = query('.header-loggedinas', this.header_node);
this.logged_header_node = query('.login_header')[0];
this.logged_user_node = query('.loggedinas .login', this.header_node)[0];
this.logged_user_link_node = query('.loggedinas a', this.header_node)[0];
this.logout_link_node = query('.logout')[0];
on(this.logout_link_node, 'click', lang.hitch(this,this.on_logout));
on(this.logged_user_link_node, 'click', lang.hitch(this,this.on_profile));
this.menu_node = this.menu_widget.render();
construct.place(this.menu_node, this.header_node);
construct.place(this.header_node, this.domNode);
},
on_profile: function(event) {
event.preventDefault();
this.emit('profile-click');
_render_nav_util: function() {
this.nav_util_node = construct.create('div', {
'class': 'navbar utility'
});
this.nav_util_inner_node = construct.create('div', {
'class': 'navbar-inner'
}, this.nav_util_node);
this._render_brand();
construct.place(this.brand_node, this.nav_util_inner_node);
this.nav_util_tool_node = construct.create('ul', {
'class': 'nav pull-right'
}, this.nav_util_inner_node);
this.password_expires_node = construct.create('li', {
'class': 'header-passwordexpires'
}, this.nav_util_tool_node);
var network_activity = construct.create('li', {
'class': 'header-network-activity-indicator network-activity-indicator'
}, this.nav_util_tool_node);
construct.create('img', {
'src': 'images/spinner-header.gif'
}, network_activity);
var user_toggle = this._render_user_toggle_nodes();
this.user_menu.set('toggle_content', user_toggle);
construct.place(this.user_menu.render(), this.nav_util_tool_node);
return this.nav_util_node;
},
on_logout: function(event) {
event.preventDefault();
this.emit('logout-click');
_render_brand: function() {
this.brand_node = construct.create('a', {
'class': 'brand',
href: '#'
});
construct.create('img', {
src: 'images/header-logo.png',
alt: 'FreeIPA' // TODO: replace with configuration value
}, this.brand_node);
return this.brand_node;
},
_render_user_toggle_nodes: function() {
var nodes = [];
nodes.push(construct.create('span', {
'class': 'icon-user icon-white'
}));
this.logged_user_node = construct.create('span', {
'class': 'loggedinas'
});
nodes.push(this.logged_user_node);
nodes.push(construct.create('b', {
'class': 'caret'
}));
return nodes;
},
on_user_menu_click: function(item) {
if (item.name === 'profile') {
this.emit('profile-click');
} else if (item.name === 'logout') {
this.emit('logout-click');
}
},
constructor: function(spec) {
spec = spec || {};
this.menu_widget = new Menu();
this.user_menu = new DropdownWidget({
el_type: 'li',
name: 'profile-menu',
items: [
{
name: 'profile',
label: 'Profile'
},
{
'class': 'divider'
},
{
name: 'logout',
label: 'Logout'
}
]
});
on(this.user_menu, 'item-click', lang.hitch(this, this.on_user_menu_click));
}
});

View File

@ -0,0 +1,211 @@
/* Authors:
* Petr Vobornik <pvoborni@redhat.com>
*
* Copyright (C) 2013 Red Hat
* see file 'COPYING' for use and warranty information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(['dojo/_base/declare',
'dojo/_base/array',
'dojo/_base/lang',
'dojo/dom',
'dojo/dom-construct',
'dojo/dom-prop',
'dojo/dom-class',
'dojo/dom-style',
'dojo/dom-attr',
'dojo/query',
'dojo/Evented',
'dojo/Stateful',
'dojo/on',
'../jquery',
'../ipa'], function(declare, array, lang, dom, construct, prop, dom_class,
dom_style, attr, query, Evented, Stateful, on, $, IPA) {
return declare([Stateful, Evented], {
/**
* Represents and creates a dropdown widget. It can contain multiple
* levels.
*
* @class widgets.DropdownWidget
*/
/**
* Raised when menu item is clicked
* @event item-click
*/
/**
* Dropdown name
* @property {string}
*/
name: '',
/**
* Element type
* @property {string}
*/
el_type: 'div',
/**
* Element class
* @property {string}
*/
'class': 'dropdown',
/**
* Submenu class
*/
submenu_class: 'dropdown-submenu',
/**
* Toggle button text
* @property {string}
*/
toggle_text: '',
/**
* Toggle button content. Replaces toggle button text if set. Can be
* use for more complex toggle buttons.
* @property {HTMLElement|HTMLElement[]}
*/
toggle_content: null,
/**
* Array of dropdown items to display. Item can have `items` field
* with an array of child items.
* @property {Array}
*/
items: [],
/**
* domNode of this widget
* @property {HTMLElement}
*/
dom_node: null,
render: function() {
if (this.dom_node) {
construct.empty(this.dom_node);
} else {
this.dom_node = construct.create(this.el_type, {
name: this.name || '',
'class': this['class']
});
}
this._render_toggle(this.dom_node);
this._render_items(this.items, this.dom_node);
return this.dom_node;
},
_render_toggle: function(container) {
this.toggle_node = construct.create('a', {
'class': 'dropdown-toggle',
'data-toggle': 'dropdown',
href: '#'
});
this._update_toggle();
if (container) {
construct.place(this.toggle_node, container);
}
return this.toggle_node;
},
_update_toggle: function() {
if (!this.toggle_node) return;
if (this.toggle_content) {
if (lang.isArray(this.toggle_content)) {
array.forEach(this.toggle_content, function(item) {
construct.place(item, this.toggle_node);
}, this);
} else {
construct.place(this.toggle_content, this.toggle_node);
}
} else {
prop.set(this.toggle_node, 'textContent', this.toggle_text);
}
},
_toggle_textSetter: function(value) {
this.toggle_text = value;
this._update_toggle();
},
_toggle_contentSetter: function(value) {
this.toggle_content = value;
this._update_toggle();
},
_render_items: function(items, container) {
var ul = construct.create('ul', {
'class': 'dropdown-menu'
});
array.forEach(items, function(item) {
this._render_item(item, ul);
}, this);
if (container) {
construct.place(ul, container);
}
return ul;
},
_render_item: function(item, container) {
var li = construct.create('li', {
'data-name': item.name || ''
});
var a = construct.create('a', {
'href': '#' + item.name || '',
innerHTML: item.label || ''
}, li);
if (item['class']) {
dom_class.add(li, item['class']);
}
if (item.items && item.items.length > 0) {
dom_class.add(li, 'dropdown-submenu');
this._render_items(item.items, li);
} else {
on(a, 'click', lang.hitch(this, function(event) {
this.on_item_click(event, item);
event.preventDefault();
}));
}
if (container) {
construct.place(li, container);
}
return li;
},
on_item_click: function(event, item) {
if (item.click) item.click();
this.emit('item-click', item);
},
constructor: function(spec) {
declare.safeMixin(this, spec);
}
});
});

View File

@ -234,7 +234,7 @@ class UI_driver(object):
"""
Test if dependencies were loaded. (Checks if UI has been rendered)
"""
indicator = self.find("span.network-activity-indicator", By.CSS_SELECTOR)
indicator = self.find(".network-activity-indicator", By.CSS_SELECTOR)
return indicator is not None
def has_ca(self):
@ -259,7 +259,7 @@ class UI_driver(object):
"""
Check if there is running AJAX request
"""
indicator = self.find("span.network-activity-indicator", By.CSS_SELECTOR)
indicator = self.find(".network-activity-indicator", By.CSS_SELECTOR)
displayed = indicator and indicator.is_displayed()
return displayed
@ -343,14 +343,13 @@ class UI_driver(object):
"""
Check if user is logged in
"""
login_as = self.find('header-loggedinas', 'class name')
visible_name = login_as and login_as.is_displayed()
login_as = self.find('loggedinas', 'class name')
visible_name = len(login_as.text) > 0
logged_in = not self.auth_dialog_opened() and visible_name
return logged_in
def logout(self):
btn = self.find('logout', 'class name')
btn.click()
self.profile_menu_action('logout')
def get_auth_dialog(self):
"""
@ -380,7 +379,7 @@ class UI_driver(object):
parent = parts[0:-1]
self.navigate_by_menu('/'.join(parent), complete)
s = ".navigation a[href='#%s']" % item
s = ".navbar a[href='#%s']" % item
link = self.find(s, By.CSS_SELECTOR, strict=True)
assert link.is_displayed(), 'Navigation link is not displayed'
link.click()
@ -595,6 +594,16 @@ class UI_driver(object):
btn.click()
self.wait_for_request()
def profile_menu_action (self, name):
"""
Execute action from profile menu
"""
menu_toggle = self.find('[name=profile-menu] > a', By.CSS_SELECTOR)
menu_toggle.click()
s = "[name=profile-menu] a[href='#%s']" % name
btn = self.find(s, By.CSS_SELECTOR, strict=True)
btn.click()
def get_form(self):
"""
Get last dialog or visible facet