webui: activity indicators

https://fedorahosted.org/freeipa/ticket/4177
https://fedorahosted.org/freeipa/ticket/4255

Reviewed-By: Endi Sukma Dewata <edewata@redhat.com>
This commit is contained in:
Petr Vobornik
2014-04-17 09:07:10 +02:00
parent dff5f6319f
commit f631b07507
16 changed files with 148 additions and 77 deletions

View File

@@ -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

View File

@@ -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 = \

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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;
});

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -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');
}
};
@@ -724,21 +722,6 @@ IPA.get_member_attribute = function(obj_name, member) {
return null;
};
/**
* 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
*

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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',

View File

@@ -534,7 +534,8 @@
"disabled": "Disabled",
"enable": "Enable",
"enabled": "Enabled",
"label": "Status"
"label": "Status",
"working": "Working"
},
"tabs": {
"audit": "Audit",

View File

@@ -671,6 +671,7 @@ class i18n_messages(Command):
"enable": _("Enable"),
"enabled": _("Enabled"),
"label": _("Status"),
"working": _("Working"),
},
"tabs": {
"audit": _("Audit"),

View File

@@ -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: