Admin timeline with usernames instead of session IDs

Maybe a bit of a hacky solution, but I modified the new admin panel timeline a bit to retrieve usernames from the cookies and plot that instead of session IDs:

Code:

def get_timeline(doc=None):
    users = []
    code = """
    window.open('http://example.com', '_blank')
    """
    callback = CustomJS(args=dict(), code=code)

    # Configure plot
    p = figure(
        y_range=list(users), x_axis_type='datetime', sizing_mode='stretch_both'
    )
    cds = ColumnDataSource(data={
        'x0': [], 'x1': [], 'y0': [], 'y1': [], 'msg': [],
        'session': [], 'color': [], 'line_color': [], 'type': [], 'IPs': [], 'email': [], 'user': [],
    })
    p.yaxis.axis_label = 'Users'
    p.xaxis.axis_label = 'Date (UTC)'
    p.quad(
        left='x0', right='x1', top='y1', bottom='y0', source=cds,
        line_color='line_color', fill_color='color', alpha=0.7,
        legend_field='type'
    )
    p.legend.location = "top_left"
    p.add_tools(
        HoverTool(
            tooltips=[
                ('User', '@user'),
                ('Email', '@email'),
                ('IPs', '@IPs'),
                ('Session', '@session'),
                ('Message', '@msg'),
                ('Date', '@x0{%F %T}'),
            ],
            formatters={'@x0': 'datetime', '@x1': 'datetime'}
        ),
        TapTool(behavior='inspect', callback=callback)
    )

    def update_cds(new, nb=False):
        if new.msg.endswith('rendered'):
            _doc = str(new.args[0])
            try:
                doc = ctypes.cast(int(_doc), ctypes.py_object).value
                cookies = doc.session_context.request.cookies
                user = get_secure_cookie(cookies,'display_name')
                email = get_secure_cookie(cookies,'user')
                # IPs = get_secure_cookie(cookies,'IPs')
                sid = doc.session_context.id

                if user not in users:
                    users.append(user)

                etype = 'rendered'
                event = {
                    'x0': [pd.datetime.utcnow().timestamp()*1000],
                    'x1': [(pd.datetime.utcnow()+pd.Timedelta('1s')).timestamp()*1000],
                    'y0': [(user, -.25)],
                    'y1': [(user, .25)],
                    'session': [sid],
                    'user': [user],
                    'email': [email],
                    'msg': [f'Session rendered'],
                    'IPs': [np.array([np.nan])],
                    'color': [EVENT_TYPES[etype]],
                    'line_color': ['black'],
                    'type': [etype]
                }
                if p.y_range.factors != users:
                    p.y_range.factors = list(users)
                cds.stream(event)
                    
            except Exception as e:
                pass

    for record in log_data_handler._data.data:
        try:
            update_cds(record)
        except Exception:
            pass

    def schedule_cds_update(event):
        new = event.new[-1]
        if doc:
            doc.add_next_tick_callback(partial(update_cds, new))
        else:
            update_cds(new, nb=True)

    watcher = log_data_handler._data.param.watch(schedule_cds_update, 'data')
    if doc:
        def _unwatch_data(session_context):
            log_data_handler._data.param.unwatch(watcher)
        doc.on_session_destroyed(_unwatch_data)

    bk_pane = Bokeh(p)
    return bk_pane

If you click on the event, it’ll execute a dummy js callback (open new window), but it could eventually be used for remotely terminating sessions of resource-intensive and/or abusive users and even ban them.

3 Likes