From 4175f26cc8b22ff5a07929fa2436c561022cccd2 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Sat, 1 Aug 2026 17:52:46 +0200 Subject: tidy up user list, make otp flag more visible --- app/views/users/_user.html.erb | 133 ++++++++++++++++++++++++----------------- config/locales/de.yml | 5 +- config/locales/en.yml | 5 +- public/stylesheets/admin.css | 106 ++++++++++++++++++++++++-------- 4 files changed, 167 insertions(+), 82 deletions(-) diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb index c9b21f0a..26d6cab2 100644 --- a/app/views/users/_user.html.erb +++ b/app/views/users/_user.html.erb @@ -1,70 +1,95 @@ <% users.each do |user| %> - <%= user.login %> - - <% if user.roles.any? %> - <%= user.role_labels.join(", ") %> + + <% if user.otp_enrolled? %> + " aria-label="<%= t(".otp_enrolled") %>"> + <%= icon("shield-check", library: "tabler", "aria-hidden": true) %> + + <% elsif user.otp_pending_secret.present? %> + " aria-label="<%= t(".otp_pending") %>"> + <%= icon("shield-half", library: "tabler", "aria-hidden": true) %> + <% else %> - <%= t(".no_roles") %> - <% end %> - - - <%= link_to user_path(user), "aria-label" => t("admin.common.show"), - title: t("admin.common.show") do %> - <%= icon("eye", library: "tabler", "aria-hidden": true) %> + " aria-label="<%= t(".otp_missing") %>"> + <%= icon("shield-off", library: "tabler", "aria-hidden": true) %> + <% end %> + <%= user.login %> - - <% if current_user.admin? || current_user == user %> - <%= link_to edit_user_path(user), "aria-label" => t("admin.common.edit"), - title: t("admin.common.edit") do %> - <%= icon("edit", library: "tabler", "aria-hidden": true) %> + + <% if user.roles.any? %> + <% user.role_labels.each do |label| %> + <%= label %> <% end %> + <% else %> + <%= t(".no_roles") %> <% end %> - - <% if current_user.admin? && current_user != user %> - <% if user.alumni? %> - <%= button_to reactivate_user_path(user), method: :put, - form: { class: 'button_to state_changing' }, - "aria-label" => t(".reactivate"), title: t(".reactivate") do %> - <%= icon("user-check", library: "tabler", "aria-hidden": true) %> + +
+ + <%= link_to user_path(user), "aria-label" => t("admin.common.show"), + title: t("admin.common.show") do %> + <%= icon("eye", library: "tabler", "aria-hidden": true) %> <% end %> - <% else %> - <%= button_to deactivate_user_path(user), method: :put, - form: { data: { confirm: t(".confirm_deactivate", :login => user.login) }, - class: 'button_to destructive' }, - "aria-label" => t(".deactivate"), title: t(".deactivate") do %> - <%= icon("user-off", library: "tabler", "aria-hidden": true) %> + + + + <% if current_user.admin? || current_user == user %> + <%= link_to edit_user_path(user), "aria-label" => t("admin.common.edit"), + title: t("admin.common.edit") do %> + <%= icon("edit", library: "tabler", "aria-hidden": true) %> + <% end %> <% end %> - <% end %> - <% end %> - - - <% if current_user.redaktion? && !user.alumni? %> - <% if user.redaktion? %> - <% unless user == current_user %> - <%= button_to revoke_redaktion_user_path(user), method: :put, - form: { data: { confirm: t(".confirm_revoke_redaktion", :login => user.login) }, - class: 'button_to destructive' }, - "aria-label" => t(".revoke_redaktion"), title: t(".revoke_redaktion") do %> - <%= icon("user-minus", library: "tabler", "aria-hidden": true) %> + + + + <% if current_user.admin? && current_user != user %> + <% if user.alumni? %> + <%= button_to reactivate_user_path(user), method: :put, + form: { class: 'button_to state_changing' }, + "aria-label" => t(".reactivate"), title: t(".reactivate") do %> + <%= icon("user-check", library: "tabler", "aria-hidden": true) %> + <% end %> + <% else %> + <%= button_to deactivate_user_path(user), method: :put, + form: { data: { confirm: t(".confirm_deactivate", :login => user.login) }, + class: 'button_to destructive' }, + "aria-label" => t(".deactivate"), title: t(".deactivate") do %> + <%= icon("user-off", library: "tabler", "aria-hidden": true) %> + <% end %> <% end %> <% end %> - <% elsif user.otp_enrolled? %> - <%= button_to grant_redaktion_user_path(user), method: :put, - form: { data: { confirm: t(".confirm_grant_redaktion", :login => user.login) }, - class: 'button_to state_changing' }, - "aria-label" => t(".grant_redaktion"), title: t(".grant_redaktion") do %> - <%= icon("user-plus", library: "tabler", "aria-hidden": true) %> + + + + <% if current_user.redaktion? && !user.alumni? %> + <% if user.redaktion? %> + <% unless user == current_user %> + <%= button_to revoke_redaktion_user_path(user), method: :put, + form: { data: { confirm: t(".confirm_revoke_redaktion", :login => user.login) }, + class: 'button_to destructive' }, + "aria-label" => t(".revoke_redaktion"), title: t(".revoke_redaktion") do %> + <%= icon("user-minus", library: "tabler", "aria-hidden": true) %> + <% end %> + <% end %> + <% elsif user.otp_enrolled? %> + <%= button_to grant_redaktion_user_path(user), method: :put, + form: { data: { confirm: t(".confirm_grant_redaktion", :login => user.login) }, + class: 'button_to state_changing' }, + "aria-label" => t(".grant_redaktion"), title: t(".grant_redaktion") do %> + <%= icon("user-plus", library: "tabler", "aria-hidden": true) %> + <% end %> + <% else %> + + <% end %> <% end %> - <% else %> - " - aria-label="<%= t(".needs_otp") %>"> - <%= icon("shield-off", library: "tabler", "aria-hidden": true) %> - - <% end %> - <% end %> + +
<% end %> diff --git a/config/locales/de.yml b/config/locales/de.yml index 67117945..fcceb023 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -298,7 +298,10 @@ de: revoke_redaktion: "Aus der Redaktion entfernen" confirm_grant_redaktion: "%{login} in die Redaktion aufnehmen? Damit darf %{login} in den geschützten Bereichen veröffentlichen." confirm_revoke_redaktion: "%{login} aus der Redaktion entfernen?" - needs_otp: "zweiter Faktor fehlt" + otp_enrolled: "Zweiter Faktor eingerichtet" + otp_pending: "Zweiter Faktor begonnen, aber nicht bestätigt" + otp_missing: "Kein zweiter Faktor" + grant_redaktion_blocked: "In die Redaktion aufnehmen — erst möglich, wenn das Konto einen zweiten Faktor eingerichtet hat" index: title: "Benutzerkonten" create_editor: "Editor-Konto anlegen" diff --git a/config/locales/en.yml b/config/locales/en.yml index 95eb7e94..81d9f4fb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -242,7 +242,10 @@ en: revoke_redaktion: "Remove from Redaktion" confirm_grant_redaktion: "Add %{login} to Redaktion? They will be able to publish in the protected sections." confirm_revoke_redaktion: "Remove %{login} from Redaktion?" - needs_otp: "no second factor" + otp_enrolled: "Second factor enrolled" + otp_pending: "Second factor started but not confirmed" + otp_missing: "No second factor" + grant_redaktion_blocked: "Add to Redaktion — only possible once the account has enrolled a second factor" index: title: "User accounts" diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css index a54c47a3..f415379a 100644 --- a/public/stylesheets/admin.css +++ b/public/stylesheets/admin.css @@ -837,41 +837,99 @@ table.revisions_table tr:hover { margin: 0; } -.user_group_heading { - margin-top: 1.5rem; -} - -/* Retired accounts: present for attribution, not for action. */ -.user_group_alumni, -.user_table_alumni { - color: var(--text-muted); +.user_table { + width: 100%; + max-width: 44rem; + margin-bottom: 1.5rem; } -.user_table td.user_action { +.user_table td.user_actions { width: 1px; white-space: nowrap; - text-align: center; } -.user_table td.user_action a { - text-decoration: none; +.user_table .user_action_grid { + display: grid; + grid-template-columns: repeat(2, auto); + gap: 0.4rem; + justify-content: end; +} + +.user_table .user_action { + display: inline-flex; + min-height: 2rem; } -.user_table td.user_action svg { +.user_table .user_action svg { width: 1.25rem; height: 1.25rem; } -.user_table td.user_action .field_hint svg { +.user_table .user_action form.button_to { + display: inline-flex; + margin: 0; +} + +.user_table .user_action a, +.user_table .user_action button[type="submit"], +.user_table .user_action .disabled_action { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.3rem; + border: 1px solid transparent; + border-radius: 2px; + background-color: var(--surface-raised); + color: var(--text); + text-decoration: none; +} + +.user_table .user_action a:hover { + color: var(--surface); + background-color: var(--text); +} + +.user_table .user_action form.button_to.destructive button:hover { + color: var(--on-solid); + background-color: var(--danger-solid); +} + +.user_table .user_action form.button_to.state_changing button:hover { + color: var(--on-solid); + background-color: var(--action-solid); +} + +.user_table .user_action .disabled_action { color: var(--text-muted); - vertical-align: middle; + background-color: transparent; + border-color: var(--border); + cursor: not-allowed; } -/* Four groups stacked, and the last one needs clearance from the wrapper. */ -.user_table { - width: 100%; - max-width: 44rem; - margin-bottom: 1.5rem; +.user_table .user_role { + display: block; +} + +.user_table .user_login svg { + color: var(--text-muted); + vertical-align: -0.15em; + margin-right: 0.5rem; + width: 1rem; + height: 1rem; +} + +.user_table .user_login .otp_missing svg { + color: var(--accent); +} + +.user_group_heading { + margin-top: 1.5rem; +} + +/* Retired accounts: present for attribution, not for action. */ +.user_group_alumni, +.user_table_alumni { + color: var(--text-muted); } .role_choice { @@ -905,10 +963,6 @@ table.revisions_table tr:hover { } } -table.user_table td.user_login { - padding-right: 30px; -} - /* ============================================================ Node display (nodes#show / nodes#new) ============================================================ */ @@ -947,7 +1001,7 @@ div.layout_row_content { padding: 0.5rem 0.75rem; } -.info_group .disabled_action { +.disabled_action { display: inline-flex; align-items: center; gap: 0.35rem; @@ -960,7 +1014,7 @@ div.layout_row_content { line-height: 1.25rem; } -.info_group .disabled_action svg { +.disabled_action svg { width: 1.25rem; height: 1.25rem; } -- cgit v1.3