summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Vobornik <pvoborni@redhat.com>2014-04-17 09:07:10 +0200
committerPetr Vobornik <pvoborni@redhat.com>2014-06-10 10:23:27 +0200
commitf631b07507d12d2ab5c1b535a987f09cb07a5565 (patch)
tree2ab335bc6dcdc4210e50d2632ff1ecf09e466b9e
parentdff5f6319fd76d6b67b30be4eadbcdb414784802 (diff)
downloadfreeipa-f631b07507d12d2ab5c1b535a987f09cb07a5565.tar.gz
freeipa-f631b07507d12d2ab5c1b535a987f09cb07a5565.tar.xz
freeipa-f631b07507d12d2ab5c1b535a987f09cb07a5565.zip
webui: activity indicators
https://fedorahosted.org/freeipa/ticket/4177 https://fedorahosted.org/freeipa/ticket/4255 Reviewed-By: Endi Sukma Dewata <edewata@redhat.com>
-rw-r--r--freeipa.spec.in1
-rw-r--r--install/ui/images/Makefile.am2
-rw-r--r--install/ui/images/spinner-header-1.gifbin9427 -> 0 bytes
-rw-r--r--install/ui/images/spinner-small.gifbin3532 -> 0 bytes
-rw-r--r--install/ui/ipa.css8
-rw-r--r--install/ui/less/widgets.less61
-rw-r--r--install/ui/src/freeipa/app_container.js9
-rw-r--r--install/ui/src/freeipa/dialog.js8
-rw-r--r--install/ui/src/freeipa/hbactest.js2
-rw-r--r--install/ui/src/freeipa/ipa.js17
-rw-r--r--install/ui/src/freeipa/search.js2
-rw-r--r--install/ui/src/freeipa/widget.js56
-rw-r--r--install/ui/src/freeipa/widgets/App.js25
-rw-r--r--install/ui/test/data/ipa_init.json3
-rw-r--r--ipalib/plugins/internal.py1
-rw-r--r--ipatests/test_webui/ui_driver.py30
16 files changed, 148 insertions, 77 deletions
diff --git a/freeipa.spec.in b/freeipa.spec.in
index b8890ff4e..e2b9e5164 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -728,7 +728,6 @@ fi
%dir %{_usr}/share/ipa/ui/images
%{_usr}/share/ipa/ui/images/*.jpg
%{_usr}/share/ipa/ui/images/*.png
-%{_usr}/share/ipa/ui/images/*.gif
%dir %{_usr}/share/ipa/wsgi
%{_usr}/share/ipa/wsgi/plugins.py*
%dir %{_sysconfdir}/ipa
diff --git a/install/ui/images/Makefile.am b/install/ui/images/Makefile.am
index c17ef1505..e74d747b7 100644
--- a/install/ui/images/Makefile.am
+++ b/install/ui/images/Makefile.am
@@ -10,8 +10,6 @@ app_DATA = \
login-screen-background.jpg \
login-screen-logo.png \
product-name.png \
- spinner-header-1.gif \
- spinner-small.gif \
$(NULL)
EXTRA_DIST = \
diff --git a/install/ui/images/spinner-header-1.gif b/install/ui/images/spinner-header-1.gif
deleted file mode 100644
index b3d5e213f..000000000
--- a/install/ui/images/spinner-header-1.gif
+++ /dev/null
Binary files differ
diff --git a/install/ui/images/spinner-small.gif b/install/ui/images/spinner-small.gif
deleted file mode 100644
index 1a2da81c2..000000000
--- a/install/ui/images/spinner-small.gif
+++ /dev/null
Binary files differ
diff --git a/install/ui/ipa.css b/install/ui/ipa.css
index 69855d7c0..ee3cbddc6 100644
--- a/install/ui/ipa.css
+++ b/install/ui/ipa.css
@@ -24,14 +24,6 @@ textarea[readonly] {
color: Gray;
}
-.network-activity-indicator {
- width: 16px;
- height: 16px;
- line-height: 16px;
- margin-right: 5px;
- display: inline-block;
-}
-
/* ---- Container ---- */
.app-container {
diff --git a/install/ui/less/widgets.less b/install/ui/less/widgets.less
index 543bafeed..3fc8cb5b7 100644
--- a/install/ui/less/widgets.less
+++ b/install/ui/less/widgets.less
@@ -1,31 +1,74 @@
+#simple-container {
+
+ .global-activity-indicator {
+
+ bottom: initial;
+ height: auto;
+ background-color: rgba(0, 0, 0, 0.3);
+ color: white;
+ width: 200px;
+ text-align: left;
+
+ .activity-row {
+ background-color: transparent;
+ display: block;
+ padding: 10px 20px;
+ }
+
+ .activity-text {
+ padding: 0px;
+ }
+
+ .activity-text {
+ background-color: transparent;
+ }
+ }
+
+ .slider {
+ transition-property: all;
+ transition-duration: .5s;
+ transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
+ }
+}
+
.global-activity-indicator {
position: fixed;
top: 0;
left: 0;
right: 0;
- background-color: rgba(0, 0, 0, 0.3);
+ bottom: 0;
text-shadow: none;
- color: white;
+ color: black;
font-size: 20px;
font-weight: 300;
- width: 200px;
- padding: 15px 20px;
+ height: 80px;
margin: auto;
+ text-align: center;
+
+ .activity-row {
+ display: inline-block;
+ background-color: rgba(0, 0, 0, 0.2);
+ padding: 7px;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ }
+
+ .activity-text {
+ padding: 3px 14px;
+ background-color: white;
+ }
}
-.slider{
+.slider {
overflow-y: hidden;
- transition-property: all;
- transition-duration: .5s;
- transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}
+#simple-container .slider.closed,
.slider.closed {
max-height: 0;
- padding: 0 20px;
+ padding: 0;
}
.validation-summary {
diff --git a/install/ui/src/freeipa/app_container.js b/install/ui/src/freeipa/app_container.js
index c56fc3bfe..ec2d71e44 100644
--- a/install/ui/src/freeipa/app_container.js
+++ b/install/ui/src/freeipa/app_container.js
@@ -24,8 +24,9 @@ define([
'dojo/when',
'./plugin_loader',
'./phases',
- './Application_controller'
-],function(lang, Deferred, when, plugin_loader, phases, Application_controller) {
+ './Application_controller',
+ 'exports'
+],function(lang, Deferred, when, plugin_loader, phases, Application_controller, app) {
/**
* Application wrapper
@@ -35,7 +36,7 @@ define([
* @class app
* @singleton
*/
- var app = {
+ lang.mixin(app, {
/**
* Application instance
@@ -89,7 +90,7 @@ define([
phases.controller.run();
}));
}
- };
+ });
return app;
}); \ No newline at end of file
diff --git a/install/ui/src/freeipa/dialog.js b/install/ui/src/freeipa/dialog.js
index 459933336..578acb55d 100644
--- a/install/ui/src/freeipa/dialog.js
+++ b/install/ui/src/freeipa/dialog.js
@@ -277,6 +277,14 @@ IPA.dialog = function(spec) {
that.create_footer();
that.footer_node.appendTo(that.content_node);
+ that.activity_indicator = IPA.activity_widget({
+ text: text.get('@i18n:status.working', 'Working'),
+ mode: 'icon',
+ visible: false
+ });
+ that.activity_indicator_node = $('<div/>').appendTo(that.dom_node);
+ that.activity_indicator.create(that.activity_indicator_node);
+
that.policies.post_create();
return that.dom_node;
};
diff --git a/install/ui/src/freeipa/hbactest.js b/install/ui/src/freeipa/hbactest.js
index a936029b6..7a9d85ab3 100644
--- a/install/ui/src/freeipa/hbactest.js
+++ b/install/ui/src/freeipa/hbactest.js
@@ -318,8 +318,6 @@ IPA.hbac.test_select_facet = function(spec) {
}
}).appendTo(filter_container);
- header.append(IPA.create_network_spinner());
-
var content = $('<div/>', {
'class': 'hbac-test-content'
}).appendTo(container);
diff --git a/install/ui/src/freeipa/ipa.js b/install/ui/src/freeipa/ipa.js
index ad70f9f71..d74881924 100644
--- a/install/ui/src/freeipa/ipa.js
+++ b/install/ui/src/freeipa/ipa.js
@@ -318,7 +318,6 @@ var IPA = function () {
*/
that.display_activity_icon = function() {
that.network_call_count++;
- $('.network-activity-indicator').css('display', '');
if (that.network_call_count === 1) {
topic.publish('network-activity-start');
}
@@ -333,7 +332,6 @@ var IPA = function () {
that.network_call_count--;
if (0 === that.network_call_count) {
- $('.network-activity-indicator').css('display', 'none');
topic.publish('network-activity-end');
}
};
@@ -725,21 +723,6 @@ IPA.get_member_attribute = function(obj_name, member) {
};
/**
- * Create HTML representation of network spinner.
- * @member IPA
- * @return {HTMLElement} Network spinner node
- */
-IPA.create_network_spinner = function(){
- var span = $('<span/>', {
- 'class': 'network-activity-indicator'
- });
- $('<img/>', {
- src: 'images/spinner-small.gif'
- }).appendTo(span);
- return span;
-};
-
-/**
* Dirty dialog
*
* Should be used as an indication of unsaved changes on page when leaving the
diff --git a/install/ui/src/freeipa/search.js b/install/ui/src/freeipa/search.js
index f9739f4c2..9c0676313 100644
--- a/install/ui/src/freeipa/search.js
+++ b/install/ui/src/freeipa/search.js
@@ -109,8 +109,6 @@ IPA.search_facet = function(spec, no_init) {
'class': 'right-aligned-facet-controls'
}).appendTo(that.controls);
- div.append(IPA.create_network_spinner());
-
that.filter_container = $('<div/>', {
'class': 'search-filter'
}).appendTo(div);
diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js
index d4c9cb366..08270e319 100644
--- a/install/ui/src/freeipa/widget.js
+++ b/install/ui/src/freeipa/widget.js
@@ -164,6 +164,7 @@ IPA.widget = function(spec) {
* @param {HTMLElement} container - Container node
*/
that.create = function(container) {
+ container = $(container);
container.addClass(that.base_css_class);
container.addClass(that.css_class);
that.container = container;
@@ -5464,6 +5465,8 @@ exp.activity_widget = IPA.activity_widget = function(spec) {
that.text_node = null;
+ that.row_node = null;
+
that.dots = spec.dots || 0;
that.step = spec.step || 1;
@@ -5474,16 +5477,41 @@ exp.activity_widget = IPA.activity_widget = function(spec) {
that.speed = spec.speed || 800;
+ that.icon = spec.icon || 'fa fa-spinner fa-spin';
+
+ /**
+ * Operation mode
+ *
+ * ['dots', 'icon']
+ *
+ * @property {string}
+ */
+ that.mode = spec.mode || "dots";
+
that.activate_event = spec.activate_event || 'network-activity-start';
that.deactivate_event = spec.deactivate_event || 'network-activity-end';
that.create = function(container) {
that.widget_create(container);
that.add_class('global-activity-indicator slider closed');
+ that.row_node = $("<div/>", { 'class': 'activity-row' }).appendTo(that.container);
that.text_node = $("<div/>", {
- text: that.text
- }).appendTo(that.container);
- if (that.visible) that.start();
+ text: that.text,
+ 'class': 'activity-text'
+ }).appendTo(that.row_node);
+
+ if (that.mode === 'icon') {
+ that.text_node.prepend(' ');
+ $('<i/>', {
+ 'class': that.icon
+ }).prependTo(that.text_node);
+ }
+
+ if (that.visible) {
+ that.show();
+ } else {
+ that.hide();
+ }
that.set_visible(that.visible);
topic.subscribe(that.activate_event, function() {
that.show();
@@ -5493,25 +5521,29 @@ exp.activity_widget = IPA.activity_widget = function(spec) {
});
};
- that.start = function() {
+ that.toggle_timer = function(start) {
- that.timer = window.setInterval( function() {
- that.make_step();
- }, that.speed);
- };
+ if (that.mode === 'icon') return;
- that.stop = function() {
- if (that.timer) window.clearInterval(that.timer);
+ if (start) {
+ that.timer = window.setInterval( function() {
+ that.make_step();
+ }, that.speed);
+ } else {
+ if (that.timer) window.clearInterval(that.timer);
+ }
};
that.hide = function() {
that.toggle_class('closed', true);
- that.stop();
+ that.row_node.detach(); // to save CPU time (spinner icon)
+ that.toggle_timer(false);
};
that.show = function() {
that.toggle_class('closed', false);
- that.start();
+ that.row_node.appendTo(that.container);
+ that.toggle_timer(true);
};
that.make_step = function() {
diff --git a/install/ui/src/freeipa/widgets/App.js b/install/ui/src/freeipa/widgets/App.js
index c7924e8df..b70b14a94 100644
--- a/install/ui/src/freeipa/widgets/App.js
+++ b/install/ui/src/freeipa/widgets/App.js
@@ -31,11 +31,13 @@ define(['dojo/_base/declare',
'./Menu',
'./DropdownWidget',
'./FacetContainer',
+ '../text',
+ '../widget',
'dojo/NodeList-dom'
],
function(declare, lang, array, dom, construct, prop, dom_class,
dom_style, query, on, Menu, DropdownWidget,
- FacetContainer) {
+ FacetContainer, text, widgets) {
/**
* Main application widget
@@ -63,10 +65,14 @@ define(['dojo/_base/declare',
menu_node: null,
+ indicator_node: null,
+
id: 'container',
logged: false,
+ use_activity_indicator: true,
+
_loggedSetter: function(value) {
this.logged = value;
//TODO show/hide menu
@@ -97,6 +103,11 @@ define(['dojo/_base/declare',
this.content_node = construct.create('div', {
'class': 'content'
}, this.dom_node);
+
+ if (this.use_activity_indicator) {
+ this.indicator_node = construct.create('div', {}, this.dom_node);
+ this.activity_indicator.create(this.indicator_node);
+ }
},
_render_navigation: function() {
@@ -162,14 +173,6 @@ define(['dojo/_base/declare',
'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-1.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);
@@ -241,6 +244,10 @@ define(['dojo/_base/declare',
constructor: function(spec) {
spec = spec || {};
this.menu_widget = new Menu();
+ this.activity_indicator = widgets.activity_widget({
+ mode: 'icon',
+ text: text.get('@i18n:status.working', 'Working')
+ });
this.user_menu = new DropdownWidget({
el_type: 'li',
name: 'profile-menu',
diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json
index 3d143d85d..500aead41 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -534,7 +534,8 @@
"disabled": "Disabled",
"enable": "Enable",
"enabled": "Enabled",
- "label": "Status"
+ "label": "Status",
+ "working": "Working"
},
"tabs": {
"audit": "Audit",
diff --git a/ipalib/plugins/internal.py b/ipalib/plugins/internal.py
index e6c17f273..0f7601955 100644
--- a/ipalib/plugins/internal.py
+++ b/ipalib/plugins/internal.py
@@ -671,6 +671,7 @@ class i18n_messages(Command):
"enable": _("Enable"),
"enabled": _("Enabled"),
"label": _("Status"),
+ "working": _("Working"),
},
"tabs": {
"audit": _("Audit"),
diff --git a/ipatests/test_webui/ui_driver.py b/ipatests/test_webui/ui_driver.py
index 4b3069640..8a8293b9b 100644
--- a/ipatests/test_webui/ui_driver.py
+++ b/ipatests/test_webui/ui_driver.py
@@ -35,6 +35,7 @@ try:
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import InvalidElementStateException
+ from selenium.common.exceptions import StaleElementReferenceException
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
@@ -262,7 +263,7 @@ class UI_driver(object):
"""
Test if dependencies were loaded. (Checks if UI has been rendered)
"""
- indicator = self.find(".network-activity-indicator", By.CSS_SELECTOR)
+ indicator = self.find(".global-activity-indicator", By.CSS_SELECTOR)
return indicator is not None
def has_ca(self):
@@ -287,11 +288,15 @@ class UI_driver(object):
"""
Check if there is running AJAX request
"""
- indicator = self.find(".network-activity-indicator", By.CSS_SELECTOR)
- i_visible = indicator and indicator.is_displayed()
- global_indicator = self.find(".global-activity-indicator", By.CSS_SELECTOR)
- g_visible = global_indicator and global_indicator.is_displayed()
- return i_visible or g_visible
+ global_indicators = self.find(".global-activity-indicator", By.CSS_SELECTOR, many=True)
+ for el in global_indicators:
+ try:
+ if not self.has_class(el, 'closed'):
+ return True
+ except StaleElementReferenceException:
+ # we don't care. Happens when indicator is part of removed dialog.
+ continue
+ return False
def wait(self, seconds=0.2):
"""
@@ -635,7 +640,7 @@ class UI_driver(object):
btn.click()
self.wait_for_request()
- def profile_menu_action (self, name):
+ def profile_menu_action(self, name):
"""
Execute action from profile menu
"""
@@ -1480,6 +1485,12 @@ class UI_driver(object):
# add multiple at once and test table delete button
self.add_table_associations(table, keys, delete=True)
+ def has_class(self, el, cls):
+ """
+ Check if el has CSS class
+ """
+ return cls in el.get_attribute("class").split()
+
def skip(self, reason):
"""
Skip tests
@@ -1543,8 +1554,7 @@ class UI_driver(object):
facet = self.get_facet()
btn = self.find(s, By.CSS_SELECTOR, facet, strict=True)
cls = 'action-button-disabled'
- has_cls = cls in btn.get_attribute("class").split()
- valid = enabled ^ has_cls
+ valid = enabled ^ self.has_class(btn, cls)
assert btn.is_displayed(), 'Button is not displayed'
assert valid, 'Button has incorrect enabled state.'
@@ -1648,7 +1658,7 @@ class UI_driver(object):
"""
Assert that element has certain class
"""
- valid = cls in element.get_attribute('class').split()
+ valid = self.has_class(element, cls)
if negative:
assert not valid, "Element contains unwanted class: %s" % cls
else: