fix: resolve 3 critical UI bugs
CI / test (push) Failing after 4s
CI / lint (push) Failing after 3s

1. Refresh button no longer doubles header
   - Added ?fragment=1 param to filter/refresh HTMX requests
   - Board route returns board_fragment.html (columns only) for fragment requests
   - Refresh button uses window.location.reload() for full page reload

2. No more raw JSON in header
   - base.html: user info rendered server-side via template variable
   - board.py: decodes session cookie, passes user to template
   - Dashboard/board templates show Login/Deconnexion based on auth state

3. New issue form works
   - Replaced HTMX post with JS fetch (handleNewIssue)
   - Adds CSRF token to form submission
   - Reloads page on successful creation
   - Cleaner error handling
This commit is contained in:
2026-07-08 09:45:24 -04:00
parent e72c0dcbd8
commit a67f42bfe6
4 changed files with 189 additions and 279 deletions
+23 -36
View File
@@ -50,45 +50,32 @@ async def board(
milestone: str = "",
label: str = "",
assignee: str = "",
fragment: bool = False,
):
"""Kanban board for a Gitea project."""
try:
# Get issues
issues = await gitea.get_issues(owner, repo, state="all")
# Get labels
labels = await gitea.get_labels(owner, repo)
# Get milestones
labels_list = await gitea.get_labels(owner, repo)
milestones = await gitea.get_milestones(owner, repo)
# Filter by milestone
if milestone:
issues = [i for i in issues if i.get("milestone", {}).get("title") == milestone]
# Filter by label
if label:
issues = [i for i in issues if any(lbl["name"] == label for lbl in i.get("labels", []))]
# Filter by assignee
if assignee:
issues = [i for i in issues if i.get("assignee", {}).get("login") == assignee]
# Separate PRs from issues (ponytail: PRs are also in /issues endpoint)
prs = [i for i in issues if "pull_request" in i]
issues_only = [i for i in issues if "pull_request" not in i]
# Get board config
board_info = _ensure_board(owner, repo)
columns = board_info["columns"]
cards = _get_cards(board_info["id"])
# Map issues to columns
column_map: dict[str, list[dict]] = {col: [] for col in columns}
card_by_issue = {c["gitea_issue_id"]: c for c in cards if c["gitea_issue_id"]}
for issue in issues_only:
# Determine column from issue state/labels first, then from DB position
col = _issue_column(issue, columns, board_info["id"])
issue["_col"] = col
issue["_card"] = card_by_issue.get(issue["number"])
@@ -97,37 +84,37 @@ async def board(
except Exception as e:
from httpx import HTTPStatusError
if isinstance(e, HTTPStatusError) and e.response is not None and e.response.status_code == 404:
html = '<div style="padding:48px;text-align:center">'
html += '<h2 style="color:var(--text-dim)">Projet introuvable</h2>'
html += f'<p style="color:var(--text-dim)">{owner}/{repo} n\'existe pas sur Gitea.</p>'
html += '<a href="/" class="btn btn-primary" style="margin-top:16px">Retour aux projets</a>'
html += '</div>'
return HTMLResponse(html, status_code=404)
return HTMLResponse(
'<div style="padding:48px;text-align:center">'
f'<h2 style="color:#8b949e">Projet introuvable</h2>'
f'<p style="color:#8b949e">{owner}/{repo} n\'existe pas sur Gitea.</p>'
'<a href="/" class="btn btn-primary" style="margin-top:16px">Retour aux projets</a></div>',
status_code=404)
logger.error("Board error: %s", e)
return HTMLResponse(f"<p class='error'>Erreur: {e}</p>", status_code=500)
from jinja2 import Environment, FileSystemLoader
from datetime import datetime
from app.config import settings
from app.auth.session import SessionManager
env = Environment(loader=FileSystemLoader("app/templates"))
template = env.get_template("board.html")
return template.render(
request=request,
owner=owner,
repo=repo,
columns=columns,
column_map=column_map,
labels=labels,
milestones=milestones,
current_milestone=milestone,
current_label=label,
current_assignee=assignee,
prs=prs,
settings=settings,
now=datetime.now().isoformat()[:10],
now_str = datetime.now().isoformat()[:10]
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
ctx = dict(
request=request, owner=owner, repo=repo, columns=columns,
column_map=column_map, labels=labels_list, milestones=milestones,
current_milestone=milestone, current_label=label, current_assignee=assignee,
prs=prs, settings=settings, now=now_str, user=user,
)
if fragment:
template = env.get_template("board_fragment.html")
else:
template = env.get_template("board.html")
return template.render(**ctx)
def _issue_column(issue: dict, columns: list[str], board_id: int) -> str:
"""Determine which column an issue belongs to."""