diff --git a/app/urls.py b/app/urls.py index dea5db9..02b7110 100644 --- a/app/urls.py +++ b/app/urls.py @@ -28,6 +28,7 @@ from core.views.manage.threshold.irc import ( ThresholdIRCNetworkInfo, ThresholdIRCNetworkInfoEdit, ThresholdIRCNetworkRelays, + ThresholdIRCNetworkRelayStatus, ThresholdIRCNetworks, ThresholdIRCStats, ) @@ -119,11 +120,27 @@ urlpatterns = [ ThresholdIRCNetworkRelays.as_view(), name="threshold_irc_network_relays", ), + path( + "manage/threshold/irc/network////", + ThresholdIRCNetworkRelayStatus.as_view(), + name="threshold_irc_network_relay_status", + ), path( "manage/threshold/irc/network//channels/", ThresholdIRCNetworkChannels.as_view(), name="threshold_irc_network_channels", ), + path( + "manage/threshold/irc/network//channel//", + ThresholdIRCNetworkChannels.as_view(), + name="threshold_irc_network_channel", + ), + # No channel argument as we're gonna do a form + path( + "manage/threshold/irc/network//channel/", + ThresholdIRCNetworkChannels.as_view(), + name="threshold_irc_network_channel", + ), ## path("api/chans/", ThresholdChans.as_view(), name="chans"), path("api/users/", ThresholdUsers.as_view(), name="users"), diff --git a/core/lib/manage/threshold.py b/core/lib/manage/threshold.py index f3ab1fc..422cd30 100644 --- a/core/lib/manage/threshold.py +++ b/core/lib/manage/threshold.py @@ -1,3 +1,5 @@ +import urllib.parse + from core.lib.threshold import threshold_request @@ -31,8 +33,15 @@ def get_irc_network(net): def edit_irc_network(net, data): url = f"irc/network/{net}/edit" payload = dict(data) - network = threshold_request(url, payload) - return network + result = threshold_request(url, payload) + return result + + +def change_network_status(net, num, status): + url = f"irc/network/{net}/{num}" + payload = {"status": status} + result = threshold_request(url, payload) + return result def get_irc_relays(net): @@ -40,7 +49,7 @@ def get_irc_relays(net): payload = {} relays = threshold_request(url, payload) if not relays: - return [] + return {} return relays @@ -49,5 +58,25 @@ def get_irc_channels(net): payload = {} channels = threshold_request(url, payload) if not channels: - return [] + return {} return channels + + +def part_channel(net, channel): + channel = urllib.parse.quote(channel, safe="") + url = f"irc/network/{net}/channel/{channel}" + payload = {} + parted = threshold_request(url, payload, method="DELETE") + if not parted: + return {} + return parted + + +def join_channel(net, channel): + channel = urllib.parse.quote(channel, safe="") + url = f"irc/network/{net}/channel/{channel}" + payload = {} + joined = threshold_request(url, payload, method="PUT") + if not joined: + return {} + return joined diff --git a/core/lib/threshold.py b/core/lib/threshold.py index e4cf70e..954dd37 100644 --- a/core/lib/threshold.py +++ b/core/lib/threshold.py @@ -37,14 +37,28 @@ def sort_data(data): data[item] = OrderedDict({k: v for k, v in sorted_item}) -def threshold_request(url, data): +def threshold_request(url, data, method="POST"): headers = { "ApiKey": settings.THRESHOLD_API_KEY, "Token": settings.THRESHOLD_API_TOKEN, } for key in data: data[key] = escape(data[key]) - r = requests.post( + + if method == "POST": + method = requests.post + elif method == "GET": + method = requests.get + elif method == "DELETE": + method = requests.delete + elif method == "PUT": + method = requests.put + else: + logger.error("Invalid method specified") + method = requests.get + print("SENDING TO", url) + + r = method( f"{settings.THRESHOLD_ENDPOINT}/{url}/", data=dumps(data), headers=headers ) if not r.headers.get("Counter") == settings.THRESHOLD_API_COUNTER: diff --git a/core/templates/manage/threshold/irc/network/channels.html b/core/templates/manage/threshold/irc/network/channels.html index 4fbabe8..32d751c 100644 --- a/core/templates/manage/threshold/irc/network/channels.html +++ b/core/templates/manage/threshold/irc/network/channels.html @@ -1,8 +1,17 @@
+ {% if message is not None %} +
+ {{ message }} +
+ {% endif %} {% if channels is not None %}
+ + + + {% for channel, info in channels.items %} @@ -12,6 +21,18 @@ {{ info }} + {% endfor %} diff --git a/core/templates/manage/threshold/irc/network/network.html b/core/templates/manage/threshold/irc/network/network.html index 18c6600..2c0c72e 100644 --- a/core/templates/manage/threshold/irc/network/network.html +++ b/core/templates/manage/threshold/irc/network/network.html @@ -5,7 +5,7 @@ style="display: none;" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-post="{% url 'threshold_irc_network_info' net %}" - hx-trigger="load" + hx-trigger="load, every 5s" hx-target="#info" hx-swap="outerHTML"> @@ -13,8 +13,8 @@
@@ -23,7 +23,7 @@ style="display: none;" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-post="{% url 'threshold_irc_network_channels' net %}" - hx-trigger="load" + hx-trigger="load, every 5s" hx-target="#channels" hx-swap="outerHTML"> @@ -57,4 +57,37 @@ +
+
+
+
+
+
+
+
+ + + + +
+
+
+
+
+
+ +
+
+ +
+
+
{% endblock %} diff --git a/core/templates/manage/threshold/irc/network/relays.html b/core/templates/manage/threshold/irc/network/relays.html index 80bb039..1b788ec 100644 --- a/core/templates/manage/threshold/irc/network/relays.html +++ b/core/templates/manage/threshold/irc/network/relays.html @@ -1,4 +1,10 @@ +{% load index %}
+ {% if message is not None %} +
+ {{ message }} +
+ {% endif %} {% if relays is not None %}
@@ -34,10 +40,30 @@ + {% else %} + {% endif %}
channelactions
+ +
diff --git a/core/views/manage/threshold/irc.py b/core/views/manage/threshold/irc.py index 14da424..2e50961 100644 --- a/core/views/manage/threshold/irc.py +++ b/core/views/manage/threshold/irc.py @@ -3,14 +3,7 @@ from django.views import View from rest_framework.parsers import FormParser from rest_framework.views import APIView -from core.lib.manage.threshold import ( - edit_irc_network, - get_irc_channels, - get_irc_network, - get_irc_networks, - get_irc_relays, - get_irc_stats, -) +from core.lib.manage import threshold from core.views.manage.permissions import SuperUserRequiredMixin @@ -18,7 +11,7 @@ class ThresholdIRCStats(SuperUserRequiredMixin, View): stats_template = "manage/threshold/irc/overview/stats.html" def post(self, request): - stats = get_irc_stats() + stats = threshold.get_irc_stats() context = {"stats": stats} return render(request, self.stats_template, context) @@ -27,7 +20,7 @@ class ThresholdIRCNetworks(SuperUserRequiredMixin, View): template_name = "manage/threshold/irc/overview/networks.html" def post(self, request): - networks = get_irc_networks() + networks = threshold.get_irc_networks() context = {"networks": networks} return render(request, self.template_name, context) @@ -36,7 +29,7 @@ class ThresholdIRCNetworkInfo(SuperUserRequiredMixin, View): template_name = "manage/threshold/irc/network/info.html" def post(self, request, net): - network = get_irc_network(net) + network = threshold.get_irc_network(net) context = {"network": network} return render(request, self.template_name, context) @@ -49,7 +42,7 @@ class ThresholdIRCNetworkInfoEdit(SuperUserRequiredMixin, APIView): """ Return the form to edit a network. """ - network = get_irc_network(net) + network = threshold.get_irc_network(net) editable = ["auth", "host", "last", "port", "security"] context = { "net": net, @@ -63,7 +56,7 @@ class ThresholdIRCNetworkInfoEdit(SuperUserRequiredMixin, APIView): Returns the info pane with a message about the success. """ template_name = "manage/threshold/irc/network/info.html" - edited = edit_irc_network(net, request.data) + edited = threshold.edit_irc_network(net, request.data) if edited["success"]: message = "Successfully edited!" message_class = "success" @@ -73,7 +66,7 @@ class ThresholdIRCNetworkInfoEdit(SuperUserRequiredMixin, APIView): else: message = "Error editing network" message_class = "danger" - network = get_irc_network(net) + network = threshold.get_irc_network(net) context = {"network": network, "message": message, "class": message_class} return render(request, template_name, context) @@ -81,16 +74,105 @@ class ThresholdIRCNetworkInfoEdit(SuperUserRequiredMixin, APIView): class ThresholdIRCNetworkRelays(SuperUserRequiredMixin, View): template_name = "manage/threshold/irc/network/relays.html" - def post(self, request, net): - relays = get_irc_relays(net) + def get(self, request, net): + relays = threshold.get_irc_relays(net) context = {"relays": relays["relays"]} return render(request, self.template_name, context) -class ThresholdIRCNetworkChannels(SuperUserRequiredMixin, View): +class ThresholdIRCNetworkRelayStatus(SuperUserRequiredMixin, APIView): + template_name = "manage/threshold/irc/network/relays.html" + + def get(self, request, net, num, status): + """ + Change the status of a relay. + """ + edited = threshold.change_network_status(net, num, status) + if edited["success"]: + message = "Successfully completed!" + message_class = "success" + else: + if "reason" in edited: + message = f"Error updating status: {edited['reason']}" + else: + message = "Error updating status" + message_class = "danger" + relays = threshold.get_irc_relays(net) + context = { + "relays": relays["relays"], + "message": message, + "class": message_class, + } + return render(request, self.template_name, context) + + +class ThresholdIRCNetworkChannels(SuperUserRequiredMixin, APIView): + """ + List the channels a network is on. + Allow parting and joining new ones. + Re-render the same template if we join/part to make it simple. + """ + template_name = "manage/threshold/irc/network/channels.html" + parser_classes = [FormParser] def post(self, request, net): - channels = get_irc_channels(net) - context = {"channels": channels["channels"]} + """ + Get list of channels for network. + :param net: network name + """ + channels = threshold.get_irc_channels(net) + context = {"net": net, "channels": channels["channels"]} + return render(request, self.template_name, context) + + def delete(self, request, net, channel): + """ + Part a channel. + :param net: network name + :param channel: channel name + """ + parted = threshold.part_channel(net, channel) + if parted: + message = f"Requested part on relays: {', '.join(parted['relays'])}" + message_class = "success" + else: + message = parted["reason"] + message_class = "danger" + + channels = threshold.get_irc_channels(net) + context = { + "net": net, + "channels": channels["channels"], + "message": message, + "class": message_class, + } + return render(request, self.template_name, context) + + def put(self, request, net): + """ + Join a channel. + :param net: network name + """ + if "channel" not in request.data: + message = "No channel specified" + message_class = "danger" + else: + channel = request.data["channel"] + print("CHANNEL", channel) + joined = threshold.join_channel(net, channel) + print("JOINED", joined) + if joined: + message = f"Requested join on relay: {joined['relays']}" + message_class = "success" + else: + message = joined["reason"] + message_class = "danger" + + channels = threshold.get_irc_channels(net) + context = { + "net": net, + "channels": channels["channels"], + "message": message, + "class": message_class, + } return render(request, self.template_name, context) diff --git a/core/views/ui/drilldown.py b/core/views/ui/drilldown.py index d62d33c..b0a953d 100644 --- a/core/views/ui/drilldown.py +++ b/core/views/ui/drilldown.py @@ -1,14 +1,14 @@ +import json + from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import HttpResponse, HttpResponseForbidden, JsonResponse from django.shortcuts import render from django.views import View -from django.http import HttpResponse, HttpResponseForbidden, JsonResponse from rest_framework.parsers import FormParser from rest_framework.views import APIView -import json + from core.lib.opensearch import query_results - - from core.lib.threshold import ( annotate_num_chans, annotate_num_users, @@ -16,6 +16,7 @@ from core.lib.threshold import ( get_users, ) + class Drilldown(LoginRequiredMixin, View): template_name = "ui/drilldown/drilldown.html" plan_name = "drilldown" @@ -28,6 +29,7 @@ class Drilldown(LoginRequiredMixin, View): } return render(request, self.template_name, context) + class DrilldownSearch(LoginRequiredMixin, View): # parser_classes = [JSONParser] template_name = "ui/drilldown/results.html" diff --git a/core/views/ui/insights.py b/core/views/ui/insights.py index 4efbbc3..1957f95 100644 --- a/core/views/ui/insights.py +++ b/core/views/ui/insights.py @@ -1,10 +1,12 @@ +from ast import literal_eval + from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import HttpResponse, HttpResponseForbidden, JsonResponse from django.shortcuts import render from django.views import View from rest_framework.parsers import FormParser -from django.http import HttpResponse, HttpResponseForbidden, JsonResponse - from rest_framework.views import APIView + from core.lib.meta import get_meta from core.lib.nicktrace import get_nicks from core.lib.opensearch import query_single_result @@ -15,7 +17,6 @@ from core.lib.threshold import ( get_chans, get_users, ) -from ast import literal_eval class Insights(LoginRequiredMixin, View): @@ -27,6 +28,7 @@ class Insights(LoginRequiredMixin, View): return render(request, "denied.html") return render(request, self.template_name) + class InsightsSearch(LoginRequiredMixin, View): # parser_classes = [JSONParser] template_name = "ui/insights/info.html"