You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
neptune/core/views/ui/drilldown.py

423 lines
14 KiB
Python

import json
import urllib
from copy import deepcopy
from django.conf import settings
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.urls import reverse
from django.views import View
from django_tables2 import SingleTableView
from rest_framework.parsers import FormParser
from rest_framework.views import APIView
from core.lib.context import construct_query
from core.lib.opensearch import query_results
from core.lib.threshold import (
annotate_num_chans,
annotate_num_users,
get_chans,
get_users,
)
from core.views.helpers import hash_list, hash_lookup
from core.views.ui.tables import DrilldownTable
def parse_dates(dates):
spl = dates.split(" - ")
if all(spl):
spl = [f"{x.replace(' ', 'T')}" for x in spl]
if not len(spl) == 2:
message = "Invalid dates"
message_class = "danger"
return {"message": message, "class": message_class}
from_ts, to_ts = spl
from_date, from_time = from_ts.split("T")
to_date, to_time = to_ts.split("T")
return {
"from_date": from_date,
"to_date": to_date,
"from_time": from_time,
"to_time": to_time,
}
def create_tags(query):
"""
Grab the tags out of the query and make a list
we can add to the Bulma tags element when the page loads.
"""
spl = query.split("AND")
spl = [x.strip() for x in spl if ":" in x]
spl = [x.replace('"', "") for x in spl]
tags = [f"{tag}: {elem}" for tag, elem in [x.split(":")[:2] for x in spl]]
return tags
def make_table(context):
table = DrilldownTable(context["object_list"])
context["table"] = table
# del context["results"]
return context
def make_graph(results):
graph = json.dumps(
[
{
"text": item.get("msg", None) or item.get("id"),
"nick": item.get("nick", None),
"value": item.get("sentiment", None) or None,
"date": item.get("ts"),
}
for item in results
]
)
return graph
def drilldown_search(request, return_context=False, template=None):
if not template:
template_name = "ui/drilldown/table_results.html"
else:
template_name = template
if request.user.is_anonymous:
sizes = settings.OPENSEARCH_MAIN_SIZES_ANON
else:
sizes = settings.OPENSEARCH_MAIN_SIZES
if request.GET:
if not request.htmx:
template_name = "ui/drilldown/drilldown.html"
query_params = request.GET.dict()
elif request.POST:
query_params = request.POST.dict()
else:
template_name = "ui/drilldown/drilldown.html"
context = {"sizes": sizes}
return render(request, template_name, context)
tmp_post = request.POST.dict()
tmp_get = request.GET.dict()
tmp_post = {k: v for k, v in tmp_post.items() if v and not v == "None"}
tmp_get = {k: v for k, v in tmp_get.items() if v and not v == "None"}
query_params.update(tmp_post)
query_params.update(tmp_get)
if "index" in query_params:
if not request.user.is_superuser:
message = "You can't use the index parameter"
message_class = "danger"
context = {"message": message, "class": message_class}
return render(request, template_name, context)
# Parse the dates
if "dates" in query_params:
dates = parse_dates(query_params["dates"])
del query_params["dates"]
if dates:
if "message" in dates:
return render(request, template_name, dates)
query_params["from_date"] = dates["from_date"]
query_params["to_date"] = dates["to_date"]
query_params["from_time"] = dates["from_time"]
query_params["to_time"] = dates["to_time"]
if "query" in query_params:
if request.GET:
context = query_results(request, query_params)
elif request.POST:
context = query_results(request, query_params)
else:
context = {"object_list": []}
# URI we're passing to the template for linking
if "csrfmiddlewaretoken" in query_params:
del query_params["csrfmiddlewaretoken"]
url_params = urllib.parse.urlencode(query_params)
context["client_uri"] = url_params
# Turn the query into tags for populating the taglist
if "query" in query_params:
tags = create_tags(query_params["query"])
context["tags"] = tags
context["params"] = query_params
if "message" in context:
response = render(request, template_name, context)
if request.GET:
if request.htmx:
response["HX-Push"] = reverse("home") + "?" + url_params
elif request.POST:
response["HX-Push"] = reverse("home") + "?" + url_params
return response
# Create data for chart.js sentiment graph
graph = make_graph(context["object_list"])
context["data"] = graph
if context:
context["sizes"] = sizes
context = make_table(context)
# URI we're passing to the template for linking, table fields removed
table_fields = ["page", "sort"]
clean_params = {k: v for k, v in query_params.items() if k not in table_fields}
clean_url_params = urllib.parse.urlencode(clean_params)
context["uri"] = clean_url_params
response = render(request, template_name, context)
if request.GET:
if request.htmx:
response["HX-Push"] = reverse("home") + "?" + url_params
elif request.POST:
response["HX-Push"] = reverse("home") + "?" + url_params
if return_context:
return context
return response
else:
return HttpResponse("No results")
class DrilldownTableView(SingleTableView):
table_class = DrilldownTable
template_name = "ui/drilldown/table_results.html"
paginate_by = settings.DRILLDOWN_RESULTS_PER_PAGE
def get_queryset(self, request, **kwargs):
context = drilldown_search(request, return_context=True)
# Save the context as we will need to merge other attributes later
self.context = context
if "object_list" in context:
return context["object_list"]
else:
return []
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset(request)
show = []
show = set().union(*(d.keys() for d in self.object_list))
allow_empty = self.get_allow_empty()
if not allow_empty:
# When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if self.get_paginate_by(self.object_list) is not None and hasattr(
self.object_list, "exists"
):
is_empty = not self.object_list.exists() # noqa
else:
is_empty = not self.object_list # noqa
context = self.get_context_data()
if isinstance(self.context, HttpResponse):
return self.context
for k, v in self.context.items():
if k not in context:
context[k] = v
context["show"] = show
if request.method == "GET":
if not request.htmx:
self.template_name = "ui/drilldown/drilldown.html"
response = self.render_to_response(context)
# if not request.method == "GET":
if "client_uri" in context:
response["HX-Push"] = reverse("home") + "?" + context["client_uri"]
return response
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
class Drilldown(View):
template_name = "ui/drilldown/drilldown.html"
plan_name = "drilldown"
def get(self, request):
return drilldown_search(request)
def post(self, request):
return drilldown_search(request)
class DrilldownContextModal(APIView):
parser_classes = [FormParser]
plan_name = "drilldown"
template_name = "modals/context.html"
def post(self, request):
if request.resolver_match.url_name == "modal_context_table":
self.template_name = "modals/context_table.html"
size = 20
nicks = None
query = False
# Create the query params from the POST arguments
mandatory = ["net", "channel", "num", "src", "index", "nick", "type", "mtype"]
invalid = [None, False, "", "None"]
query_params = {k: v for k, v in request.data.items() if v}
for key in query_params:
if query_params[key] in invalid:
query_params[key] = None
for key in mandatory:
if key not in query_params:
query_params[key] = None
# Lookup the hash values but don't disclose them to the user
if settings.HASHING:
SAFE_PARAMS = deepcopy(query_params)
hash_lookup(SAFE_PARAMS)
type = None
# SUPERUSER BLOCK #
if request.user.is_superuser:
if "type" in SAFE_PARAMS:
type = SAFE_PARAMS["type"]
if type == "znc":
SAFE_PARAMS["channel"] = "*status"
if type in ["query", "notice"]:
nicks = [SAFE_PARAMS["channel"], SAFE_PARAMS["nick"]]
query = True
if (
SAFE_PARAMS["index"] == "int"
and SAFE_PARAMS["mtype"] == "msg"
and not type == "query"
):
SAFE_PARAMS["index"] = "main"
if SAFE_PARAMS["type"] in ["znc", "auth"]:
query = True
# SUPERUSER BLOCK #
if not request.user.is_superuser:
if "index" in SAFE_PARAMS:
SAFE_PARAMS["index"] = "main"
SAFE_PARAMS["sorting"] = "desc"
annotate = False
if SAFE_PARAMS["src"] == "irc":
if SAFE_PARAMS["type"] in ["query", "notice", "msg", "highlight"]:
annotate = True
# Create the query with the context helper
search_query = construct_query(
SAFE_PARAMS["index"],
SAFE_PARAMS["net"],
SAFE_PARAMS["channel"],
SAFE_PARAMS["src"],
SAFE_PARAMS["num"],
size,
type=type,
nicks=nicks,
)
results = query_results(
request,
SAFE_PARAMS,
annotate=annotate,
custom_query=search_query,
reverse=True,
dedup_fields=["net", "type", "msg"],
lookup_hashes=False,
)
if "message" in results:
return render(request, self.template_name, results)
# Make the time nicer
# for index, item in enumerate(results["object_list"]):
# results["object_list"][index]["time"] = item["time"]+"SSS"
context = {
"net": query_params["net"],
"channel": query_params["channel"],
"src": query_params["src"],
"ts": f"{query_params['date']} {query_params['time']}",
"object_list": results["object_list"],
"time": query_params["time"],
"date": query_params["date"],
"index": query_params["index"],
"num": query_params["num"],
"type": query_params["type"],
"mtype": query_params["mtype"],
"nick": query_params["nick"],
"params": query_params,
}
if request.user.is_superuser:
if query:
context["query"] = True
return render(request, self.template_name, context)
class ThresholdInfoModal(APIView):
parser_classes = [FormParser]
plan_name = "drilldown"
template_name = "modals/drilldown.html"
def post(self, request):
# if not request.user.has_plan(self.plan_name):
# return JsonResponse({"success": False})
if "net" not in request.data:
return JsonResponse({"success": False})
if "nick" not in request.data:
return JsonResponse({"success": False})
if "channel" not in request.data:
return JsonResponse({"success": False})
net = request.data["net"]
nick = request.data["nick"]
channel = request.data["channel"]
# SAFE BLOCK #
# Lookup the hash values but don't disclose them to the user
if settings.HASHING:
SAFE_PARAMS = request.data.dict()
hash_lookup(SAFE_PARAMS)
safe_net = SAFE_PARAMS["net"]
safe_nick = SAFE_PARAMS["nick"]
safe_channel = SAFE_PARAMS["channel"]
channels = get_chans(safe_net, [safe_nick])
users = get_users(safe_net, [safe_channel])
num_users = annotate_num_users(safe_net, channels)
num_chans = annotate_num_chans(safe_net, users)
if channels:
inter_users = get_users(safe_net, channels)
else:
inter_users = []
if users:
inter_chans = get_chans(safe_net, users)
else:
inter_chans = []
hash_list(inter_chans)
hash_list(inter_users)
hash_list(num_chans, hash_keys=True)
hash_list(num_users, hash_keys=True)
hash_list(channels)
hash_list(users)
# SAFE BLOCK END #
nick = nick.replace("|", "")
channel = channel.replace("|", "")
context = {
"net": net,
"nick": nick,
"channel": channel,
"chans": channels,
"users": users,
"inter_chans": inter_chans,
"inter_users": inter_users,
"num_users": num_users,
"num_chans": num_chans,
}
return render(request, self.template_name, context)