[Kimchi-devel] [PATCH] [WoK] Asynchronous UI notification implementation
Aline Manera
alinefm at linux.vnet.ibm.com
Fri Mar 10 12:36:19 UTC 2017
On 03/10/2017 09:32 AM, Aline Manera wrote:
>
>
> On 03/10/2017 09:30 AM, Daniel Henrique Barboza wrote:
>>
>>
>> On 03/10/2017 08:40 AM, Aline Manera wrote:
>>> Hi Daniel,
>>>
>>> On 03/07/2017 03:52 PM, dhbarboza82 at gmail.com wrote:
>>>> From: Daniel Henrique Barboza <danielhb at linux.vnet.ibm.com>
>>>>
>>>> This patch makes backend and UI changes to implement the asynchronous
>>>> UI notification in WoK.
>>>>
>>>> - Backend:
>>>>
>>>> A push server was implemented from scratch to manage the opened
>>>> websocket
>>>> connections. The push server connects to the
>>>> /run/user/<user_id>/woknotifications UNIX socket and broadcasts all
>>>> messages
>>>> to all connections.
>>>>
>>>> The websocket module is the same module that exists in the Kimchi
>>>> plug-in. The idea is to remove the module from Kimchi and make it
>>>> use the module from WoK. ws_proxy initialization was also added
>>>> in src/wok/server.py.
>>>>
>>>> A change were made in Wok base control classes to allow every
>>>> call of log_request to also send a websocket notification in
>>>> the following format:
>>>>
>>>> <METHOD>:/<plugin>/<entity>/<action>
>>>>
>>>> For example, creating a new user in Ginger would trigger the
>>>> following websocket notification:
>>>>
>>>> 'POST:/ginger/users'
>>>>
>>>> - Frontend:
>>>>
>>>> In ui/js/wok.main.js two new functions were added to help the
>>>> usage of asynchronous notifications in the frontend. The idea:
>>>> a single websocket is opened per session. This opened websocket
>>>> will broadcast all incoming messages to all listeners registered.
>>>> Listeners can be added by the new wok.addNotificationListener()
>>>> method. This method will clean up any registered listener by
>>>> itself when the user changes tabs/URL.
>>>>
>>>> The single websocket sends heartbeats to the backend side each
>>>> 30 seconds. No reply from the backend is issued or expected. This
>>>> heartbeat is just a way to ensure that the browser does not
>>>> close the connection due to inactivity. This behavior varies from
>>>> browser to browser but this 30 second heartbeat is more than enough
>>>> to ensure that the websocket is kept alive.
>>>>
>>>> - Working example in User Log:
>>>>
>>>> A simple usage is provided in this patch. Changes were made in the
>>>> UI of the User Log feature to refresh the listing each time a new
>>>> log entry websocket notification is received. The idea is to allow
>>>> this code to be a working example of how other tabs can consume
>>>> the asynchronous notifications.
>>>>
>>>> Signed-off-by: Daniel Henrique Barboza <danielhb at linux.vnet.ibm.com>
>>>> ---
>>>> contrib/DEBIAN/control.in | 1 +
>>>> contrib/wok.spec.fedora.in | 1 +
>>>> contrib/wok.spec.suse.in | 1 +
>>>> docs/fedora-deps.md | 2 +-
>>>> docs/opensuse-deps.md | 3 +-
>>>> docs/ubuntu-deps.md | 3 +-
>>>> src/wok/config.py.in | 8 ++
>>>> src/wok/control/base.py | 16 ++--
>>>> src/wok/model/notifications.py | 2 +-
>>>> src/wok/pushserver.py | 163
>>>> +++++++++++++++++++++++++++++++++++++++++
>>>> src/wok/reqlogger.py | 11 ++-
>>>> src/wok/root.py | 3 +
>>>> src/wok/server.py | 13 +++-
>>>> src/wok/websocket.py | 123 +++++++++++++++++++++++++++++++
>>>> ui/js/src/wok.main.js | 43 +++++++++++
>>>> ui/js/wok.user-log.js | 12 +++
>>>> 16 files changed, 391 insertions(+), 14 deletions(-)
>>>> create mode 100644 src/wok/pushserver.py
>>>> create mode 100644 src/wok/websocket.py
>>>
>>>> diff --git a/ui/js/src/wok.main.js b/ui/js/src/wok.main.js
>>>> index c8c3889..b58706d 100644
>>>> --- a/ui/js/src/wok.main.js
>>>> +++ b/ui/js/src/wok.main.js
>>>> @@ -29,6 +29,46 @@ wok.getConfig(function(result) {
>>>> wok.config = {};
>>>> });
>>>
>>>> +wok.notificationListeners = [];
>>>> +wok.addNotificationListener = function(func) {
>>>> + wok.notificationListeners.push(func);
>>>> + $(window).one("hashchange", function() {
>>>> + var del_index = wok.notificationListeners.indexOf(func);
>>>> + wok.notificationListeners.splice(del_index, 1);
>>>> + });
>>>> +};
>>>> +
>>>> +wok.notificationsWebSocket = undefined;
>>>> +wok.startNotificationWebSocket = function () {
>>>> + var addr = window.location.hostname + ':' + window.location.port;
>>>> + var token =
>>>> wok.urlSafeB64Encode('woknotifications').replace(/=*$/g, "");
>>>> + var url = 'wss://' + addr + '/websockify?token=' + token;
>>>> + wok.notificationsWebSocket = new WebSocket(url, ['base64']);
>>>> +
>>>> + wok.notificationsWebSocket.onmessage = function(event) {
>>>> + var buffer_rcv = window.atob(event.data);
>>>> + var messages = buffer_rcv.split("//EOM//");
>>>> + for (var i = 0; i < messages.length; i++) {
>>>> + if (messages[i] === "") {
>>>> + continue;
>>>> + }
>>>> + for (var j = 0; j < wok.notificationListeners.length;
>>>> j++) {
>>>> + wok.notificationListeners[j](messages[i]);
>>>> + }
>>>> + }
>>>> + };
>>>> +
>>>
>>> I am not sure I understood this code correctly.
>>> From what I had in mind wok.notificationListeners should be an dict
>>> instead of an array. In which the key is the notification message
>>> and the value the functions to be executed on message received.
>>>
>>> wok.notificationListeners = {}
>>>
>>> When adding a new listener, we would do:
>>>
>>> wok.notificationListeners[<msg>] = <func-to-handle-msg>
>>>
>>> That way, we avoid calling each wok.notificationListeners sending
>>> the same message to listeners not related to that message.
>>>
>>> To call the listener, we do:
>>>
>>> func = wok.notificationListeners.get(<message>)
>>> if func != undefined:
>>> #call func
>>> func()
>>
>> The idea was to make it generic. Every message would be forwarded to
>> all functions
>> that is currently listening to websocket events. The job of filtering
>> what you want
>> would be done inside each listener itself.
>>
>> This allows for multiple listeners being able to receive the same
>> message. Otherwise,
>> suppose that in the same tab we have multiple independent features
>> (like Ginger with
>> sidebar), common interest messages like "WoK is going to be reloaded"
>> or "A task of
>> id=N has been finished" would be captured by only one of them.
>>
Also thinking about the Ginger scenario, the $(window).one("hashchange")
will not work
So it is better to check the function is defined or not.
func_list = wok.listenerNotification[<msg>]
if func_list:
for func in func_list:
if func != undefined:
#call func
>
> So do a list of functions to be called.
>
> wok.listenerNotifications[<msg>] = [<list of listener>]
>
> Doesn't make sense forward all the messgaes to all the listener. The
> listener should be trciked by a message.
>
>>
>>>
>>>> + sessionStorage.setItem('wokNotificationWebSocket', 'true');
>>>> + var heartbeat = setInterval(function() {
>>>> + wok.notificationsWebSocket.send(window.btoa('heartbeat'));
>>>> + }, 30000);
>>>> +
>>>> + wok.notificationsWebSocket.onclose = function() {
>>>> + clearInterval(heartbeat);
>>>> + };
>>>> +};
>>>> +
>>>> +
>>>> wok.main = function() {
>>>> wok.isLoggingOut = false;
>>>> wok.popable();
>>>> @@ -395,6 +435,9 @@ wok.main = function() {
>>>>
>>>> // Set handler for help button
>>>> $('#btn-help').on('click', wok.openHelp);
>>>> +
>>>> + // start WebSocket
>>>> + wok.startNotificationWebSocket();
>>>> };
>>>>
>>>> var initUI = function() {
>>>> diff --git a/ui/js/wok.user-log.js b/ui/js/wok.user-log.js
>>>> index 0e8fb09..765b75a 100644
>>>> --- a/ui/js/wok.user-log.js
>>>> +++ b/ui/js/wok.user-log.js
>>>> @@ -153,6 +153,18 @@ wok.initUserLogContent = function() {
>>>> $("#user-log-grid").bootgrid("search");
>>>> wok.initUserLogConfigGridData();
>>>> });
>>>> +
>>>> + wok.addNotificationListener(function(message) {
>>>
>>>> + var msg_attr = message.split(":");
>>>> + if (msg_attr.length != 2) {
>>>> + return;
>>>> + }
>>>> + var method = msg_attr[0];
>>>> + var target = msg_attr[1];
>>>> + if (method === "POST" && target === '/wok/logs') {
>>>
>>> All the above block is not needed when you follow what I described
>>> above, as this function would only be called when POST:/wok/logs
>>
>> Yes, if we restrict the messages to just be received by one listener
>> this filtering is
>> unnecessary.
>>>
>>>> + $("#refresh-button").click();
>>>> + }
>>>> + });
>>>> };
>>>>
>>>> wok.initUserLogWindow = function() {
>>>
>>
>
> _______________________________________________
> Kimchi-devel mailing list
> Kimchi-devel at ovirt.org
> http://lists.ovirt.org/mailman/listinfo/kimchi-devel
>
More information about the Kimchi-devel
mailing list