diff --git a/netbox/templates/inc/profile_button.html b/netbox/templates/inc/profile_button.html index e48ec2148..b63b25464 100644 --- a/netbox/templates/inc/profile_button.html +++ b/netbox/templates/inc/profile_button.html @@ -19,17 +19,17 @@ {% endif %}
  • - + Profile
  • - + Preferences
  • - + API Tokens
  • diff --git a/netbox/templates/users/api_tokens.html b/netbox/templates/users/api_tokens.html index 24b32cc9b..e1641468c 100644 --- a/netbox/templates/users/api_tokens.html +++ b/netbox/templates/users/api_tokens.html @@ -1,78 +1,25 @@ {% extends 'users/base.html' %} {% load helpers %} +{% load render_table from django_tables2 %} {% block title %}API Tokens{% endblock %} {% block content %} -
    -
    - {% for token in tokens %} -
    -
    -
    - Copy - Edit - Delete -
    - - {{ token.key }} - {% if token.is_expired %} - Expired - {% endif %} -
    -
    -
    -
    - Created
    - {{ token.created|annotated_date }} -
    -
    - Expires
    - {% if token.expires %} - {{ token.expires|annotated_date }} - {% else %} - Never - {% endif %} -
    -
    - Last Used
    - {% if token.last_used %} - {{ token.last_used|annotated_date }} - {% else %} - Never - {% endif %} -
    -
    - Create/Edit/Delete Operations
    - {% if token.write_enabled %} - Enabled - {% else %} - Disabled - {% endif %} -
    -
    - Allowed Source IPs
    - {% if token.allowed_ips %} - {{ token.allowed_ips|join:', ' }} - {% else %} - Any - {% endif %} -
    - {% if token.description %} -
    {{ token.description }} - {% endif %} -
    -
    - {% empty %} -
    You do not have any API tokens.
    -

    Tokens are used to authenticate REST and GraphQL API requests.

    - {% endfor %} - -
    +
    + +
    +
    +
    +
    +
    + {% render_table table 'inc/table.html' %} + {% include 'inc/paginator.html' with paginator=table.paginator page=table.page %} +
    +
    +
    {% endblock %} diff --git a/netbox/templates/users/base.html b/netbox/templates/users/base.html index cf4cd585d..58861ee90 100644 --- a/netbox/templates/users/base.html +++ b/netbox/templates/users/base.html @@ -3,18 +3,18 @@ {% block tabs %} {% endblock %} diff --git a/netbox/templates/users/password.html b/netbox/templates/users/password.html index 81082792d..02e80bb26 100644 --- a/netbox/templates/users/password.html +++ b/netbox/templates/users/password.html @@ -13,7 +13,7 @@ {% render_field form.new_password2 %}
    - Cancel + Cancel
    diff --git a/netbox/templates/users/preferences.html b/netbox/templates/users/preferences.html index 6ce60a458..f2c88db3c 100644 --- a/netbox/templates/users/preferences.html +++ b/netbox/templates/users/preferences.html @@ -79,7 +79,7 @@
    - Cancel + Cancel
    diff --git a/netbox/users/tables.py b/netbox/users/tables.py new file mode 100644 index 000000000..27547b955 --- /dev/null +++ b/netbox/users/tables.py @@ -0,0 +1,42 @@ +from .models import Token +from netbox.tables import NetBoxTable, columns + +__all__ = ( + 'TokenTable', +) + + +TOKEN = """{{ value }}""" + +ALLOWED_IPS = """{{ value|join:", " }}""" + +COPY_BUTTON = """ + + + +""" + + +class TokenTable(NetBoxTable): + key = columns.TemplateColumn( + template_code=TOKEN + ) + write_enabled = columns.BooleanColumn( + verbose_name='Write' + ) + created = columns.DateColumn() + expired = columns.DateColumn() + last_used = columns.DateTimeColumn() + allowed_ips = columns.TemplateColumn( + template_code=ALLOWED_IPS + ) + actions = columns.ActionsColumn( + actions=('edit', 'delete'), + extra_buttons=COPY_BUTTON + ) + + class Meta(NetBoxTable.Meta): + model = Token + fields = ( + 'pk', 'key', 'write_enabled', 'created', 'expires', 'last_used', 'allowed_ips', 'description', + ) diff --git a/netbox/users/urls.py b/netbox/users/urls.py index 0cfcfc9de..62b17a663 100644 --- a/netbox/users/urls.py +++ b/netbox/users/urls.py @@ -2,7 +2,7 @@ from django.urls import path from . import views -app_name = 'user' +app_name = 'users' urlpatterns = [ path('profile/', views.ProfileView.as_view(), name='profile'), diff --git a/netbox/users/views.py b/netbox/users/views.py index 6a923e77e..aabdd6774 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -21,6 +21,7 @@ from netbox.config import get_config from utilities.forms import ConfirmationForm from .forms import LoginForm, PasswordChangeForm, TokenForm, UserConfigForm from .models import Token +from .tables import TokenTable # @@ -157,7 +158,7 @@ class UserConfigView(LoginRequiredMixin, View): form.save() messages.success(request, "Your preferences have been updated.") - return redirect('user:preferences') + return redirect('users:preferences') return render(request, self.template_name, { 'form': form, @@ -172,7 +173,7 @@ class ChangePasswordView(LoginRequiredMixin, View): # LDAP users cannot change their password here if getattr(request.user, 'ldap_username', None): messages.warning(request, "LDAP-authenticated user credentials cannot be changed within NetBox.") - return redirect('user:profile') + return redirect('users:profile') form = PasswordChangeForm(user=request.user) @@ -187,7 +188,7 @@ class ChangePasswordView(LoginRequiredMixin, View): form.save() update_session_auth_hash(request, form.user) messages.success(request, "Your password has been changed successfully.") - return redirect('user:profile') + return redirect('users:profile') return render(request, self.template_name, { 'form': form, @@ -204,10 +205,13 @@ class TokenListView(LoginRequiredMixin, View): def get(self, request): tokens = Token.objects.filter(user=request.user) + table = TokenTable(tokens) + table.configure(request) return render(request, 'users/api_tokens.html', { 'tokens': tokens, 'active_tab': 'api-tokens', + 'table': table, }) @@ -225,7 +229,7 @@ class TokenEditView(LoginRequiredMixin, View): return render(request, 'generic/object_edit.html', { 'object': token, 'form': form, - 'return_url': reverse('user:token_list'), + 'return_url': reverse('users:token_list'), }) def post(self, request, pk=None): @@ -248,12 +252,12 @@ class TokenEditView(LoginRequiredMixin, View): if '_addanother' in request.POST: return redirect(request.path) else: - return redirect('user:token_list') + return redirect('users:token_list') return render(request, 'generic/object_edit.html', { 'object': token, 'form': form, - 'return_url': reverse('user:token_list'), + 'return_url': reverse('users:token_list'), }) @@ -263,14 +267,14 @@ class TokenDeleteView(LoginRequiredMixin, View): token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk) initial_data = { - 'return_url': reverse('user:token_list'), + 'return_url': reverse('users:token_list'), } form = ConfirmationForm(initial=initial_data) return render(request, 'generic/object_delete.html', { 'object': token, 'form': form, - 'return_url': reverse('user:token_list'), + 'return_url': reverse('users:token_list'), }) def post(self, request, pk): @@ -280,10 +284,10 @@ class TokenDeleteView(LoginRequiredMixin, View): if form.is_valid(): token.delete() messages.success(request, "Token deleted") - return redirect('user:token_list') + return redirect('users:token_list') return render(request, 'generic/object_delete.html', { 'object': token, 'form': form, - 'return_url': reverse('user:token_list'), + 'return_url': reverse('users:token_list'), })