create admin instances page

This commit is contained in:
Izalia Mae 2024-03-02 19:58:06 -05:00
parent 7af3b9c20b
commit e6831f04eb
7 changed files with 154 additions and 47 deletions

View file

@ -1 +0,0 @@

View file

@ -1,5 +1,41 @@
-extends "base.haml" -extends "base.haml"
-set page="Instances" -set page="Instances"
-block content -block content
.section %details.section
UvU %summary << Add Instance
%form(target="/admin/instances", method="POST")
#add-instance
%label(for="domain") << Domain
%input(type="domain", name="domain", placeholder="Domain")
%label(for="actor") << Actor URL
%input(type="url", name="actor", placeholder="Actor URL")
%label(for="inbox") << Inbox URL
%input(type="url", name="inbox", placeholder="Inbox URL")
%label(for="software") << Software
%input(name="software", placeholder="software")
%input(type="submit" value="Add Instance")
#instances.section
%table
%thead
%tr
%td.instance << Instance
%td.software << Software
%td.date << Joined
%td.remove
%tbody
-for instance in instances
%tr
%td.instance
%a(href="https://{{instance.domain}}/" target="_new") -> =instance.domain
%td.software
=instance.software or "n/a"
%td.date
=instance.created.strftime("%Y-%m-%d")
%td.remove
%a(href="/admin/instances/delete/{{instance.domain}}" title="Remove Instance") << &#10006;

View file

@ -32,6 +32,10 @@ a:hover {
text-decoration: underline; text-decoration: underline;
} }
details summary {
cursor: pointer;
}
p { p {
line-height: 1em; line-height: 1em;
margin: 0px; margin: 0px;

View file

@ -0,0 +1,33 @@
form input[type="submit"] {
display: block;
margin: 0 auto;
}
#instances table {
width: 100%;
}
#instances .instance {
width: 100%;
}
#instances .software {
text-align: center;
}
#instances .date {
width: max-content;
text-align: right;
}
#instances thead td {
text-align: center !important;
}
#add-instance {
display: grid;
grid-template-columns: max-content auto;
grid-gap: var(--spacing);
margin-top: var(--spacing);
margin-bottom: var(--spacing);
}

View file

@ -18,7 +18,7 @@ from . import logger as logging
from .application import Application from .application import Application
from .compat import RelayConfig, RelayDatabase from .compat import RelayConfig, RelayDatabase
from .database import RELAY_SOFTWARE, get_database from .database import RELAY_SOFTWARE, get_database
from .misc import IS_DOCKER, Message from .misc import ACTOR_FORMATS, SOFTWARE, IS_DOCKER, Message
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from tinysql import Row from tinysql import Row
@ -33,23 +33,6 @@ CONFIG_IGNORE = (
'private-key' 'private-key'
) )
ACTOR_FORMATS = {
'mastodon': 'https://{domain}/actor',
'akkoma': 'https://{domain}/relay',
'pleroma': 'https://{domain}/relay'
}
SOFTWARE = (
'mastodon',
'akkoma',
'pleroma',
'misskey',
'friendica',
'hubzilla',
'firefish',
'gotosocial'
)
def check_alphanumeric(text: str) -> str: def check_alphanumeric(text: str) -> str:
if not text.isalnum(): if not text.isalnum():

View file

@ -36,6 +36,23 @@ NODEINFO_NS = {
'21': 'http://nodeinfo.diaspora.software/ns/schema/2.1' '21': 'http://nodeinfo.diaspora.software/ns/schema/2.1'
} }
ACTOR_FORMATS = {
'mastodon': 'https://{domain}/actor',
'akkoma': 'https://{domain}/relay',
'pleroma': 'https://{domain}/relay'
}
SOFTWARE = (
'mastodon',
'akkoma',
'pleroma',
'misskey',
'friendica',
'hubzilla',
'firefish',
'gotosocial'
)
def boolean(value: Any) -> bool: def boolean(value: Any) -> bool:
if isinstance(value, str): if isinstance(value, str):

View file

@ -7,34 +7,21 @@ from argon2.exceptions import VerifyMismatchError
from .base import View, register_route from .base import View, register_route
from ..misc import Response from ..misc import ACTOR_FORMATS, Message, Response
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from aiohttp.web import Request from aiohttp.web import Request
AUTH_ROUTES = {
'/admin',
'/admin/instances',
'/admin/domain_bans',
'/admin/software_bans',
'/admin/whitelist',
'/admin/config',
'/logout'
}
UNAUTH_ROUTES = { UNAUTH_ROUTES = {
'/', '/',
'/login' '/login'
} }
ALL_ROUTES = {*AUTH_ROUTES, *UNAUTH_ROUTES}
@web.middleware @web.middleware
async def handle_frontend_path(request: web.Request, handler: Coroutine) -> Response: async def handle_frontend_path(request: web.Request, handler: Coroutine) -> Response:
if request.path in ALL_ROUTES: if request.path in UNAUTH_ROUTES or request.path.startswith('/admin'):
request['token'] = request.cookies.get('user-token') request['token'] = request.cookies.get('user-token')
request['user'] = None request['user'] = None
@ -57,16 +44,11 @@ async def handle_frontend_path(request: web.Request, handler: Coroutine) -> Resp
class HomeView(View): class HomeView(View):
async def get(self, request: Request) -> Response: async def get(self, request: Request) -> Response:
with self.database.session() as conn: with self.database.session() as conn:
instances = tuple(conn.execute('SELECT * FROM inboxes').all()) context = {
'instances': tuple(conn.execute('SELECT * FROM inboxes').all())
}
# text = HOME_TEMPLATE.format( data = self.template.render('page/home.haml', self, **context)
# host = self.config.domain,
# note = config['note'],
# count = len(inboxes),
# targets = '<br>'.join(inbox['domain'] for inbox in inboxes)
# )
data = self.template.render('page/home.haml', self, instances = instances)
return Response.new(data, ctype='html') return Response.new(data, ctype='html')
@ -137,11 +119,64 @@ class Admin(View):
@register_route('/admin/instances') @register_route('/admin/instances')
class AdminInstances(View): class AdminInstances(View):
async def get(self, request: Request) -> Response: async def get(self,
data = self.template.render('page/admin-instances.haml', self) request: Request,
error: str | None = None,
message: str | None = None) -> Response:
with self.database.session() as conn:
context = {
'instances': tuple(conn.execute('SELECT * FROM inboxes').all())
}
if error:
context['error'] = error
if message:
context['message'] = message
data = self.template.render('page/admin-instances.haml', self, **context)
return Response.new(data, ctype = 'html') return Response.new(data, ctype = 'html')
async def post(self, request: Request) -> Response:
data = {key: value for key, value in (await request.post()).items()}
if not data['actor'] and not data['domain']:
return await self.get(request, error = 'Missing actor and/or domain')
if not data['domain']:
data['domain'] = urlparse(data['actor']).netloc
if not data['software']:
nodeinfo = await self.client.fetch_nodeinfo(data['domain'])
data['software'] = nodeinfo.sw_name
if not data['actor'] and data['software'] in ACTOR_FORMATS:
data['actor'] = ACTOR_FORMATS[data['software']].format(domain = data['domain'])
if not data['inbox'] and data['actor']:
actor = await self.client.get(data['actor'], sign_headers = True, loads = Message.parse)
data['inbox'] = actor.shared_inbox
with self.database.session(True) as conn:
conn.put_inbox(**data)
return await self.get(request, message = "Added new inbox")
@register_route('/admin/instances/delete/{domain}')
class AdminInstancesDelete(View):
async def get(self, request: Request, domain: str) -> Response:
with self.database.session() as conn:
if not (conn.get_inbox(domain)):
return await AdminInstances(request).get(request, message = 'Instance not found')
conn.del_inbox(domain)
return await AdminInstances(request).get(request, message = 'Removed instance')
@register_route('/admin/whitelist') @register_route('/admin/whitelist')
class AdminWhitelist(View): class AdminWhitelist(View):
async def get(self, request: Request) -> Response: async def get(self, request: Request) -> Response: