restructure for docker

This commit is contained in:
Ville Rantanen
2023-07-24 20:02:45 +03:00
parent 79780f0769
commit 58abf04d2c
45 changed files with 152 additions and 17 deletions

View File

@@ -0,0 +1,45 @@
{# Don't use the default theme to emphasize this page is special -#}
<!doctype html>
<head>
<title>{{ title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta content="utf-8" http-equiv="encoding">
<style>
body {
font-family: sans-serif;
}
.flash.success {
background-color: lightgreen;
border-radius: 5px;
padding: 8px;
}
.flash.error {
background-color: #ff4646;
border-radius: 5px;
padding: 8px;
}
table {
border-collapse: collapse;
width: 80%;
}
th, td {
border: 1px solid;
padding: 5px;
text-align: left;
}
textarea {
width: 95%;
}
</style>
</head>
<body>
<h1>{{ title }}</h1>
<p>
<a href="{{ url_for('admin') }}">Admin panel</a>
<a href="{{ url_for('index') }}">Home page</a>
</p>
{%- for category, msg in get_flashed_messages(True) -%}
<p class="flash {{ category }}">{{ msg }}</p>
{%- endfor %}
{%- block content %}{% endblock -%}
</body>

View File

@@ -0,0 +1,129 @@
{% extends 'admin/base.html' -%}
{% block content -%}
<h2>Query</h2>
<p>&#9888; Only use queries if you know what you're doing &#9888;</p>
<form action=query/ method=post>
<input type=text name=q placeholder="SELECT * from users">
<input type=submit value=Submit>
</form>
<h2>Configuration</h2>
<form action=config/edit/ method=post>
<table>
<tr>
<td>Server name</td>
<td><input type=text name=server_name value="{{ config.server_name }}"></td>
</tr>
<tr>
<td>Server description</td>
<td><textarea name=server_description>{{ config.server_description }}</textarea></td>
</tr>
<tr>
<td>Registration enabled</td>
<td><input name=registration_enabled type=checkbox {{ 'checked' if config.registration_enabled else '' }}></td>
</tr>
<tr>
<td>Login required</td>
<td><input name=login_required type=checkbox {{ 'checked' if config.login_required else '' }}></td>
</tr>
</table>
<input type=submit value=Update>
</form>
<p>
<form action=config/new_secrets/ method=post>
<input type=submit value="Generate new secrets">
</form>
</p>
<p>
<form action=restart/ method=post>
<input type=submit value="Restart">
</form>
</p>
<h2>Forums</h2>
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Actions</th>
</tr>
{% for id, name, description, _, _, _ in forums %}
<tr>
<td>{{ id }}</td>
<td>
<form method=post action="forum/{{ id }}/edit/name/">
<input type=text name=name value="{{ name }}"</input>
<input type=submit value="Set name">
</form>
<td>
<form method=post action="forum/{{ id }}/edit/description/">
<textarea name=description>{{ description }}</textarea>
<input type=submit value="Set description">
</form>
</td>
<td><a href="#">Remove</a></td>
</tr>
{% endfor %}
</table>
<h3>Add forum</h3>
<form method=post action="forum/new/">
<table>
<tr>
<td>Name</td>
<td><input type=text name=name></td>
</tr>
<tr>
<td>Description</td>
<td><textarea name=description></textarea></td>
</tr>
</table>
<input type=submit value="Add forum">
</form>
<h2>Users</h2>
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Join date</th>
<th>Role</th>
<th>Banned</th>
</tr>
{%- for id, name, join_date, role, banned_until in users -%}
<tr>
<td>{{ id }}</td>
<td>{{ name }}</td>
<td>{{ format_time(join_date) }}</td>
<td>
<form method=post action="user/{{ id }}/edit/role/">
<select name=role>
<option value=0 {{ 'selected' if role == 0 else '' }}>user</option>
<option value=1 {{ 'selected' if role == 1 else '' }}>moderator</option>
<option value=2 {{ 'selected' if role == 2 else '' }}>admin</option>
</select>
<input type=submit value="Set role">
</form>
</td>
<td>
{%- if banned_until > 0 -%}
<form method=post action="{{ url_for('admin_unban_user', user_id = id) }}">
{{- format_time(banned_until) }}
<input type=submit value=Unban>
</form>
{%- endif -%}
<form method=post action="{{ url_for('admin_ban_user', user_id = id) }}">
<input type=number name=days placeholder=days>
<input type=time name=time>
<input type=submit value=Ban>
</form>
</td>
</tr>
{%- endfor -%}
</table>
<h3>Add user</h3>
<form method=post action=user/new/>
<table>
<tr><td>Name</td><td><input type=text name=name></td></tr>
<tr><td>Password</td><td><input type=password name=password></td></tr>
</table>
<input type=submit value="Add user">
</form>
{%- endblock %}

View File

@@ -0,0 +1,17 @@
{% extends 'admin/base.html' -%}
{% block content -%}
<form method=post>
<input type=text name=q placeholder="SELECT * from users">
<input type=submit value=Submit>
</form>
<table style="font-family:monospace;white-space:pre">
{%- for r in rows -%}
<tr>
{%- for c in r -%}
<td>{{ c }}</td>
{%- endfor -%}
</tr>
{%- endfor -%}
</table>
{%- endblock -%}

48
forum/templates/base.html Normal file
View File

@@ -0,0 +1,48 @@
<!doctype html>
<head>
<title>{{ title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="generator" content="Agreper - minimal, no-JS forum software">
<meta content="utf-8" http-equiv="encoding">
<link rel=stylesheet href="{{ url_for('static', filename='theme.css') }}">
{%- if config.server_name -%}
<link rel=stylesheet href="{{ url_for('static', filename='user.css') }}">
{%- endif -%}
</head>
<body>
<nav>
<a class=logo href="{{ url_for('index') }}">A</a>
<div style="margin:auto"></div>
{%- if user is not none -%}
<a href="{{ url_for('user_edit') }}">
{{- user.name }}
{%- if user.is_banned() %} (banned for {{ format_until(user.banned_until) }}){% endif -%}
</a>
<span> | </span>
{%- if user.is_admin() -%}
<a href="{{ url_for('admin') }}">Admin panel</a>
<span> | </span>
{%- endif -%}
<a href="{{ url_for('logout') }}">Logout</a>
{%- else -%}
{%- if config.registration_enabled -%}
<a href="{{ url_for('register') }}">Register</a>
<span> | </span>
{%- endif -%}
<a href="{{ url_for('login') }}">Login</a>
{%- endif -%}
<span> | </span>
<a href="{{ url_for('help') }}">Help</a>
</nav>
<main>
<h1>{{ title }}</h1>
{%- for category, msg in get_flashed_messages(True) -%}
{#-
FIXME ensure all flash() messages are free of XSS vectors.
In particular, check places where we flash error messages.
-#}
<p class="flash {{ category }}">{{ msg | safe }}</p>
{%- endfor -%}
{%- block content %}{% endblock -%}
</main>
</body>

View File

@@ -0,0 +1,82 @@
{% from 'moderator.html' import moderate_comment with context -%}
{%- macro author(id, name, ctime, mtime) -%}
<i><a href="{{ url_for('user_info', user_id = id) }}">{{ name }}</a> - {{ format_since(ctime) }}{% if ctime != mtime %} (last modified {{ format_since(mtime) }}){% endif %}</i>
{%- endmacro -%}
{%- macro comment_author(comment, thread_id, can_delete) -%}
<span class=small>
{{- '[hidden]' if comment.hidden else '' }}
{{ author(comment.author_id, comment.author, comment.create_time, comment.modify_time) }} |
{# Suffixing a # prevents unnecessary reloads #}
<a href="{{ url_for('thread', thread_id = thread_id) }}#"> thread</a>
{%- if comment.parent_id is not none -%}
<a href="{{ url_for('comment', comment_id = comment.parent_id) }}#"> parent</a>
{%- endif -%}
{%- if user is not none and (comment.author_id == user.id or user.is_moderator()) and not user.is_banned() -%}
<a href="{{ url_for('edit_comment', comment_id = comment.id) }}"> edit</a>
{%- if can_delete -%}
<a href="{{ url_for('confirm_delete_comment', comment_id = comment.id) }}"> delete</a>
{%- endif -%}
{%- if user.is_moderator() -%}
{{ moderate_comment(comment.id, comment.hidden) }}
{%- endif -%}
{%- endif -%}
</span>
{%- endmacro -%}
{%- macro thread_author(author_id, name, ctime, mtime) -%}
<span class=small>
{{- author(author_id, name, ctime, mtime) -}}
{%- if user is not none and (author_id == user.id or user.is_moderator()) and not user.is_banned() -%}
<a href="{{ url_for('edit_thread', thread_id = thread_id) }}"> edit</a>
<a href="{{ url_for('confirm_delete_thread', thread_id = thread_id) }}"> delete</a>
{%- endif -%}
</span>
{%- endmacro -%}
{%- macro render_comment_pre(comment, thread_id, can_delete) -%}
<div class=comment>
{{- comment_author(comment, thread_id, can_delete) -}}
<input type=checkbox class="collapse small">
<div>{{- minimd(comment.text) | safe -}}
{%- endmacro -%}
{%- macro render_comment_post(comment, thread_id) -%}
{%- for c in comment.children -%}
{{- render_comment(c, thread_id) -}}
{%- endfor -%}
</div>
</div>
{%- endmacro -%}
{%- macro render_comment(comment, thread_id) -%}
{{- render_comment_pre(comment, thread_id, comment.children | length == 0) -}}
<sup><a href="{{ url_for("comment", comment_id = comment.id) }}">reply</a></sup>
{{- render_comment_post(comment, thread_id) -}}
{%- endmacro -%}
{%- macro reply() -%}
{%- if user is none -%}
{%- if config.registration_enabled -%}
<form method="post" action="comment/">
<p><textarea name=text></textarea></p>
{#-
Using the password generator for usernames should be sufficient to ensure it is unique.
If not, it means the password generator is broken and *must* be fixed.
-#}
<input type=text name=username value="{{ rand_password() }}" hidden>
<input type=password name=password value="{{ rand_password() }}" hidden>
{% set q, a = gen_captcha() %}
<p>Captcha: {{ q }} <input type=text name=captcha></p>
<input type=text name=answer value="{{ a }}" hidden>
<p><input type=submit value="Register & post comment"> (<a href="{{ url_for('login') }}">I already have an account</a>)</p>
</form>
{%- endif -%}
{%- elif not user.is_banned() -%}
<form method="post" action="comment/">
<p><textarea name="text"></textarea></p>
<p><input type="submit" value="Post comment"></p>
</form>
{%- endif -%}
{%- endmacro -%}

View File

@@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% from 'comment.html' import render_comment, render_comment_pre, render_comment_post, reply with context %}
{% block content %}
<p><span> &laquo; </span><a href="{{ url_for('forum', forum_id = forum_id) }}">{{ forum_title }}</a><span> &laquo; </span><a href="{{ url_for('thread', thread_id = thread_id) }}">{{ title }}</a></p>
{{ render_comment_pre(reply_comment, thread_id, comments | length == 0) }}
{{ reply() }}
{% for c in comments %}
{{ render_comment(c, thread_id) }}
{% endfor %}
{{ render_comment_post(reply_comment, thread_id) }}
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends 'base.html' %}
{% block content %}
<p>Are you sure you want to delete this comment on "{{ thread_title }}"?</p>
<div class=comment>{{ minimd(text) | safe }}</div>
<p>
<form method="post" action="../delete" style=inline>
<input type="submit" value="Yes">
</form>
<form method="get" action=".." style=inline>
<input type="submit" value="No">
</form>
</p>
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block content %}
<p>Are you sure you want to delete "{{ thread_title }}"?</p>
<p>
<form method="post" action="../delete" style=inline>
<input type="submit" value="Yes">
</form>
<form method="get" action=".." style=inline>
<input type="submit" value="No">
</form>
</p>
{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% block content %}
<form method="post">
<table class=form>
<tr>
<td>Thread</td>
<td>{{ thread_title }}</td>
</tr>
<tr>
<td>Text</td>
<td><textarea name="text">{{ text }}</textarea></td>
</tr>
</table>
<p><input type="submit" value="Post"></p>
</form>
{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% block content %}
<form method="post">
<table class=form>
<tr>
<td>Title</td>
<td><input type="text" name="title" value="{{ thread_title }}"></td>
</tr>
<tr>
<td>Text</td>
<td><textarea name="text">{{ text }}</textarea></td>
</tr>
</table>
<p><input type="submit" value="Post"></p>
</form>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends 'base.html' %}
{%- from 'moderator.html' import moderate_thread with context %}
{%- macro nav() -%}
<p style=text-align:center>
{%- if prev_page is not none %}<a href="./?p={{ prev_page }}">prev</a>{% endif -%}
{%- if prev_page is not none and next_page is not none %} | {% endif -%}
{%- if next_page is not none %}<a href="./?p={{ next_page }}">next</a>{% endif -%}
</p>
{%- endmacro -%}
{% block content -%}
<p>{{ minimd(description) | safe }}</p>
<p><span> &laquo; </span><a href="{{ url_for('index') }}">Forum list</a><span> | </span><a href="{{ url_for('new_thread', forum_id = forum_id) }}">Create thread</a></p>
{{- nav() -}}
<table>
<tr>
<th>Topic</th>
<th>Author</th>
<th>Created</th>
<th>Updated</th>
<th>Comments</th>
{%- if user is not none and user.is_moderator() -%}
<th>Action</th>
{%- endif -%}
</tr>
{% for id, title, ctime, utime, author_id, author, comment_count, hidden in threads %}
<tr>
<th>{{ '[hidden] ' if hidden else '' }}<a href="{{ url_for('thread', thread_id = id) }}">{{ title }}</a></th>
<td><a href="{{ url_for('user_info', user_id = author_id) }}">{{ author }}</a></td>
<td>{{ format_since(ctime) }}</td>
<td>{{ format_since(utime) }}</td>
<td>{{ comment_count }}</td>
<td>
{%- if user is not none and user.is_moderator() %}
{{- moderate_thread(id, hidden) }}
{%- endif -%}
</td>
</tr>
{%- endfor -%}
</table>
{{- nav() -}}
{% endblock %}

19
forum/templates/help.html Normal file
View File

@@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% block content %}
<h2>Formatting</h2>
<ul>
<li>
Prepend two spaces to format code, e.g.
<div>{{ minimd(' This is code\n More code') | safe }}</div>
</li>
<li>
Lists, starting with '- ' or '0.', '1.' ... automatically have linebreaks added.
e.g.
<div>{{ minimd('- Item A\n- Item B') | safe }}</div>
Note that there must be a single space between '-' and the rest of the line.
</li>
<li>
Words starting with 'http://' or 'https://' are automatically turned into links.
</li>
</ul>
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% extends 'base.html' %}
{%- block content %}
<p>{{ minimd(description) | safe }}</p>
<table>
<tr>
<th>Forum</th>
<th>Last update</th>
</tr>
{% for id, name, description, t_id, t_title, t_mtime in forums %}
<tr>
<td>
<p><a href="{{ url_for('forum', forum_id = id) }}"><b>{{ name }}</b></a></p>
<p>{{ minimd(description) | safe }}</p>
</td>
{% if t_id %}
<td>
<p><a href="{{ url_for('thread', thread_id = t_id) }}"><b>{{ t_title }}</b></a></p>
<p>{{ format_since(t_mtime) }}</p>
</td>
{% else %}
<td>No threads</td>
{% endif %}
</tr>
{%- endfor -%}
</table>
{%- endblock %}

View File

@@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block content %}
<form method="post" class=login>
<table>
<tr><td>Username</td><td><input type="text" name="username" required></td></tr>
<tr><td>Password</td><td><input type="password" name="password" required></td></tr>
</table>
<input type="submit" value="Login">
</form>
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% macro moderate_thread(id, hidden) %}
<form method=post action="{{ url_for('set_hide_thread', thread_id = id) }}">
<input name=redirect value="{{ request.full_path }}" hidden>
<input name=hide value={{ 0 if hidden else 1 }} hidden>
<input type=submit value="{{ 'Unhide' if hidden else 'Hide' }}">
</form>
{% endmacro %}
{% macro moderate_comment(id, hidden) %}
<form method=post action="{{ url_for('set_hide_comment', comment_id = id) }}" style=display:inline>
<input name=redirect value="{{ request.full_path }}" hidden>
<input name=hide value={{ 0 if hidden else 1 }} hidden>
<input type=submit value="{{ 'Unhide' if hidden else 'Hide' }}">
</form>
{% endmacro %}

View File

@@ -0,0 +1,45 @@
{% extends 'base.html' %}
{% block content %}
{%- if user is none -%}
<form method="post">
{#-
Using the password generator for usernames should be sufficient to ensure it is unique.
If not, it means the password generator is broken and *must* be fixed.
-#}
<input type=text name=username value="{{ rand_password() }}" hidden>
<input type=password name=password value="{{ rand_password() }}" hidden>
{%- set q, a = gen_captcha() -%}
<input type=text name=answer value="{{ a }}" hidden>
<table class=form>
<tr>
<td>Title</td>
<td><input type="text" name="title" required></td>
</tr>
<tr>
<td>Text</td>
<td><textarea name="text" required></textarea></td>
</tr>
<tr>
<td>{{ q }}</td>
<td><input type=text name=captcha required></td>
</tr>
</table>
<p><input type="submit" value="Register & post"> (<a href="{{ url_for('login') }}">I already have an account</a>)</p>
</form>
{%- else -%}
<form method="post">
<table class=form>
<tr>
<td>Title</td>
<td><input type="text" name="title" required></td>
</tr>
<tr>
<td>Text</td>
<td><textarea name="text" required></textarea></td>
</tr>
</table>
<p><input type="submit" value="Post"></p>
</form>
{%- endif -%}
{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends 'base.html' %}
{%- block content %}
{%- if config.registration_enabled -%}
<form method="post" class=login>
<table>
<tr><td>Username</td><td><input type="text" name="username" minlength=3 required></td></tr>
<tr><td>Password</td><td><input type="password" name="password" minlength=8 required></td></tr>
<tr><td>{{ captcha }}</td><td><input type="text" name="captcha" required></td></tr>
</table>
<input name="answer" value="{{ answer }}" hidden>
<input type="submit" value="Register">
</form>
{%- else -%}
<p>Registrations are disabled.</p>
{%- endif %}
{%- endblock %}

View File

@@ -0,0 +1,18 @@
{%- extends 'base.html' %}
{%- from 'comment.html' import render_comment, reply, thread_author with context %}
{%- from 'moderator.html' import moderate_thread with context %}
{%- block content %}
<p><span> &laquo; </span><a href="{{ url_for('forum', forum_id = forum_id) }}">{{ forum_title }}</a></p>
{%- if user is not none and user.is_moderator() -%}
<p>{{ moderate_thread(thread_id, hidden) }}</p>
{%- endif -%}
{{- thread_author(author_id, author, create_time, modify_time) }}
<p>{{ minimd(text) | safe }}</p>
{{- reply() }}
{%- for c in comments %}
{{- render_comment(c, thread_id) }}
{%- endfor %}
{%- endblock %}

View File

@@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% block content %}
<p><a href="{{ url_for('user_info', user_id = user.id) }}">View public profile</a></p>
<form method="post">
<table>
<tr><td>Username</td><td>{{ user.name }}</td></tr>
<tr><td>ID</td><td>{{ user.id }}</td></tr>
<tr><td>About</td><td><textarea name="about">{{ about }}</textarea></td></tr>
</table>
<input type="submit" value="Update">
</form>
<br>
<form method="post" action=edit/password/>
<table>
<tr><td>Old password</td><td><input type=password name=old required></td></tr>
<tr><td>New password</td><td><input type=password name=new required></td></tr>
</table>
<input type="submit" value="Set password">
</form>
{% endblock %}

View File

@@ -0,0 +1,21 @@
{% extends 'base.html' %}
{%- block content %}
{%- if user is not none and user.is_moderator() -%}
<p>
<form method=post action="{{ url_for('moderator_ban_user', user_id = id) }}">
<input type=number name=days placeholder=days>
<input type=time name=time>
<input type=submit value=Ban>
</form>
{%- if banned_until > 0 -%}
<form method=post action="{{ url_for('moderator_unban_user', user_id = id) }}">
{{- format_time(banned_until) -}}
<input type=submit value=Unban>
</form>
{%- endif -%}
</p>
{%- endif -%}
<p><sup><i>{{ name }}</i></sup></p>
<p>{{ minimd(about) | safe }}<p>
{%- endblock %}