diff --git a/accounts/templates/account.html b/accounts/templates/account.html index e56d7259..0f58d8d3 100644 --- a/accounts/templates/account.html +++ b/accounts/templates/account.html @@ -4,118 +4,94 @@ {% load bootstrap_icons %} {% block title %}{% trans "User Profile" %} - {{ user }}{% endblock %} -{% block page_heading %}{% trans "User Profile" %}: {{ user }}{% endblock page_heading %} - -{% block page_heading_extra %} -{% if otp_enabled %} -
{% csrf_token %} - -
-{% endif %} - - {% bs_icon 'pencil' %} - - - {% bs_icon 'plus-circle-fill' %} - -{% endblock page_heading_extra %} +{% block page_heading %}{% trans "User Profile" %}{% endblock page_heading %} {% block content %} - - -
-
-
- - - - - - - - - - - - - {% for inst in user_insts %} - - - - - - - - - - {% empty %} - - {% endfor %} - -
#{% trans "Instance" %}{% trans "VNC" %}{% trans "Resize" %}{% trans "Delete" %}{% trans "Action" %}
{{ forloop.counter }}{{ inst.instance.name }}{% if inst.is_vnc %}{% bs_icon 'check' %}{% endif %}{% if inst.is_change %}{% bs_icon 'check' %}{% endif %}{% if inst.is_delete %}{% bs_icon 'check' %}{% endif %} - - {% bs_icon 'pencil' %} - - - - {% bs_icon 'trash' %} - -
{% trans "No VMs." %}
+
+
+
+
+
+

{{ user.get_full_name|default:user.username }}

+ {% if user.is_superuser %} + {% trans "Superuser" %} + {% elif user.is_staff %} + {% trans "Template editor" %} + {% endif %} + {% if not user.is_active %}{% trans "Blocked" %}{% endif %} +
+
+ {{ user.username }} + {% if user.email %}{{ user.email }}{% endif %} + {% for group in user.groups.all %}{{ group.name }}{% endfor %} +
+
+ {% trans "Last login" %} {{ user.last_login|date:"Y-m-d H:i"|default:_("never") }} + {% trans "Joined" %} {{ user.date_joined|date:"Y-m-d" }} +
-
-
-
- - - - - - - - - - {% for publickey in publickeys %} - - - - - - {% endfor %} - -
{% trans "Key name" %}{% trans "Public key" %}{% trans "Action" %}
{{ publickey.keyname }}{{ publickey.keypublic|truncatechars:64 }} - - {% bs_icon 'trash' %} - -
+
+ {% bs_icon 'pencil' %} {% trans "Edit user" %} + {% if otp_enabled %} +
{% csrf_token %} + +
+ {% endif %}
-
- {% if api_tokens %} -
{% include "api_token_list.html" with owner=user %}
- {% else %} -

{% trans "No API tokens." %}

- {% endif %} -
+ +
+ +
+
+

{% trans "VMs" %} {{ user_insts|length }}

