mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-17 01:02:18 -06:00
Compare commits
19 Commits
v4.3.3
...
67c4da607d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67c4da607d | ||
|
|
2905c124c3 | ||
|
|
0c95ac6b1a | ||
|
|
7338898ccb | ||
|
|
aa4533e331 | ||
|
|
ee94fb0b94 | ||
|
|
8fb8f4c75b | ||
|
|
e400a7cb29 | ||
|
|
e33793dc82 | ||
|
|
3b8841ee3b | ||
|
|
ea4c205a37 | ||
|
|
600f85ca83 | ||
|
|
2a5d3abafb | ||
|
|
9d6abcf57b | ||
|
|
fbf639fad1 | ||
|
|
9a46c8e30d | ||
|
|
4ca48843af | ||
|
|
874d020d57 | ||
|
|
d0129811e2 |
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
NetBox includes the ability to execute certain functions as background tasks. These include:
|
NetBox includes the ability to execute certain functions as background tasks. These include:
|
||||||
|
|
||||||
* [Report](../customization/reports.md) execution
|
|
||||||
* [Custom script](../customization/custom-scripts.md) execution
|
* [Custom script](../customization/custom-scripts.md) execution
|
||||||
* Synchronization of [remote data sources](../integrations/synchronized-data.md)
|
* Synchronization of [remote data sources](../integrations/synchronized-data.md)
|
||||||
|
* Housekeeping tasks
|
||||||
|
|
||||||
Additionally, NetBox plugins can enqueue their own background tasks. This is accomplished using the [Job model](../models/core/job.md). Background tasks are executed by the `rqworker` process(es).
|
Additionally, NetBox plugins can enqueue their own background tasks. This is accomplished using the [Job model](../models/core/job.md). Background tasks are executed by the `rqworker` process(es).
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ Check out the desired release by specifying its tag. For example:
|
|||||||
|
|
||||||
```
|
```
|
||||||
cd /opt/netbox && \
|
cd /opt/netbox && \
|
||||||
sudo git fetch && \
|
sudo git fetch --tags && \
|
||||||
sudo git checkout v4.2.7
|
sudo git checkout v4.2.7
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ A background job implements a basic [Job](../../models/core/job.md) executor for
|
|||||||
```python title="jobs.py"
|
```python title="jobs.py"
|
||||||
from netbox.jobs import JobRunner
|
from netbox.jobs import JobRunner
|
||||||
|
|
||||||
|
|
||||||
class MyTestJob(JobRunner):
|
class MyTestJob(JobRunner):
|
||||||
class Meta:
|
class Meta:
|
||||||
name = "My Test Job"
|
name = "My Test Job"
|
||||||
@@ -25,6 +24,8 @@ class MyTestJob(JobRunner):
|
|||||||
# your logic goes here
|
# your logic goes here
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Completed jobs will have their status updated to "completed" by default, or "errored" if an unhandled exception was raised by the `run()` method. To intentionally mark a job as failed, raise the `core.exceptions.JobFailed` exception. (Note that "failed" differs from "errored" in that a failure may be expected under certain conditions, whereas an error is not.)
|
||||||
|
|
||||||
You can schedule the background job from within your code (e.g. from a model's `save()` method or a view) by calling `MyTestJob.enqueue()`. This method passes through all arguments to `Job.enqueue()`. However, no `name` argument must be passed, as the background job name will be used instead.
|
You can schedule the background job from within your code (e.g. from a model's `save()` method or a view) by calling `MyTestJob.enqueue()`. This method passes through all arguments to `Job.enqueue()`. However, no `name` argument must be passed, as the background job name will be used instead.
|
||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
class SyncError(Exception):
|
'IncompatiblePluginError',
|
||||||
pass
|
'JobFailed',
|
||||||
|
'SyncError',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class IncompatiblePluginError(ImproperlyConfigured):
|
class IncompatiblePluginError(ImproperlyConfigured):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class JobFailed(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SyncError(Exception):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -187,15 +187,14 @@ class Job(models.Model):
|
|||||||
"""
|
"""
|
||||||
Mark the job as completed, optionally specifying a particular termination status.
|
Mark the job as completed, optionally specifying a particular termination status.
|
||||||
"""
|
"""
|
||||||
valid_statuses = JobStatusChoices.TERMINAL_STATE_CHOICES
|
if status not in JobStatusChoices.TERMINAL_STATE_CHOICES:
|
||||||
if status not in valid_statuses:
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
_("Invalid status for job termination. Choices are: {choices}").format(
|
_("Invalid status for job termination. Choices are: {choices}").format(
|
||||||
choices=', '.join(valid_statuses)
|
choices=', '.join(JobStatusChoices.TERMINAL_STATE_CHOICES)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mark the job as completed
|
# Set the job's status and completion time
|
||||||
self.status = status
|
self.status = status
|
||||||
if error:
|
if error:
|
||||||
self.error = error
|
self.error = error
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ def load_initial_data(apps, schema_editor):
|
|||||||
'gpu',
|
'gpu',
|
||||||
'hard_disk',
|
'hard_disk',
|
||||||
'memory',
|
'memory',
|
||||||
'power_supply'
|
'power_supply',
|
||||||
|
'expansion_card'
|
||||||
)
|
)
|
||||||
|
|
||||||
for name in initial_profiles:
|
for name in initial_profiles:
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "Expansion card",
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"connector_type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Connector type e.g. PCIe x4"
|
||||||
|
},
|
||||||
|
"bandwidth": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Total Bandwidth for this module"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ from django_pglocks import advisory_lock
|
|||||||
from rq.timeouts import JobTimeoutException
|
from rq.timeouts import JobTimeoutException
|
||||||
|
|
||||||
from core.choices import JobStatusChoices
|
from core.choices import JobStatusChoices
|
||||||
|
from core.exceptions import JobFailed
|
||||||
from core.models import Job, ObjectType
|
from core.models import Job, ObjectType
|
||||||
from netbox.constants import ADVISORY_LOCK_KEYS
|
from netbox.constants import ADVISORY_LOCK_KEYS
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
@@ -73,15 +74,21 @@ class JobRunner(ABC):
|
|||||||
This method is called by the Job Scheduler to handle the execution of all job commands. It will maintain the
|
This method is called by the Job Scheduler to handle the execution of all job commands. It will maintain the
|
||||||
job's metadata and handle errors. For periodic jobs, a new job is automatically scheduled using its `interval`.
|
job's metadata and handle errors. For periodic jobs, a new job is automatically scheduled using its `interval`.
|
||||||
"""
|
"""
|
||||||
|
logger = logging.getLogger('netbox.jobs')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
job.start()
|
job.start()
|
||||||
cls(job).run(*args, **kwargs)
|
cls(job).run(*args, **kwargs)
|
||||||
job.terminate()
|
job.terminate()
|
||||||
|
|
||||||
|
except JobFailed:
|
||||||
|
logger.warning(f"Job {job} failed")
|
||||||
|
job.terminate(status=JobStatusChoices.STATUS_FAILED)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=repr(e))
|
job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=repr(e))
|
||||||
if type(e) is JobTimeoutException:
|
if type(e) is JobTimeoutException:
|
||||||
logging.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
# If the executed job is a periodic job, schedule its next execution at the specified interval.
|
# If the executed job is a periodic job, schedule its next execution at the specified interval.
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Sequence, Optional
|
from typing import Sequence, Optional
|
||||||
|
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'get_model_item',
|
'get_model_item',
|
||||||
@@ -22,20 +24,46 @@ class MenuItemButton:
|
|||||||
link: str
|
link: str
|
||||||
title: str
|
title: str
|
||||||
icon_class: str
|
icon_class: str
|
||||||
|
_url: Optional[str] = None
|
||||||
permissions: Optional[Sequence[str]] = ()
|
permissions: Optional[Sequence[str]] = ()
|
||||||
color: Optional[str] = None
|
color: Optional[str] = None
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.link:
|
||||||
|
self._url = reverse_lazy(self.link)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return self._url
|
||||||
|
|
||||||
|
@url.setter
|
||||||
|
def url(self, value):
|
||||||
|
self._url = value
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MenuItem:
|
class MenuItem:
|
||||||
|
|
||||||
link: str
|
link: str
|
||||||
link_text: str
|
link_text: str
|
||||||
|
_url: Optional[str] = None
|
||||||
permissions: Optional[Sequence[str]] = ()
|
permissions: Optional[Sequence[str]] = ()
|
||||||
auth_required: Optional[bool] = False
|
auth_required: Optional[bool] = False
|
||||||
staff_only: Optional[bool] = False
|
staff_only: Optional[bool] = False
|
||||||
buttons: Optional[Sequence[MenuItemButton]] = ()
|
buttons: Optional[Sequence[MenuItemButton]] = ()
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.link:
|
||||||
|
self._url = reverse_lazy(self.link)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return self._url
|
||||||
|
|
||||||
|
@url.setter
|
||||||
|
def url(self, value):
|
||||||
|
self._url = value
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MenuGroup:
|
class MenuGroup:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from django.urls import reverse_lazy
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
@@ -32,17 +33,23 @@ class PluginMenuItem:
|
|||||||
This class represents a navigation menu item. This constitutes primary link and its text, but also allows for
|
This class represents a navigation menu item. This constitutes primary link and its text, but also allows for
|
||||||
specifying additional link buttons that appear to the right of the item in the van menu.
|
specifying additional link buttons that appear to the right of the item in the van menu.
|
||||||
|
|
||||||
Links are specified as Django reverse URL strings.
|
Links are specified as Django reverse URL strings suitable for rendering via {% url item.link %}.
|
||||||
|
Alternatively, a pre-generated url can be set on the object which will be rendered literally.
|
||||||
Buttons are each specified as a list of PluginMenuButton instances.
|
Buttons are each specified as a list of PluginMenuButton instances.
|
||||||
"""
|
"""
|
||||||
permissions = []
|
permissions = []
|
||||||
buttons = []
|
buttons = []
|
||||||
|
_url = None
|
||||||
|
|
||||||
def __init__(self, link, link_text, auth_required=False, staff_only=False, permissions=None, buttons=None):
|
def __init__(
|
||||||
|
self, link, link_text, auth_required=False, staff_only=False, permissions=None, buttons=None
|
||||||
|
):
|
||||||
self.link = link
|
self.link = link
|
||||||
self.link_text = link_text
|
self.link_text = link_text
|
||||||
self.auth_required = auth_required
|
self.auth_required = auth_required
|
||||||
self.staff_only = staff_only
|
self.staff_only = staff_only
|
||||||
|
if link:
|
||||||
|
self._url = reverse_lazy(link)
|
||||||
if permissions is not None:
|
if permissions is not None:
|
||||||
if type(permissions) not in (list, tuple):
|
if type(permissions) not in (list, tuple):
|
||||||
raise TypeError(_("Permissions must be passed as a tuple or list."))
|
raise TypeError(_("Permissions must be passed as a tuple or list."))
|
||||||
@@ -52,6 +59,14 @@ class PluginMenuItem:
|
|||||||
raise TypeError(_("Buttons must be passed as a tuple or list."))
|
raise TypeError(_("Buttons must be passed as a tuple or list."))
|
||||||
self.buttons = buttons
|
self.buttons = buttons
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return self._url
|
||||||
|
|
||||||
|
@url.setter
|
||||||
|
def url(self, value):
|
||||||
|
self._url = value
|
||||||
|
|
||||||
|
|
||||||
class PluginMenuButton:
|
class PluginMenuButton:
|
||||||
"""
|
"""
|
||||||
@@ -60,11 +75,14 @@ class PluginMenuButton:
|
|||||||
"""
|
"""
|
||||||
color = ButtonColorChoices.DEFAULT
|
color = ButtonColorChoices.DEFAULT
|
||||||
permissions = []
|
permissions = []
|
||||||
|
_url = None
|
||||||
|
|
||||||
def __init__(self, link, title, icon_class, color=None, permissions=None):
|
def __init__(self, link, title, icon_class, color=None, permissions=None):
|
||||||
self.link = link
|
self.link = link
|
||||||
self.title = title
|
self.title = title
|
||||||
self.icon_class = icon_class
|
self.icon_class = icon_class
|
||||||
|
if link:
|
||||||
|
self._url = reverse_lazy(link)
|
||||||
if permissions is not None:
|
if permissions is not None:
|
||||||
if type(permissions) not in (list, tuple):
|
if type(permissions) not in (list, tuple):
|
||||||
raise TypeError(_("Permissions must be passed as a tuple or list."))
|
raise TypeError(_("Permissions must be passed as a tuple or list."))
|
||||||
@@ -73,3 +91,11 @@ class PluginMenuButton:
|
|||||||
if color not in ButtonColorChoices.values():
|
if color not in ButtonColorChoices.values():
|
||||||
raise ValueError(_("Button color must be a choice within ButtonColorChoices."))
|
raise ValueError(_("Button color must be a choice within ButtonColorChoices."))
|
||||||
self.color = color
|
self.color = color
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return self._url
|
||||||
|
|
||||||
|
@url.setter
|
||||||
|
def url(self, value):
|
||||||
|
self._url = value
|
||||||
|
|||||||
@@ -7,11 +7,15 @@ from django_rq import get_queue
|
|||||||
from ..jobs import *
|
from ..jobs import *
|
||||||
from core.models import DataSource, Job
|
from core.models import DataSource, Job
|
||||||
from core.choices import JobStatusChoices
|
from core.choices import JobStatusChoices
|
||||||
|
from core.exceptions import JobFailed
|
||||||
|
from utilities.testing import disable_warnings
|
||||||
|
|
||||||
|
|
||||||
class TestJobRunner(JobRunner):
|
class TestJobRunner(JobRunner):
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
pass
|
if kwargs.get('make_fail', False):
|
||||||
|
raise JobFailed()
|
||||||
|
|
||||||
|
|
||||||
class JobRunnerTestCase(TestCase):
|
class JobRunnerTestCase(TestCase):
|
||||||
@@ -49,6 +53,12 @@ class JobRunnerTest(JobRunnerTestCase):
|
|||||||
|
|
||||||
self.assertEqual(job.status, JobStatusChoices.STATUS_COMPLETED)
|
self.assertEqual(job.status, JobStatusChoices.STATUS_COMPLETED)
|
||||||
|
|
||||||
|
def test_handle_failed(self):
|
||||||
|
with disable_warnings('netbox.jobs'):
|
||||||
|
job = TestJobRunner.enqueue(immediate=True, make_fail=True)
|
||||||
|
|
||||||
|
self.assertEqual(job.status, JobStatusChoices.STATUS_FAILED)
|
||||||
|
|
||||||
def test_handle_errored(self):
|
def test_handle_errored(self):
|
||||||
class ErroredJobRunner(TestJobRunner):
|
class ErroredJobRunner(TestJobRunner):
|
||||||
EXP = Exception('Test error')
|
EXP = Exception('Test error')
|
||||||
|
|||||||
2
netbox/project-static/dist/netbox.css
vendored
2
netbox/project-static/dist/netbox.css
vendored
File diff suppressed because one or more lines are too long
4
netbox/project-static/styles/custom/racks.scss
Normal file
4
netbox/project-static/styles/custom/racks.scss
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.rack-loading-container {
|
||||||
|
min-height: 200px;
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
@@ -27,3 +27,4 @@
|
|||||||
@import 'custom/markdown';
|
@import 'custom/markdown';
|
||||||
@import 'custom/misc';
|
@import 'custom/misc';
|
||||||
@import 'custom/notifications';
|
@import 'custom/notifications';
|
||||||
|
@import 'custom/racks';
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div style="margin-left: -30px">
|
<div style="margin-left: -30px">
|
||||||
<object data="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{face}}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}" class="rack_elevation" aria-label="{% trans "Rack elevation" %}"></object>
|
<div
|
||||||
|
hx-get="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{ face }}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}"
|
||||||
|
hx-trigger="intersect"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
aria-label="{% trans "Rack elevation" %}"
|
||||||
|
>
|
||||||
|
<div class="d-flex justify-content-center align-items-center rack-loading-container">
|
||||||
|
<div class="spinner-border" role="status">
|
||||||
|
<span class="visually-hidden">{% trans "Loading..." %}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center mt-3">
|
<div class="text-center mt-3">
|
||||||
<a class="btn btn-outline-primary" href="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{face}}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}" hx-boost="false">
|
<a class="btn btn-outline-primary" href="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{face}}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}" hx-boost="false">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-06-26 05:02+0000\n"
|
"POT-Creation-Date: 2025-07-03 05:04+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -20,7 +20,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: netbox/account/tables.py:27 netbox/templates/account/token.html:22
|
#: netbox/account/tables.py:27 netbox/templates/account/token.html:22
|
||||||
#: netbox/templates/users/token.html:17 netbox/users/forms/bulk_import.py:39
|
#: netbox/templates/users/token.html:17 netbox/users/forms/bulk_import.py:39
|
||||||
#: netbox/users/forms/model_forms.py:112
|
#: netbox/users/forms/model_forms.py:113
|
||||||
msgid "Key"
|
msgid "Key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: netbox/account/tables.py:45 netbox/templates/account/token.html:55
|
#: netbox/account/tables.py:45 netbox/templates/account/token.html:55
|
||||||
#: netbox/templates/users/token.html:47 netbox/users/forms/bulk_edit.py:122
|
#: netbox/templates/users/token.html:47 netbox/users/forms/bulk_edit.py:122
|
||||||
#: netbox/users/forms/model_forms.py:124
|
#: netbox/users/forms/model_forms.py:125
|
||||||
msgid "Allowed IPs"
|
msgid "Allowed IPs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -705,7 +705,7 @@ msgstr ""
|
|||||||
#: netbox/dcim/tables/devices.py:852 netbox/dcim/tables/power.py:77
|
#: netbox/dcim/tables/devices.py:852 netbox/dcim/tables/power.py:77
|
||||||
#: netbox/dcim/tables/racks.py:141 netbox/extras/forms/bulk_import.py:42
|
#: netbox/dcim/tables/racks.py:141 netbox/extras/forms/bulk_import.py:42
|
||||||
#: netbox/extras/tables/tables.py:449 netbox/extras/tables/tables.py:509
|
#: netbox/extras/tables/tables.py:449 netbox/extras/tables/tables.py:509
|
||||||
#: netbox/netbox/tables/tables.py:272 netbox/templates/circuits/circuit.html:30
|
#: netbox/netbox/tables/tables.py:274 netbox/templates/circuits/circuit.html:30
|
||||||
#: netbox/templates/circuits/virtualcircuit.html:39
|
#: netbox/templates/circuits/virtualcircuit.html:39
|
||||||
#: netbox/templates/circuits/virtualcircuittermination.html:64
|
#: netbox/templates/circuits/virtualcircuittermination.html:64
|
||||||
#: netbox/templates/core/datasource.html:38 netbox/templates/dcim/cable.html:15
|
#: netbox/templates/core/datasource.html:38 netbox/templates/dcim/cable.html:15
|
||||||
@@ -804,7 +804,7 @@ msgstr ""
|
|||||||
#: netbox/templates/vpn/l2vpn.html:26 netbox/templates/vpn/tunnel.html:25
|
#: netbox/templates/vpn/l2vpn.html:26 netbox/templates/vpn/tunnel.html:25
|
||||||
#: netbox/templates/wireless/wirelesslan.html:22
|
#: netbox/templates/wireless/wirelesslan.html:22
|
||||||
#: netbox/templates/wireless/wirelesslink.html:17
|
#: netbox/templates/wireless/wirelesslink.html:17
|
||||||
#: netbox/users/forms/filtersets.py:32 netbox/users/forms/model_forms.py:194
|
#: netbox/users/forms/filtersets.py:32 netbox/users/forms/model_forms.py:195
|
||||||
#: netbox/virtualization/forms/bulk_edit.py:71
|
#: netbox/virtualization/forms/bulk_edit.py:71
|
||||||
#: netbox/virtualization/forms/bulk_edit.py:100
|
#: netbox/virtualization/forms/bulk_edit.py:100
|
||||||
#: netbox/virtualization/forms/bulk_import.py:55
|
#: netbox/virtualization/forms/bulk_import.py:55
|
||||||
@@ -972,7 +972,7 @@ msgstr ""
|
|||||||
#: netbox/ipam/forms/filtersets.py:406 netbox/ipam/forms/filtersets.py:492
|
#: netbox/ipam/forms/filtersets.py:406 netbox/ipam/forms/filtersets.py:492
|
||||||
#: netbox/ipam/forms/filtersets.py:505 netbox/ipam/forms/filtersets.py:530
|
#: netbox/ipam/forms/filtersets.py:505 netbox/ipam/forms/filtersets.py:530
|
||||||
#: netbox/ipam/forms/filtersets.py:601 netbox/ipam/forms/filtersets.py:619
|
#: netbox/ipam/forms/filtersets.py:601 netbox/ipam/forms/filtersets.py:619
|
||||||
#: netbox/netbox/tables/tables.py:288 netbox/templates/dcim/moduletype.html:68
|
#: netbox/netbox/tables/tables.py:290 netbox/templates/dcim/moduletype.html:68
|
||||||
#: netbox/virtualization/forms/filtersets.py:46
|
#: netbox/virtualization/forms/filtersets.py:46
|
||||||
#: netbox/virtualization/forms/filtersets.py:109
|
#: netbox/virtualization/forms/filtersets.py:109
|
||||||
#: netbox/virtualization/forms/filtersets.py:204
|
#: netbox/virtualization/forms/filtersets.py:204
|
||||||
@@ -1369,7 +1369,7 @@ msgstr ""
|
|||||||
#: netbox/templates/extras/configcontext.html:60
|
#: netbox/templates/extras/configcontext.html:60
|
||||||
#: netbox/templates/ipam/ipaddress.html:59
|
#: netbox/templates/ipam/ipaddress.html:59
|
||||||
#: netbox/templates/ipam/vlan_edit.html:42
|
#: netbox/templates/ipam/vlan_edit.html:42
|
||||||
#: netbox/tenancy/forms/filtersets.py:87 netbox/users/forms/model_forms.py:314
|
#: netbox/tenancy/forms/filtersets.py:87 netbox/users/forms/model_forms.py:315
|
||||||
msgid "Assignment"
|
msgid "Assignment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2179,7 +2179,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: netbox/core/data_backends.py:56 netbox/templates/account/base.html:23
|
#: netbox/core/data_backends.py:56 netbox/templates/account/base.html:23
|
||||||
#: netbox/templates/account/password.html:12
|
#: netbox/templates/account/password.html:12
|
||||||
#: netbox/users/forms/model_forms.py:170
|
#: netbox/users/forms/model_forms.py:171
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2231,7 +2231,7 @@ msgstr ""
|
|||||||
#: netbox/extras/forms/filtersets.py:335 netbox/extras/tables/tables.py:166
|
#: netbox/extras/forms/filtersets.py:335 netbox/extras/tables/tables.py:166
|
||||||
#: netbox/extras/tables/tables.py:267 netbox/extras/tables/tables.py:300
|
#: netbox/extras/tables/tables.py:267 netbox/extras/tables/tables.py:300
|
||||||
#: netbox/extras/tables/tables.py:459 netbox/netbox/preferences.py:22
|
#: netbox/extras/tables/tables.py:459 netbox/netbox/preferences.py:22
|
||||||
#: netbox/templates/core/datasource.html:42
|
#: netbox/netbox/preferences.py:61 netbox/templates/core/datasource.html:42
|
||||||
#: netbox/templates/dcim/interface.html:61
|
#: netbox/templates/dcim/interface.html:61
|
||||||
#: netbox/templates/extras/customlink.html:17
|
#: netbox/templates/extras/customlink.html:17
|
||||||
#: netbox/templates/extras/eventrule.html:17
|
#: netbox/templates/extras/eventrule.html:17
|
||||||
@@ -2346,7 +2346,7 @@ msgstr ""
|
|||||||
#: netbox/templates/users/user.html:4 netbox/templates/users/user.html:12
|
#: netbox/templates/users/user.html:4 netbox/templates/users/user.html:12
|
||||||
#: netbox/users/filtersets.py:107 netbox/users/filtersets.py:174
|
#: netbox/users/filtersets.py:107 netbox/users/filtersets.py:174
|
||||||
#: netbox/users/forms/filtersets.py:84 netbox/users/forms/filtersets.py:125
|
#: netbox/users/forms/filtersets.py:84 netbox/users/forms/filtersets.py:125
|
||||||
#: netbox/users/forms/model_forms.py:155 netbox/users/forms/model_forms.py:192
|
#: netbox/users/forms/model_forms.py:156 netbox/users/forms/model_forms.py:193
|
||||||
#: netbox/users/tables.py:19
|
#: netbox/users/tables.py:19
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2449,7 +2449,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: netbox/core/forms/model_forms.py:170 netbox/dcim/forms/filtersets.py:752
|
#: netbox/core/forms/model_forms.py:170 netbox/dcim/forms/filtersets.py:752
|
||||||
#: netbox/templates/core/inc/config_data.html:127
|
#: netbox/templates/core/inc/config_data.html:127
|
||||||
#: netbox/users/forms/model_forms.py:64
|
#: netbox/users/forms/model_forms.py:65
|
||||||
msgid "Miscellaneous"
|
msgid "Miscellaneous"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2738,12 +2738,12 @@ msgstr ""
|
|||||||
msgid "Jobs cannot be assigned to this object type ({type})."
|
msgid "Jobs cannot be assigned to this object type ({type})."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/jobs.py:193
|
#: netbox/core/models/jobs.py:192
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid status for job termination. Choices are: {choices}"
|
msgid "Invalid status for job termination. Choices are: {choices}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/jobs.py:235
|
#: netbox/core/models/jobs.py:234
|
||||||
msgid ""
|
msgid ""
|
||||||
"enqueue() cannot be called with values for both schedule_at and immediate."
|
"enqueue() cannot be called with values for both schedule_at and immediate."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2763,7 +2763,7 @@ msgstr ""
|
|||||||
#: netbox/extras/tables/tables.py:341 netbox/extras/tables/tables.py:373
|
#: netbox/extras/tables/tables.py:341 netbox/extras/tables/tables.py:373
|
||||||
#: netbox/extras/tables/tables.py:453 netbox/extras/tables/tables.py:514
|
#: netbox/extras/tables/tables.py:453 netbox/extras/tables/tables.py:514
|
||||||
#: netbox/extras/tables/tables.py:637 netbox/extras/tables/tables.py:677
|
#: netbox/extras/tables/tables.py:637 netbox/extras/tables/tables.py:677
|
||||||
#: netbox/extras/tables/tables.py:731 netbox/netbox/tables/tables.py:276
|
#: netbox/extras/tables/tables.py:731 netbox/netbox/tables/tables.py:278
|
||||||
#: netbox/templates/core/objectchange.html:58
|
#: netbox/templates/core/objectchange.html:58
|
||||||
#: netbox/templates/extras/eventrule.html:78
|
#: netbox/templates/extras/eventrule.html:78
|
||||||
#: netbox/templates/extras/journalentry.html:18
|
#: netbox/templates/extras/journalentry.html:18
|
||||||
@@ -2801,7 +2801,7 @@ msgstr ""
|
|||||||
#: netbox/core/tables/jobs.py:10 netbox/core/tables/tasks.py:76
|
#: netbox/core/tables/jobs.py:10 netbox/core/tables/tasks.py:76
|
||||||
#: netbox/dcim/tables/devicetypes.py:169 netbox/extras/tables/tables.py:230
|
#: netbox/dcim/tables/devicetypes.py:169 netbox/extras/tables/tables.py:230
|
||||||
#: netbox/extras/tables/tables.py:504 netbox/extras/tables/tables.py:702
|
#: netbox/extras/tables/tables.py:504 netbox/extras/tables/tables.py:702
|
||||||
#: netbox/netbox/tables/tables.py:221
|
#: netbox/netbox/tables/tables.py:223
|
||||||
#: netbox/templates/dcim/virtualchassis_edit.html:56
|
#: netbox/templates/dcim/virtualchassis_edit.html:56
|
||||||
#: netbox/utilities/forms/forms.py:73 netbox/wireless/tables/wirelesslink.py:16
|
#: netbox/utilities/forms/forms.py:73 netbox/wireless/tables/wirelesslink.py:16
|
||||||
msgid "ID"
|
msgid "ID"
|
||||||
@@ -3381,8 +3381,9 @@ msgid "Three-phase"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/choices.py:1657 netbox/extras/choices.py:53
|
#: netbox/dcim/choices.py:1657 netbox/extras/choices.py:53
|
||||||
#: netbox/netbox/preferences.py:21 netbox/templates/extras/customfield.html:78
|
#: netbox/netbox/preferences.py:21 netbox/netbox/preferences.py:60
|
||||||
#: netbox/vpn/choices.py:20 netbox/wireless/choices.py:27
|
#: netbox/templates/extras/customfield.html:78 netbox/vpn/choices.py:20
|
||||||
|
#: netbox/wireless/choices.py:27
|
||||||
msgid "Disabled"
|
msgid "Disabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8201,7 +8202,7 @@ msgstr ""
|
|||||||
#: netbox/extras/forms/model_forms.py:254
|
#: netbox/extras/forms/model_forms.py:254
|
||||||
#: netbox/extras/forms/model_forms.py:297
|
#: netbox/extras/forms/model_forms.py:297
|
||||||
#: netbox/extras/forms/model_forms.py:450
|
#: netbox/extras/forms/model_forms.py:450
|
||||||
#: netbox/extras/forms/model_forms.py:567 netbox/users/forms/model_forms.py:276
|
#: netbox/extras/forms/model_forms.py:567 netbox/users/forms/model_forms.py:277
|
||||||
msgid "Object types"
|
msgid "Object types"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8297,8 +8298,8 @@ msgstr ""
|
|||||||
#: netbox/extras/forms/bulk_import.py:275
|
#: netbox/extras/forms/bulk_import.py:275
|
||||||
#: netbox/extras/forms/model_forms.py:398 netbox/netbox/navigation/menu.py:413
|
#: netbox/extras/forms/model_forms.py:398 netbox/netbox/navigation/menu.py:413
|
||||||
#: netbox/templates/extras/notificationgroup.html:41
|
#: netbox/templates/extras/notificationgroup.html:41
|
||||||
#: netbox/templates/users/group.html:29 netbox/users/forms/model_forms.py:236
|
#: netbox/templates/users/group.html:29 netbox/users/forms/model_forms.py:237
|
||||||
#: netbox/users/forms/model_forms.py:248 netbox/users/forms/model_forms.py:300
|
#: netbox/users/forms/model_forms.py:249 netbox/users/forms/model_forms.py:301
|
||||||
#: netbox/users/tables.py:102
|
#: netbox/users/tables.py:102
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8314,8 +8315,8 @@ msgstr ""
|
|||||||
#: netbox/templates/tenancy/contact.html:21
|
#: netbox/templates/tenancy/contact.html:21
|
||||||
#: netbox/tenancy/forms/bulk_edit.py:139 netbox/tenancy/forms/filtersets.py:78
|
#: netbox/tenancy/forms/bulk_edit.py:139 netbox/tenancy/forms/filtersets.py:78
|
||||||
#: netbox/tenancy/forms/model_forms.py:99 netbox/tenancy/tables/contacts.py:64
|
#: netbox/tenancy/forms/model_forms.py:99 netbox/tenancy/tables/contacts.py:64
|
||||||
#: netbox/users/forms/model_forms.py:181 netbox/users/forms/model_forms.py:193
|
#: netbox/users/forms/model_forms.py:182 netbox/users/forms/model_forms.py:194
|
||||||
#: netbox/users/forms/model_forms.py:305 netbox/users/tables.py:35
|
#: netbox/users/forms/model_forms.py:306 netbox/users/tables.py:35
|
||||||
#: netbox/users/tables.py:106
|
#: netbox/users/tables.py:106
|
||||||
msgid "Groups"
|
msgid "Groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -10212,7 +10213,7 @@ msgid ""
|
|||||||
"One of parent or parent_object_id must be included with parent_object_type"
|
"One of parent or parent_object_id must be included with parent_object_type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/ipam/forms/bulk_import.py:638
|
#: netbox/ipam/forms/bulk_import.py:641
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{ip} is not assigned to this parent."
|
msgid "{ip} is not assigned to this parent."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -11834,9 +11835,9 @@ msgstr ""
|
|||||||
msgid "API Tokens"
|
msgid "API Tokens"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/netbox/navigation/menu.py:460 netbox/users/forms/model_forms.py:187
|
#: netbox/netbox/navigation/menu.py:460 netbox/users/forms/model_forms.py:188
|
||||||
#: netbox/users/forms/model_forms.py:195 netbox/users/forms/model_forms.py:242
|
#: netbox/users/forms/model_forms.py:196 netbox/users/forms/model_forms.py:243
|
||||||
#: netbox/users/forms/model_forms.py:249
|
#: netbox/users/forms/model_forms.py:250
|
||||||
msgid "Permissions"
|
msgid "Permissions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -11959,11 +11960,19 @@ msgstr ""
|
|||||||
msgid "Where the paginator controls will be displayed relative to a table"
|
msgid "Where the paginator controls will be displayed relative to a table"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/netbox/preferences.py:60
|
#: netbox/netbox/preferences.py:58
|
||||||
|
msgid "Striped table rows"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: netbox/netbox/preferences.py:63
|
||||||
|
msgid "Render table rows with alternating colors to increase readability"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: netbox/netbox/preferences.py:68
|
||||||
msgid "Data format"
|
msgid "Data format"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/netbox/preferences.py:65
|
#: netbox/netbox/preferences.py:73
|
||||||
msgid "The preferred syntax for displaying generic data within the UI"
|
msgid "The preferred syntax for displaying generic data within the UI"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -12062,12 +12071,12 @@ msgstr ""
|
|||||||
msgid "No {model_name} found"
|
msgid "No {model_name} found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/netbox/tables/tables.py:281
|
#: netbox/netbox/tables/tables.py:283
|
||||||
#: netbox/templates/generic/bulk_import.html:117
|
#: netbox/templates/generic/bulk_import.html:117
|
||||||
msgid "Field"
|
msgid "Field"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/netbox/tables/tables.py:284
|
#: netbox/netbox/tables/tables.py:286
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -13716,7 +13725,7 @@ msgstr ""
|
|||||||
#: netbox/templates/dcim/virtualchassis_add_member.html:27
|
#: netbox/templates/dcim/virtualchassis_add_member.html:27
|
||||||
#: netbox/templates/generic/object_edit.html:78
|
#: netbox/templates/generic/object_edit.html:78
|
||||||
#: netbox/templates/users/objectpermission.html:31
|
#: netbox/templates/users/objectpermission.html:31
|
||||||
#: netbox/users/forms/filtersets.py:67 netbox/users/forms/model_forms.py:312
|
#: netbox/users/forms/filtersets.py:67 netbox/users/forms/model_forms.py:313
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -14874,7 +14883,7 @@ msgid "View"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/templates/users/objectpermission.html:52
|
#: netbox/templates/users/objectpermission.html:52
|
||||||
#: netbox/users/forms/model_forms.py:315
|
#: netbox/users/forms/model_forms.py:316
|
||||||
msgid "Constraints"
|
msgid "Constraints"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -15358,60 +15367,60 @@ msgstr ""
|
|||||||
msgid "Can Delete"
|
msgid "Can Delete"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:62
|
#: netbox/users/forms/model_forms.py:63
|
||||||
msgid "User Interface"
|
msgid "User Interface"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:114
|
#: netbox/users/forms/model_forms.py:115
|
||||||
msgid ""
|
msgid ""
|
||||||
"Keys must be at least 40 characters in length. <strong>Be sure to record "
|
"Keys must be at least 40 characters in length. <strong>Be sure to record "
|
||||||
"your key</strong> prior to submitting this form, as it may no longer be "
|
"your key</strong> prior to submitting this form, as it may no longer be "
|
||||||
"accessible once the token has been created."
|
"accessible once the token has been created."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:126
|
#: netbox/users/forms/model_forms.py:127
|
||||||
msgid ""
|
msgid ""
|
||||||
"Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for "
|
"Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for "
|
||||||
"no restrictions. Example: <code>10.1.1.0/24,192.168.10.16/32,2001:"
|
"no restrictions. Example: <code>10.1.1.0/24,192.168.10.16/32,2001:"
|
||||||
"db8:1::/64</code>"
|
"db8:1::/64</code>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:175
|
#: netbox/users/forms/model_forms.py:176
|
||||||
msgid "Confirm password"
|
msgid "Confirm password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:178
|
#: netbox/users/forms/model_forms.py:179
|
||||||
msgid "Enter the same password as before, for verification."
|
msgid "Enter the same password as before, for verification."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:227
|
#: netbox/users/forms/model_forms.py:228
|
||||||
msgid "Passwords do not match! Please check your input and try again."
|
msgid "Passwords do not match! Please check your input and try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:294
|
#: netbox/users/forms/model_forms.py:295
|
||||||
msgid "Additional actions"
|
msgid "Additional actions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:297
|
#: netbox/users/forms/model_forms.py:298
|
||||||
msgid "Actions granted in addition to those listed above"
|
msgid "Actions granted in addition to those listed above"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:313
|
#: netbox/users/forms/model_forms.py:314
|
||||||
msgid "Objects"
|
msgid "Objects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:325
|
#: netbox/users/forms/model_forms.py:326
|
||||||
msgid ""
|
msgid ""
|
||||||
"JSON expression of a queryset filter that will return only permitted "
|
"JSON expression of a queryset filter that will return only permitted "
|
||||||
"objects. Leave null to match all objects of this type. A list of multiple "
|
"objects. Leave null to match all objects of this type. A list of multiple "
|
||||||
"objects will result in a logical OR operation."
|
"objects will result in a logical OR operation."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:364
|
#: netbox/users/forms/model_forms.py:365
|
||||||
msgid "At least one action must be selected."
|
msgid "At least one action must be selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/forms/model_forms.py:382
|
#: netbox/users/forms/model_forms.py:383
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid filter for {model}: {error}"
|
msgid "Invalid filter for {model}: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -41,11 +41,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{% for item, buttons in items %}
|
{% for item, buttons in items %}
|
||||||
<div class="dropdown-item d-flex justify-content-between ps-3 py-0">
|
<div class="dropdown-item d-flex justify-content-between ps-3 py-0">
|
||||||
<a href="{% url item.link %}" class="d-inline-flex flex-fill py-1">{{ item.link_text }}</a>
|
<a href="{{ item.url }}" class="d-inline-flex flex-fill py-1">{{ item.link_text }}</a>
|
||||||
{% if buttons %}
|
{% if buttons %}
|
||||||
<div class="btn-group ms-1">
|
<div class="btn-group ms-1">
|
||||||
{% for button in buttons %}
|
{% for button in buttons %}
|
||||||
<a href="{% url button.link %}" class="btn btn-sm btn-{{ button.color|default:"outline" }} lh-2 px-2" title="{{ button.title }}">
|
<a href="{{ button.url }}" class="btn btn-sm btn-{{ button.color|default:"outline" }} lh-2 px-2" title="{{ button.title }}">
|
||||||
<i class="{{ button.icon_class }}"></i>
|
<i class="{{ button.icon_class }}"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
Reference in New Issue
Block a user