mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-12-22 03:01:08 +00:00
use jinja for web pages
This commit is contained in:
parent
26c5c05320
commit
a08d1c9612
|
@ -1,2 +1,5 @@
|
|||
include data/statements.sql
|
||||
include data/swagger.yaml
|
||||
include frontend/page/home.haml
|
||||
include frontend/base.haml
|
||||
include frontend/style.css
|
||||
|
|
|
@ -13,6 +13,7 @@ a = Analysis(
|
|||
binaries=[],
|
||||
datas=[
|
||||
('relay/data', 'relay/data'),
|
||||
('relay/frontend', 'relay/frontend'),
|
||||
(aiohttp_swagger_path, 'aiohttp_swagger')
|
||||
],
|
||||
hiddenimports=[
|
||||
|
|
|
@ -23,6 +23,7 @@ from .config import Config
|
|||
from .database import get_database
|
||||
from .http_client import HttpClient
|
||||
from .misc import check_open_port, get_resource
|
||||
from .template import Template
|
||||
from .views import VIEWS
|
||||
from .views.api import handle_api_path
|
||||
|
||||
|
@ -56,12 +57,13 @@ class Application(web.Application):
|
|||
self['client'] = HttpClient()
|
||||
self['cache'] = get_cache(self)
|
||||
self['cache'].setup()
|
||||
self['template'] = Template(self)
|
||||
self['push_queue'] = multiprocessing.Queue()
|
||||
self['workers'] = []
|
||||
|
||||
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)
|
||||
|
||||
for path, view in VIEWS:
|
||||
|
|
|
@ -163,7 +163,30 @@ class Config:
|
|||
def save(self) -> None:
|
||||
self.path.parent.mkdir(exist_ok = True, parents = True)
|
||||
|
||||
config = {
|
||||
with self.path.open('w', encoding = 'utf-8') as fd:
|
||||
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,
|
||||
'port': self.port,
|
||||
'domain': self.domain,
|
||||
|
@ -187,24 +210,3 @@ class Config:
|
|||
'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,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import typing
|
||||
|
||||
from .. import logger as logging
|
||||
|
@ -10,12 +11,52 @@ if typing.TYPE_CHECKING:
|
|||
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]] = {
|
||||
'schema-version': ('int', 20240206),
|
||||
'log-level': ('loglevel', logging.LogLevel.INFO),
|
||||
'name': ('str', 'ActivityRelay'),
|
||||
'note': ('str', 'Make a note about your instance here.'),
|
||||
'private-key': ('str', None),
|
||||
'theme': ('str', 'default'),
|
||||
'whitelist-enabled': ('bool', False)
|
||||
}
|
||||
|
||||
|
@ -24,6 +65,7 @@ CONFIG_CONVERT: dict[str, tuple[Callable, Callable]] = {
|
|||
'str': (str, str),
|
||||
'int': (str, int),
|
||||
'bool': (str, boolean),
|
||||
'json': (json.dumps, json.loads),
|
||||
'loglevel': (lambda x: x.name, logging.LogLevel.parse)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,14 @@ from datetime import datetime, timezone
|
|||
from urllib.parse import urlparse
|
||||
from uuid import uuid4
|
||||
|
||||
from .config import CONFIG_DEFAULTS, get_default_type, get_default_value, serialize, deserialize
|
||||
from .config import (
|
||||
CONFIG_DEFAULTS,
|
||||
THEMES,
|
||||
get_default_type,
|
||||
get_default_value,
|
||||
serialize,
|
||||
deserialize
|
||||
)
|
||||
|
||||
from .. import logger as logging
|
||||
from ..misc import get_app
|
||||
|
@ -95,6 +102,13 @@ class Connection(SqlConnection):
|
|||
value = logging.LogLevel.parse(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 = {
|
||||
'key': key,
|
||||
'value': serialize(key, value) if value is not None else None,
|
||||
|
|
16
relay/frontend/base.haml
Normal file
16
relay/frontend/base.haml
Normal file
|
@ -0,0 +1,16 @@
|
|||
!!!
|
||||
%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
|
34
relay/frontend/page/home.haml
Normal file
34
relay/frontend/page/home.haml
Normal file
|
@ -0,0 +1,34 @@
|
|||
-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")
|
151
relay/frontend/style.css
Normal file
151
relay/frontend/style.css
Normal file
|
@ -0,0 +1,151 @@
|
|||
: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;
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ if typing.TYPE_CHECKING:
|
|||
IS_DOCKER = bool(os.environ.get('DOCKER_RUNNING'))
|
||||
MIMETYPES = {
|
||||
'activity': 'application/activity+json',
|
||||
'css': 'text/css',
|
||||
'html': 'text/html',
|
||||
'json': 'application/json',
|
||||
'text': 'text/plain'
|
||||
|
|
51
relay/template.py
Normal file
51
relay/template.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from hamlish_jinja.extension import HamlishExtension
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
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)
|
|
@ -17,6 +17,7 @@ if typing.TYPE_CHECKING:
|
|||
from ..cache import Cache
|
||||
from ..config import Config
|
||||
from ..http_client import HttpClient
|
||||
from ..template import Template
|
||||
|
||||
|
||||
VIEWS = []
|
||||
|
@ -91,6 +92,11 @@ class View(AbstractView):
|
|||
return self.app.database
|
||||
|
||||
|
||||
@property
|
||||
def template(self) -> Template:
|
||||
return self.app['template']
|
||||
|
||||
|
||||
async def get_api_data(self,
|
||||
required: list[str],
|
||||
optional: list[str]) -> dict[str, str] | Response:
|
||||
|
|
|
@ -10,49 +10,27 @@ if typing.TYPE_CHECKING:
|
|||
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
|
||||
|
||||
@register_route('/')
|
||||
class HomeView(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
with self.database.session() as conn:
|
||||
config = conn.get_config_all()
|
||||
inboxes = tuple(conn.execute('SELECT * FROM inboxes').all())
|
||||
instances = tuple(conn.execute('SELECT * FROM inboxes').all())
|
||||
|
||||
text = HOME_TEMPLATE.format(
|
||||
host = self.config.domain,
|
||||
note = config['note'],
|
||||
count = len(inboxes),
|
||||
targets = '<br>'.join(inbox['domain'] for inbox in inboxes)
|
||||
)
|
||||
# text = HOME_TEMPLATE.format(
|
||||
# host = self.config.domain,
|
||||
# note = config['note'],
|
||||
# count = len(inboxes),
|
||||
# targets = '<br>'.join(inbox['domain'] for inbox in inboxes)
|
||||
# )
|
||||
|
||||
return Response.new(text, ctype='html')
|
||||
data = self.template.render('page/home.haml', instances = instances)
|
||||
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,10 +2,11 @@ aiohttp>=3.9.1
|
|||
aiohttp-swagger[performance]==1.0.16
|
||||
aputils@https://git.barkshark.xyz/barkshark/aputils/archive/0.1.6a.tar.gz
|
||||
argon2-cffi==23.1.0
|
||||
barkshark-sql@https://git.barkshark.xyz/barkshark/bsql/archive/0.1.1.tar.gz
|
||||
click>=8.1.2
|
||||
hamlish-jinja@https://git.barkshark.xyz/barkshark/hamlish-jinja/archive/0.3.5.tar.gz
|
||||
hiredis==2.3.2
|
||||
pyyaml>=6.0
|
||||
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'
|
||||
|
|
Loading…
Reference in a new issue