summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--install/ui/ipa.css51
-rw-r--r--install/ui/less/brand.less32
-rw-r--r--install/ui/less/rcue.less1
-rw-r--r--install/ui/src/freeipa/widgets/App.js155
-rw-r--r--install/ui/src/freeipa/widgets/DropdownWidget.js211
-rw-r--r--ipatests/test_webui/ui_driver.py23
6 files changed, 364 insertions, 109 deletions
diff --git a/install/ui/ipa.css b/install/ui/ipa.css
index 126c51ed7..f2605acd6 100644
--- a/install/ui/ipa.css
+++ b/install/ui/ipa.css
@@ -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 {
diff --git a/install/ui/less/brand.less b/install/ui/less/brand.less
new file mode 100644
index 000000000..72270a254
--- /dev/null
+++ b/install/ui/less/brand.less
@@ -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;
+ }
+} \ No newline at end of file
diff --git a/install/ui/less/rcue.less b/install/ui/less/rcue.less
index 9553e37d7..f27583698 100644
--- a/install/ui/less/rcue.less
+++ b/install/ui/less/rcue.less
@@ -4,3 +4,4 @@
@import "rcue/navbar";
@import "rcue/buttons";
@import "rcue/forms";
+@import "brand";
diff --git a/install/ui/src/freeipa/widgets/App.js b/install/ui/src/freeipa/widgets/App.js
index e89d4cc61..42705649f 100644
--- a/install/ui/src/freeipa/widgets/App.js
+++ b/install/ui/src/freeipa/widgets/App.js
@@ -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);
-
- // 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._render_nav_util();
+ construct.place(this.nav_util_node, this.header_node);
+
+ 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;
+ },
+
+ _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;
},
- on_logout: function(event) {
- event.preventDefault();
- this.emit('logout-click');
+ _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));
}
});
diff --git a/install/ui/src/freeipa/widgets/DropdownWidget.js b/install/ui/src/freeipa/widgets/DropdownWidget.js
new file mode 100644
index 000000000..992bcf378
--- /dev/null
+++ b/install/ui/src/freeipa/widgets/DropdownWidget.js
@@ -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);
+ }
+ });
+});
diff --git a/ipatests/test_webui/ui_driver.py b/ipatests/test_webui/ui_driver.py
index cf95a8cdf..dabe4a7db 100644
--- a/ipatests/test_webui/ui_driver.py
+++ b/ipatests/test_webui/ui_driver.py
@@ -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