diff --git a/relay/frontend/page/admin-instances.haml b/relay/frontend/page/admin-instances.haml
index aaab1c9..fcb0630 100644
--- a/relay/frontend/page/admin-instances.haml
+++ b/relay/frontend/page/admin-instances.haml
@@ -1,29 +1,32 @@
-extends "base.haml"
-set page="Instances"
+
+-block head
+ %script(type="application/javascript" src="/static/instance.js" nonce="{{view.request['hash']}}")
+
-block content
%details.section
%summary << Add Instance
- %form(action="/admin/instances" method="POST")
- #add-item
- %label(for="domain") << Domain
- %input(type="domain" id="domain" name="domain" placeholder="Domain")
+ #add-item
+ %label(for="new-actor") << Actor
+ %input(type="url" id="new-actor" placeholder="Actor URL")
- %label(for="actor") << Actor URL
- %input(type="url" id="actor" name="actor" placeholder="Actor URL")
+ %label(for="new-inbox") << Inbox
+ %input(type="url" id="new-inbox" placeholder="Inbox URL")
- %label(for="inbox") << Inbox URL
- %input(type="url" id="inbox" name="inbox" placeholder="Inbox URL")
+ %label(for="new-followid") << Follow ID
+ %input(type="url" id="new-followid" placeholder="Follow ID URL")
- %label(for="software") << Software
- %input(name="software" id="software" placeholder="software")
+ %label(for="new-software") << Software
+ %input(id="new-software" placeholder="software")
- %input(type="submit" value="Add Instance")
+ %input(type="button" value="Add Instance", onclick="add_instance()")
-if requests
- %fieldset.section
+ %fieldset.section.requests
%legend << Follow Requests
.data-table
- %table
+ %table#requests
%thead
%tr
%td.instance << Instance
@@ -34,7 +37,7 @@
%tbody
-for request in requests
- %tr
+ %tr(id="{{request.domain}}")
%td.instance
%a(href="https://{{request.domain}}" target="_new") -> =request.domain
@@ -45,16 +48,16 @@
=request.created.strftime("%Y-%m-%d")
%td.approve
- %a(href="/admin/instances/approve/{{request.domain}}" title="Approve Request") << ✓
+ %a(href="#" onclick="req_response('{{request.domain}}', true)" title="Approve Request") << ✓
%td.deny
- %a(href="/admin/instances/deny/{{request.domain}}" title="Deny Request") << ✖
+ %a(href="#" onclick="req_response('{{request.domain}}', false)" title="Deny Request") << ✖
- %fieldset.section
+ %fieldset.section.instances
%legend << Instances
.data-table
- %table
+ %table#instances
%thead
%tr
%td.instance << Instance
@@ -64,7 +67,7 @@
%tbody
-for instance in instances
- %tr
+ %tr(id="{{instance.domain}}")
%td.instance
%a(href="https://{{instance.domain}}/" target="_new") -> =instance.domain
@@ -75,4 +78,4 @@
=instance.created.strftime("%Y-%m-%d")
%td.remove
- %a(href="/admin/instances/delete/{{instance.domain}}" title="Remove Instance") << ✖
+ %a(href="#" onclick="del_instance('{{instance.domain}}')" title="Remove Instance") << ✖
diff --git a/relay/frontend/static/api.js b/relay/frontend/static/api.js
index 43dd433..cbdb1c9 100644
--- a/relay/frontend/static/api.js
+++ b/relay/frontend/static/api.js
@@ -62,8 +62,17 @@ class Client {
throw new Error(message.error);
}
- if (Object.hasOwn(message, "created")) {
- message.created = new Date(message.created);
+ if (Array.isArray(message)) {
+ message.forEach((msg) => {
+ if (Object.hasOwn(msg, "created")) {
+ msg.created = new Date(msg.created);
+ }
+ });
+
+ } else {
+ if (Object.hasOwn(message, "created")) {
+ message.created = new Date(message.created);
+ }
}
return message;
diff --git a/relay/frontend/static/domain_ban.js b/relay/frontend/static/domain_ban.js
index f01f675..ae92420 100644
--- a/relay/frontend/static/domain_ban.js
+++ b/relay/frontend/static/domain_ban.js
@@ -15,8 +15,6 @@ function create_ban_object(domain, reason, note) {
async function ban() {
var table = document.querySelector("table");
- var row = table.insertRow(-1);
-
var elems = {
domain: document.getElementById("new-domain"),
reason: document.getElementById("new-reason"),
@@ -42,7 +40,9 @@ async function ban() {
return
}
+ var row = table.insertRow(-1);
row.id = ban.domain;
+
var new_domain = row.insertCell(0);
var new_date = row.insertCell(1);
var new_remove = row.insertCell(2);
diff --git a/relay/frontend/static/instance.js b/relay/frontend/static/instance.js
new file mode 100644
index 0000000..e5f422c
--- /dev/null
+++ b/relay/frontend/static/instance.js
@@ -0,0 +1,105 @@
+function append_table_row(table, instance) {
+ var row = table.insertRow(-1);
+ row.id = instance.domain;
+
+ var domain = row.insertCell(0);
+ domain.className = "domain";
+ domain.innerHTML = `${instance.domain}`;
+
+ var software = row.insertCell(1);
+ software.className = "software";
+ software.innerHTML = instance.software
+
+ var date = row.insertCell(2);
+ date.className = "date";
+ date.innerHTML = get_date_string(instance.created);
+
+ var remove = row.insertCell(3);
+ remove.className = "remove";
+ remove.innerHTML = `✖`;
+}
+
+
+async function add_instance() {
+ var elems = {
+ actor: document.getElementById("new-actor"),
+ inbox: document.getElementById("new-inbox"),
+ followid: document.getElementById("new-followid"),
+ software: document.getElementById("new-software")
+ }
+
+ var values = {
+ actor: elems.actor.value.trim(),
+ inbox: elems.inbox.value.trim(),
+ followid: elems.followid.value.trim(),
+ software: elems.software.value.trim()
+ }
+
+ if (values.actor === "") {
+ alert("Domain, actor, and inbox are required");
+ return;
+ }
+
+ try {
+ var instance = await client.request("POST", "v1/instance", values);
+
+ } catch (err) {
+ alert(err);
+ return
+ }
+
+ append_table_row(document.getElementById("instances"), instance);
+
+ elems.actor.value = null;
+ elems.inbox.value = null;
+ elems.followid.value = null;
+ elems.software.value = null;
+
+ document.querySelector("details.section").open = false;
+}
+
+
+async function del_instance(domain) {
+ try {
+ await client.request("DELETE", "v1/instance", {"domain": domain});
+
+ } catch (error) {
+ alert(error);
+ return;
+ }
+
+ document.getElementById(domain).remove();
+}
+
+
+async function req_response(domain, accept) {
+ params = {
+ "domain": domain,
+ "accept": accept
+ }
+
+ try {
+ await client.request("POST", "v1/request", params);
+
+ } catch (error) {
+ alert(error);
+ return;
+ }
+
+ document.getElementById(domain).remove();
+
+ if (document.getElementById("requests").rows.length < 2) {
+ document.querySelector("fieldset.requests").remove()
+ }
+
+ if (!accept) {
+ return;
+ }
+
+ instances = await client.request("GET", `v1/instance`, null);
+ instances.forEach((instance) => {
+ if (instance.domain === domain) {
+ append_table_row(document.getElementById("instances"), instance);
+ }
+ });
+}
diff --git a/relay/views/api.py b/relay/views/api.py
index db7ac7a..ca0470c 100644
--- a/relay/views/api.py
+++ b/relay/views/api.py
@@ -10,7 +10,7 @@ from .base import View, register_route
from .. import __version__
from ..database import ConfigData
-from ..misc import Message, Response, get_app
+from ..misc import Message, Response, boolean, get_app
if typing.TYPE_CHECKING:
from aiohttp.web import Request
@@ -161,7 +161,7 @@ class Config(View):
class Inbox(View):
async def get(self, request: Request) -> Response:
with self.database.session() as conn:
- data = tuple(conn.execute('SELECT * FROM inboxes').all())
+ data = conn.get_inboxes()
return Response.new(data, ctype = 'json')
@@ -186,6 +186,12 @@ class Inbox(View):
data['inbox'] = actor_data.shared_inbox
+ if not data.get('software'):
+ nodeinfo = await self.client.fetch_nodeinfo(data['domain'])
+
+ if nodeinfo is not None:
+ data['software'] = nodeinfo.sw_name
+
row = conn.put_inbox(**data)
return Response.new(row, ctype = 'json')
@@ -206,7 +212,7 @@ class Inbox(View):
return Response.new(instance, ctype = 'json')
- async def delete(self, request: Request, domain: str) -> Response:
+ async def delete(self, request: Request) -> Response:
with self.database.session() as conn:
data = await self.get_api_data(['domain'], [])
@@ -232,10 +238,7 @@ class RequestView(View):
async def post(self, request: Request) -> Response:
data = await self.get_api_data(['domain', 'accept'], [])
-
- if not isinstance(data['accept'], bool):
- atype = type(data['accept']).__name__
- return Response.new_error(400, f'Invalid type for "accept": {atype}', 'json')
+ data['accept'] = boolean(data['accept'])
try:
with self.database.session(True) as conn: