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.