Implement joining and parting channels

This commit is contained in:
Mark Veidemanis 2022-07-27 20:53:41 +01:00
parent 3d91c4164e
commit b401fe939f
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
9 changed files with 262 additions and 36 deletions

View File

@ -28,6 +28,7 @@ from core.views.manage.threshold.irc import (
ThresholdIRCNetworkInfo, ThresholdIRCNetworkInfo,
ThresholdIRCNetworkInfoEdit, ThresholdIRCNetworkInfoEdit,
ThresholdIRCNetworkRelays, ThresholdIRCNetworkRelays,
ThresholdIRCNetworkRelayStatus,
ThresholdIRCNetworks, ThresholdIRCNetworks,
ThresholdIRCStats, ThresholdIRCStats,
) )
@ -119,11 +120,27 @@ urlpatterns = [
ThresholdIRCNetworkRelays.as_view(), ThresholdIRCNetworkRelays.as_view(),
name="threshold_irc_network_relays", name="threshold_irc_network_relays",
), ),
path(
"manage/threshold/irc/network/<str:net>/<int:num>/<int:status>/",
ThresholdIRCNetworkRelayStatus.as_view(),
name="threshold_irc_network_relay_status",
),
path( path(
"manage/threshold/irc/network/<str:net>/channels/", "manage/threshold/irc/network/<str:net>/channels/",
ThresholdIRCNetworkChannels.as_view(), ThresholdIRCNetworkChannels.as_view(),
name="threshold_irc_network_channels", name="threshold_irc_network_channels",
), ),
path(
"manage/threshold/irc/network/<str:net>/channel/<channel>/",
ThresholdIRCNetworkChannels.as_view(),
name="threshold_irc_network_channel",
),
# No channel argument as we're gonna do a form
path(
"manage/threshold/irc/network/<str:net>/channel/",
ThresholdIRCNetworkChannels.as_view(),
name="threshold_irc_network_channel",
),
## ##
path("api/chans/", ThresholdChans.as_view(), name="chans"), path("api/chans/", ThresholdChans.as_view(), name="chans"),
path("api/users/", ThresholdUsers.as_view(), name="users"), path("api/users/", ThresholdUsers.as_view(), name="users"),

View File

@ -1,3 +1,5 @@
import urllib.parse
from core.lib.threshold import threshold_request from core.lib.threshold import threshold_request
@ -31,8 +33,15 @@ def get_irc_network(net):
def edit_irc_network(net, data): def edit_irc_network(net, data):
url = f"irc/network/{net}/edit" url = f"irc/network/{net}/edit"
payload = dict(data) payload = dict(data)
network = threshold_request(url, payload) result = threshold_request(url, payload)
return network 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): def get_irc_relays(net):
@ -40,7 +49,7 @@ def get_irc_relays(net):
payload = {} payload = {}
relays = threshold_request(url, payload) relays = threshold_request(url, payload)
if not relays: if not relays:
return [] return {}
return relays return relays
@ -49,5 +58,25 @@ def get_irc_channels(net):
payload = {} payload = {}
channels = threshold_request(url, payload) channels = threshold_request(url, payload)
if not channels: if not channels:
return [] return {}
return channels 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

View File

@ -37,14 +37,28 @@ def sort_data(data):
data[item] = OrderedDict({k: v for k, v in sorted_item}) data[item] = OrderedDict({k: v for k, v in sorted_item})
def threshold_request(url, data): def threshold_request(url, data, method="POST"):
headers = { headers = {
"ApiKey": settings.THRESHOLD_API_KEY, "ApiKey": settings.THRESHOLD_API_KEY,
"Token": settings.THRESHOLD_API_TOKEN, "Token": settings.THRESHOLD_API_TOKEN,
} }
for key in data: for key in data:
data[key] = escape(data[key]) 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 f"{settings.THRESHOLD_ENDPOINT}/{url}/", data=dumps(data), headers=headers
) )
if not r.headers.get("Counter") == settings.THRESHOLD_API_COUNTER: if not r.headers.get("Counter") == settings.THRESHOLD_API_COUNTER:

View File

@ -1,8 +1,17 @@
<div id="channels"> <div id="channels">
{% if message is not None %}
<div class="notification is-{{ class }}" hx-ext="remove-me" remove-me="3s">
{{ message }}
</div>
{% endif %}
{% if channels is not None %} {% if channels is not None %}
<div class="content" style="max-height: 30em; overflow: auto;"> <div class="content" style="max-height: 30em; overflow: auto;">
<div class="table-container"> <div class="table-container">
<table class="table is-fullwidth is-hoverable"> <table class="table is-fullwidth is-hoverable">
<thead>
<th>channel</th>
<th>actions</th>
</thead>
<tbody> <tbody>
{% for channel, info in channels.items %} {% for channel, info in channels.items %}
<tr> <tr>
@ -12,6 +21,18 @@
{{ info }} {{ info }}
</span> </span>
</td> </td>
<td>
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete="{% url 'threshold_irc_network_channel' net channel %}"
hx-target="#channels"
hx-swap="outerHTML"
class="button is-danger is-small">
<span class="icon" data-tooltip="Part">
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
</span>
</button>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -5,7 +5,7 @@
style="display: none;" style="display: none;"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'threshold_irc_network_info' net %}" hx-post="{% url 'threshold_irc_network_info' net %}"
hx-trigger="load" hx-trigger="load, every 5s"
hx-target="#info" hx-target="#info"
hx-swap="outerHTML"> hx-swap="outerHTML">
</div> </div>
@ -13,8 +13,8 @@
<div <div
style="display: none;" style="display: none;"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'threshold_irc_network_relays' net %}" hx-get="{% url 'threshold_irc_network_relays' net %}"
hx-trigger="load" hx-trigger="load, every 5s"
hx-target="#relays" hx-target="#relays"
hx-swap="outerHTML"> hx-swap="outerHTML">
</div> </div>
@ -23,7 +23,7 @@
style="display: none;" style="display: none;"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'threshold_irc_network_channels' net %}" hx-post="{% url 'threshold_irc_network_channels' net %}"
hx-trigger="load" hx-trigger="load, every 5s"
hx-target="#channels" hx-target="#channels"
hx-swap="outerHTML"> hx-swap="outerHTML">
</div> </div>
@ -57,4 +57,37 @@
</div> </div>
</div> </div>
</div> </div>
<div class="columns">
<div class="column is-half">
<div class="box">
<form method="POST">
<div class="field">
<div class="field-body">
<div class="field">
<div class="control is-expanded has-icons-left">
<input id="query" name="channel" class="input" type="text" placeholder="channel">
<span class="icon is-small is-left">
<i class="fa-solid fa-hashtag"></i>
</span>
</div>
</div>
</div>
</div>
<div class="field">
<div class="control">
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
class="button is-primary is-fullwidth"
hx-put="{% url 'threshold_irc_network_channel' net %}"
hx-trigger="click"
hx-target="#channels"
hx-swap="outerHTML">
Join
</button>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -1,4 +1,10 @@
{% load index %}
<div id="relays"> <div id="relays">
{% if message is not None %}
<div class="notification is-{{ class }}" hx-ext="remove-me" remove-me="3s">
{{ message }}
</div>
{% endif %}
{% if relays is not None %} {% if relays is not None %}
<div class="content" style="max-height: 30em; overflow: auto;"> <div class="content" style="max-height: 30em; overflow: auto;">
<div class="table-container"> <div class="table-container">
@ -34,10 +40,30 @@
<span class="icon"> <span class="icon">
<i class="fa-solid fa-check" aria-hidden="true"></i> <i class="fa-solid fa-check" aria-hidden="true"></i>
</span> </span>
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'threshold_irc_network_relay_status' relay|index:'net' relay|index:'id' 0 %}"
hx-target="#relays"
hx-swap="outerHTML"
class="button is-danger is-small">
<span class="icon" data-tooltip="Disable">
<i class="fa-solid fa-wifi-slash" aria-hidden="true"></i>
</span>
</button>
{% else %} {% else %}
<span class="icon"> <span class="icon">
<i class="fa-solid fa-xmark" aria-hidden="true"></i> <i class="fa-solid fa-xmark" aria-hidden="true"></i>
</span> </span>
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'threshold_irc_network_relay_status' relay|index:'net' relay|index:'id' 1 %}"
hx-target="#relays"
hx-swap="outerHTML"
class="button is-success is-small">
<span class="icon" data-tooltip="Enable">
<i class="fa-solid fa-wifi" aria-hidden="true"></i>
</span>
</button>
{% endif %} {% endif %}
</td> </td>
<td> <td>

View File

@ -3,14 +3,7 @@ from django.views import View
from rest_framework.parsers import FormParser from rest_framework.parsers import FormParser
from rest_framework.views import APIView from rest_framework.views import APIView
from core.lib.manage.threshold import ( from core.lib.manage import threshold
edit_irc_network,
get_irc_channels,
get_irc_network,
get_irc_networks,
get_irc_relays,
get_irc_stats,
)
from core.views.manage.permissions import SuperUserRequiredMixin from core.views.manage.permissions import SuperUserRequiredMixin
@ -18,7 +11,7 @@ class ThresholdIRCStats(SuperUserRequiredMixin, View):
stats_template = "manage/threshold/irc/overview/stats.html" stats_template = "manage/threshold/irc/overview/stats.html"
def post(self, request): def post(self, request):
stats = get_irc_stats() stats = threshold.get_irc_stats()
context = {"stats": stats} context = {"stats": stats}
return render(request, self.stats_template, context) return render(request, self.stats_template, context)
@ -27,7 +20,7 @@ class ThresholdIRCNetworks(SuperUserRequiredMixin, View):
template_name = "manage/threshold/irc/overview/networks.html" template_name = "manage/threshold/irc/overview/networks.html"
def post(self, request): def post(self, request):
networks = get_irc_networks() networks = threshold.get_irc_networks()
context = {"networks": networks} context = {"networks": networks}
return render(request, self.template_name, context) return render(request, self.template_name, context)
@ -36,7 +29,7 @@ class ThresholdIRCNetworkInfo(SuperUserRequiredMixin, View):
template_name = "manage/threshold/irc/network/info.html" template_name = "manage/threshold/irc/network/info.html"
def post(self, request, net): def post(self, request, net):
network = get_irc_network(net) network = threshold.get_irc_network(net)
context = {"network": network} context = {"network": network}
return render(request, self.template_name, context) return render(request, self.template_name, context)
@ -49,7 +42,7 @@ class ThresholdIRCNetworkInfoEdit(SuperUserRequiredMixin, APIView):
""" """
Return the form to edit a network. Return the form to edit a network.
""" """
network = get_irc_network(net) network = threshold.get_irc_network(net)
editable = ["auth", "host", "last", "port", "security"] editable = ["auth", "host", "last", "port", "security"]
context = { context = {
"net": net, "net": net,
@ -63,7 +56,7 @@ class ThresholdIRCNetworkInfoEdit(SuperUserRequiredMixin, APIView):
Returns the info pane with a message about the success. Returns the info pane with a message about the success.
""" """
template_name = "manage/threshold/irc/network/info.html" 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"]: if edited["success"]:
message = "Successfully edited!" message = "Successfully edited!"
message_class = "success" message_class = "success"
@ -73,7 +66,7 @@ class ThresholdIRCNetworkInfoEdit(SuperUserRequiredMixin, APIView):
else: else:
message = "Error editing network" message = "Error editing network"
message_class = "danger" message_class = "danger"
network = get_irc_network(net) network = threshold.get_irc_network(net)
context = {"network": network, "message": message, "class": message_class} context = {"network": network, "message": message, "class": message_class}
return render(request, template_name, context) return render(request, template_name, context)
@ -81,16 +74,105 @@ class ThresholdIRCNetworkInfoEdit(SuperUserRequiredMixin, APIView):
class ThresholdIRCNetworkRelays(SuperUserRequiredMixin, View): class ThresholdIRCNetworkRelays(SuperUserRequiredMixin, View):
template_name = "manage/threshold/irc/network/relays.html" template_name = "manage/threshold/irc/network/relays.html"
def post(self, request, net): def get(self, request, net):
relays = get_irc_relays(net) relays = threshold.get_irc_relays(net)
context = {"relays": relays["relays"]} context = {"relays": relays["relays"]}
return render(request, self.template_name, context) 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" template_name = "manage/threshold/irc/network/channels.html"
parser_classes = [FormParser]
def post(self, request, net): 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) return render(request, self.template_name, context)

View File

@ -1,14 +1,14 @@
import json
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse, HttpResponseForbidden, JsonResponse
from django.shortcuts import render from django.shortcuts import render
from django.views import View from django.views import View
from django.http import HttpResponse, HttpResponseForbidden, JsonResponse
from rest_framework.parsers import FormParser from rest_framework.parsers import FormParser
from rest_framework.views import APIView from rest_framework.views import APIView
import json
from core.lib.opensearch import query_results from core.lib.opensearch import query_results
from core.lib.threshold import ( from core.lib.threshold import (
annotate_num_chans, annotate_num_chans,
annotate_num_users, annotate_num_users,
@ -16,6 +16,7 @@ from core.lib.threshold import (
get_users, get_users,
) )
class Drilldown(LoginRequiredMixin, View): class Drilldown(LoginRequiredMixin, View):
template_name = "ui/drilldown/drilldown.html" template_name = "ui/drilldown/drilldown.html"
plan_name = "drilldown" plan_name = "drilldown"
@ -28,6 +29,7 @@ class Drilldown(LoginRequiredMixin, View):
} }
return render(request, self.template_name, context) return render(request, self.template_name, context)
class DrilldownSearch(LoginRequiredMixin, View): class DrilldownSearch(LoginRequiredMixin, View):
# parser_classes = [JSONParser] # parser_classes = [JSONParser]
template_name = "ui/drilldown/results.html" template_name = "ui/drilldown/results.html"

View File

@ -1,10 +1,12 @@
from ast import literal_eval
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse, HttpResponseForbidden, JsonResponse
from django.shortcuts import render from django.shortcuts import render
from django.views import View from django.views import View
from rest_framework.parsers import FormParser from rest_framework.parsers import FormParser
from django.http import HttpResponse, HttpResponseForbidden, JsonResponse
from rest_framework.views import APIView from rest_framework.views import APIView
from core.lib.meta import get_meta from core.lib.meta import get_meta
from core.lib.nicktrace import get_nicks from core.lib.nicktrace import get_nicks
from core.lib.opensearch import query_single_result from core.lib.opensearch import query_single_result
@ -15,7 +17,6 @@ from core.lib.threshold import (
get_chans, get_chans,
get_users, get_users,
) )
from ast import literal_eval
class Insights(LoginRequiredMixin, View): class Insights(LoginRequiredMixin, View):
@ -27,6 +28,7 @@ class Insights(LoginRequiredMixin, View):
return render(request, "denied.html") return render(request, "denied.html")
return render(request, self.template_name) return render(request, self.template_name)
class InsightsSearch(LoginRequiredMixin, View): class InsightsSearch(LoginRequiredMixin, View):
# parser_classes = [JSONParser] # parser_classes = [JSONParser]
template_name = "ui/insights/info.html" template_name = "ui/insights/info.html"