mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-11-12 18:58:00 +00:00
Compare commits
No commits in common. "6f03e2ad4cffd5ca91359785801acce24b652ff7" and "26c5c05320cca3ba46cde55419394c2c6516c94b" have entirely different histories.
6f03e2ad4c
...
26c5c05320
|
@ -1,5 +1,2 @@
|
||||||
include frontend/base.haml
|
|
||||||
include frontend/style.css
|
|
||||||
include data/statements.sql
|
include data/statements.sql
|
||||||
include data/swagger.yaml
|
include data/swagger.yaml
|
||||||
include frontend/page/home.haml
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
flake8 == 7.0.0
|
flake8 == 7.0.0
|
||||||
pyinstaller == 6.3.0
|
pyinstaller == 6.3.0
|
||||||
pylint == 3.0
|
pylint == 3.0
|
||||||
watchdog == 4.0.0
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ a = Analysis(
|
||||||
binaries=[],
|
binaries=[],
|
||||||
datas=[
|
datas=[
|
||||||
('relay/data', 'relay/data'),
|
('relay/data', 'relay/data'),
|
||||||
('relay/frontend', 'relay/frontend'),
|
|
||||||
(aiohttp_swagger_path, 'aiohttp_swagger')
|
(aiohttp_swagger_path, 'aiohttp_swagger')
|
||||||
],
|
],
|
||||||
hiddenimports=[
|
hiddenimports=[
|
||||||
|
|
|
@ -2,7 +2,10 @@ from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import typing
|
import typing
|
||||||
|
@ -20,7 +23,6 @@ from .config import Config
|
||||||
from .database import get_database
|
from .database import get_database
|
||||||
from .http_client import HttpClient
|
from .http_client import HttpClient
|
||||||
from .misc import check_open_port, get_resource
|
from .misc import check_open_port, get_resource
|
||||||
from .template import Template
|
|
||||||
from .views import VIEWS
|
from .views import VIEWS
|
||||||
from .views.api import handle_api_path
|
from .views.api import handle_api_path
|
||||||
|
|
||||||
|
@ -54,13 +56,12 @@ class Application(web.Application):
|
||||||
self['client'] = HttpClient()
|
self['client'] = HttpClient()
|
||||||
self['cache'] = get_cache(self)
|
self['cache'] = get_cache(self)
|
||||||
self['cache'].setup()
|
self['cache'].setup()
|
||||||
self['template'] = Template(self)
|
|
||||||
self['push_queue'] = multiprocessing.Queue()
|
self['push_queue'] = multiprocessing.Queue()
|
||||||
self['workers'] = []
|
self['workers'] = []
|
||||||
|
|
||||||
self.cache.setup()
|
self.cache.setup()
|
||||||
|
|
||||||
# self.on_response_prepare.append(handle_access_log)
|
self.on_response_prepare.append(handle_access_log)
|
||||||
self.on_cleanup.append(handle_cleanup)
|
self.on_cleanup.append(handle_cleanup)
|
||||||
|
|
||||||
for path, view in VIEWS:
|
for path, view in VIEWS:
|
||||||
|
@ -124,15 +125,10 @@ class Application(web.Application):
|
||||||
if self["running"]:
|
if self["running"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
domain = self.config.domain
|
if not check_open_port(self.config.listen, self.config.port):
|
||||||
host = self.config.listen
|
return logging.error(f'A server is already running on port {self.config.port}')
|
||||||
port = self.config.port
|
|
||||||
|
|
||||||
if not check_open_port(host, port):
|
logging.info(f'Starting webserver at {self.config.domain} ({self.config.listen}:{self.config.port})')
|
||||||
logging.error(f'A server is already running on {host}:{port}')
|
|
||||||
return
|
|
||||||
|
|
||||||
logging.info(f'Starting webserver at {domain} ({host}:{port})')
|
|
||||||
asyncio.run(self.handle_run())
|
asyncio.run(self.handle_run())
|
||||||
|
|
||||||
|
|
||||||
|
@ -160,7 +156,7 @@ class Application(web.Application):
|
||||||
self['cleanup_thread'] = CacheCleanupThread(self)
|
self['cleanup_thread'] = CacheCleanupThread(self)
|
||||||
self['cleanup_thread'].start()
|
self['cleanup_thread'].start()
|
||||||
|
|
||||||
for _ in range(self.config.workers):
|
for i in range(self.config.workers):
|
||||||
worker = PushWorker(self['push_queue'])
|
worker = PushWorker(self['push_queue'])
|
||||||
worker.start()
|
worker.start()
|
||||||
|
|
||||||
|
@ -183,7 +179,7 @@ class Application(web.Application):
|
||||||
|
|
||||||
await site.stop()
|
await site.stop()
|
||||||
|
|
||||||
for worker in self['workers']: # pylint: disable=not-an-iterable
|
for worker in self['workers']:
|
||||||
worker.stop()
|
worker.stop()
|
||||||
|
|
||||||
self.set_signal_handler(False)
|
self.set_signal_handler(False)
|
||||||
|
|
|
@ -163,30 +163,7 @@ class Config:
|
||||||
def save(self) -> None:
|
def save(self) -> None:
|
||||||
self.path.parent.mkdir(exist_ok = True, parents = True)
|
self.path.parent.mkdir(exist_ok = True, parents = True)
|
||||||
|
|
||||||
with self.path.open('w', encoding = 'utf-8') as fd:
|
config = {
|
||||||
yaml.dump(self.to_dict(), fd, sort_keys = False)
|
|
||||||
|
|
||||||
|
|
||||||
def set(self, key: str, value: Any) -> None:
|
|
||||||
if key not in DEFAULTS:
|
|
||||||
raise KeyError(key)
|
|
||||||
|
|
||||||
if key in {'port', 'pg_port', 'workers'} and not isinstance(value, int):
|
|
||||||
if (value := int(value)) < 1:
|
|
||||||
if key == 'port':
|
|
||||||
value = 8080
|
|
||||||
|
|
||||||
elif key == 'pg_port':
|
|
||||||
value = 5432
|
|
||||||
|
|
||||||
elif key == 'workers':
|
|
||||||
value = len(os.sched_getaffinity(0))
|
|
||||||
|
|
||||||
setattr(self, key, value)
|
|
||||||
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
|
||||||
return {
|
|
||||||
'listen': self.listen,
|
'listen': self.listen,
|
||||||
'port': self.port,
|
'port': self.port,
|
||||||
'domain': self.domain,
|
'domain': self.domain,
|
||||||
|
@ -210,3 +187,24 @@ class Config:
|
||||||
'refix': self.rd_prefix
|
'refix': self.rd_prefix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with self.path.open('w', encoding = 'utf-8') as fd:
|
||||||
|
yaml.dump(config, fd, sort_keys = False)
|
||||||
|
|
||||||
|
|
||||||
|
def set(self, key: str, value: Any) -> None:
|
||||||
|
if key not in DEFAULTS:
|
||||||
|
raise KeyError(key)
|
||||||
|
|
||||||
|
if key in {'port', 'pg_port', 'workers'} and not isinstance(value, int):
|
||||||
|
if (value := int(value)) < 1:
|
||||||
|
if key == 'port':
|
||||||
|
value = 8080
|
||||||
|
|
||||||
|
elif key == 'pg_port':
|
||||||
|
value = 5432
|
||||||
|
|
||||||
|
elif key == 'workers':
|
||||||
|
value = len(os.sched_getaffinity(0))
|
||||||
|
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from .. import logger as logging
|
from .. import logger as logging
|
||||||
|
@ -11,52 +10,12 @@ if typing.TYPE_CHECKING:
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
THEMES = {
|
|
||||||
'default': {
|
|
||||||
'text': '#DDD',
|
|
||||||
'background': '#222',
|
|
||||||
'primary': '#D85',
|
|
||||||
'primary-hover': '#DA8',
|
|
||||||
'section-background': '#333',
|
|
||||||
'table-background': '#444',
|
|
||||||
'border': '#444',
|
|
||||||
'message-text': '#DDD',
|
|
||||||
'message-background': '#335',
|
|
||||||
'message-border': '#446'
|
|
||||||
},
|
|
||||||
'pink': {
|
|
||||||
'text': '#DDD',
|
|
||||||
'background': '#222',
|
|
||||||
'primary': '#D69',
|
|
||||||
'primary-hover': '#D36',
|
|
||||||
'section-background': '#333',
|
|
||||||
'table-background': '#444',
|
|
||||||
'border': '#444',
|
|
||||||
'message-text': '#DDD',
|
|
||||||
'message-background': '#335',
|
|
||||||
'message-border': '#446'
|
|
||||||
},
|
|
||||||
'blue': {
|
|
||||||
'text': '#DDD',
|
|
||||||
'background': '#222',
|
|
||||||
'primary': '#69D',
|
|
||||||
'primary-hover': '#36D',
|
|
||||||
'section-background': '#333',
|
|
||||||
'table-background': '#444',
|
|
||||||
'border': '#444',
|
|
||||||
'message-text': '#DDD',
|
|
||||||
'message-background': '#335',
|
|
||||||
'message-border': '#446'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CONFIG_DEFAULTS: dict[str, tuple[str, Any]] = {
|
CONFIG_DEFAULTS: dict[str, tuple[str, Any]] = {
|
||||||
'schema-version': ('int', 20240206),
|
'schema-version': ('int', 20240206),
|
||||||
'log-level': ('loglevel', logging.LogLevel.INFO),
|
'log-level': ('loglevel', logging.LogLevel.INFO),
|
||||||
'name': ('str', 'ActivityRelay'),
|
'name': ('str', 'ActivityRelay'),
|
||||||
'note': ('str', 'Make a note about your instance here.'),
|
'note': ('str', 'Make a note about your instance here.'),
|
||||||
'private-key': ('str', None),
|
'private-key': ('str', None),
|
||||||
'theme': ('str', 'default'),
|
|
||||||
'whitelist-enabled': ('bool', False)
|
'whitelist-enabled': ('bool', False)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +24,6 @@ CONFIG_CONVERT: dict[str, tuple[Callable, Callable]] = {
|
||||||
'str': (str, str),
|
'str': (str, str),
|
||||||
'int': (str, int),
|
'int': (str, int),
|
||||||
'bool': (str, boolean),
|
'bool': (str, boolean),
|
||||||
'json': (json.dumps, json.loads),
|
|
||||||
'loglevel': (lambda x: x.name, logging.LogLevel.parse)
|
'loglevel': (lambda x: x.name, logging.LogLevel.parse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,17 +8,10 @@ from datetime import datetime, timezone
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from .config import (
|
from .config import CONFIG_DEFAULTS, get_default_type, get_default_value, serialize, deserialize
|
||||||
CONFIG_DEFAULTS,
|
|
||||||
THEMES,
|
|
||||||
get_default_type,
|
|
||||||
get_default_value,
|
|
||||||
serialize,
|
|
||||||
deserialize
|
|
||||||
)
|
|
||||||
|
|
||||||
from .. import logger as logging
|
from .. import logger as logging
|
||||||
from ..misc import boolean, get_app
|
from ..misc import get_app
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
@ -102,13 +95,6 @@ class Connection(SqlConnection):
|
||||||
value = logging.LogLevel.parse(value)
|
value = logging.LogLevel.parse(value)
|
||||||
logging.set_level(value)
|
logging.set_level(value)
|
||||||
|
|
||||||
elif key == 'whitelist-enabled':
|
|
||||||
value = boolean(value)
|
|
||||||
|
|
||||||
elif key == 'theme':
|
|
||||||
if value not in THEMES:
|
|
||||||
raise ValueError(f'"{value}" is not a valid theme')
|
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'key': key,
|
'key': key,
|
||||||
'value': serialize(key, value) if value is not None else None,
|
'value': serialize(key, value) if value is not None else None,
|
||||||
|
|
153
relay/dev.py
153
relay/dev.py
|
@ -1,153 +0,0 @@
|
||||||
import click
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
try:
|
|
||||||
from watchdog.observers import Observer
|
|
||||||
from watchdog.events import PatternMatchingEventHandler
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
class PatternMatchingEventHandler:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SCRIPT = Path(__file__).parent
|
|
||||||
REPO = SCRIPT.parent
|
|
||||||
IGNORE_EXT = {
|
|
||||||
'.py',
|
|
||||||
'.pyc'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@click.group('cli')
|
|
||||||
def cli():
|
|
||||||
'Useful commands for development'
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command('install')
|
|
||||||
def cli_install():
|
|
||||||
cmd = [
|
|
||||||
sys.executable, '-m', 'pip', 'install',
|
|
||||||
'-r', 'requirements.txt',
|
|
||||||
'-r', 'dev-requirements.txt'
|
|
||||||
]
|
|
||||||
|
|
||||||
subprocess.run(cmd, check = False)
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command('lint')
|
|
||||||
@click.argument('path', required = False, default = 'relay')
|
|
||||||
def cli_lint(path):
|
|
||||||
subprocess.run([sys.executable, '-m', 'flake8', 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')
|
|
||||||
def cli_build():
|
|
||||||
cmd = [sys.executable, '-m', 'PyInstaller', 'relay.spec']
|
|
||||||
subprocess.run(cmd, check = False)
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command('run')
|
|
||||||
def cli_run():
|
|
||||||
print('Starting process watcher')
|
|
||||||
|
|
||||||
handler = WatchHandler()
|
|
||||||
handler.run_proc()
|
|
||||||
|
|
||||||
watcher = Observer()
|
|
||||||
watcher.schedule(handler, str(SCRIPT), recursive=True)
|
|
||||||
watcher.start()
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
handler.proc.stdin.write(sys.stdin.read().encode('UTF-8'))
|
|
||||||
handler.proc.stdin.flush()
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
|
|
||||||
handler.kill_proc()
|
|
||||||
watcher.stop()
|
|
||||||
watcher.join()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WatchHandler(PatternMatchingEventHandler):
|
|
||||||
patterns = ['*.py']
|
|
||||||
cmd = [sys.executable, '-m', 'relay', 'run']
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
PatternMatchingEventHandler.__init__(self)
|
|
||||||
|
|
||||||
self.proc = None
|
|
||||||
self.last_restart = None
|
|
||||||
|
|
||||||
|
|
||||||
def kill_proc(self):
|
|
||||||
if self.proc.poll() is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
print(f'Terminating process {self.proc.pid}')
|
|
||||||
self.proc.terminate()
|
|
||||||
sec = 0.0
|
|
||||||
|
|
||||||
while self.proc.poll() is None:
|
|
||||||
time.sleep(0.1)
|
|
||||||
sec += 0.1
|
|
||||||
|
|
||||||
if sec >= 5:
|
|
||||||
print('Failed to terminate. Killing process...')
|
|
||||||
self.proc.kill()
|
|
||||||
break
|
|
||||||
|
|
||||||
print('Process terminated')
|
|
||||||
|
|
||||||
|
|
||||||
def run_proc(self, restart=False):
|
|
||||||
timestamp = datetime.timestamp(datetime.now())
|
|
||||||
self.last_restart = timestamp if not self.last_restart else 0
|
|
||||||
|
|
||||||
if restart and self.proc.pid != '':
|
|
||||||
if timestamp - 3 < self.last_restart:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.kill_proc()
|
|
||||||
|
|
||||||
# pylint: disable=consider-using-with
|
|
||||||
self.proc = subprocess.Popen(self.cmd, stdin = subprocess.PIPE)
|
|
||||||
self.last_restart = timestamp
|
|
||||||
|
|
||||||
print(f'Started process with PID {self.proc.pid}')
|
|
||||||
|
|
||||||
|
|
||||||
def on_any_event(self, event):
|
|
||||||
if event.event_type not in ['modified', 'created', 'deleted']:
|
|
||||||
return
|
|
||||||
|
|
||||||
print(event.src_path)
|
|
||||||
self.run_proc(restart = True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
cli()
|
|
|
@ -1,16 +0,0 @@
|
||||||
!!!
|
|
||||||
%html
|
|
||||||
%head
|
|
||||||
%title << {{config.name}}: {{page}}
|
|
||||||
%meta(charset="UTF-8")
|
|
||||||
%meta(name="viewport" content="width=device-width, initial-scale=1")
|
|
||||||
%link(rel="stylesheet" type="text/css" href="/style.css")
|
|
||||||
-block head
|
|
||||||
|
|
||||||
%body
|
|
||||||
#container
|
|
||||||
#header.section
|
|
||||||
%a(href="https://{{domain}}/") -> =config.name
|
|
||||||
|
|
||||||
#content
|
|
||||||
-block content
|
|
|
@ -1,34 +0,0 @@
|
||||||
-extends "base.haml"
|
|
||||||
-set page = "Home"
|
|
||||||
-block content
|
|
||||||
.section
|
|
||||||
=config.note
|
|
||||||
|
|
||||||
.section
|
|
||||||
%p
|
|
||||||
This is an Activity Relay for fediverse instances.
|
|
||||||
|
|
||||||
%p
|
|
||||||
You may subscribe to this relay with the address:
|
|
||||||
%a(href="https://{{domain}}/actor") << https://{{domain}}/actor</a>
|
|
||||||
%p
|
|
||||||
To host your own relay, you may download the code at
|
|
||||||
%a(href="https://git.pleroma.social/pleroma/relay") << git.pleroma.social/pleroma/relay
|
|
||||||
|
|
||||||
-if config["whitelist-enabled"]
|
|
||||||
%p.section.message
|
|
||||||
Note: The whitelist is enabled on this instance. Ask the admin to add your instance
|
|
||||||
before joining.
|
|
||||||
|
|
||||||
#instances.section
|
|
||||||
%table
|
|
||||||
%thead
|
|
||||||
%tr
|
|
||||||
%td.instance << Instance
|
|
||||||
%td.date << Joined
|
|
||||||
|
|
||||||
%tbody
|
|
||||||
-for instance in instances
|
|
||||||
%tr
|
|
||||||
%td.instance -> %a(href="https://{{instance.domain}}/" target="_new") -> =instance.domain
|
|
||||||
%td.date -> =instance.created.strftime("%Y-%m-%d")
|
|
|
@ -1,151 +0,0 @@
|
||||||
:root {
|
|
||||||
--text: {{theme["text"]}};
|
|
||||||
--background: {{theme["background"]}};
|
|
||||||
--primary: {{theme["primary"]}};
|
|
||||||
--primary-hover: {{theme["primary-hover"]}};
|
|
||||||
--section-background: {{theme["section-background"]}};
|
|
||||||
--table-background: {{theme["table-background"]}};
|
|
||||||
--border: {{theme["border"]}};
|
|
||||||
--message-text: {{theme["message-text"]}};
|
|
||||||
--message-background: {{theme["message-background"]}};
|
|
||||||
--message-border: {{theme["message-border"]}};
|
|
||||||
--spacing: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
color: var(--text);
|
|
||||||
background-color: #222;
|
|
||||||
margin: var(--spacing);
|
|
||||||
font-family: sans serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--primary);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: var(--primary-hover);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 1em;
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p:not(:first-child) {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p:not(:last-child) {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-spacing: 0px;
|
|
||||||
/* border-radius: 10px; */
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
border: 1px solid var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*thead td:first-child {
|
|
||||||
border-top-left-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead td:last-child {
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr:last-child td:first-child {
|
|
||||||
border-bottom-left-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr:last-child td:last-child {
|
|
||||||
border-bottom-right-radius: 10px;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
table td {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table thead td {
|
|
||||||
background-color: var(--primary);
|
|
||||||
color: var(--table-background)
|
|
||||||
}
|
|
||||||
|
|
||||||
table tbody td {
|
|
||||||
background-color: var(--table-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
#container {
|
|
||||||
width: 1024px;
|
|
||||||
margin: 0px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header a {
|
|
||||||
font-size: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances .instance {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances .date {
|
|
||||||
width: max-content;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances thead td {
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.message {
|
|
||||||
color: var(--message-text) !important;
|
|
||||||
background-color: var(--message-background) !important;
|
|
||||||
border: 1px solid var(--message-border) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
background-color: var(--section-background);
|
|
||||||
padding: var(--spacing);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section:not(:first-child) {
|
|
||||||
margin-top: var(--spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section:not(:last-child) {
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 1026px) {
|
|
||||||
body {
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container {
|
|
||||||
width: unset;
|
|
||||||
margin: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
border-width: 0px;
|
|
||||||
border-radius: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,6 +5,8 @@ import asyncio
|
||||||
import click
|
import click
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from aputils.signer import Signer
|
from aputils.signer import Signer
|
||||||
|
|
|
@ -25,7 +25,6 @@ if typing.TYPE_CHECKING:
|
||||||
IS_DOCKER = bool(os.environ.get('DOCKER_RUNNING'))
|
IS_DOCKER = bool(os.environ.get('DOCKER_RUNNING'))
|
||||||
MIMETYPES = {
|
MIMETYPES = {
|
||||||
'activity': 'application/activity+json',
|
'activity': 'application/activity+json',
|
||||||
'css': 'text/css',
|
|
||||||
'html': 'text/html',
|
'html': 'text/html',
|
||||||
'json': 'application/json',
|
'json': 'application/json',
|
||||||
'text': 'text/plain'
|
'text': 'text/plain'
|
||||||
|
@ -88,11 +87,11 @@ def get_resource(path: str) -> Path:
|
||||||
|
|
||||||
|
|
||||||
class JsonEncoder(json.JSONEncoder):
|
class JsonEncoder(json.JSONEncoder):
|
||||||
def default(self, o: Any) -> str:
|
def default(self, obj: Any) -> str:
|
||||||
if isinstance(o, datetime):
|
if isinstance(obj, datetime):
|
||||||
return o.isoformat()
|
return obj.isoformat()
|
||||||
|
|
||||||
return json.JSONEncoder.default(self, o)
|
return JSONEncoder.default(self, obj)
|
||||||
|
|
||||||
|
|
||||||
class Message(ApMessage):
|
class Message(ApMessage):
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import typing
|
|
||||||
|
|
||||||
from hamlish_jinja.extension import HamlishExtension
|
|
||||||
from jinja2 import Environment, FileSystemLoader
|
|
||||||
|
|
||||||
from .database.config import THEMES
|
|
||||||
from .misc import get_resource
|
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
|
||||||
from typing import Any
|
|
||||||
from .application import Application
|
|
||||||
from .views.base import View
|
|
||||||
|
|
||||||
|
|
||||||
class Template(Environment):
|
|
||||||
def __init__(self, app: Application):
|
|
||||||
Environment.__init__(self,
|
|
||||||
autoescape = True,
|
|
||||||
trim_blocks = True,
|
|
||||||
lstrip_blocks = True,
|
|
||||||
extensions = [
|
|
||||||
HamlishExtension
|
|
||||||
],
|
|
||||||
loader = FileSystemLoader([
|
|
||||||
get_resource('frontend'),
|
|
||||||
app.config.path.parent.joinpath('template')
|
|
||||||
])
|
|
||||||
)
|
|
||||||
|
|
||||||
self.app = app
|
|
||||||
self.hamlish_enable_div_shortcut = True
|
|
||||||
self.hamlish_mode = 'indented'
|
|
||||||
|
|
||||||
|
|
||||||
def render(self, path: str, view: View | None = None, **context: Any) -> str:
|
|
||||||
with self.app.database.session(False) as s:
|
|
||||||
config = s.get_config_all()
|
|
||||||
|
|
||||||
new_context = {
|
|
||||||
'view': view,
|
|
||||||
'domain': self.app.config.domain,
|
|
||||||
'config': config,
|
|
||||||
'theme': THEMES.get(config['theme'], THEMES['default']),
|
|
||||||
**(context or {})
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.get_template(path).render(new_context)
|
|
|
@ -4,6 +4,7 @@ import typing
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from argon2.exceptions import VerifyMismatchError
|
from argon2.exceptions import VerifyMismatchError
|
||||||
|
from datetime import datetime, timezone
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from .base import View, register_route
|
from .base import View, register_route
|
||||||
|
|
|
@ -17,7 +17,6 @@ if typing.TYPE_CHECKING:
|
||||||
from ..cache import Cache
|
from ..cache import Cache
|
||||||
from ..config import Config
|
from ..config import Config
|
||||||
from ..http_client import HttpClient
|
from ..http_client import HttpClient
|
||||||
from ..template import Template
|
|
||||||
|
|
||||||
|
|
||||||
VIEWS = []
|
VIEWS = []
|
||||||
|
@ -92,11 +91,6 @@ class View(AbstractView):
|
||||||
return self.app.database
|
return self.app.database
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def template(self) -> Template:
|
|
||||||
return self.app['template']
|
|
||||||
|
|
||||||
|
|
||||||
async def get_api_data(self,
|
async def get_api_data(self,
|
||||||
required: list[str],
|
required: list[str],
|
||||||
optional: list[str]) -> dict[str, str] | Response:
|
optional: list[str]) -> dict[str, str] | Response:
|
||||||
|
|
|
@ -10,27 +10,49 @@ if typing.TYPE_CHECKING:
|
||||||
from aiohttp.web import Request
|
from aiohttp.web import Request
|
||||||
|
|
||||||
|
|
||||||
|
HOME_TEMPLATE = """
|
||||||
|
<html><head>
|
||||||
|
<title>ActivityPub Relay at {host}</title>
|
||||||
|
<style>
|
||||||
|
p {{ color: #FFFFFF; font-family: monospace, arial; font-size: 100%; }}
|
||||||
|
body {{ background-color: #000000; }}
|
||||||
|
a {{ color: #26F; }}
|
||||||
|
a:visited {{ color: #46C; }}
|
||||||
|
a:hover {{ color: #8AF; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>This is an Activity Relay for fediverse instances.</p>
|
||||||
|
<p>{note}</p>
|
||||||
|
<p>
|
||||||
|
You may subscribe to this relay with the address:
|
||||||
|
<a href="https://{host}/actor">https://{host}/actor</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To host your own relay, you may download the code at this address:
|
||||||
|
<a href="https://git.pleroma.social/pleroma/relay">
|
||||||
|
https://git.pleroma.social/pleroma/relay
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<br><p>List of {count} registered instances:<br>{targets}</p>
|
||||||
|
</body></html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
|
||||||
@register_route('/')
|
@register_route('/')
|
||||||
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())
|
config = conn.get_config_all()
|
||||||
|
inboxes = tuple(conn.execute('SELECT * FROM inboxes').all())
|
||||||
|
|
||||||
# text = HOME_TEMPLATE.format(
|
text = HOME_TEMPLATE.format(
|
||||||
# host = self.config.domain,
|
host = self.config.domain,
|
||||||
# note = config['note'],
|
note = config['note'],
|
||||||
# count = len(inboxes),
|
count = len(inboxes),
|
||||||
# targets = '<br>'.join(inbox['domain'] for inbox in inboxes)
|
targets = '<br>'.join(inbox['domain'] for inbox in inboxes)
|
||||||
# )
|
)
|
||||||
|
|
||||||
data = self.template.render('page/home.haml', instances = instances)
|
return Response.new(text, ctype='html')
|
||||||
return Response.new(data, ctype='html')
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/style.css')
|
|
||||||
class StyleCss(View):
|
|
||||||
async def get(self, request: Request) -> Response:
|
|
||||||
data = self.template.render('style.css')
|
|
||||||
return Response.new(data, ctype = 'css')
|
|
||||||
|
|
|
@ -2,11 +2,10 @@ aiohttp>=3.9.1
|
||||||
aiohttp-swagger[performance]==1.0.16
|
aiohttp-swagger[performance]==1.0.16
|
||||||
aputils@https://git.barkshark.xyz/barkshark/aputils/archive/0.1.6a.tar.gz
|
aputils@https://git.barkshark.xyz/barkshark/aputils/archive/0.1.6a.tar.gz
|
||||||
argon2-cffi==23.1.0
|
argon2-cffi==23.1.0
|
||||||
barkshark-sql@https://git.barkshark.xyz/barkshark/bsql/archive/0.1.1.tar.gz
|
|
||||||
click>=8.1.2
|
click>=8.1.2
|
||||||
hamlish-jinja@https://git.barkshark.xyz/barkshark/hamlish-jinja/archive/0.3.5.tar.gz
|
|
||||||
hiredis==2.3.2
|
hiredis==2.3.2
|
||||||
pyyaml>=6.0
|
pyyaml>=6.0
|
||||||
redis==5.0.1
|
redis==5.0.1
|
||||||
|
barkshark-sql@https://git.barkshark.xyz/barkshark/bsql/archive/0.1.1.tar.gz
|
||||||
|
|
||||||
importlib_resources==6.1.1;python_version<'3.9'
|
importlib_resources==6.1.1;python_version<'3.9'
|
||||||
|
|
Loading…
Reference in a new issue