Name the tenant page Tenant administration.
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 47s
Account journey acceptance / journeys (push) Successful in 8s

Assistant: grok
Assistant-Session: 01a0d25d-d358-7e13-b84a-d007fbb7e34f
This commit is contained in:
tegwick 2026-09-27 00:51:14 +02:00
parent 104405ccbf
commit 662305391f
5 changed files with 16 additions and 10 deletions

View file

@ -53,4 +53,4 @@ def page(csrf, result=None):
for receipt in reversed(history): for receipt in reversed(history):
html += '<li>'+escape(str(receipt.get('reference','')))+' — '+escape(str(receipt.get('client','')))+': '+escape(LABELS.get(receipt.get('before'),'Unknown'))+' → '+escape(LABELS.get(receipt.get('after'),'Unknown'))+'; actor '+escape(str(receipt.get('actor','')))+', revision '+escape(str(receipt.get('revision','')))+'.</li>' html += '<li>'+escape(str(receipt.get('reference','')))+' — '+escape(str(receipt.get('client','')))+': '+escape(LABELS.get(receipt.get('before'),'Unknown'))+' → '+escape(LABELS.get(receipt.get('after'),'Unknown'))+'; actor '+escape(str(receipt.get('actor','')))+', revision '+escape(str(receipt.get('revision','')))+'.</li>'
html += '</ul>' html += '</ul>'
return html+'<p><a href="/platform/authentication-policy">Check current policy</a> · <a href="/platform">Return to platform administration</a></p>' return html+'<p><a href="/platform/authentication-policy">Check current policy</a> · <a href="/platform">Return to tenant administration</a></p>'

View file

@ -92,7 +92,7 @@ def page(csrf, result=None, submitted=None, management_url=''):
html+=apply_form(common,submitted['confirmation'],'Retry this recovery') html+=apply_form(common,submitted['confirmation'],'Retry this recovery')
html+='<form method="post" action="/platform/factor-recovery">'+common+hidden('action','preview')+'<label>Directory login <input name="user" maxlength="150" required value="'+escape(str(submitted.get('user','')),quote=True)+'"></label><label>Support reference <input name="reference" maxlength="150" required value="'+escape(str(submitted.get('reference','')),quote=True)+'"></label><button type="submit">Preview authenticators</button></form>' html+='<form method="post" action="/platform/factor-recovery">'+common+hidden('action','preview')+'<label>Directory login <input name="user" maxlength="150" required value="'+escape(str(submitted.get('user','')),quote=True)+'"></label><label>Support reference <input name="reference" maxlength="150" required value="'+escape(str(submitted.get('reference','')),quote=True)+'"></label><button type="submit">Preview authenticators</button></form>'
html+='<form method="post" action="/platform/factor-recovery">'+common+'<input type="hidden" name="action" value="status"><label>Existing support reference <input name="reference" maxlength="150" required></label><button type="submit">Check recovery result</button></form>' html+='<form method="post" action="/platform/factor-recovery">'+common+'<input type="hidden" name="action" value="status"><label>Existing support reference <input name="reference" maxlength="150" required></label><button type="submit">Check recovery result</button></form>'
html+='<p><a href="/platform">Cancel and return to platform administration</a> · <a href="/platform/activity">Investigate support activity</a></p>' html+='<p><a href="/platform">Cancel and return to tenant administration</a> · <a href="/platform/activity">Investigate support activity</a></p>'
return html return html

View file