+ {% bs_icon 'plus-lg' %} {% trans "Add VM" %} +
+
+ {% for inst in user_insts %} +
+
+
+
+ {{ inst.instance.name }} +
{% trans "Host" %}: {{ inst.instance.compute.name }}
+
+
+ {# the rights as the VM's Users tab names them: every owner powers the VM and opens its console #} + {% if inst.is_change %}{% trans "Change settings" %}{% endif %} + {% if inst.is_delete %}{% trans "Delete the VM" %}{% endif %} + {% if inst.is_change and inst.is_vnc %}{% trans "Change console settings" %}{% endif %} + {% if not inst.is_change and not inst.is_delete %}{% trans "Power and console only" %}{% endif %} +
+
+ +
+
+ {% empty %} +
{% trans "No VMs." %}
+ {% endfor %} +
+
+ +
+

{% trans "SSH Keys" %} {{ publickeys|length }}

+
{% include "ssh_key_list.html" with owner=user %}
+
+ +
+

{% trans "API Tokens" %} {{ api_tokens|length }}

+
{% include "api_token_list.html" with owner=user %}
+
{% endblock content %} -{% block script %} -{% include "tab_from_hash.html" %} -{% endblock %} diff --git a/accounts/templates/api_token_list.html b/accounts/templates/api_token_list.html index 994d6494..41d47a55 100644 --- a/accounts/templates/api_token_list.html +++ b/accounts/templates/api_token_list.html @@ -20,4 +20,6 @@
{% trans "Last used" %} {% if token.last_used %}{{ token.last_used|date:"Y-m-d H:i" }}{% else %}{% trans "never" %}{% endif %}
+{% empty %} +
{% trans "No API tokens." %}
{% endfor %} diff --git a/accounts/templates/profile.html b/accounts/templates/profile.html index af08750d..22a58a33 100644 --- a/accounts/templates/profile.html +++ b/accounts/templates/profile.html @@ -2,7 +2,6 @@ {% load i18n %} {% load django_bootstrap5 %} {% load bootstrap_icons %} -{% load tags_fingerprint %} {% block title %}{% trans "Profile" %}{% endblock %} @@ -48,23 +47,7 @@
{% trans "Offered when you create a VM and on a VM's Access tab." %}
-
- {% for key in publickeys %} -
-
- {{ key.keyname }} - -
-
{% ssh_to_fingerprint key.keypublic %}
-
- {% endfor %} -
+
{% include "ssh_key_list.html" %}
{% csrf_token %} diff --git a/accounts/templates/ssh_key_list.html b/accounts/templates/ssh_key_list.html new file mode 100644 index 00000000..ad503768 --- /dev/null +++ b/accounts/templates/ssh_key_list.html @@ -0,0 +1,21 @@ +{% load i18n %} +{% load bootstrap_icons %} +{% load tags_fingerprint %} +{# the keys of the signed-in user, or of owner on a superuser's view of a user: the items of a list-group #} +{% for key in publickeys %} +
+
+ {{ key.keyname }} + +
+
{% ssh_to_fingerprint key.keypublic %}
+
+{% empty %} +
{% trans "No SSH keys." %}
+{% endfor %} diff --git a/accounts/templates/tab_from_hash.html b/accounts/templates/tab_from_hash.html deleted file mode 100644 index b418e8a5..00000000 --- a/accounts/templates/tab_from_hash.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/accounts/tests/test_account_page.py b/accounts/tests/test_account_page.py index 039179c0..a94306c3 100644 --- a/accounts/tests/test_account_page.py +++ b/accounts/tests/test_account_page.py @@ -30,9 +30,29 @@ class AccountPageTests(TestCase): self.assertContains(response, "ap-vm") self.assertNotContains(response, "True", html=True) self.assertNotContains(response, "False", html=True) + # what the user may do beyond power and console, in the VM Users tab's words, and the VM's host + self.assertContains(response, "Power and console only") + self.assertNotContains(response, "Change settings") + self.assertContains(response, "Host: ap-host") + # the console settings come with the right to change the VM + UserInstance.objects.filter(user=self.alice).update(is_change=True) + response = self.page() + self.assertContains(response, "Change settings") + self.assertContains(response, "Change console settings") + self.assertNotContains(response, "Power and console only") - def test_a_user_without_vms_is_told_so(self): - self.assertContains(self.page(), "No VMs.") + def test_a_blocked_user_is_marked(self): + self.assertNotContains(self.page(), "Blocked") + self.alice.is_active = False + self.alice.save() + self.assertContains(self.page(), 'Blocked', html=True) + + def test_a_user_without_vms_or_tokens_is_told_so(self): + response = self.page() + self.assertContains(response, "No VMs.") + self.assertContains(response, "No API tokens.") + # the whole key shows over its fingerprint + self.assertContains(response, f'title="{KEY}"') def test_a_superuser_deletes_a_users_key(self): url = reverse("accounts:account_ssh_key_delete", args=[self.alice.pk, self.key.pk]) diff --git a/accounts/views.py b/accounts/views.py index ef75f062..d29571bb 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -169,7 +169,7 @@ def ssh_key_delete(request, pk, user_id=None): @superuser_only def account(request, user_id): user = get_object_or_404(User, id=user_id) - user_insts = UserInstance.objects.filter(user_id=user_id).select_related("instance") + user_insts = UserInstance.objects.filter(user_id=user_id).select_related("instance__compute") publickeys = UserSSHKey.objects.filter(user_id=user_id) return render(