mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Merge branch 'develop' of https://github.com/digitalocean/netbox into 2813-addtenantgroupfilter
This commit is contained in:
commit
a91a79681f
61
CHANGELOG.md
61
CHANGELOG.md
@ -1,14 +1,73 @@
|
||||
v2.5.8 (FUTURE)
|
||||
v2.5.10 (2019-04-08)
|
||||
|
||||
## Enhancements
|
||||
|
||||
* [#3052](https://github.com/digitalocean/netbox/issues/3052) - Add Jinja2 support for export templates
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* [#2937](https://github.com/digitalocean/netbox/issues/2937) - Redirect to list view after editing an object from list view
|
||||
* [#3036](https://github.com/digitalocean/netbox/issues/3036) - DCIM interfaces API endpoint should not include VM interfaces
|
||||
* [#3039](https://github.com/digitalocean/netbox/issues/3039) - Fix exception when retrieving change object for a component template via API
|
||||
* [#3041](https://github.com/digitalocean/netbox/issues/3041) - Fix form widget for bulk cable label update
|
||||
* [#3044](https://github.com/digitalocean/netbox/issues/3044) - Ignore site/rack fields when connecting a new cable via device search
|
||||
* [#3046](https://github.com/digitalocean/netbox/issues/3046) - Fix exception at reports API endpoint
|
||||
* [#3047](https://github.com/digitalocean/netbox/issues/3047) - Fix exception when writing mac address for an interface via API
|
||||
|
||||
---
|
||||
|
||||
v2.5.9 (2019-04-01)
|
||||
|
||||
## Enhancements
|
||||
|
||||
* [#2933](https://github.com/digitalocean/netbox/issues/2933) - Add username to outbound webhook requests
|
||||
* [#3011](https://github.com/digitalocean/netbox/issues/3011) - Add SSL support for django-rq (requires django-rq v1.3.1+)
|
||||
* [#3025](https://github.com/digitalocean/netbox/issues/3025) - Add request ID to outbound webhook requests (for correlating all changes part of a single request)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* [#2207](https://github.com/digitalocean/netbox/issues/2207) - Fixes deterministic ordering of interfaces
|
||||
* [#2577](https://github.com/digitalocean/netbox/issues/2577) - Clarification of wording in API regarding filtering
|
||||
* [#2924](https://github.com/digitalocean/netbox/issues/2924) - Add interface type for QSFP28 50GE
|
||||
* [#2936](https://github.com/digitalocean/netbox/issues/2936) - Fix device role selection showing duplicate first entry
|
||||
* [#2998](https://github.com/digitalocean/netbox/issues/2998) - Limit device query to non-racked devices if no rack selected when creating a cable
|
||||
* [#3001](https://github.com/digitalocean/netbox/issues/3001) - Fix API representation of ObjectChange `action` and add `changed_object_type`
|
||||
* [#3014](https://github.com/digitalocean/netbox/issues/3014) - Fixes VM Role filtering
|
||||
* [#3019](https://github.com/digitalocean/netbox/issues/3019) - Fix tag population when running NetBox within a path
|
||||
* [#3022](https://github.com/digitalocean/netbox/issues/3022) - Add missing cable termination types to DCIM `_choices` endpoint
|
||||
* [#3026](https://github.com/digitalocean/netbox/issues/3026) - Tweak prefix/IP filter forms to filter using VRF ID rather than route distinguisher
|
||||
* [#3027](https://github.com/digitalocean/netbox/issues/3027) - Ignore empty local context data when rendering config contexts
|
||||
* [#3032](https://github.com/digitalocean/netbox/issues/3032) - Save assigned tags when creating a new secret
|
||||
|
||||
---
|
||||
|
||||
v2.5.8 (2019-03-11)
|
||||
|
||||
## Enhancements
|
||||
|
||||
* [#2435](https://github.com/digitalocean/netbox/issues/2435) - Printer friendly CSS
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* [#2065](https://github.com/digitalocean/netbox/issues/2065) - Correct documentation for VM interface serializer
|
||||
* [#2705](https://github.com/digitalocean/netbox/issues/2705) - Fix endpoint grouping in API docs
|
||||
* [#2781](https://github.com/digitalocean/netbox/issues/2781) - Fix filtering of sites/devices/VMs by multiple regions
|
||||
* [#2923](https://github.com/digitalocean/netbox/issues/2923) - Provider filter form's site field should be blank by default
|
||||
* [#2938](https://github.com/digitalocean/netbox/issues/2938) - Enforce deterministic ordering of device components returned by API
|
||||
* [#2939](https://github.com/digitalocean/netbox/issues/2939) - Exclude circuit terminations from API interface connections endpoint
|
||||
* [#2940](https://github.com/digitalocean/netbox/issues/2940) - Allow CSV import of prefixes/IPs to VRF without an RD assigned
|
||||
* [#2944](https://github.com/digitalocean/netbox/issues/2944) - Record the deletion of an IP address in the changelog of its parent interface (if any)
|
||||
* [#2952](https://github.com/digitalocean/netbox/issues/2952) - Added the `slug` field to the Tenant filter for use in the API and search function
|
||||
* [#2954](https://github.com/digitalocean/netbox/issues/2954) - Remove trailing slashes to fix root/template paths on Windows
|
||||
* [#2961](https://github.com/digitalocean/netbox/issues/2961) - Prevent exception when exporting inventory items belonging to unnamed devices
|
||||
* [#2962](https://github.com/digitalocean/netbox/issues/2962) - Increase ExportTemplate `mime_type` field length
|
||||
* [#2966](https://github.com/digitalocean/netbox/issues/2966) - Accept `null` cable length_unit via API
|
||||
* [#2972](https://github.com/digitalocean/netbox/issues/2972) - Improve ContentTypeField serializer to elegantly handle invalid data
|
||||
* [#2976](https://github.com/digitalocean/netbox/issues/2976) - Add delete button to tag view
|
||||
* [#2980](https://github.com/digitalocean/netbox/issues/2980) - Improve rendering time for API docs
|
||||
* [#2982](https://github.com/digitalocean/netbox/issues/2982) - Correct CSS class assignment on color picker
|
||||
* [#2984](https://github.com/digitalocean/netbox/issues/2984) - Fix logging of unlabeled cable ID on cable deletion
|
||||
* [#2985](https://github.com/digitalocean/netbox/issues/2985) - Fix pagination page length for rack elevations
|
||||
|
||||
---
|
||||
|
||||
|
@ -45,13 +45,13 @@ and run `upgrade.sh`.
|
||||
|
||||
## Supported SDK
|
||||
|
||||
- [pynetbox](https://github.com/digitalocean/pynetbox) Python API client library for Netbox.
|
||||
- [pynetbox](https://github.com/digitalocean/pynetbox) - A Python API client library for Netbox
|
||||
|
||||
## Community SDK
|
||||
|
||||
- [netbox-client-ruby](https://github.com/ninech/netbox-client-ruby) A ruby client library for Netbox v2.
|
||||
- [netbox-client-ruby](https://github.com/ninech/netbox-client-ruby) - A Ruby client library for Netbox
|
||||
- [powerbox](https://github.com/BatmanAMA/powerbox) - A PowerShell library for Netbox
|
||||
|
||||
## Ansible Inventory
|
||||
|
||||
- [netbox-as-ansible-inventory](https://github.com/AAbouZaid/netbox-as-ansible-inventory) Ansible dynamic inventory script for Netbox.
|
||||
|
||||
- [netbox-as-ansible-inventory](https://github.com/AAbouZaid/netbox-as-ansible-inventory) - Ansible dynamic inventory script for Netbox
|
||||
|
@ -261,7 +261,7 @@ A list of objects retrieved via the API can be filtered by passing one or more q
|
||||
GET /api/ipam/prefixes/?status=1
|
||||
```
|
||||
|
||||
The same filter can be incldued multiple times. These will effect a logical OR and return objects matching any of the given values. For example, the following will return all active and reserved prefixes:
|
||||
Certain filters can be included multiple times within a single request. These will effect a logical OR and return objects matching any of the given values. For example, the following will return all active and reserved prefixes:
|
||||
|
||||
```
|
||||
GET /api/ipam/prefixes/?status=1&status=2
|
||||
|
@ -283,6 +283,7 @@ REDIS = {
|
||||
'PASSWORD': '',
|
||||
'DATABASE': 0,
|
||||
'DEFAULT_TIMEOUT': 300,
|
||||
'SSL': False,
|
||||
}
|
||||
```
|
||||
|
||||
@ -315,3 +316,9 @@ The TCP port to use when connecting to the Redis server.
|
||||
Default: None
|
||||
|
||||
The password to use when authenticating to the Redis server (optional).
|
||||
|
||||
### SSL
|
||||
|
||||
Default: False
|
||||
|
||||
Use secure sockets layer to encrypt the connections to the Redis server.
|
||||
|
@ -11,7 +11,7 @@ CIRCUITTYPE_ACTIONS = """
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.circuit.change_circuittype %}
|
||||
<a href="{% url 'circuits:circuittype_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'circuits:circuittype_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -59,7 +59,7 @@ class CircuitTypeTable(BaseTable):
|
||||
name = tables.LinkColumn()
|
||||
circuit_count = tables.Column(verbose_name='Circuits')
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=CIRCUITTYPE_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name=''
|
||||
template_code=CIRCUITTYPE_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name=''
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
|
@ -1,3 +1,4 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
||||
@ -502,12 +503,16 @@ class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
#
|
||||
|
||||
class CableSerializer(ValidatedModelSerializer):
|
||||
termination_a_type = ContentTypeField()
|
||||
termination_b_type = ContentTypeField()
|
||||
termination_a_type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(model__in=CABLE_TERMINATION_TYPES)
|
||||
)
|
||||
termination_b_type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(model__in=CABLE_TERMINATION_TYPES)
|
||||
)
|
||||
termination_a = serializers.SerializerMethodField(read_only=True)
|
||||
termination_b = serializers.SerializerMethodField(read_only=True)
|
||||
status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
|
||||
length_unit = ChoiceField(choices=CABLE_LENGTH_UNIT_CHOICES, required=False)
|
||||
length_unit = ChoiceField(choices=CABLE_LENGTH_UNIT_CHOICES, required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = Cable
|
||||
|
@ -1,7 +1,7 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import F, Q
|
||||
from django.db.models import F
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.shortcuts import get_object_or_404
|
||||
from drf_yasg import openapi
|
||||
@ -35,7 +35,7 @@ from .exceptions import MissingFilterException
|
||||
|
||||
class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
fields = (
|
||||
(Cable, ['length_unit', 'status', 'type']),
|
||||
(Cable, ['length_unit', 'status', 'termination_a_type', 'termination_b_type', 'type']),
|
||||
(ConsolePort, ['connection_status']),
|
||||
(Device, ['face', 'status']),
|
||||
(DeviceType, ['subdevice_role']),
|
||||
@ -419,7 +419,9 @@ class PowerOutletViewSet(CableTraceMixin, ModelViewSet):
|
||||
|
||||
|
||||
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
||||
queryset = Interface.objects.select_related(
|
||||
queryset = Interface.objects.filter(
|
||||
device__isnull=False
|
||||
).select_related(
|
||||
'device', '_connected_interface', '_connected_circuittermination', 'cable'
|
||||
).prefetch_related(
|
||||
'ip_addresses', 'tags'
|
||||
|
@ -83,6 +83,7 @@ IFACE_FF_10GE_XENPAK = 1310
|
||||
IFACE_FF_10GE_X2 = 1320
|
||||
IFACE_FF_25GE_SFP28 = 1350
|
||||
IFACE_FF_40GE_QSFP_PLUS = 1400
|
||||
IFACE_FF_50GE_QSFP28 = 1420
|
||||
IFACE_FF_100GE_CFP = 1500
|
||||
IFACE_FF_100GE_CFP2 = 1510
|
||||
IFACE_FF_100GE_CFP4 = 1520
|
||||
@ -164,6 +165,7 @@ IFACE_FF_CHOICES = [
|
||||
[IFACE_FF_10GE_X2, 'X2 (10GE)'],
|
||||
[IFACE_FF_25GE_SFP28, 'SFP28 (25GE)'],
|
||||
[IFACE_FF_40GE_QSFP_PLUS, 'QSFP+ (40GE)'],
|
||||
[IFACE_FF_50GE_QSFP28, 'QSFP28 (50GE)'],
|
||||
[IFACE_FF_100GE_CFP, 'CFP (100GE)'],
|
||||
[IFACE_FF_100GE_CFP2, 'CFP2 (100GE)'],
|
||||
[IFACE_FF_200GE_CFP2, 'CFP2 (200GE)'],
|
||||
|
@ -31,7 +31,7 @@ class MACAddressField(models.Field):
|
||||
try:
|
||||
return EUI(value, version=48, dialect=mac_unix_expanded_uppercase)
|
||||
except AddrFormatError as e:
|
||||
raise ValidationError(e)
|
||||
raise ValidationError("Invalid MAC address format: {}".format(value))
|
||||
|
||||
def db_type(self, connection):
|
||||
return 'macaddr'
|
||||
|
@ -1678,7 +1678,6 @@ class DeviceFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/device-roles/",
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
)
|
||||
manufacturer_id = FilterChoiceField(
|
||||
@ -2675,12 +2674,12 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
status = forms.ChoiceField(
|
||||
choices=add_blank_choice(CONNECTION_STATUS_CHOICES),
|
||||
required=False,
|
||||
widget=StaticSelect2(),
|
||||
initial=''
|
||||
)
|
||||
label = forms.CharField(
|
||||
max_length=100,
|
||||
required=False,
|
||||
widget=StaticSelect2()
|
||||
required=False
|
||||
)
|
||||
color = forms.CharField(
|
||||
max_length=6,
|
||||
|
@ -64,11 +64,15 @@ class InterfaceManager(Manager):
|
||||
|
||||
The original `name` field is considered in its entirety to serve as a fallback in the event interfaces do not
|
||||
match any of the prescribed fields.
|
||||
|
||||
The `id` field is included to enforce deterministic ordering of interfaces in similar vein of other device
|
||||
components.
|
||||
"""
|
||||
|
||||
sql_col = '{}.name'.format(self.model._meta.db_table)
|
||||
ordering = [
|
||||
'_slot', '_subslot', '_position', '_subposition', '_type', '_id', '_channel', '_vc', 'name',
|
||||
'_slot', '_subslot', '_position', '_subposition', '_type', '_id', '_channel', '_vc', 'name', 'pk'
|
||||
|
||||
]
|
||||
|
||||
fields = {
|
||||
|
@ -2423,7 +2423,7 @@ class InventoryItem(ComponentModel):
|
||||
|
||||
def to_csv(self):
|
||||
return (
|
||||
self.device.name or '{' + self.device.pk + '}',
|
||||
self.device.name or '{{{}}}'.format(self.device.pk),
|
||||
self.name,
|
||||
self.manufacturer.name if self.manufacturer else None,
|
||||
self.part_id,
|
||||
@ -2557,16 +2557,15 @@ class Cable(ChangeLoggedModel):
|
||||
('termination_b_type', 'termination_b_id'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Create an ID string for use by __str__(). We have to save a copy of pk since it's nullified after .delete()
|
||||
# is called.
|
||||
self.id_string = '#{}'.format(self.pk)
|
||||
|
||||
def __str__(self):
|
||||
return self.label or self.id_string
|
||||
if self.label:
|
||||
return self.label
|
||||
|
||||
# Save a copy of the PK on the instance since it's nullified if .delete() is called
|
||||
if not hasattr(self, 'id_string'):
|
||||
self.id_string = '#{}'.format(self.pk)
|
||||
|
||||
return self.id_string
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:cable', args=[self.pk])
|
||||
|
@ -44,7 +44,7 @@ REGION_ACTIONS = """
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.dcim.change_region %}
|
||||
<a href="{% url 'dcim:region_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'dcim:region_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -56,7 +56,7 @@ RACKGROUP_ACTIONS = """
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
{% if perms.dcim.change_rackgroup %}
|
||||
<a href="{% url 'dcim:rackgroup_edit' pk=record.pk %}" class="btn btn-xs btn-warning" title="Edit">
|
||||
<a href="{% url 'dcim:rackgroup_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning" title="Edit">
|
||||
<i class="glyphicon glyphicon-pencil"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -67,7 +67,7 @@ RACKROLE_ACTIONS = """
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.dcim.change_rackrole %}
|
||||
<a href="{% url 'dcim:rackrole_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'dcim:rackrole_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -88,7 +88,7 @@ RACKRESERVATION_ACTIONS = """
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.dcim.change_rackreservation %}
|
||||
<a href="{% url 'dcim:rackreservation_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'dcim:rackreservation_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -97,7 +97,7 @@ MANUFACTURER_ACTIONS = """
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.dcim.change_manufacturer %}
|
||||
<a href="{% url 'dcim:manufacturer_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'dcim:manufacturer_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -106,7 +106,7 @@ DEVICEROLE_ACTIONS = """
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.dcim.change_devicerole %}
|
||||
<a href="{% url 'dcim:devicerole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'dcim:devicerole_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -131,7 +131,7 @@ PLATFORM_ACTIONS = """
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.dcim.change_platform %}
|
||||
<a href="{% url 'dcim:platform_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'dcim:platform_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -168,7 +168,7 @@ VIRTUALCHASSIS_ACTIONS = """
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.dcim.change_virtualchassis %}
|
||||
<a href="{% url 'dcim:virtualchassis_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'dcim:virtualchassis_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -196,7 +196,7 @@ class RegionTable(BaseTable):
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=REGION_ACTIONS,
|
||||
attrs={'td': {'class': 'text-right'}},
|
||||
attrs={'td': {'class': 'text-right noprint'}},
|
||||
verbose_name=''
|
||||
)
|
||||
|
||||
@ -239,7 +239,7 @@ class RackGroupTable(BaseTable):
|
||||
slug = tables.Column()
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=RACKGROUP_ACTIONS,
|
||||
attrs={'td': {'class': 'text-right'}},
|
||||
attrs={'td': {'class': 'text-right noprint'}},
|
||||
verbose_name=''
|
||||
)
|
||||
|
||||
@ -258,7 +258,7 @@ class RackRoleTable(BaseTable):
|
||||
rack_count = tables.Column(verbose_name='Racks')
|
||||
color = tables.TemplateColumn(COLOR_LABEL, verbose_name='Color')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
actions = tables.TemplateColumn(template_code=RACKROLE_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||
actions = tables.TemplateColumn(template_code=RACKROLE_ACTIONS, attrs={'td': {'class': 'text-right noprint'}},
|
||||
verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
@ -309,7 +309,7 @@ class RackReservationTable(BaseTable):
|
||||
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
|
||||
unit_list = tables.Column(orderable=False, verbose_name='Units')
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=RACKRESERVATION_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name=''
|
||||
template_code=RACKRESERVATION_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name=''
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
@ -327,7 +327,7 @@ class ManufacturerTable(BaseTable):
|
||||
devicetype_count = tables.Column(verbose_name='Device Types')
|
||||
platform_count = tables.Column(verbose_name='Platforms')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
actions = tables.TemplateColumn(template_code=MANUFACTURER_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||
actions = tables.TemplateColumn(template_code=MANUFACTURER_ACTIONS, attrs={'td': {'class': 'text-right noprint'}},
|
||||
verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
@ -463,7 +463,7 @@ class DeviceRoleTable(BaseTable):
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=DEVICEROLE_ACTIONS,
|
||||
attrs={'td': {'class': 'text-right'}},
|
||||
attrs={'td': {'class': 'text-right noprint'}},
|
||||
verbose_name=''
|
||||
)
|
||||
|
||||
@ -492,7 +492,7 @@ class PlatformTable(BaseTable):
|
||||
)
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=PLATFORM_ACTIONS,
|
||||
attrs={'td': {'class': 'text-right'}},
|
||||
attrs={'td': {'class': 'text-right noprint'}},
|
||||
verbose_name=''
|
||||
)
|
||||
|
||||
@ -779,7 +779,7 @@ class VirtualChassisTable(BaseTable):
|
||||
member_count = tables.Column(verbose_name='Members')
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=VIRTUALCHASSIS_ACTIONS,
|
||||
attrs={'td': {'class': 'text-right'}},
|
||||
attrs={'td': {'class': 'text-right noprint'}},
|
||||
verbose_name=''
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.core.paginator import EmptyPage, PageNotAnInteger
|
||||
@ -353,8 +354,9 @@ class RackElevationListView(View):
|
||||
total_count = racks.count()
|
||||
|
||||
# Pagination
|
||||
paginator = EnhancedPaginator(racks, 25)
|
||||
per_page = request.GET.get('per_page', settings.PAGINATE_COUNT)
|
||||
page_number = request.GET.get('page', 1)
|
||||
paginator = EnhancedPaginator(racks, per_page)
|
||||
try:
|
||||
page = paginator.page(page_number)
|
||||
except PageNotAnInteger:
|
||||
|
@ -1,3 +1,4 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework import serializers
|
||||
from taggit.models import Tag
|
||||
@ -15,7 +16,8 @@ from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantG
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from users.api.nested_serializers import NestedUserSerializer
|
||||
from utilities.api import (
|
||||
ChoiceField, ContentTypeField, get_serializer_for_model, SerializedPKRelatedField, ValidatedModelSerializer,
|
||||
ChoiceField, ContentTypeField, get_serializer_for_model, SerializerNotFound, SerializedPKRelatedField,
|
||||
ValidatedModelSerializer,
|
||||
)
|
||||
from .nested_serializers import *
|
||||
|
||||
@ -53,10 +55,17 @@ class RenderedGraphSerializer(serializers.ModelSerializer):
|
||||
#
|
||||
|
||||
class ExportTemplateSerializer(ValidatedModelSerializer):
|
||||
template_language = ChoiceField(
|
||||
choices=TEMPLATE_LANGUAGE_CHOICES,
|
||||
default=TEMPLATE_LANGUAGE_JINJA2
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ExportTemplate
|
||||
fields = ['id', 'content_type', 'name', 'description', 'template_code', 'mime_type', 'file_extension']
|
||||
fields = [
|
||||
'id', 'content_type', 'name', 'description', 'template_language', 'template_code', 'mime_type',
|
||||
'file_extension',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
@ -88,7 +97,9 @@ class TagSerializer(ValidatedModelSerializer):
|
||||
#
|
||||
|
||||
class ImageAttachmentSerializer(ValidatedModelSerializer):
|
||||
content_type = ContentTypeField()
|
||||
content_type = ContentTypeField(
|
||||
queryset=ContentType.objects.all()
|
||||
)
|
||||
parent = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@ -205,14 +216,25 @@ class ReportDetailSerializer(ReportSerializer):
|
||||
#
|
||||
|
||||
class ObjectChangeSerializer(serializers.ModelSerializer):
|
||||
user = NestedUserSerializer(read_only=True)
|
||||
content_type = ContentTypeField(read_only=True)
|
||||
changed_object = serializers.SerializerMethodField(read_only=True)
|
||||
user = NestedUserSerializer(
|
||||
read_only=True
|
||||
)
|
||||
action = ChoiceField(
|
||||
choices=OBJECTCHANGE_ACTION_CHOICES,
|
||||
read_only=True
|
||||
)
|
||||
changed_object_type = ContentTypeField(
|
||||
read_only=True
|
||||
)
|
||||
changed_object = serializers.SerializerMethodField(
|
||||
read_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ObjectChange
|
||||
fields = [
|
||||
'id', 'time', 'user', 'user_name', 'request_id', 'action', 'content_type', 'changed_object', 'object_data',
|
||||
'id', 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type', 'changed_object',
|
||||
'object_data',
|
||||
]
|
||||
|
||||
def get_changed_object(self, obj):
|
||||
@ -221,9 +243,14 @@ class ObjectChangeSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
if obj.changed_object is None:
|
||||
return None
|
||||
serializer = get_serializer_for_model(obj.changed_object, prefix='Nested')
|
||||
if serializer is None:
|
||||
|
||||
try:
|
||||
serializer = get_serializer_for_model(obj.changed_object, prefix='Nested')
|
||||
except SerializerNotFound:
|
||||
return obj.object_repr
|
||||
context = {'request': self.context['request']}
|
||||
context = {
|
||||
'request': self.context['request']
|
||||
}
|
||||
data = serializer(obj.changed_object, context=context).data
|
||||
|
||||
return data
|
||||
|
@ -10,7 +10,7 @@ from taggit.models import Tag
|
||||
|
||||
from extras import filters
|
||||
from extras.models import (
|
||||
ConfigContext, CustomField, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
|
||||
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
|
||||
)
|
||||
from extras.reports import get_report, get_reports
|
||||
from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
|
||||
@ -23,8 +23,9 @@ from . import serializers
|
||||
|
||||
class ExtrasFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
fields = (
|
||||
(CustomField, ['type']),
|
||||
(ExportTemplate, ['template_language']),
|
||||
(Graph, ['type']),
|
||||
(ObjectChange, ['action']),
|
||||
)
|
||||
|
||||
|
||||
|
@ -22,6 +22,7 @@ class ExtrasConfig(AppConfig):
|
||||
port=settings.REDIS_PORT,
|
||||
db=settings.REDIS_DATABASE,
|
||||
password=settings.REDIS_PASSWORD or None,
|
||||
ssl=settings.REDIS_SSL,
|
||||
)
|
||||
rs.ping()
|
||||
except redis.exceptions.ConnectionError:
|
||||
|
@ -56,6 +56,14 @@ EXPORTTEMPLATE_MODELS = [
|
||||
'cluster', 'virtualmachine', # Virtualization
|
||||
]
|
||||
|
||||
# ExportTemplate language choices
|
||||
TEMPLATE_LANGUAGE_DJANGO = 10
|
||||
TEMPLATE_LANGUAGE_JINJA2 = 20
|
||||
TEMPLATE_LANGUAGE_CHOICES = (
|
||||
(TEMPLATE_LANGUAGE_DJANGO, 'Django'),
|
||||
(TEMPLATE_LANGUAGE_JINJA2, 'Jinja2'),
|
||||
)
|
||||
|
||||
# Topology map types
|
||||
TOPOLOGYMAP_TYPE_NETWORK = 1
|
||||
TOPOLOGYMAP_TYPE_CONSOLE = 2
|
||||
|
@ -82,7 +82,7 @@ class ExportTemplateFilter(django_filters.FilterSet):
|
||||
|
||||
class Meta:
|
||||
model = ExportTemplate
|
||||
fields = ['content_type', 'name']
|
||||
fields = ['content_type', 'name', 'template_language']
|
||||
|
||||
|
||||
class TagFilter(django_filters.FilterSet):
|
||||
|
@ -4,7 +4,6 @@ from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from mptt.forms import TreeNodeMultipleChoiceField
|
||||
from taggit.forms import TagField
|
||||
from taggit.models import Tag
|
||||
|
||||
@ -12,7 +11,7 @@ from dcim.models import DeviceRole, Platform, Region, Site
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.forms import (
|
||||
add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ContentTypeSelect,
|
||||
FilterChoiceField, FilterTreeNodeMultipleChoiceField, LaxURLField, JSONField, SlugField,
|
||||
FilterChoiceField, LaxURLField, JSONField, SlugField,
|
||||
)
|
||||
from .constants import (
|
||||
CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
|
||||
|
@ -37,7 +37,7 @@ def _record_object_deleted(request, instance, **kwargs):
|
||||
if hasattr(instance, 'log_change'):
|
||||
instance.log_change(request.user, request.id, OBJECTCHANGE_ACTION_DELETE)
|
||||
|
||||
enqueue_webhooks(instance, OBJECTCHANGE_ACTION_DELETE)
|
||||
enqueue_webhooks(instance, request.user, request.id, OBJECTCHANGE_ACTION_DELETE)
|
||||
|
||||
|
||||
class ObjectChangeMiddleware(object):
|
||||
@ -83,7 +83,7 @@ class ObjectChangeMiddleware(object):
|
||||
obj.log_change(request.user, request.id, action)
|
||||
|
||||
# Enqueue webhooks
|
||||
enqueue_webhooks(obj, action)
|
||||
enqueue_webhooks(obj, request.user, request.id, action)
|
||||
|
||||
# Housekeeping: 1% chance of clearing out expired ObjectChanges
|
||||
if _thread_locals.changed_objects and settings.CHANGELOG_RETENTION and random.randint(1, 100) == 1:
|
||||
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.1.7 on 2019-03-05 18:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0016_exporttemplate_add_cable'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='exporttemplate',
|
||||
name='mime_type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
]
|
27
netbox/extras/migrations/0018_exporttemplate_add_jinja2.py
Normal file
27
netbox/extras/migrations/0018_exporttemplate_add_jinja2.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Generated by Django 2.1.7 on 2019-04-08 14:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def set_template_language(apps, schema_editor):
|
||||
"""
|
||||
Set the language for all existing ExportTemplates to Django (Jinja2 is the default for new ExportTemplates).
|
||||
"""
|
||||
ExportTemplate = apps.get_model('extras', 'ExportTemplate')
|
||||
ExportTemplate.objects.update(template_language=10)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0017_exporttemplate_mime_type_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exporttemplate',
|
||||
name='template_language',
|
||||
field=models.PositiveSmallIntegerField(default=20),
|
||||
),
|
||||
migrations.RunPython(set_template_language),
|
||||
]
|
@ -1,7 +1,6 @@
|
||||
from collections import OrderedDict
|
||||
from datetime import date
|
||||
|
||||
import graphviz
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@ -12,6 +11,8 @@ from django.db.models import F, Q
|
||||
from django.http import HttpResponse
|
||||
from django.template import Template, Context
|
||||
from django.urls import reverse
|
||||
import graphviz
|
||||
from jinja2 import Environment
|
||||
|
||||
from dcim.constants import CONNECTION_STATUS_CONNECTED
|
||||
from utilities.utils import deepmerge, foreground_color
|
||||
@ -355,9 +356,13 @@ class ExportTemplate(models.Model):
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
template_language = models.PositiveSmallIntegerField(
|
||||
choices=TEMPLATE_LANGUAGE_CHOICES,
|
||||
default=TEMPLATE_LANGUAGE_JINJA2
|
||||
)
|
||||
template_code = models.TextField()
|
||||
mime_type = models.CharField(
|
||||
max_length=15,
|
||||
max_length=50,
|
||||
blank=True
|
||||
)
|
||||
file_extension = models.CharField(
|
||||
@ -374,16 +379,36 @@ class ExportTemplate(models.Model):
|
||||
def __str__(self):
|
||||
return '{}: {}'.format(self.content_type, self.name)
|
||||
|
||||
def render(self, queryset):
|
||||
"""
|
||||
Render the contents of the template.
|
||||
"""
|
||||
context = {
|
||||
'queryset': queryset
|
||||
}
|
||||
|
||||
if self.template_language == TEMPLATE_LANGUAGE_DJANGO:
|
||||
template = Template(self.template_code)
|
||||
output = template.render(Context(context))
|
||||
|
||||
elif self.template_language == TEMPLATE_LANGUAGE_JINJA2:
|
||||
template = Environment().from_string(source=self.template_code)
|
||||
output = template.render(**context)
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
# Replace CRLF-style line terminators
|
||||
output = output.replace('\r\n', '\n')
|
||||
|
||||
return output
|
||||
|
||||
def render_to_response(self, queryset):
|
||||
"""
|
||||
Render the template to an HTTP response, delivered as a named file attachment
|
||||
"""
|
||||
template = Template(self.template_code)
|
||||
output = self.render(queryset)
|
||||
mime_type = 'text/plain' if not self.mime_type else self.mime_type
|
||||
output = template.render(Context({'queryset': queryset}))
|
||||
|
||||
# Replace CRLF-style line terminators
|
||||
output = output.replace('\r\n', '\n')
|
||||
|
||||
# Build the response
|
||||
response = HttpResponse(output, content_type=mime_type)
|
||||
@ -720,7 +745,7 @@ class ConfigContextModel(models.Model):
|
||||
data = deepmerge(data, context.data)
|
||||
|
||||
# If the object has local config context data defined, merge it last
|
||||
if self.local_context_data is not None:
|
||||
if self.local_context_data:
|
||||
data = deepmerge(data, self.local_context_data)
|
||||
|
||||
return data
|
||||
|
@ -68,7 +68,7 @@ class TagTable(BaseTable):
|
||||
)
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=TAG_ACTIONS,
|
||||
attrs={'td': {'class': 'text-right'}},
|
||||
attrs={'td': {'class': 'text-right noprint'}},
|
||||
verbose_name=''
|
||||
)
|
||||
|
||||
|
@ -9,7 +9,7 @@ from utilities.api import get_serializer_for_model
|
||||
from .constants import WEBHOOK_MODELS
|
||||
|
||||
|
||||
def enqueue_webhooks(instance, action):
|
||||
def enqueue_webhooks(instance, user, request_id, action):
|
||||
"""
|
||||
Find Webhook(s) assigned to this instance + action and enqueue them
|
||||
to be processed
|
||||
@ -47,5 +47,7 @@ def enqueue_webhooks(instance, action):
|
||||
serializer.data,
|
||||
instance._meta.model_name,
|
||||
action,
|
||||
str(datetime.datetime.now())
|
||||
str(datetime.datetime.now()),
|
||||
user.username,
|
||||
request_id
|
||||
)
|
||||
|
@ -10,7 +10,7 @@ from extras.constants import WEBHOOK_CT_JSON, WEBHOOK_CT_X_WWW_FORM_ENCODED, OBJ
|
||||
|
||||
|
||||
@job('default')
|
||||
def process_webhook(webhook, data, model_name, event, timestamp):
|
||||
def process_webhook(webhook, data, model_name, event, timestamp, username, request_id):
|
||||
"""
|
||||
Make a POST request to the defined Webhook
|
||||
"""
|
||||
@ -18,6 +18,8 @@ def process_webhook(webhook, data, model_name, event, timestamp):
|
||||
'event': dict(OBJECTCHANGE_ACTION_CHOICES)[event].lower(),
|
||||
'timestamp': timestamp,
|
||||
'model': model_name,
|
||||
'username': username,
|
||||
'request_id': request_id,
|
||||
'data': data
|
||||
}
|
||||
headers = {
|
||||
|
@ -128,6 +128,7 @@ class VLANSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
#
|
||||
|
||||
class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
family = ChoiceField(choices=AF_CHOICES, read_only=True)
|
||||
site = NestedSiteSerializer(required=False, allow_null=True)
|
||||
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||
@ -189,6 +190,7 @@ class IPAddressInterfaceSerializer(WritableNestedSerializer):
|
||||
|
||||
|
||||
class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
family = ChoiceField(choices=AF_CHOICES, read_only=True)
|
||||
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||
status = ChoiceField(choices=IPADDRESS_STATUS_CHOICES, required=False)
|
||||
|
@ -341,11 +341,11 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
|
||||
|
||||
class PrefixCSVForm(forms.ModelForm):
|
||||
vrf = forms.ModelChoiceField(
|
||||
vrf = FlexibleModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
to_field_name='rd',
|
||||
help_text='Route distinguisher of parent VRF',
|
||||
required=False,
|
||||
help_text='Route distinguisher of parent VRF (or {ID})',
|
||||
error_messages={
|
||||
'invalid_choice': 'VRF not found.',
|
||||
}
|
||||
@ -518,14 +518,12 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
|
||||
label='Mask length',
|
||||
widget=StaticSelect2()
|
||||
)
|
||||
vrf = FilterChoiceField(
|
||||
vrf_id = FilterChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
to_field_name='rd',
|
||||
label='VRF',
|
||||
null_label='-- Global --',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/ipam/vrfs/",
|
||||
value_field="rd",
|
||||
null_option=True,
|
||||
)
|
||||
)
|
||||
@ -748,11 +746,11 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
|
||||
|
||||
class IPAddressCSVForm(forms.ModelForm):
|
||||
vrf = forms.ModelChoiceField(
|
||||
vrf = FlexibleModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
to_field_name='rd',
|
||||
help_text='Route distinguisher of the assigned VRF',
|
||||
required=False,
|
||||
help_text='Route distinguisher of parent VRF (or {ID})',
|
||||
error_messages={
|
||||
'invalid_choice': 'VRF not found.',
|
||||
}
|
||||
@ -959,14 +957,12 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
|
||||
label='Mask length',
|
||||
widget=StaticSelect2()
|
||||
)
|
||||
vrf = FilterChoiceField(
|
||||
vrf_id = FilterChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
to_field_name='rd',
|
||||
label='VRF',
|
||||
null_label='-- Global --',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/ipam/vrfs/",
|
||||
value_field="rd",
|
||||
null_option=True,
|
||||
)
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import netaddr
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
@ -10,8 +10,9 @@ from django.urls import reverse
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
from dcim.models import Interface
|
||||
from extras.models import CustomFieldModel
|
||||
from extras.models import CustomFieldModel, ObjectChange
|
||||
from utilities.models import ChangeLoggedModel
|
||||
from utilities.utils import serialize_object
|
||||
from .constants import *
|
||||
from .fields import IPNetworkField, IPAddressField
|
||||
from .querysets import PrefixQuerySet
|
||||
@ -629,6 +630,27 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
|
||||
self.family = self.address.version
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def log_change(self, user, request_id, action):
|
||||
"""
|
||||
Include the connected Interface (if any).
|
||||
"""
|
||||
|
||||
# It's possible that an IPAddress can be deleted _after_ its parent Interface, in which case trying to resolve
|
||||
# the interface will raise DoesNotExist.
|
||||
try:
|
||||
parent_obj = self.interface
|
||||
except ObjectDoesNotExist:
|
||||
parent_obj = None
|
||||
|
||||
ObjectChange(
|
||||
user=user,
|
||||
request_id=request_id,
|
||||
changed_object=self,
|
||||
related_object=parent_obj,
|
||||
action=action,
|
||||
object_data=serialize_object(self)
|
||||
).save()
|
||||
|
||||
def to_csv(self):
|
||||
|
||||
# Determine if this IP is primary for a Device
|
||||
|
@ -30,7 +30,7 @@ RIR_ACTIONS = """
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.ipam.change_rir %}
|
||||
<a href="{% url 'ipam:rir_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'ipam:rir_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -52,7 +52,7 @@ ROLE_ACTIONS = """
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.ipam.change_role %}
|
||||
<a href="{% url 'ipam:role_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'ipam:role_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -152,7 +152,7 @@ VLANGROUP_ACTIONS = """
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% if perms.ipam.change_vlangroup %}
|
||||
<a href="{% url 'ipam:vlangroup_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'ipam:vlangroup_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -207,7 +207,7 @@ class RIRTable(BaseTable):
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
is_private = BooleanColumn(verbose_name='Private')
|
||||
aggregate_count = tables.Column(verbose_name='Aggregates')
|
||||
actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='')
|
||||
actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = RIR
|
||||
@ -292,7 +292,7 @@ class RoleTable(BaseTable):
|
||||
orderable=False,
|
||||
verbose_name='VLANs'
|
||||
)
|
||||
actions = tables.TemplateColumn(template_code=ROLE_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='')
|
||||
actions = tables.TemplateColumn(template_code=ROLE_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Role
|
||||
@ -398,7 +398,7 @@ class VLANGroupTable(BaseTable):
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
vlan_count = tables.Column(verbose_name='VLANs')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
actions = tables.TemplateColumn(template_code=VLANGROUP_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||
actions = tables.TemplateColumn(template_code=VLANGROUP_ACTIONS, attrs={'td': {'class': 'text-right noprint'}},
|
||||
verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
@ -444,7 +444,7 @@ class VLANMemberTable(BaseTable):
|
||||
)
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=VLAN_MEMBER_ACTIONS,
|
||||
attrs={'td': {'class': 'text-right'}},
|
||||
attrs={'td': {'class': 'text-right noprint'}},
|
||||
verbose_name=''
|
||||
)
|
||||
|
||||
|
@ -147,18 +147,18 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
|
||||
# Miscellaneous
|
||||
#
|
||||
|
||||
def get_view_name(view_cls, suffix=None):
|
||||
def get_view_name(view, suffix=None):
|
||||
"""
|
||||
Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name`.
|
||||
"""
|
||||
if hasattr(view_cls, 'queryset'):
|
||||
if hasattr(view, 'queryset'):
|
||||
# Determine the model name from the queryset.
|
||||
name = view_cls.queryset.model._meta.verbose_name
|
||||
name = view.queryset.model._meta.verbose_name
|
||||
name = ' '.join([w[0].upper() + w[1:] for w in name.split()]) # Capitalize each word
|
||||
|
||||
else:
|
||||
# Replicate DRF's built-in behavior.
|
||||
name = view_cls.__name__
|
||||
name = view.__class__.__name__
|
||||
name = formatting.remove_trailing_string(name, 'View')
|
||||
name = formatting.remove_trailing_string(name, 'ViewSet')
|
||||
name = formatting.camelcase_to_spaces(name)
|
||||
|
@ -132,6 +132,7 @@ REDIS = {
|
||||
'PASSWORD': '',
|
||||
'DATABASE': 0,
|
||||
'DEFAULT_TIMEOUT': 300,
|
||||
'SSL': False,
|
||||
}
|
||||
|
||||
# The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of
|
||||
|
@ -22,7 +22,7 @@ except ImportError:
|
||||
)
|
||||
|
||||
|
||||
VERSION = '2.5.8-dev'
|
||||
VERSION = '2.5.11-dev'
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
@ -131,6 +131,7 @@ REDIS_PORT = REDIS.get('PORT', 6379)
|
||||
REDIS_PASSWORD = REDIS.get('PASSWORD', '')
|
||||
REDIS_DATABASE = REDIS.get('DATABASE', 0)
|
||||
REDIS_DEFAULT_TIMEOUT = REDIS.get('DEFAULT_TIMEOUT', 300)
|
||||
REDIS_SSL = REDIS.get('SSL', False)
|
||||
|
||||
# Email
|
||||
EMAIL_HOST = EMAIL.get('SERVER')
|
||||
@ -291,6 +292,7 @@ RQ_QUEUES = {
|
||||
'DB': REDIS_DATABASE,
|
||||
'PASSWORD': REDIS_PASSWORD,
|
||||
'DEFAULT_TIMEOUT': REDIS_DEFAULT_TIMEOUT,
|
||||
'SSL': REDIS_SSL,
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,6 +317,7 @@ SWAGGER_SETTINGS = {
|
||||
'utilities.custom_inspectors.IdInFilterInspector',
|
||||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||
],
|
||||
'DEFAULT_MODEL_DEPTH': 1,
|
||||
'DEFAULT_PAGINATOR_INSPECTORS': [
|
||||
'utilities.custom_inspectors.NullablePaginatorInspector',
|
||||
'drf_yasg.inspectors.DjangoRestResponsePagination',
|
||||
|
@ -49,6 +49,19 @@ footer p {
|
||||
}
|
||||
}
|
||||
|
||||
/* Printer friendly CSS class and various fixes for printing. */
|
||||
@media print {
|
||||
body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
a[href]:after {
|
||||
content: none !important;
|
||||
}
|
||||
.noprint {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Collapse the nav menu on displays less than 960px wide */
|
||||
@media (max-width: 959px) {
|
||||
.navbar-header {
|
||||
@ -575,4 +588,4 @@ td .progress {
|
||||
}
|
||||
textarea {
|
||||
font-family: Consolas, Lucida Console, monospace;
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +90,10 @@ $(document).ready(function() {
|
||||
// Assign color picker selection classes
|
||||
function colorPickerClassCopy(data, container) {
|
||||
if (data.element) {
|
||||
// Remove any existing color-selection classes
|
||||
$(container).attr('class', function(i, c) {
|
||||
return c.replace(/(^|\s)color-selection-\S+/g, '');
|
||||
});
|
||||
$(container).addClass($(data.element).attr("class"));
|
||||
}
|
||||
return data.text;
|
||||
@ -151,10 +155,14 @@ $(document).ready(function() {
|
||||
|
||||
filter_for_elements.each(function(index, filter_for_element) {
|
||||
var param_name = $(filter_for_element).attr(attr_name);
|
||||
var is_nullable = $(filter_for_element).attr("nullable");
|
||||
var is_visible = $(filter_for_element).is(":visible");
|
||||
var value = $(filter_for_element).val();
|
||||
|
||||
if (param_name && value) {
|
||||
if (param_name && is_visible && value) {
|
||||
parameters[param_name] = value;
|
||||
} else if (param_name && is_visible && is_nullable) {
|
||||
parameters[param_name] = "null";
|
||||
}
|
||||
});
|
||||
|
||||
@ -243,7 +251,7 @@ $(document).ready(function() {
|
||||
|
||||
ajax: {
|
||||
delay: 250,
|
||||
url: "/api/extras/tags/",
|
||||
url: netbox_api_path + "extras/tags/",
|
||||
|
||||
data: function(params) {
|
||||
// Paging. Note that `params.page` indexes at 1
|
||||
|
@ -8,7 +8,7 @@ SECRETROLE_ACTIONS = """
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.secrets.change_secretrole %}
|
||||
<a href="{% url 'secrets:secretrole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
<a href="{% url 'secrets:secretrole_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -23,7 +23,7 @@ class SecretRoleTable(BaseTable):
|
||||
secret_count = tables.Column(verbose_name='Secrets')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=SECRETROLE_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name=''
|
||||
template_code=SECRETROLE_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name=''
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
|
@ -120,6 +120,8 @@ def secret_add(request, pk):
|
||||
secret.plaintext = str(form.cleaned_data['plaintext'])
|
||||
secret.encrypt(master_key)
|
||||
secret.save()
|
||||
form.save_m2m()
|
||||
|
||||
messages.success(request, "Added new secret: {}.".format(secret))
|
||||
if '_addanother' in request.POST:
|
||||
return redirect('dcim:device_addsecret', pk=device.pk)
|
||||
|
@ -54,7 +54,7 @@
|
||||
<div class="col-xs-4 text-center">
|
||||
<p class="text-muted">{% now 'Y-m-d H:i:s T' %}</p>
|
||||
</div>
|
||||
<div class="col-xs-4 text-right">
|
||||
<div class="col-xs-4 text-right noprint">
|
||||
<p class="text-muted">
|
||||
<i class="fa fa-fw fa-book text-primary"></i> <a href="http://netbox.readthedocs.io/">Docs</a> ·
|
||||
<i class="fa fa-fw fa-cloud text-primary"></i> <a href="{% url 'api_docs' %}">API</a> ·
|
||||
|
@ -4,7 +4,7 @@
|
||||
{% block title %}{{ circuit }}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'circuits:circuit_list' %}">Circuits</a></li>
|
||||
@ -25,7 +25,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.circuits.change_circuit %}
|
||||
<a href="{% url 'circuits:circuit_edit' pk=circuit.pk %}" class="btn btn-warning">
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.circuits.add_circuit %}
|
||||
{% add_button 'circuits:circuit_add' %}
|
||||
{% import_button 'circuits:circuit_import' %}
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='circuits:circuit_bulk_edit' bulk_delete_url='circuits:circuit_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.circuits.add_circuittype %}
|
||||
{% add_button 'circuits:circuittype_add' %}
|
||||
{% import_button 'circuits:circuittype_import' %}
|
||||
|
@ -5,7 +5,7 @@
|
||||
{% block title %}{{ provider }}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'circuits:provider_list' %}">Providers</a></li>
|
||||
@ -25,7 +25,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if show_graphs %}
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ provider.name }}" data-url="{% url 'circuits-api:provider-graphs' pk=provider.pk %}" title="Show graphs">
|
||||
<i class="fa fa-signal" aria-hidden="true"></i>
|
||||
@ -172,7 +172,7 @@
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if perms.circuits.add_circuit %}
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
<a href="{% url 'circuits:circuit_add' %}?provider={{ provider.pk }}" class="btn btn-xs btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add circuit
|
||||
</a>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.circuits.add_provider %}
|
||||
{% add_button 'circuits:provider_add' %}
|
||||
{% import_button 'circuits:provider_import' %}
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='circuits:provider_bulk_edit' bulk_delete_url='circuits:provider_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-md-12">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'dcim:cable_list' %}">Cables</a></li>
|
||||
@ -10,7 +10,7 @@
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.change_cable %}
|
||||
<a href="{% url 'dcim:cable_edit' pk=cable.pk %}" class="btn btn-warning">
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span> Edit this cable
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_cable %}
|
||||
{% import_button 'dcim:cable_import' %}
|
||||
{% endif %}
|
||||
@ -13,7 +13,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:cable_bulk_edit' bulk_delete_url='dcim:cable_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% export_button content_type %}
|
||||
</div>
|
||||
<h1>{% block title %}Console Connections{% endblock %}</h1>
|
||||
@ -11,7 +11,7 @@
|
||||
{% include 'responsive_table.html' %}
|
||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,7 +5,7 @@
|
||||
{% block title %}{{ device }}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'dcim:site' slug=device.site.slug %}">{{ device.site }}</a></li>
|
||||
@ -33,7 +33,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.change_device %}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
@ -199,7 +199,7 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
{% if perms.dcim.change_virtualchassis %}
|
||||
<a href="{% url 'dcim:virtualchassis_add_member' pk=device.virtual_chassis.pk %}?site={{ device.site.pk }}&rack={{ device.rack.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Member
|
||||
@ -317,7 +317,7 @@
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %}
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
{% if perms.dcim.add_consoleport %}
|
||||
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
|
||||
@ -352,7 +352,7 @@
|
||||
<form id="secret_form">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
<a href="{% url 'dcim:device_addsecret' pk=device.pk %}" class="btn btn-xs btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
Add secret
|
||||
@ -377,7 +377,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.ipam.add_service %}
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
<a href="{% url 'dcim:device_service_assign' device=device.pk %}" class="btn btn-xs btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Assign service
|
||||
</a>
|
||||
@ -390,7 +390,7 @@
|
||||
</div>
|
||||
{% include 'inc/image_attachments.html' with images=device.images.all %}
|
||||
{% if perms.extras.add_imageattachment %}
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
<a href="{% url 'dcim:device_add_image' object_id=device.pk %}" class="btn btn-primary btn-xs">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
Attach an image
|
||||
@ -398,7 +398,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel panel-default noprint">
|
||||
<div class="panel-heading">
|
||||
<strong>Related Devices</strong>
|
||||
</div>
|
||||
@ -459,7 +459,7 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="panel-footer">
|
||||
<div class="panel-footer noprint">
|
||||
{% if device_bays and perms.dcim.change_devicebay %}
|
||||
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||
@ -493,7 +493,7 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Interfaces</strong>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
<button class="btn btn-default btn-xs toggle-ips" selected="selected">
|
||||
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
|
||||
</button>
|
||||
@ -521,7 +521,7 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="panel-footer">
|
||||
<div class="panel-footer noprint">
|
||||
{% if interfaces and perms.dcim.change_interface %}
|
||||
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||
@ -581,7 +581,7 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="panel-footer">
|
||||
<div class="panel-footer noprint">
|
||||
{% if consoleserverports and perms.dcim.change_consoleport %}
|
||||
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||
@ -636,7 +636,7 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="panel-footer">
|
||||
<div class="panel-footer noprint">
|
||||
{% if poweroutlets and perms.dcim.change_powerport %}
|
||||
<button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||
@ -693,7 +693,7 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="panel-footer">
|
||||
<div class="panel-footer noprint">
|
||||
{% if front_ports and perms.dcim.change_frontport %}
|
||||
<button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||
@ -750,7 +750,7 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="panel-footer">
|
||||
<div class="panel-footer noprint">
|
||||
{% if rear_ports and perms.dcim.change_rearport %}
|
||||
<button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||
|
@ -53,7 +53,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{% if perms.dcim.add_inventoryitem %}
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
<a href="{% url 'dcim:inventoryitem_add' device=device.pk %}" class="btn btn-primary btn-xs">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span> Add Inventory Item
|
||||
</a>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_device %}
|
||||
{% add_button 'dcim:device_add' %}
|
||||
{% import_button 'dcim:device_import' %}
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'dcim/inc/device_table.html' with bulk_edit_url='dcim:device_bulk_edit' bulk_delete_url='dcim:device_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_devicerole %}
|
||||
{% add_button 'dcim:devicerole_add' %}
|
||||
{% import_button 'dcim:devicerole_import' %}
|
||||
|
@ -4,7 +4,7 @@
|
||||
{% block title %}{{ devicetype.manufacturer }} {{ devicetype.model }}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-md-12">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'dcim:devicetype_list' %}">Device Types</a></li>
|
||||
@ -14,7 +14,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.dcim.change_devicetype or perms.dcim.delete_devicetype %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.change_devicetype %}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_devicetype %}
|
||||
{% add_button 'dcim:devicetype_add' %}
|
||||
{% import_button 'dcim:devicetype_import' %}
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:devicetype_bulk_edit' bulk_delete_url='dcim:devicetype_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
</div>
|
||||
|
@ -29,7 +29,7 @@
|
||||
{% endif %}
|
||||
|
||||
{# Actions #}
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
{% if cp.cable %}
|
||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=cp.cable %}
|
||||
{% elif perms.dcim.add_cable %}
|
||||
|
@ -36,7 +36,7 @@
|
||||
{% endif %}
|
||||
|
||||
{# Actions #}
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
{% if csp.cable %}
|
||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=csp.cable %}
|
||||
{% elif perms.dcim.add_cable %}
|
||||
|
@ -23,7 +23,7 @@
|
||||
<span class="text-muted">Vacant</span>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
{% if perms.dcim.change_devicebay %}
|
||||
{% if devicebay.installed_device %}
|
||||
<a href="{% url 'dcim:devicebay_depopulate' pk=devicebay.pk %}" class="btn btn-danger btn-xs">
|
||||
|
@ -6,7 +6,7 @@
|
||||
<strong>{{ title }}</strong>
|
||||
</div>
|
||||
{% include 'responsive_table.html' %}
|
||||
<div class="panel-footer">
|
||||
<div class="panel-footer noprint">
|
||||
{% if table.rows %}
|
||||
{% if edit_url %}
|
||||
<button type="submit" name="_edit" formaction="{% url edit_url pk=devicetype.pk %}?return_url={{ devicetype.get_absolute_url }}" class="btn btn-xs btn-warning">
|
||||
|
@ -54,7 +54,7 @@
|
||||
{% endif %}
|
||||
|
||||
{# Actions #}
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
{% if frontport.cable %}
|
||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=frontport.cable %}
|
||||
{% elif perms.dcim.add_cable %}
|
||||
|
@ -134,7 +134,7 @@
|
||||
{% endif %}
|
||||
|
||||
{# Buttons #}
|
||||
<td class="text-right text-nowrap">
|
||||
<td class="text-right text-nowrap noprint">
|
||||
{% if show_graphs %}
|
||||
{% if iface.connected_endpoint %}
|
||||
<button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ device.name }} - {{ iface.name }}" data-url="{% url 'dcim-api:interface-graphs' pk=iface.pk %}" title="Show graphs">
|
||||
@ -231,7 +231,7 @@
|
||||
</td>
|
||||
|
||||
{# Buttons #}
|
||||
<td class="text-right text-nowrap">
|
||||
<td class="text-right text-nowrap noprint">
|
||||
{% if perms.ipam.change_ipaddress %}
|
||||
<a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs">
|
||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<td>{{ item.serial }}</td>
|
||||
<td>{{ item.asset_tag|default:"" }}</td>
|
||||
<td>{{ item.description }}</td>
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
{% if perms.dcim.change_inventoryitem %}
|
||||
<a href="{% url 'dcim:inventoryitem_edit' pk=item.pk %}" class="btn btn-xs btn-warning"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
|
||||
{% endif %}
|
||||
|
@ -36,7 +36,7 @@
|
||||
{% endif %}
|
||||
|
||||
{# Actions #}
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
{% if po.cable %}
|
||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=po.cable %}
|
||||
{% elif perms.dcim.add_cable %}
|
||||
|
@ -29,7 +29,7 @@
|
||||
{% endif %}
|
||||
|
||||
{# Actions #}
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
{% if pp.cable %}
|
||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=pp.cable %}
|
||||
{% elif perms.dcim.add_cable %}
|
||||
|
@ -53,7 +53,7 @@
|
||||
{% endif %}
|
||||
|
||||
{# Actions #}
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
{% if rearport.cable %}
|
||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=rearport.cable %}
|
||||
{% elif perms.dcim.add_cable %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-md-12">
|
||||
<ol class="breadcrumb">
|
||||
{% if interface.device %}
|
||||
@ -15,7 +15,7 @@
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.change_interface %}
|
||||
<a href="{% if interface.device %}{% url 'dcim:interface_edit' pk=interface.pk %}{% else %}{% url 'virtualization:interface_edit' pk=interface.pk %}{% endif %}" class="btn btn-warning">
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span> Edit this interface
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% export_button content_type %}
|
||||
</div>
|
||||
<h1>{% block title %}Interface Connections{% endblock %}</h1>
|
||||
@ -11,7 +11,7 @@
|
||||
{% include 'responsive_table.html' %}
|
||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_devicetype %}
|
||||
{% import_button 'dcim:inventoryitem_import' %}
|
||||
{% endif %}
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:inventoryitem_bulk_edit' bulk_delete_url='dcim:inventoryitem_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_manufacturer %}
|
||||
{% add_button 'dcim:manufacturer_add' %}
|
||||
{% import_button 'dcim:manufacturer_import' %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_platform %}
|
||||
{% add_button 'dcim:platform_add' %}
|
||||
{% import_button 'dcim:platform_import' %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% export_button content_type %}
|
||||
</div>
|
||||
<h1>{% block title %}Power Connections{% endblock %}</h1>
|
||||
@ -11,7 +11,7 @@
|
||||
{% include 'responsive_table.html' %}
|
||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'dcim:rack_list' %}">Racks</a></li>
|
||||
@ -23,7 +23,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
<a {% if prev_rack %}href="{% url 'dcim:rack' pk=prev_rack.pk %}"{% else %}disabled="disabled"{% endif %} class="btn btn-primary">
|
||||
<span class="fa fa-chevron-left" aria-hidden="true"></span> Previous Rack
|
||||
</a>
|
||||
@ -223,7 +223,7 @@
|
||||
<div class="panel-body text-muted">None</div>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_device %}
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
<a href="{% url 'dcim:device_add' %}?site={{ rack.site.pk }}&rack={{ rack.pk }}" class="btn btn-primary btn-xs">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
Add a non-racked device
|
||||
@ -237,7 +237,7 @@
|
||||
</div>
|
||||
{% include 'inc/image_attachments.html' with images=rack.images.all %}
|
||||
{% if perms.extras.add_imageattachment %}
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
<a href="{% url 'dcim:rack_add_image' object_id=rack.pk %}" class="btn btn-primary btn-xs">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
Attach an image
|
||||
@ -271,7 +271,7 @@
|
||||
{{ resv.description }}<br />
|
||||
<small>{{ resv.user }} · {{ resv.created }}</small>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
{% if perms.dcim.change_rackreservation %}
|
||||
<a href="{% url 'dcim:rackreservation_edit' pk=resv.pk %}" class="btn btn-warning btn-xs" title="Edit reservation">
|
||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
|
||||
@ -290,7 +290,7 @@
|
||||
<div class="panel-body text-muted">None</div>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_rackreservation %}
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
<a href="{% url 'dcim:rack_add_reservation' rack=rack.pk %}" class="btn btn-primary btn-xs">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
Add a reservation
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block content %}
|
||||
<div class="btn-group pull-right" role="group">
|
||||
<div class="btn-group pull-right noprint" role="group">
|
||||
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face=0 %}" class="btn btn-default{% if request.GET.face != '1' %} active{% endif %}">Front</a>
|
||||
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face=1 %}" class="btn btn-default{% if request.GET.face == '1' %} active{% endif %}">Rear</a>
|
||||
</div>
|
||||
@ -38,7 +38,7 @@
|
||||
<p>No racks found</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_rack %}
|
||||
{% add_button 'dcim:rack_add' %}
|
||||
{% import_button 'dcim:rack_import' %}
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rack_bulk_edit' bulk_delete_url='dcim:rack_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_rackgroup %}
|
||||
{% add_button 'dcim:rackgroup_add' %}
|
||||
{% import_button 'dcim:rackgroup_import' %}
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:rackgroup_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rackreservation_bulk_edit' bulk_delete_url='dcim:rackreservation_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_rackrole %}
|
||||
{% add_button 'dcim:rackrole_add' %}
|
||||
{% import_button 'dcim:rackrole_import' %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_region %}
|
||||
{% add_button 'dcim:region_add' %}
|
||||
{% import_button 'dcim:region_import' %}
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:region_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'dcim:site_list' %}">Sites</a></li>
|
||||
@ -30,7 +30,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if show_graphs %}
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ site.name }}" data-url="{% url 'dcim-api:site-graphs' pk=site.pk %}" title="Show graphs">
|
||||
<i class="fa fa-signal" aria-hidden="true"></i>
|
||||
@ -138,7 +138,7 @@
|
||||
<td>Physical Address</td>
|
||||
<td>
|
||||
{% if site.physical_address %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
<a href="http://maps.google.com/?q={{ site.physical_address|oneline|urlencode }}" target="_blank" class="btn btn-primary btn-xs">
|
||||
<i class="glyphicon glyphicon-map-marker"></i> Map it
|
||||
</a>
|
||||
@ -157,7 +157,7 @@
|
||||
<td>GPS Coordinates</td>
|
||||
<td>
|
||||
{% if site.latitude and site.longitude %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
<a href="http://maps.google.com/?q={{ site.latitude }},{{ site.longitude }}" target="_blank" class="btn btn-primary btn-xs">
|
||||
<i class="glyphicon glyphicon-map-marker"></i> Map it
|
||||
</a>
|
||||
@ -251,7 +251,7 @@
|
||||
<tr>
|
||||
<td><i class="fa fa-fw fa-folder-o"></i> <a href="{{ rg.get_absolute_url }}">{{ rg }}</a></td>
|
||||
<td>{{ rg.rack_count }}</td>
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
<a href="{% url 'dcim:rack_elevation_list' %}?group_id={{ rg.pk }}" class="btn btn-xs btn-primary" title="View elevations">
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
@ -271,7 +271,7 @@
|
||||
</div>
|
||||
{% include 'inc/image_attachments.html' with images=site.images.all %}
|
||||
{% if perms.extras.add_imageattachment %}
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
<a href="{% url 'dcim:site_add_image' object_id=site.pk %}" class="btn btn-primary btn-xs">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
Attach an image
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.dcim.add_site %}
|
||||
{% add_button 'dcim:site_add' %}
|
||||
{% import_button 'dcim:site_import' %}
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:site_bulk_edit' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% export_button content_type %}
|
||||
</div>
|
||||
<h1>{% block title %}Virtual Chassis{% endblock %}</h1>
|
||||
@ -11,7 +11,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'extras:configcontext_list' %}">Config Contexts</a></li>
|
||||
@ -22,7 +22,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.extras.change_configcontext %}
|
||||
<a href="{% url 'extras:configcontext_edit' pk=configcontext.pk %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.extras.add_configcontext %}
|
||||
{% add_button 'extras:configcontext_add' %}
|
||||
{% endif %}
|
||||
@ -12,7 +12,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='extras:configcontext_bulk_edit' bulk_delete_url='extras:configcontext_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@
|
||||
{% block title %}{{ objectchange }}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'extras:objectchange_list' %}">Changelog</a></li>
|
||||
@ -97,7 +97,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% include 'panel_table.html' with table=related_changes_table heading='Related Changes' %}
|
||||
{% include 'panel_table.html' with table=related_changes_table heading='Related Changes' panel_class='noprint' %}
|
||||
{% if related_changes_count > related_changes_table.rows|length %}
|
||||
<div class="pull-right">
|
||||
<a href="{% url 'extras:objectchange_list' %}?request_id={{ objectchange.request_id }}" class="btn btn-primary">See all {{ related_changes_count|add:"1" }} changes</a>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% export_button content_type %}
|
||||
</div>
|
||||
<h1>{% block title %}Changelog{% endblock %}</h1>
|
||||
@ -10,7 +10,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@
|
||||
{% block title %}{{ report.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-md-12">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'extras:report_list' %}">Reports</a></li>
|
||||
@ -14,7 +14,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.extras.add_reportresult %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
<form action="{% url 'extras:report_run' name=report.full_name %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{ run_form }}
|
||||
|
@ -29,6 +29,12 @@
|
||||
Edit this tag
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.taggit.delete_tag %}
|
||||
<a href="{% url 'extras:tag_delete' slug=tag.slug %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
Delete this tag
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h1>{% block title %}Tag: {{ tag }}{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
@ -14,7 +14,7 @@
|
||||
</td>
|
||||
<td>{{ attachment.size|filesizeformat }}</td>
|
||||
<td>{{ attachment.created }}</td>
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
{% if perms.extras.change_imageattachment %}
|
||||
<a href="{% url 'extras:imageattachment_edit' pk=attachment.pk %}" class="btn btn-warning btn-xs" title="Edit image">
|
||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
|
||||
|
@ -26,7 +26,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="text-right">
|
||||
<div class="text-right noprint">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="fa fa-search" aria-hidden="true"></span> Apply
|
||||
</button>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'ipam:aggregate_list' %}">Aggregates</a></li>
|
||||
@ -23,7 +23,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.ipam.change_aggregate %}
|
||||
<a href="{% url 'ipam:aggregate_edit' pk=aggregate.pk %}" class="btn btn-warning">
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
|
@ -3,7 +3,7 @@
|
||||
{% load humanize %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.ipam.add_aggregate %}
|
||||
{% add_button 'ipam:aggregate_add' %}
|
||||
{% import_button 'ipam:aggregate_import' %}
|
||||
@ -15,7 +15,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='ipam:aggregate_bulk_edit' bulk_delete_url='ipam:aggregate_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
<div class="panel panel-default">
|
||||
|
@ -13,7 +13,7 @@
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ service.description }}</td>
|
||||
<td class="text-right">
|
||||
<td class="text-right noprint">
|
||||
<a href="{% url 'ipam:service_changelog' pk=service.pk %}" class="btn btn-default btn-xs" title="Changelog">
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'ipam:ipaddress_list' %}">IP Addresses</a></li>
|
||||
@ -25,7 +25,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.ipam.change_ipaddress %}
|
||||
<a href="{% url 'ipam:ipaddress_edit' pk=ipaddress.pk %}" class="btn btn-warning">
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
@ -150,7 +150,7 @@
|
||||
{% if duplicate_ips_table.rows %}
|
||||
{% include 'panel_table.html' with table=duplicate_ips_table heading='Duplicate IP Addresses' panel_class='danger' %}
|
||||
{% endif %}
|
||||
{% include 'panel_table.html' with table=related_ips_table heading='Related IP Addresses' panel_class='default' %}
|
||||
{% include 'panel_table.html' with table=related_ips_table heading='Related IP Addresses' panel_class='default noprint' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.ipam.add_ipaddress %}
|
||||
{% add_button 'ipam:ipaddress_add' %}
|
||||
{% import_button 'ipam:ipaddress_import' %}
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'ipam:prefix_list' %}">Prefixes</a></li>
|
||||
@ -25,7 +25,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.ipam.add_prefix and active_tab == 'prefixes' and first_available_prefix %}
|
||||
<a href="{% url 'ipam:prefix_add' %}?prefix={{ first_available_prefix }}&vrf={{ prefix.vrf.pk }}&site={{ prefix.site.pk }}&tenant_group={{ prefix.tenant.group.pk }}&tenant={{ prefix.tenant.pk }}" class="btn btn-success">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i> Add Child Prefix
|
||||
|
@ -3,7 +3,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'ipam:prefix_list' %}{% querystring request expand=None page=1 %}" class="btn btn-default{% if not request.GET.expand %} active{% endif %}">Collapse</a>
|
||||
<a href="{% url 'ipam:prefix_list' %}{% querystring request expand='on' page=1 %}" class="btn btn-default{% if request.GET.expand %} active{% endif %}">Expand</a>
|
||||
@ -19,7 +19,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
{% load humanize %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if request.GET.family == '6' %}
|
||||
<a href="{% url 'ipam:rir_list' %}" class="btn btn-default">
|
||||
<span class="fa fa-table" aria-hidden="true"></span>
|
||||
@ -29,7 +29,7 @@
|
||||
<div class="alert alert-info pull-right"><strong>Note:</strong> Numbers shown indicate /64 prefixes.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.ipam.add_role %}
|
||||
{% add_button 'ipam:role_add' %}
|
||||
{% import_button 'ipam:role_import' %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'ipam:service_list' %}">Services</a></li>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% export_button content_type %}
|
||||
</div>
|
||||
<h1>{% block title %}Services{% endblock %}</h1>
|
||||
@ -10,7 +10,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='ipam:service_bulk_edit' bulk_delete_url='ipam:service_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row">
|
||||
<div class="row noprint">
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'ipam:vlan_list' %}">VLANs</a></li>
|
||||
@ -28,7 +28,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.ipam.change_vlan %}
|
||||
<a href="{% url 'ipam:vlan_edit' pk=vlan.pk %}" class="btn btn-warning">
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
@ -143,7 +143,7 @@
|
||||
</div>
|
||||
{% include 'responsive_table.html' with table=prefix_table %}
|
||||
{% if perms.ipam.add_prefix %}
|
||||
<div class="panel-footer text-right">
|
||||
<div class="panel-footer text-right noprint">
|
||||
<a href="{% url 'ipam:prefix_add' %}?{% if vlan.tenant %}tenant={{ vlan.tenant.pk }}&{% endif %}site={{ vlan.site.pk }}&vlan={{ vlan.pk }}" class="btn btn-primary btn-xs">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
Add a prefix
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load buttons %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.ipam.add_vlan %}
|
||||
{% add_button 'ipam:vlan_add' %}
|
||||
{% import_button 'ipam:vlan_import' %}
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='ipam:vlan_bulk_edit' bulk_delete_url='ipam:vlan_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 noprint">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/tags_panel.html' %}
|
||||
</div>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user