You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by gs...@apache.org on 2026/08/12 23:55:49 UTC
(tooling-llmao) branch main updated: Show GET page errors as flashes via page_errors.
This is an automated email from the ASF dual-hosted git repository.
gstein pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-llmao.git
The following commit(s) were added to refs/heads/main by this push:
new 29a76e2 Show GET page errors as flashes via page_errors.
29a76e2 is described below
commit 29a76e2ea16d15fcdca9f5817013790c9a41fd86
Author: Greg Stein <gs...@gmail.com>
AuthorDate: Wed Aug 12 18:55:44 2026 -0500
Show GET page errors as flashes via page_errors.
basic_info no longer drains Quart flashes up front, so a flash() after
that call (including from the decorator) is still visible: page_errors
catches AuthzError/BackendUnavailable, flashes, then attaches messages
immediately before use_template renders. Drop result.error and the
per-template error alerts; policy denials raise AuthzError.
Co-authored-by: Grok <gr...@x.ai>
---
pages.py | 142 +++++++++++++++++++++++------------------
templates/key_create.ezt | 4 --
templates/key_create_other.ezt | 4 --
templates/keys.ezt | 6 --
templates/keys_other.ezt | 6 --
templates/models.ezt | 6 --
templates/project.ezt | 4 --
templates/projects.ezt | 6 --
8 files changed, 81 insertions(+), 97 deletions(-)
diff --git a/pages.py b/pages.py
index f61c56d..909c708 100644
--- a/pages.py
+++ b/pages.py
@@ -54,12 +54,51 @@ def _render(template_name: str, data) -> str:
return asfquart.utils.render(APP.load_template(TEMPLATES / template_name), data)
+def _flash_rows():
+ """Drain Quart flashes for EZT. Call immediately before use_template render."""
+ try:
+ msgs = quart.get_flashed_messages(with_categories=True)
+ except Exception:
+ return []
+ return [edict(category=c, message=m) for c, m in msgs]
+
+
+def page_errors(*extra_exc, category: str = "warning", title: str | None = None):
+ """Catch page failures, flash them, attach flashes after the view returns.
+
+ Innermost under ``@APP.use_template`` so ``flash()`` happens before EZT.
+ ``basic_info()`` does not drain flashes — this decorator does, last.
+ """
+ types = (AuthzError, BackendUnavailable) + extra_exc
+
+ def deco(fn):
+ @functools.wraps(fn)
+ async def wrapper(*args, **kwargs):
+ try:
+ data = await fn(*args, **kwargs)
+ except types as e:
+ await quart.flash(str(e), category)
+ data = await basic_info()
+ if title:
+ data.title = title
+ if "project" in kwargs:
+ data.project = kwargs["project"]
+ if not title:
+ data.title = kwargs["project"]
+ if data is not None and hasattr(data, "update"):
+ data.flashes = _flash_rows()
+ return data
+
+ return wrapper
+
+ return deco
+
+
async def basic_info() -> edict:
"""Base-level EZT template data shared by HTML pages."""
basic = edict()
basic.title = "llmao"
basic.flashes = []
- basic.error = None
# Form defaults for create pages
basic.form_project = ""
@@ -67,12 +106,6 @@ async def basic_info() -> edict:
basic.keys_back = "/keys"
basic.keys_create_another = "/keys/new"
- try:
- msgs = quart.get_flashed_messages(with_categories=True)
- basic.flashes = [edict({"category": c, "message": m}) for c, m in msgs]
- except Exception:
- basic.flashes = []
-
client_session = await asfquart.session.read()
if client_session is not None and getattr(client_session, "uid", None):
basic.uid = client_session.uid
@@ -141,6 +174,7 @@ def _key_rows(keys: list[KeyInfo], *, after_path: str = "/keys") -> list:
@APP.get("/")
@APP.use_template(TEMPLATES / "home.ezt")
+@page_errors()
async def home_page():
result = await basic_info()
result.title = "Home"
@@ -150,18 +184,15 @@ async def home_page():
@APP.get("/models")
@asfquart.auth.require
@APP.use_template(TEMPLATES / "models.ezt")
+@page_errors(FileNotFoundError, ValueError, OSError, title="Models")
async def models_page():
"""Gateway model inventory (public fields; supply path for site admins)."""
result = await basic_info()
result.title = "Models"
result.models = []
- result.error = None
result.reveal_supply = bool(result.is_site_admin)
- try:
- raw = ux_models(cfg=APP.cfg, reveal_supply=result.reveal_supply)
- result.models = [edict(m) for m in raw]
- except (FileNotFoundError, ValueError, OSError) as e:
- result.error = str(e)
+ raw = ux_models(cfg=APP.cfg, reveal_supply=result.reveal_supply)
+ result.models = [edict(m) for m in raw]
return result
@@ -193,40 +224,34 @@ def _project_list_rows(rows) -> list:
@APP.get("/projects")
@asfquart.auth.require
@APP.use_template(TEMPLATES / "projects.ezt")
+@page_errors(title="Projects")
async def projects_list():
"""Projects you belong to, with project-budget summary."""
result = await basic_info()
result.title = "Projects"
result.project_rows = []
- result.error = None
- try:
- ident = await current_identity(APP.cfg)
- assert ident is not None
- seam = APP.config["LLMAO_SEAM"]
- result.project_rows = _project_list_rows(
- await seam.list_projects_for(ident)
- )
- except (AuthzError, BackendUnavailable) as e:
- result.error = str(e)
+ ident = await current_identity(APP.cfg)
+ assert ident is not None
+ seam = APP.config["LLMAO_SEAM"]
+ result.project_rows = _project_list_rows(
+ await seam.list_projects_for(ident)
+ )
return result
@APP.get("/projects/<project>")
@asfquart.auth.require
@APP.use_template(TEMPLATES / "project.ezt")
+@page_errors()
async def project_stub(project: str):
"""Member-gated stub until P0.3 overview."""
result = await basic_info()
result.title = project
result.project = project
- result.error = None
- try:
- ident = await current_identity(APP.cfg)
- assert ident is not None
- seam = APP.config["LLMAO_SEAM"]
- await seam.team_status(ident, project)
- except (AuthzError, BackendUnavailable) as e:
- result.error = str(e)
+ ident = await current_identity(APP.cfg)
+ assert ident is not None
+ seam = APP.config["LLMAO_SEAM"]
+ await seam.team_status(ident, project)
return result
@@ -261,58 +286,52 @@ async def _flash_key_created(created, *, kind_label: str, keys_back: str, keys_c
@APP.get("/keys")
@asfquart.auth.require
@APP.use_template(TEMPLATES / "keys.ezt")
+@page_errors(title="My Keys")
async def keys_list():
"""My Keys — personal PATs only (one list_keys call)."""
result = await basic_info()
result.title = "My Keys"
result.keys = []
- result.error = None
- try:
- ident = await current_identity(APP.cfg)
- assert ident is not None
- seam = APP.config["LLMAO_SEAM"]
- my_keys = await seam.list_my_keys(ident)
- result.keys = _key_rows(my_keys, after_path="/keys")
- except (AuthzError, BackendUnavailable) as e:
- result.error = str(e)
+ ident = await current_identity(APP.cfg)
+ assert ident is not None
+ seam = APP.config["LLMAO_SEAM"]
+ my_keys = await seam.list_my_keys(ident)
+ result.keys = _key_rows(my_keys, after_path="/keys")
return result
@APP.get("/keys/other")
@asfquart.auth.require
@APP.use_template(TEMPLATES / "keys_other.ezt")
+@page_errors(title="Other Keys")
async def keys_other_list():
"""Other Keys — automation / team-scoped (PMC / site admin)."""
result = await basic_info()
result.title = "Other Keys"
result.keys = []
- result.error = None
if not result.can_create_automation:
- result.error = "Other Keys is limited to PMC members and site admins."
- return result
- try:
- ident = await current_identity(APP.cfg)
- assert ident is not None
- seam = APP.config["LLMAO_SEAM"]
- admin_projects = (
- ident.all_projects() if ident.is_site_admin else list(ident.committees)
- )
- by_id: dict[str, KeyInfo] = {}
- for p in admin_projects:
- try:
- for k in await seam.list_automation_keys(ident, p):
- by_id.setdefault(k.token_id, k)
- except (AuthzError, BackendUnavailable):
- continue
- result.keys = _key_rows(list(by_id.values()), after_path="/keys/other")
- except (AuthzError, BackendUnavailable) as e:
- result.error = str(e)
+ raise AuthzError("Other Keys is limited to PMC members and site admins.")
+ ident = await current_identity(APP.cfg)
+ assert ident is not None
+ seam = APP.config["LLMAO_SEAM"]
+ admin_projects = (
+ ident.all_projects() if ident.is_site_admin else list(ident.committees)
+ )
+ by_id: dict[str, KeyInfo] = {}
+ for p in admin_projects:
+ try:
+ for k in await seam.list_automation_keys(ident, p):
+ by_id.setdefault(k.token_id, k)
+ except (AuthzError, BackendUnavailable):
+ continue
+ result.keys = _key_rows(list(by_id.values()), after_path="/keys/other")
return result
@APP.get("/keys/new")
@asfquart.auth.require
@APP.use_template(TEMPLATES / "key_create.ezt")
+@page_errors(title="Create personal key")
async def keys_new_form():
result = await basic_info()
result.title = "Create personal key"
@@ -345,11 +364,12 @@ async def do_create_key():
@APP.get("/keys/other/new")
@asfquart.auth.require
@APP.use_template(TEMPLATES / "key_create_other.ezt")
+@page_errors(title="Create automation key")
async def keys_other_new_form():
result = await basic_info()
result.title = "Create automation key"
if not result.can_create_automation:
- result.error = "Only PMC members and site admins may create automation keys."
+ raise AuthzError("Only PMC members and site admins may create automation keys.")
return result
diff --git a/templates/key_create.ezt b/templates/key_create.ezt
index 2acb81b..539836f 100644
--- a/templates/key_create.ezt
+++ b/templates/key_create.ezt
@@ -8,10 +8,6 @@
password manager or secrets system.
</p>
- [if-any error]
- <div class="alert alert-danger">[error]</div>
- [end]
-
<form method="post" action="/do-create-key" class="vstack gap-3">
<div>
<label class="form-label" for="project">Project</label>
diff --git a/templates/key_create_other.ezt b/templates/key_create_other.ezt
index 2e85fb6..d04ba76 100644
--- a/templates/key_create_other.ezt
+++ b/templates/key_create_other.ezt
@@ -9,10 +9,6 @@
you will be recorded as <code>created_by</code>.
</p>
- [if-any error]
- <div class="alert alert-danger">[error]</div>
- [end]
-
[if-any can_create_automation]
<form method="post" action="/do-create-other-key" class="vstack gap-3">
<div>
diff --git a/templates/keys.ezt b/templates/keys.ezt
index 3faf01e..e66db29 100644
--- a/templates/keys.ezt
+++ b/templates/keys.ezt
@@ -14,10 +14,6 @@
attributed to you. The secret is shown once when you create the key.
</p>
- [if-any error]
- <div class="alert alert-warning">[error]</div>
- [end]
-
[if-any keys]
<div class="table-responsive">
<table class="table table-sm table-hover align-middle">
@@ -55,10 +51,8 @@
</table>
</div>
[else]
- [if-any error][else]
<p class="text-muted">No personal keys yet. <a href="/keys/new">Create one</a>.</p>
[end]
- [end]
</div>
<div class="modal fade" id="revokeModal" tabindex="-1" aria-labelledby="revokeModalLabel" aria-hidden="true">
diff --git a/templates/keys_other.ezt b/templates/keys_other.ezt
index 283b82b..28dab25 100644
--- a/templates/keys_other.ezt
+++ b/templates/keys_other.ezt
@@ -18,10 +18,6 @@
<strong>Created by</strong> is who saw it and who teammates should ask.
</p>
- [if-any error]
- <div class="alert alert-warning">[error]</div>
- [end]
-
[if-any keys]
<div class="table-responsive">
<table class="table table-sm table-hover align-middle">
@@ -61,14 +57,12 @@
</table>
</div>
[else]
- [if-any error][else]
<p class="text-muted">No automation keys for your admin projects yet.
[if-any can_create_automation]
<a href="/keys/other/new">Create one</a>.
[end]
</p>
[end]
- [end]
</div>
<div class="modal fade" id="revokeModal" tabindex="-1" aria-labelledby="revokeModalLabel" aria-hidden="true">
diff --git a/templates/models.ezt b/templates/models.ezt
index 0d37140..9502dc0 100644
--- a/templates/models.ezt
+++ b/templates/models.ezt
@@ -10,10 +10,6 @@
can call when configured.
</p>
- [if-any error]
- <div class="alert alert-warning">[error]</div>
- [end]
-
[if-any models]
<div class="table-responsive">
<table class="table table-sm table-hover align-middle">
@@ -76,10 +72,8 @@
</table>
</div>
[else]
- [if-any error][else]
<p class="text-muted">No models in the inventory.</p>
[end]
- [end]
</div>
<div class="modal fade" id="modelDetailModal" tabindex="-1"
diff --git a/templates/project.ezt b/templates/project.ezt
index a2c037d..a6f7304 100644
--- a/templates/project.ezt
+++ b/templates/project.ezt
@@ -7,12 +7,8 @@
</p>
<h1 class="h3 mb-3">[project]</h1>
- [if-any error]
- <div class="alert alert-warning">[error]</div>
- [else]
<p class="text-muted">
Project overview (budget meters, by-person usage) is next.
</p>
- [end]
</div>
[include "footer.ezt"]
diff --git a/templates/projects.ezt b/templates/projects.ezt
index 81baff9..2f90714 100644
--- a/templates/projects.ezt
+++ b/templates/projects.ezt
@@ -8,10 +8,6 @@
<strong>stewards</strong> for that project.
</p>
- [if-any error]
- <div class="alert alert-warning">[error]</div>
- [end]
-
[if-any project_rows]
<div class="table-responsive">
<table class="table table-sm table-hover align-middle">
@@ -52,9 +48,7 @@
</table>
</div>
[else]
- [if-any error][else]
<p class="text-muted">You're not listed on any ASF project.</p>
[end]
- [end]
</div>
[include "footer.ezt"]
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org