Handle user events Redis not working in tutorial
Also does some basic restyling Fixes #1586
This commit is contained in:
parent
073b643aab
commit
310ecd11cc
6 changed files with 169 additions and 162 deletions
|
@ -5,6 +5,9 @@ import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class CannotReadUserEventsException(Exception):
|
||||||
|
""" Exception raised if user events cannot be read. """
|
||||||
|
|
||||||
class UserEventBuilder(object):
|
class UserEventBuilder(object):
|
||||||
"""
|
"""
|
||||||
Defines a helper class for constructing UserEvent and UserEventListener
|
Defines a helper class for constructing UserEvent and UserEventListener
|
||||||
|
@ -87,9 +90,13 @@ class UserEventListener(object):
|
||||||
def __init__(self, redis_config, username, events=set([])):
|
def __init__(self, redis_config, username, events=set([])):
|
||||||
channels = [self._user_event_key(username, e) for e in events]
|
channels = [self._user_event_key(username, e) for e in events]
|
||||||
|
|
||||||
self._redis = redis.StrictRedis(socket_connect_timeout=5, **redis_config)
|
try:
|
||||||
self._pubsub = self._redis.pubsub()
|
self._redis = redis.StrictRedis(socket_connect_timeout=5, **redis_config)
|
||||||
self._pubsub.subscribe(channels)
|
self._pubsub = self._redis.pubsub()
|
||||||
|
self._pubsub.subscribe(channels)
|
||||||
|
except redis.RedisError as re:
|
||||||
|
logger.exception('Could not reach user events redis: %s', re)
|
||||||
|
raise CannotReadUserEventsException
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _user_event_key(username, event_id):
|
def _user_event_key(username, event_id):
|
||||||
|
|
|
@ -7,6 +7,7 @@ from auth.auth import require_session_login
|
||||||
from endpoints.common import route_show_if
|
from endpoints.common import route_show_if
|
||||||
from app import app, userevents
|
from app import app, userevents
|
||||||
from auth.permissions import SuperUserPermission
|
from auth.permissions import SuperUserPermission
|
||||||
|
from data.userevent import CannotReadUserEventsException
|
||||||
|
|
||||||
import features
|
import features
|
||||||
import psutil
|
import psutil
|
||||||
|
@ -107,5 +108,9 @@ def user_subscribe():
|
||||||
if not events:
|
if not events:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
listener = userevents.get_listener(current_user.db_user().username, events)
|
try:
|
||||||
|
listener = userevents.get_listener(current_user.db_user().username, events)
|
||||||
|
except CannotReadUserEventsException:
|
||||||
|
abort(504)
|
||||||
|
|
||||||
return Response(wrapper(listener), mimetype="text/event-stream")
|
return Response(wrapper(listener), mimetype="text/event-stream")
|
||||||
|
|
147
static/css/directives/ui/angular-tour-ui.css
Normal file
147
static/css/directives/ui/angular-tour-ui.css
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
.angular-tour-ui-element.overlay {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 50px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 9999999;
|
||||||
|
|
||||||
|
background: white;
|
||||||
|
-webkit-box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
transition: opacity 750ms ease-in-out;
|
||||||
|
-webkit-transition: opacity 750ms ease-in-out;
|
||||||
|
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.overlay.touring {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.overlay.nottouring {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
left: -10000px;
|
||||||
|
width: 0px;
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.overlay .tour-title {
|
||||||
|
background-color: #3276b1;
|
||||||
|
color: white;
|
||||||
|
padding: 4px;
|
||||||
|
padding-left: 6px;
|
||||||
|
padding-right: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-bottom-left-radius: 0px;
|
||||||
|
border-bottom-right-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.overlay .tour-title h4 {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.overlay .step-title {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.overlay .step-content {
|
||||||
|
padding: 10px;
|
||||||
|
padding-left: 0px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.overlay .tour-contents {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.overlay .controls {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.overlay .controls .btn {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.overlay .fa {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.inline .fa-dot-circle-o {
|
||||||
|
font-size: 34px;
|
||||||
|
background: #ddd;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 4px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.inline .tour-title h4 {
|
||||||
|
font-size: 28px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.inline .tour-title .close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.angular-tour-ui-element.inline .step-title {
|
||||||
|
font-size: 20px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.inline .step-content {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element.inline .controls {
|
||||||
|
margin-top: 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element p {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element .wait-message {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element .wait-message .quay-spinner {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element .wait-message .quay-spinner .small-spinner {
|
||||||
|
border-top-color: #999;
|
||||||
|
border-left-color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element .note {
|
||||||
|
margin: 10px;
|
||||||
|
padding: 6px;
|
||||||
|
background: #eee;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-tour-ui-element .skip-message {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
|
@ -3019,162 +3019,6 @@ p.editable:hover i {
|
||||||
margin-top: 60px;
|
margin-top: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************/
|
|
||||||
|
|
||||||
.angular-tour-ui-element.overlay {
|
|
||||||
display: block;
|
|
||||||
position: fixed;
|
|
||||||
bottom: 50px;
|
|
||||||
right: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
z-index: 9999999;
|
|
||||||
|
|
||||||
background: white;
|
|
||||||
-webkit-box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
|
||||||
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
|
||||||
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
transition: opacity 750ms ease-in-out;
|
|
||||||
-webkit-transition: opacity 750ms ease-in-out;
|
|
||||||
|
|
||||||
min-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.overlay.touring {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.overlay.nottouring {
|
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
left: -10000px;
|
|
||||||
width: 0px;
|
|
||||||
height: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.overlay .tour-title {
|
|
||||||
background-color: #3276b1;
|
|
||||||
color: white;
|
|
||||||
padding: 4px;
|
|
||||||
padding-left: 6px;
|
|
||||||
padding-right: 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border-bottom-left-radius: 0px;
|
|
||||||
border-bottom-right-radius: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.overlay .tour-title h4 {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.overlay .step-title {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.overlay .step-content {
|
|
||||||
padding: 10px;
|
|
||||||
padding-left: 0px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.overlay .tour-contents {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.overlay .controls {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.overlay .controls .btn {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.angular-tour-ui-element.overlay .fa {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**************************************************/
|
|
||||||
|
|
||||||
.angular-tour-ui-element.inline {
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.inline .fa-dot-circle-o {
|
|
||||||
font-size: 34px;
|
|
||||||
background: #ddd;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
text-align: center;
|
|
||||||
padding-top: 4px;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.inline .tour-title h4 {
|
|
||||||
font-size: 28px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.inline .tour-title .close {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.angular-tour-ui-element.inline .step-title {
|
|
||||||
font-size: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.inline .step-content {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element.inline .controls {
|
|
||||||
margin-top: 20px;
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element p {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element .wait-message {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element .wait-message .quay-spinner {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element .wait-message .quay-spinner .small-spinner {
|
|
||||||
border-top-color: #999;
|
|
||||||
border-left-color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element .note {
|
|
||||||
margin: 10px;
|
|
||||||
padding: 6px;
|
|
||||||
background: #eee;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-tour-ui-element .skip-message {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.command {
|
pre.command {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
|
|
@ -15,12 +15,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="controls" ng-show="(hasNextStep && !step.signal) || (!hasNextStep && !inline)">
|
<div class="controls" ng-show="(hasNextStep && !step.signal) || (!hasNextStep && !inline)">
|
||||||
<button class="btn btn-primary" ng-click="next()" ng-show="hasNextStep && !step.signal">Next</button>
|
<button class="btn btn-primary" ng-click="next()" ng-show="hasNextStep && !step.signal">Continue Tutorial</button>
|
||||||
<button class="btn btn-primary" ng-click="stop()" ng-show="!hasNextStep && !inline">Done</button>
|
<button class="btn btn-primary" ng-click="stop()" ng-show="!hasNextStep && !inline">Done</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wait-message" ng-show="step.waitMessage">
|
<div class="wait-message" ng-show="step.waitMessage">
|
||||||
<div class="quay-spinner"></div> {{ step.waitMessage }}
|
<div class="cor-loader-inline"></div> {{ step.waitMessage }}
|
||||||
<span class="skip-message" ng-show="step.skipTitle"><button class="btn btn-default" ng-click="next()">{{ step.skipTitle }}</button></span>
|
<span class="skip-message" ng-show="step.skipTitle"><button class="btn btn-default" ng-click="next()">{{ step.skipTitle }}</button></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -286,6 +286,10 @@ angular.module("angular-tour", [])
|
||||||
checker.$source.onmessage = function(e) {
|
checker.$source.onmessage = function(e) {
|
||||||
checker.$message = JSON.parse(e.data);
|
checker.$message = JSON.parse(e.data);
|
||||||
};
|
};
|
||||||
|
checker.$source.onerror = function(e) {
|
||||||
|
bootbox.alert('Could not read user events from server due to a Redis issue. ' +
|
||||||
|
' Please contact support.')
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
checker.$teardown = function() {
|
checker.$teardown = function() {
|
||||||
|
|
Reference in a new issue