@ -1327,7 +1327,7 @@ class PortalApplication:
<p>Showing {min(count, 100)} of {count} matching records, newest first. Filters apply before the 100-record display limit.</p> <p>Showing {min(count, 100)} of {count} matching records, newest first. Filters apply before the 100-record display limit.</p>
<table><thead><tr><th>Time</th><th>Kind</th><th>Tenant</th><th>Action</th><th>Actor</th><th>Support reference</th><th>Known result</th><th>Next step</th></tr></thead><tbody>{rows or empty}</tbody></table> <table><thead><tr><th>Time</th><th>Kind</th><th>Tenant</th><th>Action</th><th>Actor</th><th>Support reference</th><th>Known result</th><th>Next step</th></tr></thead><tbody>{rows or empty}</tbody></table>
<p>Audit records describe recorded actions. Delivery acceptance does not prove receipt, and neither proves a provider change or rollback. Check the relevant provider before closing an incident.</p> <p>Audit records describe recorded actions. Delivery acceptance does not prove receipt, and neither proves a provider change or rollback. Check the relevant provider before closing an incident.</p>
<p><a href="/platform/operations">Service recovery</a> · <a href="/platform">Platform administration</a></p>""") <p><a href="/platform/operations">Service recovery</a> · <a href="/platform">Tenant administration</a></p>""")
def _operations_page(self, actor: Any, csrf: str, event_id: str, correlation_id: str) -> str: def _operations_page(self, actor: Any, csrf: str, event_id: str, correlation_id: str) -> str:
self.service.tenant_diagnostics(actor, tenant=PLATFORM_TENANT, correlation_id=correlation_id) self.service.tenant_diagnostics(actor, tenant=PLATFORM_TENANT, correlation_id=correlation_id)
@ -1351,7 +1351,7 @@ class PortalApplication:
'<form method="get" action="/platform/operations"><label>Delivery record ID <input name="event_id"></label><button type="submit">Find delivery</button></form>' '<form method="get" action="/platform/operations"><label>Delivery record ID <input name="event_id"></label><button type="submit">Find delivery</button></form>'
'<table><thead><tr><th>Delivery</th><th>Tenant</th><th>Kind</th><th>Status</th><th>Support reference</th><th>Recovery</th></tr></thead><tbody>' '<table><thead><tr><th>Delivery</th><th>Tenant</th><th>Kind</th><th>Status</th><th>Support reference</th><th>Recovery</th></tr></thead><tbody>'
+ (rows or '<tr><td colspan="6">No delivery records. This does not prove mail was received.</td></tr>') + (rows or '<tr><td colspan="6">No delivery records. This does not prove mail was received.</td></tr>')
+ '</tbody></table><p>Queueing a retry does not send it. Use Review delivery attempt to submit one selected record, then check its result. No background worker is enabled here.</p><p><a href="/platform">Return to platform administration</a></p>') + '</tbody></table><p>Queueing a retry does not send it. Use Review delivery attempt to submit one selected record, then check its result. No background worker is enabled here.</p><p><a href="/platform">Return to tenant administration</a></p>')
def _operation_capabilities(self) -> str: def _operation_capabilities(self) -> str:
capabilities = ( capabilities = (
@ -2140,8 +2140,8 @@ Use the login name they provide; it may differ from your display name.</p></sect
) or '<li>No tenants with user memberships are recorded yet.</li>' ) or '<li>No tenants with user memberships are recorded yet.</li>'
message = f'<p role="alert">{escape(error)}</p>' if error else "" message = f'<p role="alert">{escape(error)}</p>' if error else ""
return self._page_html( return self._page_html(
"Platform administration", "Tenant administration",
f"""<h1>Platform administration</h1> f"""<h1>Tenant administration</h1>
<p><a href="/platform/authentication-policy">Authentication policy</a></p> <p><a href="/platform/authentication-policy">Authentication policy</a></p>
<section aria-labelledby="manage-tenant"><h2 id="manage-tenant">Manage an existing tenant</h2> <section aria-labelledby="manage-tenant"><h2 id="manage-tenant">Manage an existing tenant</h2>
{message} {message}
@ -2196,7 +2196,7 @@ Use the login name they provide; it may differ from your display name.</p></sect
<label>Reason <input name="reason" required></label> <label>Reason <input name="reason" required></label>
<button type="submit">{'Reactivate tenant' if retired else 'Retire tenant'}</button></form> <button type="submit">{'Reactivate tenant' if retired else 'Retire tenant'}</button></form>
<p>Retirement is reversible and preserves grant and plan history; there is no hard delete.</p></section> <p>Retirement is reversible and preserves grant and plan history; there is no hard delete.</p></section>
<p><a href="/platform">Return to platform administration</a></p>""", <p><a href="/platform">Return to tenant administration</a></p>""",
) )
def _platform_result(self, result: Any, tenant: str, admin_prepared: bool) -> str: def _platform_result(self, result: Any, tenant: str, admin_prepared: bool) -> str:
@ -2206,7 +2206,7 @@ Use the login name they provide; it may differ from your display name.</p></sect
<p><strong>{escape(tenant)}</strong> was processed by the tenant authority.</p> <p><strong>{escape(tenant)}</strong> was processed by the tenant authority.</p>
<p>{'The first administrator is prepared and awaiting onboarding.' if admin_prepared else 'No first administrator was requested.'}</p> <p>{'The first administrator is prepared and awaiting onboarding.' if admin_prepared else 'No first administrator was requested.'}</p>
<p><a class="button" href="/admin/{escape(tenant)}">Open tenant administration</a></p> <p><a class="button" href="/admin/{escape(tenant)}">Open tenant administration</a></p>
<p><a href="/platform">Return to platform administration</a></p>""", <p><a href="/platform">Return to tenant administration</a></p>""",
) )
def _onboarding( def _onboarding(
@ -2316,7 +2316,7 @@ Use the login name they provide; it may differ from your display name.</p></sect
if "platform-operator" in actor.roles: if "platform-operator" in actor.roles:
body = ( body = (
"<li>No tenant memberships are recorded. You have no personal tenant memberships. " "<li>No tenant memberships are recorded. You have no personal tenant memberships. "
"Your platform operator role lets you manage tenants through platform administration.</li>" "Your platform operator role lets you manage tenants through tenant administration.</li>"
) )
else: else:
body = "<li>No tenant memberships are recorded.</li>" body = "<li>No tenant memberships are recorded.</li>"
@ -2465,7 +2465,7 @@ Use the login name they provide; it may differ from your display name.</p></sect
return return
links = '<a href="/">Home</a><a href="/onboarding">My account</a><a href="/security">Sign-in security</a>' links = '<a href="/">Home</a><a href="/onboarding">My account</a><a href="/security">Sign-in security</a>'
if "platform-operator" in actor.roles: if "platform-operator" in actor.roles:
links += '<a href="/platform/factor-recovery">Authenticator recovery</a><a href="/platform">Platform administration</a><a href="/platform/operations">Service recovery</a><a href="/platform/activity">Platform activity</a>' links += '<a href="/platform/factor-recovery">Authenticator recovery</a><a href="/platform">Tenant administration</a><a href="/platform/operations">Service recovery</a><a href="/platform/activity">Platform activity</a>'
elif "tenant-admin" in actor.roles: elif "tenant-admin" in actor.roles:
links += f'<a href="/admin/{escape(quote(actor.tenant, safe=""))}">Manage users</a>' links += f'<a href="/admin/{escape(quote(actor.tenant, safe=""))}">Manage users</a>'
session_id = cookie_value(str(environ.get("HTTP_COOKIE", "")), "ue_session") session_id = cookie_value(str(environ.get("HTTP_COOKIE", "")), "ue_session")

View file

@ -43,6 +43,10 @@ class PortalNavigationTests(unittest.TestCase):
_, body = self.get('/onboarding') _, body = self.get('/onboarding')
self.assertIn(b'no personal tenant memberships', body) self.assertIn(b'no personal tenant memberships', body)
self.assertIn(b'platform operator role', body) self.assertIn(b'platform operator role', body)
self.assertIn(b'through tenant administration', body)
_, home = self.get('/')
self.assertIn(b'href="/platform">Tenant administration</a>', home)
self.assertNotIn(b'Platform administration', home)
self.assertFalse(self.app.service.store.memberships_for_tenant('tenant:trial:demo-company')) self.assertFalse(self.app.service.store.memberships_for_tenant('tenant:trial:demo-company'))
def test_existing_tenant_user_navigation_preserves_authority(self): def test_existing_tenant_user_navigation_preserves_authority(self):

View file

@ -902,6 +902,8 @@ class PortalApplicationTests(unittest.TestCase):
self.app, "/platform", cookie="ue_session=platform" self.app, "/platform", cookie="ue_session=platform"
) )
self.assertIn(b"Manage an existing tenant", html) self.assertIn(b"Manage an existing tenant", html)
self.assertIn(b"<h1>Tenant administration</h1>", html)
self.assertNotIn(b"Platform administration", html)
def test_platform_tenant_authority_denial_is_redacted_and_creates_no_admin(self): def test_platform_tenant_authority_denial_is_redacted_and_creates_no_admin(self):
oidc = OIDCClient( oidc = OIDCClient(