feat(l4d2-web): add admin landing and system pages
This commit is contained in:
parent
feab09db07
commit
4b326736fe
5 changed files with 123 additions and 1 deletions
|
|
@ -6,7 +6,7 @@ from sqlalchemy import select
|
|||
from l4d2web.auth import current_user, require_admin, require_login
|
||||
from l4d2web.db import session_scope
|
||||
from l4d2web.models import Blueprint as BlueprintModel
|
||||
from l4d2web.models import BlueprintOverlay, Job, Overlay, Server
|
||||
from l4d2web.models import BlueprintOverlay, Job, Overlay, Server, User
|
||||
|
||||
|
||||
bp = Blueprint("pages", __name__)
|
||||
|
|
@ -18,6 +18,33 @@ def dashboard() -> str:
|
|||
return render_template("dashboard.html")
|
||||
|
||||
|
||||
@bp.get("/admin")
|
||||
@require_admin
|
||||
def admin_home() -> str:
|
||||
return render_template("admin.html")
|
||||
|
||||
|
||||
@bp.get("/admin/users")
|
||||
@require_admin
|
||||
def admin_users() -> str:
|
||||
with session_scope() as db:
|
||||
users = db.scalars(select(User).order_by(User.username)).all()
|
||||
return render_template("admin_users.html", users=users)
|
||||
|
||||
|
||||
@bp.get("/admin/jobs")
|
||||
@require_admin
|
||||
def admin_jobs() -> str:
|
||||
with session_scope() as db:
|
||||
rows = db.execute(
|
||||
select(Job, User, Server)
|
||||
.join(User, User.id == Job.user_id)
|
||||
.outerjoin(Server, Server.id == Job.server_id)
|
||||
.order_by(Job.created_at.desc())
|
||||
).all()
|
||||
return render_template("admin_jobs.html", rows=rows)
|
||||
|
||||
|
||||
@bp.get("/servers")
|
||||
@require_login
|
||||
def servers_page() -> str:
|
||||
|
|
|
|||
13
l4d2web/templates/admin.html
Normal file
13
l4d2web/templates/admin.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin | left4me{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="panel">
|
||||
<h1>Admin</h1>
|
||||
<ul class="link-list">
|
||||
<li><a href="/admin/users">Users</a></li>
|
||||
<li><a href="/admin/jobs">Jobs</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
{% endblock %}
|
||||
27
l4d2web/templates/admin_jobs.html
Normal file
27
l4d2web/templates/admin_jobs.html
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin Jobs | left4me{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="panel">
|
||||
<h1>Jobs</h1>
|
||||
<table class="table">
|
||||
<thead><tr><th>ID</th><th>Operation</th><th>State</th><th>User</th><th>Server</th><th>Created</th><th>Finished</th></tr></thead>
|
||||
<tbody>
|
||||
{% for job, user, server in rows %}
|
||||
<tr>
|
||||
<td>{{ job.id }}</td>
|
||||
<td>{{ job.operation }}</td>
|
||||
<td>{{ job.state }}</td>
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{% if server %}<a href="/servers/{{ server.id }}">{{ server.name }}</a>{% else %}-{% endif %}</td>
|
||||
<td>{{ job.created_at }}</td>
|
||||
<td>{{ job.finished_at or "-" }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="muted">No jobs found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% endblock %}
|
||||
19
l4d2web/templates/admin_users.html
Normal file
19
l4d2web/templates/admin_users.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin Users | left4me{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="panel">
|
||||
<h1>Users</h1>
|
||||
<table class="table">
|
||||
<thead><tr><th>Username</th><th>Admin</th><th>Created</th><th>Updated</th></tr></thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr><td>{{ user.username }}</td><td>{{ "yes" if user.admin else "no" }}</td><td>{{ user.created_at }}</td><td>{{ user.updated_at }}</td></tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4" class="muted">No users found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
|
@ -137,6 +137,42 @@ def test_servers_page_links_server_names(auth_client_with_server) -> None:
|
|||
assert ">details<" not in text
|
||||
|
||||
|
||||
def test_non_admin_does_not_see_admin_nav(auth_client_with_server) -> None:
|
||||
response = auth_client_with_server.get("/dashboard")
|
||||
text = response.get_data(as_text=True)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'href="/admin"' not in text
|
||||
|
||||
|
||||
def test_admin_can_use_admin_pages(tmp_path, monkeypatch) -> None:
|
||||
db_url = f"sqlite:///{tmp_path/'admin-pages.db'}"
|
||||
monkeypatch.setenv("DATABASE_URL", db_url)
|
||||
app = create_app({"TESTING": True, "DATABASE_URL": db_url, "SECRET_KEY": "test"})
|
||||
init_db()
|
||||
|
||||
with session_scope() as session:
|
||||
admin = User(username="admin", password_digest=hash_password("secret"), admin=True)
|
||||
session.add(admin)
|
||||
session.flush()
|
||||
admin_id = admin.id
|
||||
|
||||
client = app.test_client()
|
||||
with client.session_transaction() as sess:
|
||||
sess["user_id"] = admin_id
|
||||
|
||||
assert client.get("/admin").status_code == 200
|
||||
assert client.get("/admin/users").status_code == 200
|
||||
assert client.get("/admin/jobs").status_code == 200
|
||||
assert 'href="/admin"' in client.get("/dashboard").get_data(as_text=True)
|
||||
|
||||
|
||||
def test_non_admin_cannot_open_admin_pages(auth_client_with_server) -> None:
|
||||
assert auth_client_with_server.get("/admin").status_code == 403
|
||||
assert auth_client_with_server.get("/admin/users").status_code == 403
|
||||
assert auth_client_with_server.get("/admin/jobs").status_code == 403
|
||||
|
||||
|
||||
def test_blueprint_page_private_visibility(user_client_and_other_blueprint) -> None:
|
||||
client, blueprint_id = user_client_and_other_blueprint
|
||||
response = client.get(f"/blueprints/{blueprint_id}")
|
||||
|
|
|
|||
Loading…
Reference in a new issue