Rename Suspend to Suspend access and Make user to Change role.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s
Build and Publish Container Image / build-and-push (push) Successful in 54s
Account journey acceptance / journeys (push) Successful in 7s

Assistant: grok
Assistant-Session: 01a0d25d-d358-7e13-b84a-d007fbb7e34f
This commit is contained in:
tegwick 2026-09-27 01:46:41 +02:00
parent 8f7cf0c69d
commit 14aee356cf
2 changed files with 46 additions and 11 deletions

View file

@ -2071,10 +2071,18 @@ Use the login name they provide; it may differ from your display name.</p></sect
def _invitation_admin_row(self, tenant: str, invitation: Any, csrf_token: str) -> str:
actions = ""
if invitation.status.value == "pending":
actions = f"""<form method="post" action="/admin/{escape(tenant)}/invitations/{escape(invitation.invitation_id)}/resend">
<input type="hidden" name="csrf_token" value="{escape(csrf_token)}"><input type="hidden" name="version" value="{invitation.version}"><button type="submit">Resend</button></form>
<form method="post" action="/admin/{escape(tenant)}/invitations/{escape(invitation.invitation_id)}/expire">
<input type="hidden" name="csrf_token" value="{escape(csrf_token)}"><input type="hidden" name="version" value="{invitation.version}"><button type="submit">Expire</button></form>"""
invite_root = f"/admin/{escape(tenant)}/invitations/{escape(invitation.invitation_id)}"
hidden = f'<input type="hidden" name="csrf_token" value="{escape(csrf_token)}"><input type="hidden" name="version" value="{invitation.version}">'
actions = (
f'<form method="post" action="{invite_root}/resend">{hidden}'
f'<button type="submit">Resend</button>'
f'<button type="button" class="help" popovertarget="help-resend-{escape(invitation.invitation_id)}" aria-label="Explain Resend">?</button></form>'
f'<div id="help-resend-{escape(invitation.invitation_id)}" popover><p>Sends this invitation again.</p></div>'
f'<form method="post" action="{invite_root}/expire">{hidden}'
f'<button type="submit">Expire</button>'
f'<button type="button" class="help" popovertarget="help-expire-{escape(invitation.invitation_id)}" aria-label="Explain Expire">?</button></form>'
f'<div id="help-expire-{escape(invitation.invitation_id)}" popover><p>Stops this invitation. The person can no longer accept it.</p></div>'
)
expires = invitation.expires_at.isoformat() if invitation.expires_at else "—"
events = [e for e in self.service.store.outbox_history() if e.tenant == tenant
and e.aggregate_id == invitation.invitation_id and e.event_type in {"family_invitation.created", "family_invitation.resent", "family_member.invited"}]
@ -2092,9 +2100,16 @@ Use the login name they provide; it may differ from your display name.</p></sect
status = account.status if account else AccountStatus.INVITED
inactive = status in {AccountStatus.SUSPENDED, AccountStatus.DISABLED}
root = f"/admin/{quote(tenant, safe='')}/users/{quote(membership.user_id, safe='')}"
def form(action: str, label: str, **fields: str) -> str:
def form(action: str, label: str, help_text: str, **fields: str) -> str:
hidden = "".join(f'<input type="hidden" name="{escape(k)}" value="{escape(v)}">' for k,v in fields.items())
return f'<form method="post" action="{root}/{action}"><input type="hidden" name="csrf_token" value="{escape(csrf_token)}">{hidden}<button type="submit">{label}</button></form>'
help_id = f"help-{action}-{membership.user_id}"
return (
f'<form method="post" action="{root}/{action}">'
f'<input type="hidden" name="csrf_token" value="{escape(csrf_token)}">{hidden}'
f'<button type="submit">{escape(label)}</button>'
f'<button type="button" class="help" popovertarget="{escape(help_id)}" aria-label="Explain {escape(label)}">?</button></form>'
f'<div id="{escape(help_id)}" popover><p>{escape(help_text)}</p></div>'
)
login = (f'<p>Login name: <strong>{escape(self._directory_login(directory.subject))}</strong></p>'
'<p>Pass this name on with the sign-in address. It is not the email address, '
'and signing in by email does not work.</p>'
@ -2102,13 +2117,23 @@ Use the login name they provide; it may differ from your display name.</p></sect
'<p>Password and authenticator status are not available here.</p>') if directory else '<p>Login not created. Prepare a login before asking this person to sign in.</p>'
actions = ""
if not inactive:
actions += form("provision", "Create password setup link" if directory else "Create login")
actions += form("status", "Reactivate" if inactive else "Suspend", status="active" if inactive else "suspended")
if directory:
actions += form("provision", "Create password setup link", "Makes a new single-use link so this person can set a password for the existing login. It does not change the login name.")
else:
actions += form("provision", "Create login", "Creates the directory login. The login name comes from the email name. It is not the display name, and this page cannot rename it afterwards.")
if inactive:
actions += form("status", "Reactivate", "Lets this person use this tenant again. It does not change the login name or the password.", status="active")
else:
actions += form("status", "Suspend access", "Stops this person using this tenant. The login and any other tenant stay as they are.", status="suspended")
if status != AccountStatus.DISABLED:
actions += form("remove", "Remove account")
actions += form("remove", "Remove account", "Disables this person's account for this tenant. The shared login is kept.")
next_role = "user" if membership.kind == "tenant-admin" else "tenant-admin"
actions += form("role", "Make user" if next_role == "user" else "Make tenant administrator", role=next_role)
if platform_operator: actions += form("recover", "Restore tenant account")
if next_role == "user":
actions += form("role", "Change role", "This person administers the tenant. This changes them to an ordinary user of this tenant. It does not change the login name.", role=next_role)
else:
actions += form("role", "Make tenant administrator", "This person is an ordinary user. This lets them manage the users of this tenant.", role=next_role)
if platform_operator:
actions += form("recover", "Restore tenant account", "Turns this tenant account back on. It creates a directory login only when one is missing. It does not reset a password, remove an authenticator, or change the login name.")
return (f'<tr><td>{escape(user.display_name or membership.user_id) if user else escape(membership.user_id)}</td>'
f'<td>{escape(user.primary_email or "") if user else ""}</td><td>{escape(membership.kind)}</td>'
f'<td>{escape(status.value)} for this tenant</td><td>{login}</td><td>{actions}</td></tr>')
@ -2489,9 +2514,12 @@ h1{{font:clamp(2.2rem,7vw,5.5rem)/.98 Georgia,serif;max-width:13ch}}a{{color:var
table{{width:100%;border-collapse:collapse;background:#fff}}th,td{{padding:.75rem;text-align:left;border-bottom:1px solid var(--line)}}
section{{margin:2rem 0}}form{{display:grid;gap:.8rem;max-width:42rem}}label{{display:grid;gap:.25rem}}
label:has(input[type=checkbox]){{display:flex;align-items:center;gap:.6rem}}
td form{{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem;max-width:none}}
input,select,button{{font:inherit;padding:.65rem}}
input[type=checkbox]{{width:1.15rem;height:1.15rem;margin:0;padding:0;flex:none}}
button{{background:var(--accent);color:white;border:0;border-radius:.3rem;cursor:pointer}}
button.help{{background:transparent;color:var(--accent);border:1px solid var(--line);padding:.15rem .45rem;min-width:1.8rem}}
[popover]{{border:1px solid var(--line);background:#fff;color:var(--ink);padding:1rem;max-width:24rem;border-radius:.35rem}}
a:focus-visible,input:focus-visible,select:focus-visible,button:focus-visible{{outline:3px solid #e59f24;outline-offset:3px}}@media(max-width:640px){{body{{font-size:16px}}table{{display:block;overflow-x:auto}}}}
</style></head><body><header><strong>NetKingdom Identity</strong>{_ACCOUNT_NAVIGATION.get()}</header><main>{body}</main></body></html>"""