Bulletins have a section on dashboard now.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
from fastapi import APIRouter, Path, Query, Depends, HTTPException, Request, status
|
from fastapi import APIRouter, Path, Query, Depends, HTTPException, Request, status, Form
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from pydantic import BaseModel, Field, constr
|
from pydantic import BaseModel, Field, constr
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -130,6 +130,48 @@ async def bulletin_list_page(
|
|||||||
{"request": request, "bulletins": bulletins, "current_user": current_user.username}
|
{"request": request, "bulletins": bulletins, "current_user": current_user.username}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@html_router.get("/bulletins/new", response_class=HTMLResponse)
|
||||||
|
async def bulletin_new_form(
|
||||||
|
request: Request,
|
||||||
|
current_user: HttpUser = Depends(get_current_http_user) # require login, consistent with site
|
||||||
|
):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"bulletin_new.html",
|
||||||
|
{"request": request, "error": None}
|
||||||
|
)
|
||||||
|
|
||||||
|
@html_router.post("/bulletins/new")
|
||||||
|
async def bulletin_new_submit(
|
||||||
|
request: Request,
|
||||||
|
subject: str = Form(...),
|
||||||
|
body: str = Form(...),
|
||||||
|
current_user: HttpUser = Depends(get_current_http_user)
|
||||||
|
):
|
||||||
|
if not subject.strip() or not body.strip():
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"bulletin_new.html",
|
||||||
|
{"request": request, "error": "Subject and body are required."},
|
||||||
|
status_code=400
|
||||||
|
)
|
||||||
|
from packetserver.runners.http_server import get_db_connection
|
||||||
|
conn = get_db_connection()
|
||||||
|
root = conn.root()
|
||||||
|
|
||||||
|
if 'bulletins' not in root:
|
||||||
|
root['bulletins'] = PersistentList()
|
||||||
|
|
||||||
|
new_bulletin = Bulletin(
|
||||||
|
author=current_user.username,
|
||||||
|
subject=subject.strip(),
|
||||||
|
text=body.strip()
|
||||||
|
)
|
||||||
|
|
||||||
|
new_id = new_bulletin.write_new(root)
|
||||||
|
|
||||||
|
transaction.commit()
|
||||||
|
|
||||||
|
return RedirectResponse(url=f"/bulletins/{new_id}", status_code=303)
|
||||||
|
|
||||||
@html_router.get("/bulletins/{bid}", response_class=HTMLResponse)
|
@html_router.get("/bulletins/{bid}", response_class=HTMLResponse)
|
||||||
async def bulletin_detail_page(
|
async def bulletin_detail_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ router = APIRouter(tags=["dashboard"])
|
|||||||
|
|
||||||
# Import the function at module level (safe now that circular import is fixed)
|
# Import the function at module level (safe now that circular import is fixed)
|
||||||
from packetserver.http.routers.messages import get_messages as api_get_messages
|
from packetserver.http.routers.messages import get_messages as api_get_messages
|
||||||
|
from .bulletins import list_bulletins
|
||||||
|
|
||||||
|
|
||||||
@router.get("/dashboard", response_class=HTMLResponse)
|
@router.get("/dashboard", response_class=HTMLResponse)
|
||||||
@@ -26,9 +27,17 @@ async def dashboard(
|
|||||||
)
|
)
|
||||||
messages = messages_resp["messages"]
|
messages = messages_resp["messages"]
|
||||||
|
|
||||||
|
bulletins_resp = await list_bulletins(limit=10, since=None)
|
||||||
|
recent_bulletins = bulletins_resp["bulletins"]
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"dashboard.html",
|
"dashboard.html",
|
||||||
{"request": request, "current_user": current_user.username, "messages": messages}
|
{
|
||||||
|
"request": request,
|
||||||
|
"current_user": current_user.username,
|
||||||
|
"messages": messages,
|
||||||
|
"bulletins": recent_bulletins
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.get("/dashboard/profile", response_class=HTMLResponse)
|
@router.get("/dashboard/profile", response_class=HTMLResponse)
|
||||||
|
|||||||
0
packetserver/http/static/css/style.css
Normal file
0
packetserver/http/static/css/style.css
Normal file
@@ -6,6 +6,12 @@
|
|||||||
<title>{% block title %}PacketServer Dashboard{% endblock %}</title>
|
<title>{% block title %}PacketServer Dashboard{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', path='/css/bootstrap.min.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', path='/css/bootstrap.min.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', path='/css/style.css') }}"> {# optional custom #}
|
<link rel="stylesheet" href="{{ url_for('static', path='/css/style.css') }}"> {# optional custom #}
|
||||||
|
<style>
|
||||||
|
.bulletin-list { list-style: none; padding: 0; }
|
||||||
|
.bulletin-list li { margin: 1em 0; padding: 1em; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
||||||
|
.meta { color: #666; font-size: 0.9em; display: block; margin-top: 0.3em; }
|
||||||
|
.preview { margin-top: 0.5em; color: #444; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light">
|
<body class="bg-light">
|
||||||
<nav class="navbar navbar-dark bg-primary mb-4">
|
<nav class="navbar navbar-dark bg-primary mb-4">
|
||||||
|
|||||||
36
packetserver/http/templates/bulletin_new.html
Normal file
36
packetserver/http/templates/bulletin_new.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>New Bulletin - PacketServer</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; max-width: 800px; margin: 2em auto; padding: 1em; background: #f9f9f9; }
|
||||||
|
h1 { text-align: center; }
|
||||||
|
.error { color: #721c24; background: #f8d7da; padding: 1em; border-radius: 5px; margin-bottom: 1em; }
|
||||||
|
label { display: block; margin: 1em 0 0.5em; font-weight: bold; }
|
||||||
|
input[type="text"], textarea { width: 100%; padding: 0.8em; box-sizing: border-box; font-family: monospace; }
|
||||||
|
textarea { height: 300px; }
|
||||||
|
button { margin-top: 1em; padding: 0.8em 1.5em; background: #007bff; color: white; border: none; cursor: pointer; }
|
||||||
|
button:hover { background: #0056b3; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Create New Bulletin</h1>
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="error">{{ error }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
<label for="subject">Subject</label>
|
||||||
|
<input type="text" id="subject" name="subject" required>
|
||||||
|
|
||||||
|
<label for="body">Body</label>
|
||||||
|
<textarea id="body" name="body" required></textarea>
|
||||||
|
|
||||||
|
<button type="submit">Create Bulletin</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p><a href="/bulletins">← Back to Bulletins</a> | <a href="/dashboard">Dashboard</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -3,6 +3,24 @@
|
|||||||
{% block title %}Dashboard - {{ current_user }}{% endblock %}
|
{% block title %}Dashboard - {{ current_user }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<h2>Recent Bulletins</h2>
|
||||||
|
|
||||||
|
{% if bulletins %}
|
||||||
|
<ul class="bulletin-list">
|
||||||
|
{% for bull in bulletins %}
|
||||||
|
<li>
|
||||||
|
<strong><a href="/bulletins/{{ bull.id }}">{{ bull.subject }}</a></strong>
|
||||||
|
<span class="meta">by {{ bull.author }} on {{ bull.created_at[:10] }}</span>
|
||||||
|
<div class="preview">{{ bull.body[:150] }}{% if bull.body|length > 150 %}...{% endif %}</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<p><a href="/bulletins">View all bulletins →</a></p>
|
||||||
|
{% else %}
|
||||||
|
<p>No bulletins yet.</p>
|
||||||
|
<p><a href="/bulletins/new">Create the first one!</a></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<h2 class="mb-4">Messages</h2>
|
<h2 class="mb-4">Messages</h2>
|
||||||
|
|
||||||
<!-- Compose Modal -->
|
<!-- Compose Modal -->
|
||||||
|
|||||||
Reference in New Issue
Block a user