mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-11-23 15:08:00 +00:00
Compare commits
No commits in common. "a0ee22406bdfe9a604ac74d0f6732e03f30437e2" and "2a866eaaaa2413f6f93e2a6851d5f4e65f189b50" have entirely different histories.
a0ee22406b
...
2a866eaaaa
5
MANIFEST.in
Normal file
5
MANIFEST.in
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
include frontend/base.haml
|
||||||
|
include frontend/style.css
|
||||||
|
include data/statements.sql
|
||||||
|
include data/swagger.yaml
|
||||||
|
include frontend/page/home.haml
|
56
relay.spec
Normal file
56
relay.spec
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
import importlib
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
aiohttp_swagger_path = Path(importlib.import_module('aiohttp_swagger').__file__).parent
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['relay/__main__.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=[
|
||||||
|
('relay/data', 'relay/data'),
|
||||||
|
('relay/frontend', 'relay/frontend'),
|
||||||
|
(aiohttp_swagger_path, 'aiohttp_swagger')
|
||||||
|
],
|
||||||
|
hiddenimports=[
|
||||||
|
'pg8000',
|
||||||
|
'sqlite3'
|
||||||
|
],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher,
|
||||||
|
noarchive=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='activityrelay',
|
||||||
|
icon=None,
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
|
@ -1,8 +1,5 @@
|
||||||
import multiprocessing
|
|
||||||
|
|
||||||
from relay.manage import main
|
from relay.manage import main
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
multiprocessing.freeze_support()
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -26,10 +26,9 @@ from .views.api import handle_api_path
|
||||||
from .views.frontend import handle_frontend_path
|
from .views.frontend import handle_frontend_path
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from collections.abc import Coroutine
|
|
||||||
from tinysql import Database, Row
|
from tinysql import Database, Row
|
||||||
from .cache import Cache
|
from .cache import Cache
|
||||||
from .misc import Message, Response
|
from .misc import Message
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unsubscriptable-object
|
# pylint: disable=unsubscriptable-object
|
||||||
|
@ -37,12 +36,11 @@ if typing.TYPE_CHECKING:
|
||||||
class Application(web.Application):
|
class Application(web.Application):
|
||||||
DEFAULT: Application = None
|
DEFAULT: Application = None
|
||||||
|
|
||||||
def __init__(self, cfgpath: str | None, dev: bool = False):
|
def __init__(self, cfgpath: str | None):
|
||||||
web.Application.__init__(self,
|
web.Application.__init__(self,
|
||||||
middlewares = [
|
middlewares = [
|
||||||
handle_api_path,
|
handle_api_path,
|
||||||
handle_frontend_path,
|
handle_frontend_path
|
||||||
handle_response_headers
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,7 +50,6 @@ class Application(web.Application):
|
||||||
self['signer'] = None
|
self['signer'] = None
|
||||||
self['start_time'] = None
|
self['start_time'] = None
|
||||||
self['cleanup_thread'] = None
|
self['cleanup_thread'] = None
|
||||||
self['dev'] = dev
|
|
||||||
|
|
||||||
self['config'] = Config(cfgpath, load = True)
|
self['config'] = Config(cfgpath, load = True)
|
||||||
self['database'] = get_database(self.config)
|
self['database'] = get_database(self.config)
|
||||||
|
@ -258,18 +255,25 @@ class PushWorker(multiprocessing.Process):
|
||||||
await client.close()
|
await client.close()
|
||||||
|
|
||||||
|
|
||||||
@web.middleware
|
|
||||||
async def handle_response_headers(request: web.Request, handler: Coroutine) -> Response:
|
|
||||||
resp = await handler(request)
|
|
||||||
resp.headers['Server'] = 'ActivityRelay'
|
|
||||||
|
|
||||||
# if not request.app['dev'] and request.path.endswith(('.css', '.js')):
|
async def handle_access_log(request: web.Request, response: web.Response) -> None:
|
||||||
# resp.headers['Cache-Control'] = 'public,max-age=2628000,immutable'
|
address = request.headers.get(
|
||||||
|
'X-Forwarded-For',
|
||||||
|
request.headers.get(
|
||||||
|
'X-Real-Ip',
|
||||||
|
request.remote
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# else:
|
logging.info(
|
||||||
# resp.headers['Cache-Control'] = 'no-store'
|
'%s "%s %s" %i %i "%s"',
|
||||||
|
address,
|
||||||
return resp
|
request.method,
|
||||||
|
request.path,
|
||||||
|
response.status,
|
||||||
|
response.content_length or 0,
|
||||||
|
request.headers.get('User-Agent', 'n/a')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def handle_cleanup(app: Application) -> None:
|
async def handle_cleanup(app: Application) -> None:
|
||||||
|
|
|
@ -52,11 +52,7 @@ if IS_DOCKER:
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
def __init__(self, path: str, load: bool = False):
|
def __init__(self, path: str, load: bool = False):
|
||||||
if path:
|
self.path = Config.get_config_dir()
|
||||||
self.path = Path(path).expanduser().resolve()
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.path = Config.get_config_dir()
|
|
||||||
|
|
||||||
self.listen = None
|
self.listen = None
|
||||||
self.port = None
|
self.port = None
|
||||||
|
|
49
relay/dev.py
49
relay/dev.py
|
@ -1,14 +1,10 @@
|
||||||
import click
|
import click
|
||||||
import platform
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
|
||||||
|
|
||||||
from . import __version__
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
|
@ -50,33 +46,25 @@ def cli_lint(path):
|
||||||
subprocess.run([sys.executable, '-m', 'pylint', path], check = False)
|
subprocess.run([sys.executable, '-m', 'pylint', path], check = False)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command('manifest-gen')
|
||||||
|
def cli_manifest_install():
|
||||||
|
paths = []
|
||||||
|
|
||||||
|
for path in SCRIPT.rglob('*'):
|
||||||
|
if path.suffix.lower() in IGNORE_EXT or not path.is_file():
|
||||||
|
continue
|
||||||
|
|
||||||
|
paths.append(path)
|
||||||
|
|
||||||
|
with REPO.joinpath('MANIFEST.in').open('w', encoding = 'utf-8') as fd:
|
||||||
|
for path in paths:
|
||||||
|
fd.write(f'include {str(path.relative_to(SCRIPT))}\n')
|
||||||
|
|
||||||
|
|
||||||
@cli.command('build')
|
@cli.command('build')
|
||||||
def cli_build():
|
def cli_build():
|
||||||
with TemporaryDirectory() as tmp:
|
cmd = [sys.executable, '-m', 'PyInstaller', 'relay.spec']
|
||||||
arch = 'amd64' if sys.maxsize >= 2**32 else 'i386'
|
subprocess.run(cmd, check = False)
|
||||||
cmd = [
|
|
||||||
sys.executable, '-m', 'PyInstaller',
|
|
||||||
'--collect-data', 'relay',
|
|
||||||
'--collect-data', 'aiohttp_swagger',
|
|
||||||
'--hidden-import', 'pg8000',
|
|
||||||
'--hidden-import', 'sqlite3',
|
|
||||||
'--name', f'activityrelay-{__version__}-{platform.system().lower()}-{arch}',
|
|
||||||
'--workpath', tmp,
|
|
||||||
'--onefile', 'relay/__main__.py',
|
|
||||||
]
|
|
||||||
|
|
||||||
if platform.system() == 'Windows':
|
|
||||||
cmd.append('--console')
|
|
||||||
|
|
||||||
# putting the spec path on a different drive than the source dir breaks
|
|
||||||
if str(SCRIPT)[0] == tmp[0]:
|
|
||||||
cmd.extend(['--specpath', tmp])
|
|
||||||
|
|
||||||
else:
|
|
||||||
cmd.append('--strip')
|
|
||||||
cmd.extend(['--specpath', tmp])
|
|
||||||
|
|
||||||
subprocess.run(cmd, check = False)
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command('run')
|
@cli.command('run')
|
||||||
|
@ -106,7 +94,7 @@ def cli_run():
|
||||||
|
|
||||||
class WatchHandler(PatternMatchingEventHandler):
|
class WatchHandler(PatternMatchingEventHandler):
|
||||||
patterns = ['*.py']
|
patterns = ['*.py']
|
||||||
cmd = [sys.executable, '-m', 'relay', 'run', '-d']
|
cmd = [sys.executable, '-m', 'relay', 'run']
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -157,6 +145,7 @@ class WatchHandler(PatternMatchingEventHandler):
|
||||||
if event.event_type not in ['modified', 'created', 'deleted']:
|
if event.event_type not in ['modified', 'created', 'deleted']:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
print(event.src_path)
|
||||||
self.run_proc(restart = True)
|
self.run_proc(restart = True)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
%title << {{config.name}}: {{page}}
|
%title << {{config.name}}: {{page}}
|
||||||
%meta(charset="UTF-8")
|
%meta(charset="UTF-8")
|
||||||
%meta(name="viewport" content="width=device-width, initial-scale=1")
|
%meta(name="viewport" content="width=device-width, initial-scale=1")
|
||||||
%link(rel="stylesheet" type="text/css" href="/style.css")
|
%link(rel="stylesheet" type="text/css" href="/style.css?page={{page}}")
|
||||||
-block head
|
-block head
|
||||||
|
|
||||||
%body
|
%body
|
||||||
|
@ -27,7 +27,6 @@
|
||||||
{{menu_item("Whitelist", "/admin/whitelist")}}
|
{{menu_item("Whitelist", "/admin/whitelist")}}
|
||||||
{{menu_item("Domain Bans", "/admin/domain_bans")}}
|
{{menu_item("Domain Bans", "/admin/domain_bans")}}
|
||||||
{{menu_item("Software Bans", "/admin/software_bans")}}
|
{{menu_item("Software Bans", "/admin/software_bans")}}
|
||||||
{{menu_item("Users", "/admin/users")}}
|
|
||||||
{{menu_item("Config", "/admin/config")}}
|
{{menu_item("Config", "/admin/config")}}
|
||||||
{{menu_item("Logout", "/logout")}}
|
{{menu_item("Logout", "/logout")}}
|
||||||
|
|
||||||
|
@ -36,13 +35,8 @@
|
||||||
|
|
||||||
#container
|
#container
|
||||||
#header.section
|
#header.section
|
||||||
%span#menu-open << ⁞
|
%span#menu-open.button << ⁞
|
||||||
%span.title-container
|
%a(href="https://{{domain}}/") -> =config.name
|
||||||
%a.title(href="/") -> =config.name
|
|
||||||
|
|
||||||
-if view.request.path not in ["/", "/login"]
|
|
||||||
.page -> =page
|
|
||||||
|
|
||||||
.empty
|
.empty
|
||||||
|
|
||||||
-if error
|
-if error
|
||||||
|
@ -51,7 +45,7 @@
|
||||||
-if message
|
-if message
|
||||||
.message.section -> =message
|
.message.section -> =message
|
||||||
|
|
||||||
#content(class="page-{{page.lower().replace(' ', '_')}}")
|
#content
|
||||||
-block content
|
-block content
|
||||||
|
|
||||||
#footer.section
|
#footer.section
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
-set page="Config"
|
-set page="Config"
|
||||||
-block content
|
-block content
|
||||||
%form.section(action="/admin/config" method="POST")
|
%form.section(action="/admin/config" method="POST")
|
||||||
.grid-2col
|
#config-options
|
||||||
%label(for="name") << Name
|
%label(for="name") << Name
|
||||||
%input(id = "name" name="name" placeholder="Relay Name" value="{{config.name or ''}}")
|
%input(id = "name" name="name" placeholder="Relay Name" value="{{config.name or ''}}")
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
-block content
|
-block content
|
||||||
%details.section
|
%details.section
|
||||||
%summary << Ban Domain
|
%summary << Ban Domain
|
||||||
%form(action="/admin/domain_bans" method="POST")
|
%form(action="/admin/domain_bans", method="POST")
|
||||||
#add-item
|
#add-domain
|
||||||
%label(for="domain") << Domain
|
%label(for="domain") << Domain
|
||||||
%input(type="domain" id="domain" name="domain" placeholder="Domain")
|
%input(type="domain" id="domain" name="domain" placeholder="Domain")
|
||||||
|
|
||||||
|
@ -16,12 +16,12 @@
|
||||||
|
|
||||||
%input(type="submit" value="Ban Domain")
|
%input(type="submit" value="Ban Domain")
|
||||||
|
|
||||||
#data-table.section
|
#domains.section
|
||||||
%table
|
%table
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
%td.domain << Instance
|
%td.domain << Instance
|
||||||
%td << Date
|
%td.date << Joined
|
||||||
%td.remove
|
%td.remove
|
||||||
|
|
||||||
%tbody
|
%tbody
|
||||||
|
@ -31,14 +31,14 @@
|
||||||
%details
|
%details
|
||||||
%summary -> =ban.domain
|
%summary -> =ban.domain
|
||||||
%form(action="/admin/domain_bans" method="POST")
|
%form(action="/admin/domain_bans" method="POST")
|
||||||
.grid-2col
|
.items
|
||||||
.reason << Reason
|
.reason << Reason
|
||||||
%textarea.reason(id="reason" name="reason") << {{ban.reason or ""}}
|
%textarea.reason(id="reason" name="reason") << {{ban.reason or ""}}
|
||||||
|
|
||||||
.note << Note
|
.note << Note
|
||||||
%textarea.note(id="note" name="note") << {{ban.note or ""}}
|
%textarea.note(id="note" name="note") << {{ban.note or ""}}
|
||||||
|
|
||||||
%input(type="hidden" name="domain" value="{{ban.domain}}")
|
%input(type="hidden" name="domain", value="{{ban.domain}}")
|
||||||
%input(type="submit" value="Update")
|
%input(type="submit" value="Update")
|
||||||
|
|
||||||
%td.date
|
%td.date
|
||||||
|
|
|
@ -3,23 +3,20 @@
|
||||||
-block content
|
-block content
|
||||||
%details.section
|
%details.section
|
||||||
%summary << Add Instance
|
%summary << Add Instance
|
||||||
%form(action="/admin/instances" method="POST")
|
%form(target="/admin/instances", method="POST")
|
||||||
#add-item
|
#add-instance
|
||||||
%label(for="domain") << Domain
|
%label(for="domain") << Domain
|
||||||
%input(type="domain" id="domain" name="domain" placeholder="Domain")
|
%input(type="domain", id="domain" name="domain", placeholder="Domain")
|
||||||
|
|
||||||
%label(for="actor") << Actor URL
|
%label(for="actor") << Actor URL
|
||||||
%input(type="url" id="actor" name="actor" placeholder="Actor URL")
|
%input(type="url", id="actor" name="actor", placeholder="Actor URL")
|
||||||
|
|
||||||
%label(for="inbox") << Inbox URL
|
%label(for="inbox") << Inbox URL
|
||||||
%input(type="url" id="inbox" name="inbox" placeholder="Inbox URL")
|
%input(type="url", id="inbox" name="inbox", placeholder="Inbox URL")
|
||||||
|
|
||||||
%label(for="software") << Software
|
%label(for="software") << Software
|
||||||
%input(name="software" id="software" placeholder="software")
|
%input(name="software", id="software" placeholder="software")
|
||||||
|
|
||||||
%input(type="submit" value="Add Instance")
|
%input(type="submit" value="Add Instance")
|
||||||
|
|
||||||
#data-table.section
|
#instances.section
|
||||||
%table
|
%table
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
|
|
|
@ -1,48 +1,5 @@
|
||||||
-extends "base.haml"
|
-extends "base.haml"
|
||||||
-set page="Software Bans"
|
-set page="Software Bans"
|
||||||
-block content
|
-block content
|
||||||
%details.section
|
.section
|
||||||
%summary << Ban Software
|
UvU
|
||||||
%form(action="/admin/software_bans" method="POST")
|
|
||||||
#add-item
|
|
||||||
%label(for="name") << Name
|
|
||||||
%input(id="name" name="name" placeholder="Name")
|
|
||||||
|
|
||||||
%label(for="reason") << Ban Reason
|
|
||||||
%textarea(id="reason" name="reason") << {{""}}
|
|
||||||
|
|
||||||
%label(for="note") << Admin Note
|
|
||||||
%textarea(id="note" name="note") << {{""}}
|
|
||||||
|
|
||||||
%input(type="submit" value="Ban Software")
|
|
||||||
|
|
||||||
#data-table.section
|
|
||||||
%table
|
|
||||||
%thead
|
|
||||||
%tr
|
|
||||||
%td.name << Instance
|
|
||||||
%td << Date
|
|
||||||
%td.remove
|
|
||||||
|
|
||||||
%tbody
|
|
||||||
-for ban in bans
|
|
||||||
%tr
|
|
||||||
%td.name
|
|
||||||
%details
|
|
||||||
%summary -> =ban.name
|
|
||||||
%form(action="/admin/software_bans" method="POST")
|
|
||||||
.grid-2col
|
|
||||||
.reason << Reason
|
|
||||||
%textarea.reason(id="reason" name="reason") << {{ban.reason or ""}}
|
|
||||||
|
|
||||||
.note << Note
|
|
||||||
%textarea.note(id="note" name="note") << {{ban.note or ""}}
|
|
||||||
|
|
||||||
%input(type="hidden" name="name" value="{{ban.name}}")
|
|
||||||
%input(type="submit" value="Update")
|
|
||||||
|
|
||||||
%td.date
|
|
||||||
=ban.created.strftime("%Y-%m-%d")
|
|
||||||
|
|
||||||
%td.remove
|
|
||||||
%a(href="/admin/software_bans/delete/{{ban.name}}" title="Unban software") << ✖
|
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
-extends "base.haml"
|
|
||||||
-set page="Users"
|
|
||||||
-block content
|
|
||||||
%details.section
|
|
||||||
%summary << Add User
|
|
||||||
%form(action="/admin/users", method="POST")
|
|
||||||
#add-item
|
|
||||||
%label(for="username") << Username
|
|
||||||
%input(id="username" name="username" placeholder="Username")
|
|
||||||
|
|
||||||
%label(for="password") << Password
|
|
||||||
%input(type="password" id="password" name="password" placeholder="Password")
|
|
||||||
|
|
||||||
%label(for="password2") << Password Again
|
|
||||||
%input(type="password" id="password2" name="password2" placeholder="Password Again")
|
|
||||||
|
|
||||||
%label(for="handle") << Handle
|
|
||||||
%input(type="email" name="handle" id="handle" placeholder="handle")
|
|
||||||
|
|
||||||
%input(type="submit" value="Add User")
|
|
||||||
|
|
||||||
#data-table.section
|
|
||||||
%table
|
|
||||||
%thead
|
|
||||||
%tr
|
|
||||||
%td.username << Username
|
|
||||||
%td.handle << Handle
|
|
||||||
%td.date << Joined
|
|
||||||
%td.remove
|
|
||||||
|
|
||||||
%tbody
|
|
||||||
-for user in users
|
|
||||||
%tr
|
|
||||||
%td.username
|
|
||||||
=user.username
|
|
||||||
|
|
||||||
%td.handle
|
|
||||||
=user.handle or "n/a"
|
|
||||||
|
|
||||||
%td.date
|
|
||||||
=user.created.strftime("%Y-%m-%d")
|
|
||||||
|
|
||||||
%td.remove
|
|
||||||
%a(href="/admin/users/delete/{{user.username}}" title="Remove User") << ✖
|
|
|
@ -1,31 +1,5 @@
|
||||||
-extends "base.haml"
|
-extends "base.haml"
|
||||||
-set page="Whitelist"
|
-set page="Whitelist"
|
||||||
-block content
|
-block content
|
||||||
%details.section
|
.section
|
||||||
%summary << Add Domain
|
UvU
|
||||||
%form(action="/admin/whitelist" method="POST")
|
|
||||||
#add-item
|
|
||||||
%label(for="domain") << Domain
|
|
||||||
%input(type="domain" id="domain" name="domain" placeholder="Domain")
|
|
||||||
|
|
||||||
%input(type="submit" value="Add Domain")
|
|
||||||
|
|
||||||
#data-table.section
|
|
||||||
%table
|
|
||||||
%thead
|
|
||||||
%tr
|
|
||||||
%td.domain << Domain
|
|
||||||
%td.date << Added
|
|
||||||
%td.remove
|
|
||||||
|
|
||||||
%tbody
|
|
||||||
-for item in whitelist
|
|
||||||
%tr
|
|
||||||
%td.domain
|
|
||||||
=item.domain
|
|
||||||
|
|
||||||
%td.date
|
|
||||||
=item.created.strftime("%Y-%m-%d")
|
|
||||||
|
|
||||||
%td.remove
|
|
||||||
%a(href="/admin/whitelist/delete/{{item.domain}}" title="Remove whitlisted domain") << ✖
|
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
Note: The whitelist is enabled on this instance. Ask the admin to add your instance
|
Note: The whitelist is enabled on this instance. Ask the admin to add your instance
|
||||||
before joining.
|
before joining.
|
||||||
|
|
||||||
#data-table.section
|
#instances.section
|
||||||
%table
|
%table
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
|
@ -29,8 +29,5 @@
|
||||||
%tbody
|
%tbody
|
||||||
-for instance in instances
|
-for instance in instances
|
||||||
%tr
|
%tr
|
||||||
%td.instance -> %a(href="https://{{instance.domain}}/" target="_new")
|
%td.instance -> %a(href="https://{{instance.domain}}/" target="_new") -> =instance.domain
|
||||||
=instance.domain
|
%td.date -> =instance.created.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
%td.date
|
|
||||||
=instance.created.strftime("%Y-%m-%d")
|
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
-extends "base.haml"
|
-extends "base.haml"
|
||||||
-set page="Login"
|
-set page="Login"
|
||||||
-block content
|
-block content
|
||||||
%form.section(action="/login" method="POST")
|
%form.section(action="/login" method="post")
|
||||||
.grid-2col
|
%label(for="username") << Username
|
||||||
%label(for="username") << Username
|
%input(id="username" name="username" placeholder="Username" value="{{username or ''}}")
|
||||||
%input(id="username" name="username" placeholder="Username" value="{{username or ''}}")
|
%label(for="password") << Password
|
||||||
|
%input(id="password" name="password" placeholder="Password" type="password")
|
||||||
%label(for="password") << Password
|
|
||||||
%input(id="password" name="password" placeholder="Password" type="password")
|
|
||||||
|
|
||||||
%input(type="submit" value="Login")
|
%input(type="submit" value="Login")
|
||||||
|
|
|
@ -15,6 +15,13 @@
|
||||||
--spacing: 10px;
|
--spacing: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: var(--text);
|
||||||
|
background-color: #222;
|
||||||
|
margin: var(--spacing);
|
||||||
|
font-family: sans serif;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -25,26 +32,10 @@ a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
|
||||||
color: var(--text);
|
|
||||||
background-color: #222;
|
|
||||||
margin: var(--spacing);
|
|
||||||
font-family: sans serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
details *:nth-child(2) {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
details summary {
|
details summary {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
form input[type="submit"] {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
@ -62,7 +53,6 @@ table {
|
||||||
border: 1px solid var(--primary);
|
border: 1px solid var(--primary);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border-spacing: 0px;
|
border-spacing: 0px;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table tbody tr:nth-child(even) td {
|
table tbody tr:nth-child(even) td {
|
||||||
|
@ -91,18 +81,13 @@ table td {
|
||||||
|
|
||||||
table thead td {
|
table thead td {
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
color: var(--background);
|
color: var(--table-background)
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table tbody td {
|
table tbody td {
|
||||||
background-color: var(--table-background);
|
background-color: var(--table-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
|
||||||
height: calc(5em);
|
|
||||||
}
|
|
||||||
|
|
||||||
#container {
|
#container {
|
||||||
width: 1024px;
|
width: 1024px;
|
||||||
margin: 0px auto;
|
margin: 0px auto;
|
||||||
|
@ -120,17 +105,8 @@ textarea {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header .title-container {
|
#header > *:nth-child(2) {
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header .title {
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header .page {
|
|
||||||
font-size: 0.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu {
|
#menu {
|
||||||
|
@ -159,6 +135,7 @@ textarea {
|
||||||
|
|
||||||
#menu > a[active="true"]:not(:hover) {
|
#menu > a[active="true"]:not(:hover) {
|
||||||
background-color: var(--primary-hover);
|
background-color: var(--primary-hover);
|
||||||
|
color: var(--primary);
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,19 +153,11 @@ textarea {
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu-open {
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu-open:hover {
|
|
||||||
color: var(--primary-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu-open, #menu-close {
|
#menu-open, #menu-close {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu-open, #menu-close {
|
#menu-close, #menu-open {
|
||||||
min-width: 35px;
|
min-width: 35px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -202,23 +171,6 @@ textarea {
|
||||||
text-align: right
|
text-align: right
|
||||||
}
|
}
|
||||||
|
|
||||||
#add-item {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: max-content auto;
|
|
||||||
grid-gap: var(--spacing);
|
|
||||||
margin-top: var(--spacing);
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#data-table td:first-child {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#data-table .date {
|
|
||||||
width: max-content;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
|
@ -248,15 +200,6 @@ textarea {
|
||||||
border: 1px solid var(--error-border) !important;
|
border: 1px solid var(--error-border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-2col {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: max-content auto;
|
|
||||||
grid-gap: var(--spacing);
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
color: var(--message-text) !important;
|
color: var(--message-text) !important;
|
||||||
background-color: var(--message-background) !important;
|
background-color: var(--message-background) !important;
|
||||||
|
@ -279,10 +222,9 @@ textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* config */
|
{% if page %}
|
||||||
#content.page-config input[type="checkbox"] {
|
{% include "style/" + page.lower().replace(" ", "_") + ".css" %}
|
||||||
justify-self: left;
|
{% endif %}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 1026px) {
|
@media (max-width: 1026px) {
|
||||||
|
@ -312,8 +254,7 @@ textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
border-left-width: 0px;
|
border-width: 0px;
|
||||||
border-right-width: 0px;
|
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
relay/frontend/style/config.css
Normal file
20
relay/frontend/style/config.css
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#config-options {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content auto;
|
||||||
|
grid-gap: var(--spacing);
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input[type="submit"] {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input[type="checkbox"] {
|
||||||
|
justify-self: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 4em;
|
||||||
|
}
|
40
relay/frontend/style/domain_bans.css
Normal file
40
relay/frontend/style/domain_bans.css
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
form input[type="submit"] {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: calc(5em);
|
||||||
|
}
|
||||||
|
|
||||||
|
table .items {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content auto;
|
||||||
|
grid-gap: var(--spacing);
|
||||||
|
margin-top: var(--spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#domains table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#domains .domain {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#domains .date {
|
||||||
|
width: max-content;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#domains thead td {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#add-domain {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content auto;
|
||||||
|
grid-gap: var(--spacing);
|
||||||
|
margin-top: var(--spacing);
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
}
|
16
relay/frontend/style/home.css
Normal file
16
relay/frontend/style/home.css
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#instances table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#instances .instance {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#instances .date {
|
||||||
|
width: max-content;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#instances thead td {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
33
relay/frontend/style/instances.css
Normal file
33
relay/frontend/style/instances.css
Normal 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);
|
||||||
|
}
|
18
relay/frontend/style/login.css
Normal file
18
relay/frontend/style/login.css
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
label, input {
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
label, input:not([type="submit"]) {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:not([type="submit"]) {
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1026px) {
|
||||||
|
label, input:not([type="submit"]) {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
}
|
0
relay/frontend/style/software_bans.css
Normal file
0
relay/frontend/style/software_bans.css
Normal file
0
relay/frontend/style/whitelist.css
Normal file
0
relay/frontend/style/whitelist.css
Normal file
|
@ -188,9 +188,8 @@ def cli_setup(ctx: click.Context) -> None:
|
||||||
|
|
||||||
|
|
||||||
@cli.command('run')
|
@cli.command('run')
|
||||||
@click.option('--dev', '-d', is_flag=True, help='Enable developer mode')
|
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def cli_run(ctx: click.Context, dev: bool = False) -> None:
|
def cli_run(ctx: click.Context) -> None:
|
||||||
'Run the relay'
|
'Run the relay'
|
||||||
|
|
||||||
if ctx.obj.config.domain.endswith('example.com') or not ctx.obj.signer:
|
if ctx.obj.config.domain.endswith('example.com') or not ctx.obj.signer:
|
||||||
|
@ -217,7 +216,6 @@ def cli_run(ctx: click.Context, dev: bool = False) -> None:
|
||||||
click.echo(pip_command)
|
click.echo(pip_command)
|
||||||
return
|
return
|
||||||
|
|
||||||
ctx.obj['dev'] = dev
|
|
||||||
ctx.obj.run()
|
ctx.obj.run()
|
||||||
|
|
||||||
# todo: figure out why the relay doesn't quit properly without this
|
# todo: figure out why the relay doesn't quit properly without this
|
||||||
|
|
|
@ -11,10 +11,9 @@ from json.decoder import JSONDecodeError
|
||||||
from ..misc import Response
|
from ..misc import Response
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from aiohttp.web import Request
|
|
||||||
from collections.abc import Callable, Coroutine, Generator
|
from collections.abc import Callable, Coroutine, Generator
|
||||||
from bsql import Database
|
from bsql import Database
|
||||||
from typing import Any, Self
|
from typing import Self
|
||||||
from ..application import Application
|
from ..application import Application
|
||||||
from ..cache import Cache
|
from ..cache import Cache
|
||||||
from ..config import Config
|
from ..config import Config
|
||||||
|
|
|
@ -4,7 +4,6 @@ import typing
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from argon2.exceptions import VerifyMismatchError
|
from argon2.exceptions import VerifyMismatchError
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from .base import View, register_route
|
from .base import View, register_route
|
||||||
|
|
||||||
|
@ -14,11 +13,8 @@ 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
|
||||||
from collections.abc import Coroutine
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
|
||||||
|
|
||||||
UNAUTH_ROUTES = {
|
UNAUTH_ROUTES = {
|
||||||
'/',
|
'/',
|
||||||
'/login'
|
'/login'
|
||||||
|
@ -151,22 +147,22 @@ class AdminInstances(View):
|
||||||
|
|
||||||
|
|
||||||
async def post(self, request: Request) -> Response:
|
async def post(self, request: Request) -> Response:
|
||||||
data = await request.post()
|
data = {key: value for key, value in (await request.post()).items()}
|
||||||
|
|
||||||
if not data.get('actor') and not data.get('domain'):
|
if not data['actor'] and not data['domain']:
|
||||||
return await self.get(request, error = 'Missing actor and/or domain')
|
return await self.get(request, error = 'Missing actor and/or domain')
|
||||||
|
|
||||||
if not data.get('domain'):
|
if not data['domain']:
|
||||||
data['domain'] = urlparse(data['actor']).netloc
|
data['domain'] = urlparse(data['actor']).netloc
|
||||||
|
|
||||||
if not data.get('software'):
|
if not data['software']:
|
||||||
nodeinfo = await self.client.fetch_nodeinfo(data['domain'])
|
nodeinfo = await self.client.fetch_nodeinfo(data['domain'])
|
||||||
data['software'] = nodeinfo.sw_name
|
data['software'] = nodeinfo.sw_name
|
||||||
|
|
||||||
if not data.get('actor') and data['software'] in ACTOR_FORMATS:
|
if not data['actor'] and data['software'] in ACTOR_FORMATS:
|
||||||
data['actor'] = ACTOR_FORMATS[data['software']].format(domain = data['domain'])
|
data['actor'] = ACTOR_FORMATS[data['software']].format(domain = data['domain'])
|
||||||
|
|
||||||
if not data.get('inbox') and data['actor']:
|
if not data['inbox'] and data['actor']:
|
||||||
actor = await self.client.get(data['actor'], sign_headers = True, loads = Message.parse)
|
actor = await self.client.get(data['actor'], sign_headers = True, loads = Message.parse)
|
||||||
data['inbox'] = actor.shared_inbox
|
data['inbox'] = actor.shared_inbox
|
||||||
|
|
||||||
|
@ -180,7 +176,7 @@ class AdminInstances(View):
|
||||||
class AdminInstancesDelete(View):
|
class AdminInstancesDelete(View):
|
||||||
async def get(self, request: Request, domain: str) -> Response:
|
async def get(self, request: Request, domain: str) -> Response:
|
||||||
with self.database.session() as conn:
|
with self.database.session() as conn:
|
||||||
if not conn.get_inbox(domain):
|
if not (conn.get_inbox(domain)):
|
||||||
return await AdminInstances(request).get(request, message = 'Instance not found')
|
return await AdminInstances(request).get(request, message = 'Instance not found')
|
||||||
|
|
||||||
conn.del_inbox(domain)
|
conn.del_inbox(domain)
|
||||||
|
@ -197,7 +193,7 @@ class AdminWhitelist(View):
|
||||||
|
|
||||||
with self.database.session() as conn:
|
with self.database.session() as conn:
|
||||||
context = {
|
context = {
|
||||||
'whitelist': tuple(conn.execute('SELECT * FROM whitelist').all())
|
'domains': tuple(conn.execute('SELECT * FROM whitelist').all())
|
||||||
}
|
}
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
|
@ -210,34 +206,6 @@ class AdminWhitelist(View):
|
||||||
return Response.new(data, ctype = 'html')
|
return Response.new(data, ctype = 'html')
|
||||||
|
|
||||||
|
|
||||||
async def post(self, request: Request) -> Response:
|
|
||||||
data = await request.post()
|
|
||||||
|
|
||||||
if not data['domain']:
|
|
||||||
return await self.get(request, error = 'Missing domain')
|
|
||||||
|
|
||||||
with self.database.session(True) as conn:
|
|
||||||
if conn.get_domain_whitelist(data['domain']):
|
|
||||||
return await self.get(request, message = "Domain already in whitelist")
|
|
||||||
|
|
||||||
conn.put_domain_whitelist(data['domain'])
|
|
||||||
|
|
||||||
return await self.get(request, message = "Added/updated domain ban")
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/whitelist/delete/{domain}')
|
|
||||||
class AdminWhitlistDelete(View):
|
|
||||||
async def get(self, request: Request, domain: str) -> Response:
|
|
||||||
with self.database.session() as conn:
|
|
||||||
if not conn.get_domain_whitelist(domain):
|
|
||||||
msg = 'Whitelisted domain not found'
|
|
||||||
return await AdminWhitelist.run("GET", request, message = msg)
|
|
||||||
|
|
||||||
conn.del_domain_whitelist(domain)
|
|
||||||
|
|
||||||
return await AdminWhitelist.run("GET", request, message = 'Removed domain from whitelist')
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/domain_bans')
|
@register_route('/admin/domain_bans')
|
||||||
class AdminDomainBans(View):
|
class AdminDomainBans(View):
|
||||||
async def get(self,
|
async def get(self,
|
||||||
|
@ -262,12 +230,13 @@ class AdminDomainBans(View):
|
||||||
|
|
||||||
async def post(self, request: Request) -> Response:
|
async def post(self, request: Request) -> Response:
|
||||||
data = await request.post()
|
data = await request.post()
|
||||||
|
print(data)
|
||||||
|
|
||||||
if not data['domain']:
|
if not data['domain']:
|
||||||
return await self.get(request, error = 'Missing domain')
|
return await self.get(request, error = 'Missing domain')
|
||||||
|
|
||||||
with self.database.session(True) as conn:
|
with self.database.session(True) as conn:
|
||||||
if conn.get_domain_ban(data['domain']):
|
if (ban := conn.get_domain_ban(data['domain'])):
|
||||||
conn.update_domain_ban(
|
conn.update_domain_ban(
|
||||||
data['domain'],
|
data['domain'],
|
||||||
data.get('reason'),
|
data.get('reason'),
|
||||||
|
@ -288,7 +257,7 @@ class AdminDomainBans(View):
|
||||||
class AdminDomainBansDelete(View):
|
class AdminDomainBansDelete(View):
|
||||||
async def get(self, request: Request, domain: str) -> Response:
|
async def get(self, request: Request, domain: str) -> Response:
|
||||||
with self.database.session() as conn:
|
with self.database.session() as conn:
|
||||||
if not conn.get_domain_ban(domain):
|
if not (conn.get_domain_ban(domain)):
|
||||||
return await AdminDomainBans.run("GET", request, message = 'Domain ban not found')
|
return await AdminDomainBans.run("GET", request, message = 'Domain ban not found')
|
||||||
|
|
||||||
conn.del_domain_ban(domain)
|
conn.del_domain_ban(domain)
|
||||||
|
@ -298,115 +267,11 @@ class AdminDomainBansDelete(View):
|
||||||
|
|
||||||
@register_route('/admin/software_bans')
|
@register_route('/admin/software_bans')
|
||||||
class AdminSoftwareBans(View):
|
class AdminSoftwareBans(View):
|
||||||
async def get(self,
|
async def get(self, request: Request) -> Response:
|
||||||
request: Request,
|
data = self.template.render('page/admin-software_bans.haml', self)
|
||||||
error: str | None = None,
|
|
||||||
message: str | None = None) -> Response:
|
|
||||||
|
|
||||||
with self.database.session() as conn:
|
|
||||||
context = {
|
|
||||||
'bans': tuple(conn.execute('SELECT * FROM software_bans ORDER BY name ASC').all())
|
|
||||||
}
|
|
||||||
|
|
||||||
if error:
|
|
||||||
context['error'] = error
|
|
||||||
|
|
||||||
if message:
|
|
||||||
context['message'] = message
|
|
||||||
|
|
||||||
data = self.template.render('page/admin-software_bans.haml', self, **context)
|
|
||||||
return Response.new(data, ctype = 'html')
|
return Response.new(data, ctype = 'html')
|
||||||
|
|
||||||
|
|
||||||
async def post(self, request: Request) -> Response:
|
|
||||||
data = await request.post()
|
|
||||||
|
|
||||||
if not data['name']:
|
|
||||||
return await self.get(request, error = 'Missing name')
|
|
||||||
|
|
||||||
with self.database.session(True) as conn:
|
|
||||||
if conn.get_software_ban(data['name']):
|
|
||||||
conn.update_software_ban(
|
|
||||||
data['name'],
|
|
||||||
data.get('reason'),
|
|
||||||
data.get('note')
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
conn.put_software_ban(
|
|
||||||
data['name'],
|
|
||||||
data.get('reason'),
|
|
||||||
data.get('note')
|
|
||||||
)
|
|
||||||
|
|
||||||
return await self.get(request, message = "Added/updated software ban")
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/software_bans/delete/{name}')
|
|
||||||
class AdminSoftwareBansDelete(View):
|
|
||||||
async def get(self, request: Request, name: str) -> Response:
|
|
||||||
with self.database.session() as conn:
|
|
||||||
if not conn.get_software_ban(name):
|
|
||||||
return await AdminSoftwareBans.run("GET", request, message = 'Software ban not found')
|
|
||||||
|
|
||||||
conn.del_software_ban(name)
|
|
||||||
|
|
||||||
return await AdminSoftwareBans.run("GET", request, message = 'Unbanned software')
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/users')
|
|
||||||
class AdminUsers(View):
|
|
||||||
async def get(self,
|
|
||||||
request: Request,
|
|
||||||
error: str | None = None,
|
|
||||||
message: str | None = None) -> Response:
|
|
||||||
|
|
||||||
with self.database.session() as conn:
|
|
||||||
context = {
|
|
||||||
'users': tuple(conn.execute('SELECT * FROM users').all())
|
|
||||||
}
|
|
||||||
|
|
||||||
if error:
|
|
||||||
context['error'] = error
|
|
||||||
|
|
||||||
if message:
|
|
||||||
context['message'] = message
|
|
||||||
|
|
||||||
data = self.template.render('page/admin-users.haml', self, **context)
|
|
||||||
return Response.new(data, ctype = 'html')
|
|
||||||
|
|
||||||
|
|
||||||
async def post(self, request: Request) -> Response:
|
|
||||||
data = await request.post()
|
|
||||||
required_fields = {'username', 'password', 'password2'}
|
|
||||||
|
|
||||||
if not all(data.get(field) for field in required_fields):
|
|
||||||
return await self.get(request, error = 'Missing username and/or password')
|
|
||||||
|
|
||||||
if data['password'] != data['password2']:
|
|
||||||
return await self.get(request, error = 'Passwords do not match')
|
|
||||||
|
|
||||||
with self.database.session(True) as conn:
|
|
||||||
if conn.get_user(data['username']):
|
|
||||||
return await self.get(request, message = "User already exists")
|
|
||||||
|
|
||||||
conn.put_user(data['username'], data['password'], data['handle'])
|
|
||||||
|
|
||||||
return await self.get(request, message = "Added user")
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/users/delete/{name}')
|
|
||||||
class AdminUsersDelete(View):
|
|
||||||
async def get(self, request: Request, name: str) -> Response:
|
|
||||||
with self.database.session() as conn:
|
|
||||||
if not conn.get_user(name):
|
|
||||||
return await AdminUsers.run("GET", request, message = 'User not found')
|
|
||||||
|
|
||||||
conn.del_user(name)
|
|
||||||
|
|
||||||
return await AdminUsers.run("GET", request, message = 'User deleted')
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/config')
|
@register_route('/admin/config')
|
||||||
class AdminConfig(View):
|
class AdminConfig(View):
|
||||||
async def get(self, request: Request, message: str | None = None) -> Response:
|
async def get(self, request: Request, message: str | None = None) -> Response:
|
||||||
|
@ -443,5 +308,5 @@ class AdminConfig(View):
|
||||||
@register_route('/style.css')
|
@register_route('/style.css')
|
||||||
class StyleCss(View):
|
class StyleCss(View):
|
||||||
async def get(self, request: Request) -> Response:
|
async def get(self, request: Request) -> Response:
|
||||||
data = self.template.render('style.css', self)
|
data = self.template.render('style.css', self, page = request.query.getone('page', ""))
|
||||||
return Response.new(data, ctype = 'css')
|
return Response.new(data, ctype = 'css')
|
||||||
|
|
Loading…
Reference in a new issue