From c103649298508e29084ff9ea2afe24a2f5a80ab3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 3 Dec 2025 15:38:56 -0500 Subject: [PATCH] Closes #20917: Show example API usage for tokens --- netbox/account/views.py | 11 +++ .../templates/users/panels/token_example.html | 9 +++ netbox/templates/users/token.html | 69 ------------------- netbox/users/models/tokens.py | 9 +++ netbox/users/ui/__init__.py | 0 netbox/users/ui/panels.py | 25 +++++++ netbox/users/views.py | 10 +++ 7 files changed, 64 insertions(+), 69 deletions(-) create mode 100644 netbox/templates/users/panels/token_example.html create mode 100644 netbox/users/ui/__init__.py create mode 100644 netbox/users/ui/panels.py diff --git a/netbox/account/views.py b/netbox/account/views.py index da4aa6d74..7dd423919 100644 --- a/netbox/account/views.py +++ b/netbox/account/views.py @@ -25,10 +25,12 @@ from extras.models import Bookmark from extras.tables import BookmarkTable, NotificationTable, SubscriptionTable from netbox.authentication import get_auth_backend_display, get_saml_idps from netbox.config import get_config +from netbox.ui import layout from netbox.views import generic from users import forms from users.models import UserConfig from users.tables import TokenTable +from users.ui.panels import TokenExamplePanel, TokenPanel from utilities.request import safe_for_redirect from utilities.string import remove_linebreaks from utilities.views import register_model_view @@ -342,12 +344,21 @@ class UserTokenListView(LoginRequiredMixin, View): @register_model_view(UserToken) class UserTokenView(LoginRequiredMixin, View): + layout = layout.SimpleLayout( + left_panels=[ + TokenPanel(), + ], + right_panels=[ + TokenExamplePanel(), + ], + ) def get(self, request, pk): token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=pk) return render(request, 'account/token.html', { 'object': token, + 'layout': self.layout, }) diff --git a/netbox/templates/users/panels/token_example.html b/netbox/templates/users/panels/token_example.html new file mode 100644 index 000000000..ca70eac28 --- /dev/null +++ b/netbox/templates/users/panels/token_example.html @@ -0,0 +1,9 @@ +{% extends 'ui/panels/_base.html' %} + +{% block panel_content %} +
curl -X GET \
+-H "Authorization: {{ object.get_auth_header_prefix }}<TOKEN>" \
+-H "Content-Type: application/json" \
+-H "Accept: application/json; indent=4" \
+{{ request.scheme }}://{{ request.get_host }}{% url "api-status" %}
+{% endblock panel_content %} diff --git a/netbox/templates/users/token.html b/netbox/templates/users/token.html index 7d8ee1a2f..06e79dc84 100644 --- a/netbox/templates/users/token.html +++ b/netbox/templates/users/token.html @@ -1,73 +1,4 @@ {% extends 'generic/object.html' %} -{% load helpers %} {% load i18n %} -{% load render_table from django_tables2 %} {% block title %}{% trans "Token" %} {{ object }}{% endblock %} - -{% block subtitle %}{% endblock %} - -{% block content %} -
-
-
-

{% trans "Token" %}

- - - - - - {% if object.version == 1 %} - - - - - {% else %} - - - - - - - - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Version" %}{{ object.version }}
{% trans "Token" %}{{ object.partial }}
{% trans "Key" %}{{ object }}
{% trans "Pepper ID" %}{{ object.pepper_id }}
{% trans "User" %} - {{ object.user }} -
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Enabled" %}{% checkmark object.enabled %}
{% trans "Write enabled" %}{% checkmark object.write_enabled %}
{% trans "Created" %}{{ object.created|isodatetime }}
{% trans "Expires" %}{{ object.expires|isodatetime|placeholder }}
{% trans "Last used" %}{{ object.last_used|isodatetime|placeholder }}
{% trans "Allowed IPs" %}{{ object.allowed_ips|join:", "|placeholder }}
-
-
-
-{% endblock %} diff --git a/netbox/users/models/tokens.py b/netbox/users/models/tokens.py index f5b9f461c..bf51d6ef8 100644 --- a/netbox/users/models/tokens.py +++ b/netbox/users/models/tokens.py @@ -201,6 +201,15 @@ class Token(models.Model): """ return self.enabled and not self.is_expired + def get_auth_header_prefix(self): + """ + Return the HTTP Authorization header prefix for this token. + """ + if self.v1: + return 'Token ' + if self.v2: + return f'Bearer {TOKEN_PREFIX}{self.key}.' + def clean(self): super().clean() diff --git a/netbox/users/ui/__init__.py b/netbox/users/ui/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/netbox/users/ui/panels.py b/netbox/users/ui/panels.py new file mode 100644 index 000000000..92a4dd46a --- /dev/null +++ b/netbox/users/ui/panels.py @@ -0,0 +1,25 @@ +from django.utils.translation import gettext_lazy as _ + +from netbox.ui import actions, attrs, panels + + +class TokenPanel(panels.ObjectAttributesPanel): + version = attrs.NumericAttr('version') + key = attrs.TextAttr('key') + token = attrs.TextAttr('partial') + pepper_id = attrs.NumericAttr('pepper_id') + user = attrs.RelatedObjectAttr('user', linkify=True) + description = attrs.TextAttr('description') + enabled = attrs.BooleanAttr('enabled') + write_enabled = attrs.BooleanAttr('write_enabled') + expires = attrs.TextAttr('expires') + last_used = attrs.TextAttr('last_used') + allowed_ips = attrs.TextAttr('allowed_ips') + + +class TokenExamplePanel(panels.Panel): + template_name = 'users/panels/token_example.html' + title = _('Example Usage') + actions = [ + actions.CopyContent('token-example') + ] diff --git a/netbox/users/views.py b/netbox/users/views.py index 17b1e7f7d..09a88a14b 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -3,7 +3,9 @@ from django.db.models import Count from core.models import ObjectChange from core.tables import ObjectChangeTable from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename +from netbox.ui import layout from netbox.views import generic +from users.ui import panels from utilities.query import count_related from utilities.views import GetRelatedModelsMixin, register_model_view from . import filtersets, forms, tables @@ -26,6 +28,14 @@ class TokenListView(generic.ObjectListView): @register_model_view(Token) class TokenView(generic.ObjectView): queryset = Token.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.TokenPanel(), + ], + right_panels=[ + panels.TokenExamplePanel(), + ], + ) @register_model_view(Token, 'add', detail=False)