diff --git a/relay/frontend/functions.haml b/relay/frontend/functions.haml deleted file mode 100644 index 8b13789..0000000 --- a/relay/frontend/functions.haml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/relay/frontend/page/admin-instances.haml b/relay/frontend/page/admin-instances.haml index 04c84f9..9b2c5a6 100644 --- a/relay/frontend/page/admin-instances.haml +++ b/relay/frontend/page/admin-instances.haml @@ -1,5 +1,41 @@ -extends "base.haml" -set page="Instances" -block content - .section - UvU + %details.section + %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") << ✖ diff --git a/relay/frontend/style.css b/relay/frontend/style.css index 61c358d..cc4818f 100644 --- a/relay/frontend/style.css +++ b/relay/frontend/style.css @@ -32,6 +32,10 @@ a:hover { text-decoration: underline; } +details summary { + cursor: pointer; +} + p { line-height: 1em; margin: 0px; diff --git a/relay/frontend/style/instances.css b/relay/frontend/style/instances.css index e69de29..8e8789b 100644 --- a/relay/frontend/style/instances.css +++ b/relay/frontend/style/instances.css @@ -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); +} diff --git a/relay/manage.py b/relay/manage.py index 01e034e..9fe0b17 100644 --- a/relay/manage.py +++ b/relay/manage.py @@ -18,7 +18,7 @@ from . import logger as logging from .application import Application from .compat import RelayConfig, RelayDatabase 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: from tinysql import Row @@ -33,23 +33,6 @@ CONFIG_IGNORE = ( '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: if not text.isalnum(): diff --git a/relay/misc.py b/relay/misc.py index 3007863..33e7a06 100644 --- a/relay/misc.py +++ b/relay/misc.py @@ -36,6 +36,23 @@ NODEINFO_NS = { '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: if isinstance(value, str): diff --git a/relay/views/frontend.py b/relay/views/frontend.py index 7b45011..5206c67 100644 --- a/relay/views/frontend.py +++ b/relay/views/frontend.py @@ -7,34 +7,21 @@ from argon2.exceptions import VerifyMismatchError from .base import View, register_route -from ..misc import Response +from ..misc import ACTOR_FORMATS, Message, Response if typing.TYPE_CHECKING: from aiohttp.web import Request -AUTH_ROUTES = { - '/admin', - '/admin/instances', - '/admin/domain_bans', - '/admin/software_bans', - '/admin/whitelist', - '/admin/config', - '/logout' -} - - UNAUTH_ROUTES = { '/', '/login' } -ALL_ROUTES = {*AUTH_ROUTES, *UNAUTH_ROUTES} - @web.middleware 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['user'] = None @@ -57,16 +44,11 @@ async def handle_frontend_path(request: web.Request, handler: Coroutine) -> Resp class HomeView(View): async def get(self, request: Request) -> Response: 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( - # host = self.config.domain, - # note = config['note'], - # count = len(inboxes), - # targets = '
'.join(inbox['domain'] for inbox in inboxes) - # ) - - data = self.template.render('page/home.haml', self, instances = instances) + data = self.template.render('page/home.haml', self, **context) return Response.new(data, ctype='html') @@ -137,11 +119,64 @@ class Admin(View): @register_route('/admin/instances') class AdminInstances(View): - async def get(self, request: Request) -> Response: - data = self.template.render('page/admin-instances.haml', self) + async def get(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') + 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') class AdminWhitelist(View): async def get(self, request: Request) -> Response: