-
Notifications
You must be signed in to change notification settings - Fork 4
Show "Last login" label next to the login method #622
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2042860
0382b81
05e535e
6c0da56
2f839e2
af7c6e6
a089234
5ea2c4e
b57e447
8a5b4fa
688119a
5bece7f
25b5122
e3f8c08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -30,8 +30,9 @@ | |||||
| </div> | ||||||
| </div> | ||||||
|
|
||||||
| <div class="ui small stackable teal centered fluid wrapping menu" | ||||||
| data-bind="semanticui: {tabs: {history: true, autoTabActivation: false}}"> | ||||||
| <div class="ui small stackable teal centered fluid wrapping menu" data-bind="semanticui: {tabs: { | ||||||
| {% if last_login_tab %}autoTabActivation: '{{ last_login_tab }}'{% endif %} | ||||||
| }}"> | ||||||
|
|
||||||
| <a class="item" data-tab="vcs"> | ||||||
| <i class="fas fa-cloud icon"></i> | ||||||
|
|
@@ -42,6 +43,7 @@ | |||||
|
|
||||||
| {# If allowed providers is given, disable the email menu item #} | ||||||
| <a class="{% if allowed_providers %}disabled{% endif %} item" | ||||||
| {% if last_login_tab == "email" %} data-tooltip="Last used" data-variation="teal visible" data-position="top center" {% endif %} | ||||||
| data-tab="email"> | ||||||
| <i class="fas fa-envelope icon"></i> | ||||||
| {% block authentication_email_text %} | ||||||
|
|
@@ -50,8 +52,7 @@ | |||||
| </a> | ||||||
|
|
||||||
| {% if USE_ORGANIZATIONS %} | ||||||
| <a class="item" | ||||||
| href="{% url "saml_resolve_login" %}{% if redirect_field_value %}?{{ redirect_field_name }}={{ redirect_field_value }}{% endif %}"> | ||||||
| <a class="item" data-tab="sso"> | ||||||
| <i class="fas fa-shield-alt icon"></i> | ||||||
| {% trans "Single sign-on" %} | ||||||
| </a> | ||||||
|
|
@@ -68,44 +69,66 @@ | |||||
| </div> | ||||||
| </div> | ||||||
|
|
||||||
| <div class="ui basic center aligned tab segment" data-tab="vcs"> | ||||||
| <div class="ui relaxed list"> | ||||||
| {% block authentication_vcs %} | ||||||
| {# Translators: this will read "Log in with GitHub", where "log in" is a verb #} | ||||||
| {% blocktrans asvar text_log_in %}Log in using{% endblocktrans %} | ||||||
| {% include "socialaccount/snippets/provider_list.html" with process="login" verbiage=text_log_in %} | ||||||
| {% endblock authentication_vcs %} | ||||||
| <div data-bind="using: LoginView()"> | ||||||
| <div class="ui basic center aligned tab segment" data-tab="vcs"> | ||||||
| <div class="ui relaxed list"> | ||||||
| {% block authentication_vcs %} | ||||||
| {# Translators: this will read "Log in with GitHub", where "log in" is a verb #} | ||||||
| {% blocktrans asvar text_log_in %}Log in using{% endblocktrans %} | ||||||
| {% include "socialaccount/snippets/provider_list.html" with process="login" verbiage=text_log_in %} | ||||||
| {% endblock authentication_vcs %} | ||||||
| </div> | ||||||
| </div> | ||||||
| </div> | ||||||
| <div class="ui basic vertically fitted tab segment" data-tab="email"> | ||||||
| {% block authentication_email %} | ||||||
| <p class="ui center aligned vertically fitted small basic fluid segment"> | ||||||
| {# The /email addition is to route to the tab page on the login/signup forms, using the tab module history setting #} | ||||||
| {% blocktrans trimmed %} | ||||||
| Need an account? <a href="{{ signup_url }}#/email">Sign up</a> first. | ||||||
| {% endblocktrans %} | ||||||
| </p> | ||||||
|
|
||||||
| <form class="ui form" | ||||||
| method="post" | ||||||
| action="{% url "account_login" %}#/email"> | ||||||
| {% csrf_token %} | ||||||
| {{ form|crispy }} | ||||||
| {% if redirect_field_value %} | ||||||
| <input type="hidden" | ||||||
| name="{{ redirect_field_name }}" | ||||||
| value="{{ redirect_field_value }}" /> | ||||||
| {% endif %} | ||||||
|
|
||||||
| <div class="ui stackable relaxed text menu"> | ||||||
| <a class="item" href="{% url 'account_reset_password' %}">{% trans "Forgot your password?" %}</a> | ||||||
| <div class="right menu"> | ||||||
| <button class="ui primary button" type="submit">{% trans "Log in" %}</button> | ||||||
| <div class="ui basic vertically fitted tab segment" data-tab="email"> | ||||||
| {% block authentication_email %} | ||||||
| <p class="ui center aligned vertically fitted small basic fluid segment"> | ||||||
| {# The /email addition is to route to the tab page on the login/signup forms, using the tab module history setting #} | ||||||
| {% blocktrans trimmed %} | ||||||
| Need an account? <a href="{{ signup_url }}#/email">Sign up</a> first. | ||||||
| {% endblocktrans %} | ||||||
| </p> | ||||||
|
|
||||||
| <form class="ui form" | ||||||
| method="post" | ||||||
| action="{% url "account_login" %}#/email"> | ||||||
| {% csrf_token %} | ||||||
| {{ form|crispy }} | ||||||
| {% if redirect_field_value %} | ||||||
| <input type="hidden" | ||||||
| name="{{ redirect_field_name }}" | ||||||
| value="{{ redirect_field_value }}" /> | ||||||
| {% endif %} | ||||||
|
|
||||||
| <div class="ui stackable relaxed text menu"> | ||||||
| <a class="item" href="{% url 'account_reset_password' %}">{% trans "Forgot your password?" %}</a> | ||||||
| <div class="right menu"> | ||||||
| <button class="ui primary button" | ||||||
| data-bind="click: save_login_method" | ||||||
| data-provider="email" | ||||||
| type="submit">{% trans "Log in" %}</button> | ||||||
| </div> | ||||||
| </div> | ||||||
| </div> | ||||||
|
|
||||||
| </form> | ||||||
| {% endblock authentication_email %} | ||||||
| </form> | ||||||
| {% endblock authentication_email %} | ||||||
| </div> | ||||||
|
|
||||||
| {% if USE_ORGANIZATIONS %} | ||||||
| <div class="ui basic center aligned tab segment" data-tab="sso"> | ||||||
| <a href="{% url "saml_resolve_login" %}{% if redirect_field_value %}?{{ redirect_field_name }}={{ redirect_field_value }}{% endif %}"> | ||||||
|
|
||||||
| <button class="ui button" | ||||||
| data-bind="click: save_login_method" | ||||||
| data-provider="sso" | ||||||
| type="submit" | ||||||
| {% if last_login_method == "sso" %} data-tooltip="Last used" data-variation="teal visible" data-position="right center" {% endif %} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These all need translated:
Suggested change
|
||||||
| title="Single sign-on"> | ||||||
| <i class="fas fa-shield-alt icon" aria-hidden="true"></i> | ||||||
| {% trans "Log in using single sign-on" %} | ||||||
| </button> | ||||||
| </a> | ||||||
| </div> | ||||||
| {% endif %} | ||||||
| </div> | ||||||
|
|
||||||
| {% block authentication_extra %} | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,9 +7,15 @@ | |
| action="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}"> | ||
| {% csrf_token %} | ||
|
|
||
| {# djlint: off D018 #} | ||
| <button class="ui {{ button_classes }} button" | ||
| data-bind="click: save_login_method" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will cause the connected service page to throw exceptions and break. That view will not have this binding, so it can only be added conditionally. |
||
| data-provider="{{ provider.id }}" | ||
| type="submit" | ||
| {% if last_login_method == provider.id|lower %} data-tooltip="Last used" data-variation="teal visible" data-position="right center" {% endif %} | ||
| title="{{ provider.name }}"> | ||
| {# djlint: on #} | ||
|
|
||
| <i class="fa-brands fa-{{ provider.name|lower }} icon"></i> | ||
| {% blocktrans trimmed with provider_name=provider.app.name|default:provider.name verbiage=verbiage|default:'Connect to' %} | ||
| {{ verbiage }} {{ provider_name }} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import { Registry } from "../application/registry"; | ||
|
|
||
| /** | ||
| * LoginView manages login-related functionality including saving the | ||
| * last used login method to a cookie for session persistence. | ||
| * | ||
| * Usage in a binding context: | ||
| * | ||
| * .. code:: html | ||
| * | ||
| * <div data-bind="using: LoginView()"> | ||
| * <form method="post" action="..."> | ||
| * <button data-bind="click: save_login_method" data-provider="github"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noted above how this should probably use KO patterns instead of mixing element attribute and KO patterns. I don't see where else we're using |
||
| * Log in using GitHub | ||
| * </button> | ||
| * </form> | ||
| * </div> | ||
| */ | ||
| export class LoginView { | ||
| static view_name = "LoginView"; | ||
|
|
||
| constructor() {} | ||
|
|
||
| /** | ||
| * Save the provider used for login. | ||
| * | ||
| * This could be used like: | ||
| * | ||
| * .. code:: html | ||
| * | ||
| * <form method="post" action="..."> | ||
| * <button data-bind="click: save_login_method" data-provider="github"> | ||
| * Log in using GitHub | ||
| * </button> | ||
| * </form> | ||
| * | ||
| * @param {Object} data - Context data | ||
| * @param {Event} event - Click event | ||
| * @returns {knockout_click} | ||
| */ | ||
| save_login_method(data, event) { | ||
| const elem = event.currentTarget; | ||
| if (window.isSecureContext) { | ||
| console.debug("Setting last login method: ", elem.dataset.provider); | ||
| cookieStore.set("last-login-method", elem.dataset.provider); | ||
| } | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| Registry.add_view(LoginView); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,6 +65,16 @@ export class ApplicationView { | |
| if (found_modal.length === 0) { | ||
| console.debug("Modal not found:", selector); | ||
| } | ||
|
|
||
| // Remove "visible" variation property to remove "Last used" tooltip | ||
| const buttons = document.querySelectorAll(".button.ui"); | ||
| for (const button of buttons) { | ||
| if (button.dataset.variation) { | ||
| const variation = button.dataset.variation.split(" "); | ||
| variation.pop("visible"); | ||
| button.dataset.variation = variation.join(" "); | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should always use KO view code to manipulate the DOM. Using JS queries or jQuery to manipulate the DOM in addition is mixing patterns and should be always avoided. I'm a bit confused what this is trying to do though. What is the intention here? It also seems like this would affect all modals, which we don't want.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was the way I found to solve this issue: Peek.2025-11-26.17-58.webmI'm not sure how to solve it in a better way.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay yeah, there are better ways to achieve that. I think part of what is making this complicated is mixing non-Knockout JS patterns, template code, and SUI HTML-only tooltips together. The display issues seem to be from HTML-only tooltips mostly too, so maybe that isn't a good direction. Instead the solution here would be more consistent if it leaned into using Knockout more, and JS driven tooltips/popups are more robust anyways. |
||
| }; | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still working against KO a bit. We should really not need to do Event or Element manipulation to use data with KO. The two patterns that we already have available are:
data-bind="click: () => { save_login_method('email') }"Or better if you need to do something like ensure the click event is handled normally (
trueis returned from the function):data-bind="click: save_login_method.bind($data, 'email')"Both from: https://knockoutjs.com/documentation/click-binding.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see. I didn't know that using a KO function in
data-bindthat readsdata-*attributes was a mixing of patterns 👍🏼