[Kimchi-devel] [PATCH] [WoK] Asynchronous UI notification implementation

Aline Manera alinefm at linux.vnet.ibm.com
Fri Mar 10 12:32:54 UTC 2017



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

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



More information about the Kimchi-devel mailing list