mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
Merge branch 'feature' into docs-refresh
This commit is contained in:
commit
150c3d3a97
@ -4,7 +4,7 @@ bleach
|
|||||||
|
|
||||||
# The Python web framework on which NetBox is built
|
# The Python web framework on which NetBox is built
|
||||||
# https://github.com/django/django
|
# https://github.com/django/django
|
||||||
Django
|
Django<4.1
|
||||||
|
|
||||||
# Django middleware which permits cross-domain API requests
|
# Django middleware which permits cross-domain API requests
|
||||||
# https://github.com/OttoYiu/django-cors-headers
|
# https://github.com/OttoYiu/django-cors-headers
|
||||||
|
9
docs/_theme/main.html
vendored
Normal file
9
docs/_theme/main.html
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block site_meta %}
|
||||||
|
{{ super() }}
|
||||||
|
{# Disable search indexing unless we're building for ReadTheDocs #}
|
||||||
|
{% if not config.extra.readthedocs %}
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
@ -1,3 +1,3 @@
|
|||||||
## Front Ports
|
## Front Ports
|
||||||
|
|
||||||
Front ports are pass-through ports used to represent physical cable connections that comprise part of a longer path. For example, the ports on the front face of a UTP patch panel would be modeled in NetBox as front ports. Each port is assigned a physical type, and must be mapped to a specific rear port on the same device. A single rear port may be mapped to multiple rear ports, using numeric positions to annotate the specific alignment of each.
|
Front ports are pass-through ports used to represent physical cable connections that comprise part of a longer path. For example, the ports on the front face of a UTP patch panel would be modeled in NetBox as front ports. Each port is assigned a physical type, and must be mapped to a specific rear port on the same device. A single rear port may be mapped to multiple front ports, using numeric positions to annotate the specific alignment of each.
|
||||||
|
@ -1,6 +1,20 @@
|
|||||||
# NetBox v3.2
|
# NetBox v3.2
|
||||||
|
|
||||||
## v3.2.8 (FUTURE)
|
## v3.2.9 (FUTURE)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#9161](https://github.com/netbox-community/netbox/issues/9161) - Pretty print JSON custom field data when editing
|
||||||
|
* [#9625](https://github.com/netbox-community/netbox/issues/9625) - Add phone & email details to contacts panel
|
||||||
|
* [#9857](https://github.com/netbox-community/netbox/issues/9857) - Add clear button to quick search fields
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#9986](https://github.com/netbox-community/netbox/issues/9986) - Workaround for upstream timezone data bug
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v3.2.8 (2022-08-08)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
@ -11,13 +25,20 @@
|
|||||||
* [#9881](https://github.com/netbox-community/netbox/issues/9881) - Increase granularity in utilization graph values
|
* [#9881](https://github.com/netbox-community/netbox/issues/9881) - Increase granularity in utilization graph values
|
||||||
* [#9882](https://github.com/netbox-community/netbox/issues/9882) - Add manufacturer column to modules table
|
* [#9882](https://github.com/netbox-community/netbox/issues/9882) - Add manufacturer column to modules table
|
||||||
* [#9883](https://github.com/netbox-community/netbox/issues/9883) - Linkify location column in power panels table
|
* [#9883](https://github.com/netbox-community/netbox/issues/9883) - Linkify location column in power panels table
|
||||||
|
* [#9906](https://github.com/netbox-community/netbox/issues/9906) - Include `color` attribute in front & rear port YAML import/export
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#9827](https://github.com/netbox-community/netbox/issues/9827) - Fix assignment of module bay position during bulk creation
|
||||||
* [#9871](https://github.com/netbox-community/netbox/issues/9871) - Fix utilization graph value alignments
|
* [#9871](https://github.com/netbox-community/netbox/issues/9871) - Fix utilization graph value alignments
|
||||||
* [#9884](https://github.com/netbox-community/netbox/issues/9884) - Prevent querying assigned VRF on prefix object init
|
* [#9884](https://github.com/netbox-community/netbox/issues/9884) - Prevent querying assigned VRF on prefix object init
|
||||||
* [#9885](https://github.com/netbox-community/netbox/issues/9885) - Fix child prefix counts when editing/deleting aggregates in bulk
|
* [#9885](https://github.com/netbox-community/netbox/issues/9885) - Fix child prefix counts when editing/deleting aggregates in bulk
|
||||||
* [#9891](https://github.com/netbox-community/netbox/issues/9891) - Ensure consistent ordering for tags during object serialization
|
* [#9891](https://github.com/netbox-community/netbox/issues/9891) - Ensure consistent ordering for tags during object serialization
|
||||||
|
* [#9919](https://github.com/netbox-community/netbox/issues/9919) - Fix potential XSS avenue via linked objects in tables
|
||||||
|
* [#9948](https://github.com/netbox-community/netbox/issues/9948) - Fix TypeError exception when requesting API tokens list as non-authenticated user
|
||||||
|
* [#9949](https://github.com/netbox-community/netbox/issues/9949) - Fix KeyError exception resulting from invalid API token provisioning request
|
||||||
|
* [#9950](https://github.com/netbox-community/netbox/issues/9950) - Prevent redirection to arbitrary URLs via `next` parameter on login URL
|
||||||
|
* [#9952](https://github.com/netbox-community/netbox/issues/9952) - Prevent InvalidMove when attempting to assign a nested child object as parent
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -97,22 +97,11 @@ Custom field UI visibility has no impact on API operation.
|
|||||||
* [#9536](https://github.com/netbox-community/netbox/issues/9536) - Track API token usage times
|
* [#9536](https://github.com/netbox-community/netbox/issues/9536) - Track API token usage times
|
||||||
* [#9582](https://github.com/netbox-community/netbox/issues/9582) - Enable assigning config contexts based on device location
|
* [#9582](https://github.com/netbox-community/netbox/issues/9582) - Enable assigning config contexts based on device location
|
||||||
|
|
||||||
### Bug Fixes (from Beta1)
|
### Bug Fixes (from Beta2)
|
||||||
|
|
||||||
* [#9728](https://github.com/netbox-community/netbox/issues/9728) - Fix validation when assigning a virtual machine to a device
|
* [#9900](https://github.com/netbox-community/netbox/issues/9900) - Pre-populate site & rack fields for cable connection form
|
||||||
* [#9729](https://github.com/netbox-community/netbox/issues/9729) - Fix ordering of content type creation to ensure compatability with demo data
|
* [#9938](https://github.com/netbox-community/netbox/issues/9938) - Exclude virtual interfaces from terminations list when connecting a cable
|
||||||
* [#9730](https://github.com/netbox-community/netbox/issues/9730) - Fix validation error when creating a new cable via UI form
|
* [#9939](https://github.com/netbox-community/netbox/issues/9939) - Fix list of next nodes for split paths under trace view
|
||||||
* [#9733](https://github.com/netbox-community/netbox/issues/9733) - Handle split paths during trace when fanning out to front ports with differing cables
|
|
||||||
* [#9765](https://github.com/netbox-community/netbox/issues/9765) - Report correct segment count under cable trace UI view
|
|
||||||
* [#9778](https://github.com/netbox-community/netbox/issues/9778) - Fix exception during cable deletion after deleting a connected termination
|
|
||||||
* [#9788](https://github.com/netbox-community/netbox/issues/9788) - Ensure denormalized fields on CableTermination are kept in sync with related objects
|
|
||||||
* [#9789](https://github.com/netbox-community/netbox/issues/9789) - Fix rendering of cable traces ending at provider networks
|
|
||||||
* [#9794](https://github.com/netbox-community/netbox/issues/9794) - Fix link to connect a rear port to a circuit termination
|
|
||||||
* [#9818](https://github.com/netbox-community/netbox/issues/9818) - Fix circuit side selection when connecting a cable to a circuit termination
|
|
||||||
* [#9829](https://github.com/netbox-community/netbox/issues/9829) - Arrange custom fields by group when editing objects
|
|
||||||
* [#9843](https://github.com/netbox-community/netbox/issues/9843) - Fix rendering of custom field values (regression from #9647)
|
|
||||||
* [#9844](https://github.com/netbox-community/netbox/issues/9844) - Fix interface api request when creating/editing L2VPN termination
|
|
||||||
* [#9847](https://github.com/netbox-community/netbox/issues/9847) - Respect `desc_units` when ordering rack units
|
|
||||||
|
|
||||||
### Plugins API
|
### Plugins API
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ repo_name: netbox-community/netbox
|
|||||||
repo_url: https://github.com/netbox-community/netbox
|
repo_url: https://github.com/netbox-community/netbox
|
||||||
theme:
|
theme:
|
||||||
name: material
|
name: material
|
||||||
|
custom_dir: docs/_theme/
|
||||||
icon:
|
icon:
|
||||||
repo: fontawesome/brands/github
|
repo: fontawesome/brands/github
|
||||||
palette:
|
palette:
|
||||||
@ -37,6 +38,7 @@ plugins:
|
|||||||
show_root_toc_entry: false
|
show_root_toc_entry: false
|
||||||
show_source: false
|
show_source: false
|
||||||
extra:
|
extra:
|
||||||
|
readthedocs: !ENV READTHEDOCS
|
||||||
social:
|
social:
|
||||||
- icon: fontawesome/brands/github
|
- icon: fontawesome/brands/github
|
||||||
link: https://github.com/netbox-community/netbox
|
link: https://github.com/netbox-community/netbox
|
||||||
|
@ -125,9 +125,9 @@ class Circuit(NetBoxModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'provider', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate', 'description',
|
'provider', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['provider', 'cid']
|
ordering = ['provider', 'cid']
|
||||||
|
@ -61,9 +61,9 @@ class Provider(NetBoxModel):
|
|||||||
to='tenancy.ContactAssignment'
|
to='tenancy.ContactAssignment'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact',
|
'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
@ -84,6 +84,7 @@ def get_cable_form(a_type, b_type):
|
|||||||
disabled_indicator='_occupied',
|
disabled_indicator='_occupied',
|
||||||
query_params={
|
query_params={
|
||||||
'device_id': f'$termination_{cable_end}_device',
|
'device_id': f'$termination_{cable_end}_device',
|
||||||
|
'kind': 'physical', # Exclude virtual interfaces
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = FrontPortTemplate
|
model = FrontPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'type', 'rear_port', 'rear_port_position', 'label', 'description',
|
'device_type', 'module_type', 'name', 'type', 'color', 'rear_port', 'rear_port_position', 'label', 'description',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = RearPortTemplate
|
model = RearPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'type', 'positions', 'label', 'description',
|
'device_type', 'module_type', 'name', 'type', 'color', 'positions', 'label', 'description',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -677,6 +677,6 @@ class CablePath(models.Model):
|
|||||||
"""
|
"""
|
||||||
Return all available next segments in a split cable path.
|
Return all available next segments in a split cable path.
|
||||||
"""
|
"""
|
||||||
rearport = path_node_to_object(self._nodes[-1])
|
rearports = self.path_objects[-1]
|
||||||
|
|
||||||
return FrontPort.objects.filter(rear_port=rearport)
|
return FrontPort.objects.filter(rear_port__in=rearports)
|
||||||
|
@ -478,6 +478,7 @@ class FrontPortTemplate(ModularComponentTemplateModel):
|
|||||||
return {
|
return {
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'type': self.type,
|
'type': self.type,
|
||||||
|
'color': self.color,
|
||||||
'rear_port': self.rear_port.name,
|
'rear_port': self.rear_port.name,
|
||||||
'rear_port_position': self.rear_port_position,
|
'rear_port_position': self.rear_port_position,
|
||||||
'label': self.label,
|
'label': self.label,
|
||||||
@ -527,6 +528,7 @@ class RearPortTemplate(ModularComponentTemplateModel):
|
|||||||
return {
|
return {
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'type': self.type,
|
'type': self.type,
|
||||||
|
'color': self.color,
|
||||||
'positions': self.positions,
|
'positions': self.positions,
|
||||||
'label': self.label,
|
'label': self.label,
|
||||||
'description': self.description,
|
'description': self.description,
|
||||||
|
@ -263,7 +263,7 @@ class ConsolePort(ModularComponentModel, CabledObjectModel, PathEndpoint):
|
|||||||
help_text='Port speed in bits per second'
|
help_text='Port speed in bits per second'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ['device', 'type', 'speed']
|
clone_fields = ('device', 'module', 'type', 'speed')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
@ -290,7 +290,7 @@ class ConsoleServerPort(ModularComponentModel, CabledObjectModel, PathEndpoint):
|
|||||||
help_text='Port speed in bits per second'
|
help_text='Port speed in bits per second'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ['device', 'type', 'speed']
|
clone_fields = ('device', 'module', 'type', 'speed')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
@ -327,7 +327,7 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint):
|
|||||||
help_text="Allocated power draw (watts)"
|
help_text="Allocated power draw (watts)"
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ['device', 'maximum_draw', 'allocated_draw']
|
clone_fields = ('device', 'module', 'maximum_draw', 'allocated_draw')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
@ -441,7 +441,7 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint):
|
|||||||
help_text="Phase (for three-phase feeds)"
|
help_text="Phase (for three-phase feeds)"
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ['device', 'type', 'power_port', 'feed_leg']
|
clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
@ -672,7 +672,10 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
related_query_name='interface',
|
related_query_name='interface',
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ['device', 'parent', 'bridge', 'lag', 'type', 'mgmt_only', 'poe_mode', 'poe_type']
|
clone_fields = (
|
||||||
|
'device', 'module', 'parent', 'bridge', 'lag', 'type', 'mgmt_only', 'mtu', 'mode', 'speed', 'duplex', 'rf_role',
|
||||||
|
'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'poe_mode', 'poe_type', 'vrf',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device', CollateAsChar('_name'))
|
ordering = ('device', CollateAsChar('_name'))
|
||||||
@ -890,7 +893,7 @@ class FrontPort(ModularComponentModel, CabledObjectModel):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ['device', 'type']
|
clone_fields = ('device', 'type', 'color')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
@ -937,7 +940,7 @@ class RearPort(ModularComponentModel, CabledObjectModel):
|
|||||||
MaxValueValidator(REARPORT_POSITIONS_MAX)
|
MaxValueValidator(REARPORT_POSITIONS_MAX)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
clone_fields = ['device', 'type', 'positions']
|
clone_fields = ('device', 'type', 'color', 'positions')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
@ -972,7 +975,7 @@ class ModuleBay(ComponentModel):
|
|||||||
help_text='Identifier to reference when renaming installed components'
|
help_text='Identifier to reference when renaming installed components'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ['device']
|
clone_fields = ('device',)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
@ -994,7 +997,7 @@ class DeviceBay(ComponentModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ['device']
|
clone_fields = ('device',)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
@ -1131,7 +1134,7 @@ class InventoryItem(MPTTModel, ComponentModel):
|
|||||||
|
|
||||||
objects = TreeManager()
|
objects = TreeManager()
|
||||||
|
|
||||||
clone_fields = ['device', 'parent', 'role', 'manufacturer', 'part_id']
|
clone_fields = ('device', 'parent', 'role', 'manufacturer', 'part_id',)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device__id', 'parent__id', '_name')
|
ordering = ('device__id', 'parent__id', '_name')
|
||||||
|
@ -135,9 +135,9 @@ class DeviceType(NetBoxModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
|
'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['manufacturer', 'model']
|
ordering = ['manufacturer', 'model']
|
||||||
@ -630,9 +630,10 @@ class Device(NetBoxModel, ConfigContextModel):
|
|||||||
|
|
||||||
objects = ConfigContextModelQuerySet.as_manager()
|
objects = ConfigContextModelQuerySet.as_manager()
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'device_type', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'status', 'airflow', 'cluster',
|
'device_type', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'face', 'status', 'airflow',
|
||||||
]
|
'cluster', 'virtual_chassis',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('_name', 'pk') # Name may be null
|
ordering = ('_name', 'pk') # Name may be null
|
||||||
|
@ -126,10 +126,10 @@ class PowerFeed(NetBoxModel, PathEndpoint, CabledObjectModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
|
'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
|
||||||
'max_utilization', 'available_power',
|
'max_utilization',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['power_panel', 'name']
|
ordering = ['power_panel', 'name']
|
||||||
|
@ -183,10 +183,10 @@ class Rack(NetBoxModel):
|
|||||||
to='extras.ImageAttachment'
|
to='extras.ImageAttachment'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'site', 'location', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width',
|
'site', 'location', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width',
|
||||||
'outer_depth', 'outer_unit',
|
'outer_depth', 'outer_unit',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('site', 'location', '_name', 'pk') # (site, location, name) may be non-unique
|
ordering = ('site', 'location', '_name', 'pk') # (site, location, name) may be non-unique
|
||||||
|
@ -295,10 +295,10 @@ class Site(NetBoxModel):
|
|||||||
to='extras.ImageAttachment'
|
to='extras.ImageAttachment'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'status', 'region', 'group', 'tenant', 'facility', 'time_zone', 'description', 'physical_address',
|
'status', 'region', 'group', 'tenant', 'facility', 'time_zone', 'physical_address', 'shipping_address',
|
||||||
'shipping_address', 'latitude', 'longitude',
|
'latitude', 'longitude', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('_name',)
|
ordering = ('_name',)
|
||||||
@ -372,7 +372,7 @@ class Location(NestedGroupModel):
|
|||||||
to='extras.ImageAttachment'
|
to='extras.ImageAttachment'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ['site', 'parent', 'status', 'tenant', 'description']
|
clone_fields = ('site', 'parent', 'status', 'tenant', 'description')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['site', 'name']
|
ordering = ['site', 'name']
|
||||||
|
@ -121,9 +121,9 @@ CONSOLEPORT_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleserverport&return_url={% url 'dcim:device_consoleports' pk=object.pk %}">Console Server Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleserverport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}">Console Server Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ record.pk }}&b_terminations_type=dcim.frontport&return_url={% url 'dcim:device_consoleports' pk=object.pk %}">Front Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ record.pk }}&b_terminations_type=dcim.frontport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}">Front Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ record.pk }}&b_terminations_type=dcim.rearport&return_url={% url 'dcim:device_consoleports' pk=object.pk %}">Rear Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ record.pk }}&b_terminations_type=dcim.rearport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}">Rear Port</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -153,9 +153,9 @@ CONSOLESERVERPORT_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleport&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}">Console Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}">Console Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ record.pk }}&b_terminations_type=dcim.frontport&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}">Front Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ record.pk }}&b_terminations_type=dcim.frontport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}">Front Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ record.pk }}&b_terminations_type=dcim.rearport&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}">Rear Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ record.pk }}&b_terminations_type=dcim.rearport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}">Rear Port</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -185,8 +185,8 @@ POWERPORT_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerport&a_terminations={{ record.pk }}&b_terminations_type=dcim.poweroutlet&return_url={% url 'dcim:device_powerports' pk=object.pk %}">Power Outlet</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerport&a_terminations={{ record.pk }}&b_terminations_type=dcim.poweroutlet&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}">Power Outlet</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerport&a_terminations={{ record.pk }}&b_terminations_type=dcim.powerfeed&return_url={% url 'dcim:device_powerports' pk=object.pk %}">Power Feed</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerport&a_terminations={{ record.pk }}&b_terminations_type=dcim.powerfeed&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}">Power Feed</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -212,7 +212,7 @@ POWEROUTLET_BUTTONS = """
|
|||||||
<a href="#" class="btn btn-outline-dark btn-sm disabled"><i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i></a>
|
<a href="#" class="btn btn-outline-dark btn-sm disabled"><i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i></a>
|
||||||
<a href="#" class="btn btn-outline-dark btn-sm disabled"><i class="mdi mdi-lan-connect" aria-hidden="true"></i></a>
|
<a href="#" class="btn btn-outline-dark btn-sm disabled"><i class="mdi mdi-lan-connect" aria-hidden="true"></i></a>
|
||||||
{% if not record.mark_connected %}
|
{% if not record.mark_connected %}
|
||||||
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.poweroutlet&a_terminations={{ record.pk }}&b_terminations_type=dcim.powerport&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" title="Connect" class="btn btn-success btn-sm">
|
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.poweroutlet&a_terminations={{ record.pk }}&b_terminations_type=dcim.powerport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" title="Connect" class="btn btn-success btn-sm">
|
||||||
<i class="mdi mdi-ethernet-cable" aria-hidden="true"></i>
|
<i class="mdi mdi-ethernet-cable" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -262,10 +262,10 @@ INTERFACE_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ record.pk }}&b_terminations_type=dcim.interface&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Interface</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ record.pk }}&b_terminations_type=dcim.interface&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Interface</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ record.pk }}&b_terminations_type=dcim.frontport&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Front Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ record.pk }}&b_terminations_type=dcim.frontport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Front Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ record.pk }}&b_terminations_type=dcim.rearport&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Rear Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ record.pk }}&b_terminations_type=dcim.rearport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Rear Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ record.pk }}&b_terminations_type=circuits.circuittermination&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Circuit Termination</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ record.pk }}&b_terminations_type=circuits.circuittermination&termination_b_site={{ object.site.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Circuit Termination</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -301,12 +301,12 @@ FRONTPORT_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=dcim.interface&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Interface</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=dcim.interface&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Interface</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleserverport&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Console Server Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleserverport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Console Server Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleport&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Console Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Console Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=dcim.frontport&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Front Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=dcim.frontport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Front Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=dcim.rearport&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Rear Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=dcim.rearport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Rear Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=circuits.circuittermination&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Circuit Termination</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ record.pk }}&b_terminations_type=circuits.circuittermination&termination_b_site={{ object.site.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Circuit Termination</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -338,12 +338,12 @@ REARPORT_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=dcim.interface&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Interface</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=dcim.interface&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Interface</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleserverport&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Console Server Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleserverport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Console Server Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleport&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Console Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=dcim.consoleport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Console Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=dcim.frontport&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Front Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=dcim.frontport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Front Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=dcim.rearport&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Rear Port</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=dcim.rearport&termination_b_site={{ object.site.pk }}&termination_b_rack={{ object.rack.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Rear Port</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=circuits.circuittermination&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Circuit Termination</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ record.pk }}&b_terminations_type=circuits.circuittermination&termination_b_site={{ object.site.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Circuit Termination</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -2721,6 +2721,7 @@ class DeviceBulkAddModuleBayView(generic.BulkComponentCreateView):
|
|||||||
filterset = filtersets.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
|
patterned_fields = ('name', 'label', 'position')
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
|
class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
|
||||||
@ -3066,7 +3067,7 @@ class VirtualChassisAddMemberView(ObjectPermissionRequiredMixin, GetReturnURLMix
|
|||||||
if membership_form.is_valid():
|
if membership_form.is_valid():
|
||||||
|
|
||||||
membership_form.save()
|
membership_form.save()
|
||||||
msg = 'Added member <a href="{}">{}</a>'.format(device.get_absolute_url(), escape(device))
|
msg = f'Added member <a href="{device.get_absolute_url()}">{escape(device)}</a>'
|
||||||
messages.success(request, mark_safe(msg))
|
messages.success(request, mark_safe(msg))
|
||||||
|
|
||||||
if '_addanother' in request.POST:
|
if '_addanother' in request.POST:
|
||||||
@ -3111,8 +3112,7 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL
|
|||||||
# Protect master device from being removed
|
# Protect master device from being removed
|
||||||
virtual_chassis = VirtualChassis.objects.filter(master=device).first()
|
virtual_chassis = VirtualChassis.objects.filter(master=device).first()
|
||||||
if virtual_chassis is not None:
|
if virtual_chassis is not None:
|
||||||
msg = 'Unable to remove master device {} from the virtual chassis.'.format(escape(device))
|
messages.error(request, f'Unable to remove master device {device} from the virtual chassis.')
|
||||||
messages.error(request, mark_safe(msg))
|
|
||||||
return redirect(device.get_absolute_url())
|
return redirect(device.get_absolute_url())
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
@ -18,7 +18,7 @@ from netbox.models.features import ExportTemplatesMixin, WebhooksMixin
|
|||||||
from utilities import filters
|
from utilities import filters
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
CSVChoiceField, CSVMultipleChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
CSVChoiceField, CSVMultipleChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||||
LaxURLField, StaticSelectMultiple, StaticSelect, add_blank_choice,
|
JSONField, LaxURLField, StaticSelectMultiple, StaticSelect, add_blank_choice,
|
||||||
)
|
)
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.validators import validate_regex
|
from utilities.validators import validate_regex
|
||||||
@ -355,7 +355,7 @@ class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
|||||||
|
|
||||||
# JSON
|
# JSON
|
||||||
elif self.type == CustomFieldTypeChoices.TYPE_JSON:
|
elif self.type == CustomFieldTypeChoices.TYPE_JSON:
|
||||||
field = forms.JSONField(required=required, initial=initial)
|
field = JSONField(required=required, initial=initial)
|
||||||
|
|
||||||
# Object
|
# Object
|
||||||
elif self.type == CustomFieldTypeChoices.TYPE_OBJECT:
|
elif self.type == CustomFieldTypeChoices.TYPE_OBJECT:
|
||||||
|
@ -48,7 +48,7 @@ class FHRPGroup(NetBoxModel):
|
|||||||
related_query_name='fhrpgroup'
|
related_query_name='fhrpgroup'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ('protocol', 'auth_type', 'auth_key')
|
clone_fields = ('protocol', 'auth_type', 'auth_key', 'description')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['protocol', 'group_id', 'pk']
|
ordering = ['protocol', 'group_id', 'pk']
|
||||||
|
@ -175,9 +175,9 @@ class Aggregate(GetAvailablePrefixesMixin, NetBoxModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'rir', 'tenant', 'date_added', 'description',
|
'rir', 'tenant', 'date_added', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('prefix', 'pk') # prefix may be non-unique
|
ordering = ('prefix', 'pk') # prefix may be non-unique
|
||||||
@ -360,9 +360,9 @@ class Prefix(GetAvailablePrefixesMixin, NetBoxModel):
|
|||||||
|
|
||||||
objects = PrefixQuerySet.as_manager()
|
objects = PrefixQuerySet.as_manager()
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description',
|
'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = (F('vrf').asc(nulls_first=True), 'prefix', 'pk') # (vrf, prefix) may be non-unique
|
ordering = (F('vrf').asc(nulls_first=True), 'prefix', 'pk') # (vrf, prefix) may be non-unique
|
||||||
@ -608,9 +608,9 @@ class IPRange(NetBoxModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'vrf', 'tenant', 'status', 'role', 'description',
|
'vrf', 'tenant', 'status', 'role', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = (F('vrf').asc(nulls_first=True), 'start_address', 'pk') # (vrf, start_address) may be non-unique
|
ordering = (F('vrf').asc(nulls_first=True), 'start_address', 'pk') # (vrf, start_address) may be non-unique
|
||||||
@ -836,9 +836,9 @@ class IPAddress(NetBoxModel):
|
|||||||
|
|
||||||
objects = IPAddressManager()
|
objects = IPAddressManager()
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'vrf', 'tenant', 'status', 'role', 'description',
|
'vrf', 'tenant', 'status', 'role', 'dns_name', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('address', 'pk') # address may be non-unique
|
ordering = ('address', 'pk') # address may be non-unique
|
||||||
|
@ -55,9 +55,9 @@ class VRF(NetBoxModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'tenant', 'enforce_unique', 'description',
|
'tenant', 'enforce_unique', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('name', 'rd', 'pk') # (name, rd) may be non-unique
|
ordering = ('name', 'rd', 'pk') # (name, rd) may be non-unique
|
||||||
|
@ -109,9 +109,9 @@ class NestedGroupModel(NetBoxFeatureSet, MPTTModel):
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# An MPTT model cannot be its own parent
|
# An MPTT model cannot be its own parent
|
||||||
if self.pk and self.parent_id == self.pk:
|
if self.pk and self.parent and self.parent in self.get_descendants(include_self=True):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"parent": "Cannot assign self as parent."
|
"parent": f"Cannot assign self or child {self._meta.verbose_name} as parent."
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ from django.contrib.auth.models import AnonymousUser
|
|||||||
from django.db.models import DateField, DateTimeField
|
from django.db.models import DateField, DateTimeField
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.html import escape
|
||||||
from django.utils.formats import date_format
|
from django.utils.formats import date_format
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django_tables2.columns import library
|
from django_tables2.columns import library
|
||||||
@ -428,8 +429,8 @@ class CustomFieldColumn(tables.Column):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _likify_item(item):
|
def _likify_item(item):
|
||||||
if hasattr(item, 'get_absolute_url'):
|
if hasattr(item, 'get_absolute_url'):
|
||||||
return f'<a href="{item.get_absolute_url()}">{item}</a>'
|
return f'<a href="{item.get_absolute_url()}">{escape(item)}</a>'
|
||||||
return item
|
return escape(item)
|
||||||
|
|
||||||
def render(self, value):
|
def render(self, value):
|
||||||
if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is True:
|
if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is True:
|
||||||
@ -437,13 +438,13 @@ class CustomFieldColumn(tables.Column):
|
|||||||
if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is False:
|
if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is False:
|
||||||
return mark_safe('<i class="mdi mdi-close-thick text-danger"></i>')
|
return mark_safe('<i class="mdi mdi-close-thick text-danger"></i>')
|
||||||
if self.customfield.type == CustomFieldTypeChoices.TYPE_URL:
|
if self.customfield.type == CustomFieldTypeChoices.TYPE_URL:
|
||||||
return mark_safe(f'<a href="{value}">{value}</a>')
|
return mark_safe(f'<a href="{escape(value)}">{escape(value)}</a>')
|
||||||
if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
||||||
return ', '.join(v for v in value)
|
return ', '.join(v for v in value)
|
||||||
if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
|
if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
|
||||||
return mark_safe(', '.join([
|
return mark_safe(', '.join(
|
||||||
self._likify_item(obj) for obj in self.customfield.deserialize(value)
|
self._likify_item(obj) for obj in self.customfield.deserialize(value)
|
||||||
]))
|
))
|
||||||
if value is not None:
|
if value is not None:
|
||||||
obj = self.customfield.deserialize(value)
|
obj = self.customfield.deserialize(value)
|
||||||
return mark_safe(self._likify_item(obj))
|
return mark_safe(self._likify_item(obj))
|
||||||
|
@ -770,6 +770,7 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
model_form = None
|
model_form = None
|
||||||
filterset = None
|
filterset = None
|
||||||
table = None
|
table = None
|
||||||
|
patterned_fields = ('name', 'label')
|
||||||
|
|
||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return f'dcim.add_{self.queryset.model._meta.model_name}'
|
return f'dcim.add_{self.queryset.model._meta.model_name}'
|
||||||
@ -805,16 +806,16 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
|
|
||||||
for obj in data['pk']:
|
for obj in data['pk']:
|
||||||
|
|
||||||
names = data['name_pattern']
|
pattern_count = len(data[f'{self.patterned_fields[0]}_pattern'])
|
||||||
labels = data['label_pattern'] if 'label_pattern' in data else None
|
for i in range(pattern_count):
|
||||||
for i, name in enumerate(names):
|
|
||||||
label = labels[i] if labels else None
|
|
||||||
|
|
||||||
component_data = {
|
component_data = {
|
||||||
self.parent_field: obj.pk,
|
self.parent_field: obj.pk
|
||||||
'name': name,
|
|
||||||
'label': label
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for field_name in self.patterned_fields:
|
||||||
|
if data.get(f'{field_name}_pattern'):
|
||||||
|
component_data[field_name] = data[f'{field_name}_pattern'][i]
|
||||||
|
|
||||||
component_data.update(data)
|
component_data.update(data)
|
||||||
component_form = self.model_form(component_data)
|
component_form = self.model_form(component_data)
|
||||||
if component_form.is_valid():
|
if component_form.is_valid():
|
||||||
|
@ -389,10 +389,10 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
|
|||||||
)
|
)
|
||||||
logger.info(f"{msg} {obj} (PK: {obj.pk})")
|
logger.info(f"{msg} {obj} (PK: {obj.pk})")
|
||||||
if hasattr(obj, 'get_absolute_url'):
|
if hasattr(obj, 'get_absolute_url'):
|
||||||
msg = '{} <a href="{}">{}</a>'.format(msg, obj.get_absolute_url(), escape(obj))
|
msg = mark_safe(f'{msg} <a href="{obj.get_absolute_url()}">{escape(obj)}</a>')
|
||||||
else:
|
else:
|
||||||
msg = '{} {}'.format(msg, escape(obj))
|
msg = f'{msg} {obj}'
|
||||||
messages.success(request, mark_safe(msg))
|
messages.success(request, msg)
|
||||||
|
|
||||||
if '_addanother' in request.POST:
|
if '_addanother' in request.POST:
|
||||||
redirect_url = request.path
|
redirect_url = request.path
|
||||||
|
BIN
netbox/project-static/dist/netbox-dark.css
vendored
BIN
netbox/project-static/dist/netbox-dark.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox-light.css
vendored
BIN
netbox/project-static/dist/netbox-light.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox-print.css
vendored
BIN
netbox/project-static/dist/netbox-print.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js
vendored
BIN
netbox/project-static/dist/netbox.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js.map
vendored
BIN
netbox/project-static/dist/netbox.js.map
vendored
Binary file not shown.
@ -27,6 +27,23 @@ function handleSearchDropdownClick(event: Event, button: HTMLButtonElement): voi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show/hide quicksearch clear button.
|
||||||
|
*
|
||||||
|
* @param event "keyup" or "search" event for the quicksearch input
|
||||||
|
*/
|
||||||
|
function quickSearchEventHandler(event: Event): void {
|
||||||
|
const quicksearch = event.currentTarget as HTMLInputElement;
|
||||||
|
const inputgroup = quicksearch.parentElement as HTMLDivElement;
|
||||||
|
if (isTruthy(inputgroup)) {
|
||||||
|
if (quicksearch.value === "") {
|
||||||
|
inputgroup.classList.add("hide-last-child");
|
||||||
|
} else {
|
||||||
|
inputgroup.classList.remove("hide-last-child");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Search Bar Elements.
|
* Initialize Search Bar Elements.
|
||||||
*/
|
*/
|
||||||
@ -40,8 +57,35 @@ function initSearchBar(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Quicksearch Event listener/handlers.
|
||||||
|
*/
|
||||||
|
function initQuickSearch(): void {
|
||||||
|
const quicksearch = document.getElementById("quicksearch") as HTMLInputElement;
|
||||||
|
const clearbtn = document.getElementById("quicksearch_clear") as HTMLButtonElement;
|
||||||
|
if (isTruthy(quicksearch)) {
|
||||||
|
quicksearch.addEventListener("keyup", quickSearchEventHandler, {
|
||||||
|
passive: true
|
||||||
|
})
|
||||||
|
quicksearch.addEventListener("search", quickSearchEventHandler, {
|
||||||
|
passive: true
|
||||||
|
})
|
||||||
|
if (isTruthy(clearbtn)) {
|
||||||
|
clearbtn.addEventListener("click", async () => {
|
||||||
|
const search = new Event('search');
|
||||||
|
quicksearch.value = '';
|
||||||
|
await new Promise(f => setTimeout(f, 100));
|
||||||
|
quicksearch.dispatchEvent(search);
|
||||||
|
}, {
|
||||||
|
passive: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function initSearch(): void {
|
export function initSearch(): void {
|
||||||
for (const func of [initSearchBar]) {
|
for (const func of [initSearchBar]) {
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
|
initQuickSearch();
|
||||||
}
|
}
|
||||||
|
@ -416,6 +416,27 @@ nav.search {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Styles for the quicksearch and its clear button;
|
||||||
|
// Overrides input-group styles and adds transition effects
|
||||||
|
.quicksearch {
|
||||||
|
input[type="search"] {
|
||||||
|
border-radius: $border-radius !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left: -32px !important;
|
||||||
|
z-index: 100 !important;
|
||||||
|
outline: none !important;
|
||||||
|
border-radius: $border-radius !important;
|
||||||
|
transition: visibility 0s, opacity 0.2s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
button :hover {
|
||||||
|
opacity: 50%;
|
||||||
|
transition: visibility 0s, opacity 0.1s linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
main.layout {
|
main.layout {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
@ -714,11 +735,8 @@ textarea.form-control[rows='10'] {
|
|||||||
height: 18rem;
|
height: 18rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea#id_local_context_data,
|
|
||||||
textarea.markdown,
|
textarea.markdown,
|
||||||
textarea#id_public_key,
|
textarea.form-control[name='csv'] {
|
||||||
textarea.form-control[name='csv'],
|
|
||||||
textarea.form-control[name='data'] {
|
|
||||||
font-family: $font-family-monospace;
|
font-family: $font-family-monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,3 +34,11 @@ a[type='button'] {
|
|||||||
.badge {
|
.badge {
|
||||||
font-size: $font-size-xs;
|
font-size: $font-size-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* clears the 'X' in search inputs from webkit browsers */
|
||||||
|
input[type='search']::-webkit-search-decoration,
|
||||||
|
input[type='search']::-webkit-search-cancel-button,
|
||||||
|
input[type='search']::-webkit-search-results-button,
|
||||||
|
input[type='search']::-webkit-search-results-decoration {
|
||||||
|
-webkit-appearance: none !important;
|
||||||
|
}
|
||||||
|
@ -92,6 +92,10 @@ $input-focus-color: $input-color;
|
|||||||
$input-placeholder-color: $gray-700;
|
$input-placeholder-color: $gray-700;
|
||||||
$input-plaintext-color: $body-color;
|
$input-plaintext-color: $body-color;
|
||||||
|
|
||||||
|
input {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
|
||||||
$form-check-input-active-filter: brightness(90%);
|
$form-check-input-active-filter: brightness(90%);
|
||||||
$form-check-input-bg: $input-bg;
|
$form-check-input-bg: $input-bg;
|
||||||
$form-check-input-border: 1px solid rgba(255, 255, 255, 0.25);
|
$form-check-input-border: 1px solid rgba(255, 255, 255, 0.25);
|
||||||
|
@ -22,7 +22,6 @@ $theme-colors: (
|
|||||||
'danger': $danger,
|
'danger': $danger,
|
||||||
'light': $light,
|
'light': $light,
|
||||||
'dark': $dark,
|
'dark': $dark,
|
||||||
|
|
||||||
// General-purpose palette
|
// General-purpose palette
|
||||||
'blue': $blue-500,
|
'blue': $blue-500,
|
||||||
'indigo': $indigo-500,
|
'indigo': $indigo-500,
|
||||||
@ -36,7 +35,7 @@ $theme-colors: (
|
|||||||
'cyan': $cyan-500,
|
'cyan': $cyan-500,
|
||||||
'gray': $gray-500,
|
'gray': $gray-500,
|
||||||
'black': $black,
|
'black': $black,
|
||||||
'white': $white,
|
'white': $white
|
||||||
);
|
);
|
||||||
|
|
||||||
$light: $gray-200;
|
$light: $gray-200;
|
||||||
|
@ -42,3 +42,9 @@ table td {
|
|||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hides the last child of an element
|
||||||
|
.hide-last-child :last-child {
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
@ -111,13 +111,13 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleserverport&return_url={{ object.get_absolute_url }}" class="dropdown-item">Console Server Port</a>
|
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleserverport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}" class="dropdown-item">Console Server Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&return_url={{ object.get_absolute_url }}" class="dropdown-item">Front Port</a>
|
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}" class="dropdown-item">Front Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&return_url={{ object.get_absolute_url }}" class="dropdown-item">Rear Port</a>
|
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}" class="dropdown-item">Rear Port</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -113,13 +113,13 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleport&return_url={{ object.get_absolute_url }}" class="dropdown-item">Console Port</a>
|
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}" class="dropdown-item">Console Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&return_url={{ object.get_absolute_url }}" class="dropdown-item">Front Port</a>
|
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}" class="dropdown-item">Front Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&return_url={{ object.get_absolute_url }}" class="dropdown-item">Rear Port</a>
|
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}" class="dropdown-item">Rear Port</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,33 +4,25 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mb-3 justify-content-between">
|
<div class="row mb-3 justify-content-between">
|
||||||
<div class="col col-12 col-lg-4 my-3 my-lg-0 d-flex noprint table-controls">
|
<div class="col col-12 col-lg-4 my-3 my-lg-0 d-flex noprint table-controls">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm quicksearch hide-last-child">
|
||||||
<input
|
<input type="search" results=5 name="q" id="quicksearch" class="form-control" placeholder="Quick search"
|
||||||
type="text"
|
hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search" />
|
||||||
name="q"
|
<button class="btn bg-transparent" type="button" id="quicksearch_clear"><i
|
||||||
class="form-control"
|
class="mdi mdi-close-circle"></i></button>
|
||||||
placeholder="Quick search"
|
|
||||||
hx-get="{{ request.full_path }}"
|
|
||||||
hx-target="#object_list"
|
|
||||||
hx-trigger="keyup changed delay:500ms"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-3 mb-0 d-flex noprint table-controls">
|
<div class="col col-md-3 mb-0 d-flex noprint table-controls">
|
||||||
<div class="input-group input-group-sm justify-content-end">
|
<div class="input-group input-group-sm justify-content-end">
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<button
|
<button type="button" class="btn btn-sm btn-outline-dark" data-bs-toggle="modal"
|
||||||
type="button"
|
data-bs-target="#DeviceInterfaceTable_config" title="Configure Table">
|
||||||
class="btn btn-sm btn-outline-dark"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#DeviceInterfaceTable_config"
|
|
||||||
title="Configure Table">
|
|
||||||
<i class="mdi mdi-cog"></i> Configure Table
|
<i class="mdi mdi-cog"></i> Configure Table
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false">
|
||||||
<i class="mdi mdi-eye"></i>
|
<i class="mdi mdi-eye"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
@ -39,9 +31,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
|
|
||||||
@ -53,32 +45,41 @@
|
|||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if 'bulk_edit' in actions %}
|
{% if perms.dcim.change_interface %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
<button type="submit" name="_rename"
|
||||||
|
formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
||||||
|
class="btn btn-outline-warning btn-sm">
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-warning btn-sm">
|
<button type="submit" name="_edit"
|
||||||
|
formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
||||||
|
class="btn btn-warning btn-sm">
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
|
<button type="submit" name="_disconnect"
|
||||||
|
formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
||||||
|
class="btn btn-outline-danger btn-sm">
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'bulk_delete' in actions %}
|
{% if perms.dcim.delete_interface %}
|
||||||
<button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-danger btn-sm">
|
<button type="submit" name="_delete"
|
||||||
|
formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
||||||
|
class="btn btn-danger btn-sm">
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if perms.dcim.add_interface %}
|
{% if perms.dcim.add_interface %}
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
<a href="{% url 'dcim:interface_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-primary btn-sm">
|
<a href="{% url 'dcim:interface_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
||||||
|
class="btn btn-primary btn-sm">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Interfaces
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Interfaces
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block modals %}
|
{% block modals %}
|
||||||
|
@ -109,22 +109,22 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.interface&return_url={{ object.get_absolute_url }}">Interface</a>
|
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.interface&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}">Interface</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleserverport&return_url={{ object.get_absolute_url }}">Console Server Port</a>
|
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleserverport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}">Console Server Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleport&return_url={{ object.get_absolute_url }}">Console Port</a>
|
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}">Console Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&return_url={{ object.get_absolute_url }}">Front Port</a>
|
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}">Front Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&return_url={{ object.get_absolute_url }}">Rear Port</a>
|
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}">Rear Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=circuits.circuittermination&return_url={{ object.get_absolute_url }}">Circuit Termination</a>
|
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=circuits.circuittermination&termination_b_site={{ object.device.site.pk }}&return_url={{ object.get_absolute_url }}">Circuit Termination</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -263,16 +263,16 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.interface&return_url={{ object.get_absolute_url }}">Interface</a>
|
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.interface&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}">Interface</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&return_url={{ object.get_absolute_url }}">Front Port</a>
|
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}">Front Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&return_url={{ object.get_absolute_url }}">Rear Port</a>
|
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}">Rear Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=circuits.circuittermination&return_url={{ object.get_absolute_url }}">Circuit Termination</a>
|
<a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=circuits.circuittermination&termination_b_site={{ object.device.site.pk }}&return_url={{ object.get_absolute_url }}">Circuit Termination</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -158,7 +158,7 @@
|
|||||||
{% if not object.mark_connected and not object.cable %}
|
{% if not object.mark_connected and not object.cable %}
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
{% if perms.dcim.add_cable %}
|
{% if perms.dcim.add_cable %}
|
||||||
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerfeed&a_terminations={{ object.pk }}&b_terminations_type=dcim.powerport&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm float-end">
|
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerfeed&a_terminations={{ object.pk }}&b_terminations_type=dcim.powerport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm float-end">
|
||||||
<i class="mdi mdi-ethernet-cable" aria-hidden="true"></i> Connect
|
<i class="mdi mdi-ethernet-cable" aria-hidden="true"></i> Connect
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -111,7 +111,7 @@
|
|||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Not Connected
|
Not Connected
|
||||||
{% if perms.dcim.add_cable %}
|
{% if perms.dcim.add_cable %}
|
||||||
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.poweroutlet&a_terminations={{ object.pk }}&b_terminations_type=dcim.powerport&return_url={{ object.get_absolute_url }}" title="Connect" class="btn btn-primary btn-sm float-end">
|
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.poweroutlet&a_terminations={{ object.pk }}&b_terminations_type=dcim.powerport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}" title="Connect" class="btn btn-primary btn-sm float-end">
|
||||||
<i class="mdi mdi-ethernet-cable" aria-hidden="true"></i> Connect
|
<i class="mdi mdi-ethernet-cable" aria-hidden="true"></i> Connect
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -117,10 +117,10 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerportport&a_terminations={{ object.pk }}&termination_b_type=dcim.poweroutlet&return_url={{ object.get_absolute_url }}" class="dropdown-link">Power Outlet</a>
|
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerportport&a_terminations={{ object.pk }}&termination_b_type=dcim.poweroutlet&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}" class="dropdown-link">Power Outlet</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerportport&a_terminations={{ object.pk }}&termination_b_type=dcim.powerfeed&return_url={{ object.get_absolute_url }}" class="dropdown-link">Power Feed</a>
|
<a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerportport&a_terminations={{ object.pk }}&termination_b_type=dcim.powerfeed&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}" class="dropdown-link">Power Feed</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
|
@ -105,16 +105,16 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-link" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=dcim.interface&return_url={{ object.get_absolute_url }}">Interface</a>
|
<a class="dropdown-link" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=dcim.interface&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}">Interface</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-link" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&return_url={{ object.get_absolute_url }}">Front Port</a>
|
<a class="dropdown-link" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}">Front Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-link" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&return_url={{ object.get_absolute_url }}">Rear Port</a>
|
<a class="dropdown-link" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&termination_b_site={{ object.device.site.pk }}&termination_b_rack={{ object.device.rack.pk }}&return_url={{ object.get_absolute_url }}">Rear Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-link" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=circuits.circuittermination&return_url={{ object.get_absolute_url }}">Circuit Termination</a>
|
<a class="dropdown-link" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=circuits.circuittermination&termination_b_site={{ object.device.site.pk }}&return_url={{ object.get_absolute_url }}">Circuit Termination</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
<th>Priority</th>
|
<th>Priority</th>
|
||||||
|
<th>Phone</th>
|
||||||
|
<th>Email</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for contact in contacts %}
|
{% for contact in contacts %}
|
||||||
@ -17,6 +19,20 @@
|
|||||||
<td>{{ contact.contact|linkify }}</td>
|
<td>{{ contact.contact|linkify }}</td>
|
||||||
<td>{{ contact.role|placeholder }}</td>
|
<td>{{ contact.role|placeholder }}</td>
|
||||||
<td>{{ contact.get_priority_display|placeholder }}</td>
|
<td>{{ contact.get_priority_display|placeholder }}</td>
|
||||||
|
<td>
|
||||||
|
{% if contact.contact.phone %}
|
||||||
|
<a href="tel:{{ contact.contact.phone }}">{{ contact.contact.phone }}</a>
|
||||||
|
{% else %}
|
||||||
|
{{ ''|placeholder }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if contact.contact.email %}
|
||||||
|
<a href="mailto:{{ contact.contact.email }}">{{ contact.contact.email }}</a>
|
||||||
|
{% else %}
|
||||||
|
{{ ''|placeholder }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td class="text-end noprint">
|
<td class="text-end noprint">
|
||||||
{% if perms.tenancy.change_contactassignment %}
|
{% if perms.tenancy.change_contactassignment %}
|
||||||
<a href="{% url 'tenancy:contactassignment_edit' pk=contact.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning btn-sm lh-1" title="Edit">
|
<a href="{% url 'tenancy:contactassignment_edit' pk=contact.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning btn-sm lh-1" title="Edit">
|
||||||
|
@ -2,28 +2,18 @@
|
|||||||
|
|
||||||
<div class="row mb-3 justify-content-between">
|
<div class="row mb-3 justify-content-between">
|
||||||
<div class="table-controls noprint col col-12 col-md-8 col-lg-4">
|
<div class="table-controls noprint col col-12 col-md-8 col-lg-4">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm quicksearch hide-last-child">
|
||||||
<input
|
<input type="search" results=5 name="q" id="quicksearch" class="form-control" placeholder="Quick search"
|
||||||
type="text"
|
hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search" />
|
||||||
name="q"
|
<button class="btn bg-transparent" type="button" id="quicksearch_clear"><i
|
||||||
class="form-control"
|
class="mdi mdi-close-circle"></i></button>
|
||||||
placeholder="Quick search"
|
|
||||||
hx-get="{{ request.full_path }}"
|
|
||||||
hx-target="#object_list"
|
|
||||||
hx-trigger="keyup changed delay:500ms"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-controls noprint col col-md-3 mb-0">
|
<div class="table-controls noprint col col-md-3 mb-0">
|
||||||
{% if request.user.is_authenticated and table_modal %}
|
{% if request.user.is_authenticated and table_modal %}
|
||||||
<div class="table-configure input-group input-group-sm">
|
<div class="table-configure input-group input-group-sm">
|
||||||
<button
|
<button type="button" data-bs-toggle="modal" title="Configure Table" data-bs-target="#{{ table_modal }}"
|
||||||
type="button"
|
class="btn btn-sm btn-outline-dark">
|
||||||
data-bs-toggle="modal"
|
|
||||||
title="Configure Table"
|
|
||||||
data-bs-target="#{{ table_modal }}"
|
|
||||||
class="btn btn-sm btn-outline-dark"
|
|
||||||
>
|
|
||||||
<i class="mdi mdi-cog"></i> Configure Table
|
<i class="mdi mdi-cog"></i> Configure Table
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -112,9 +112,9 @@ class Contact(NetBoxModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'group',
|
'group', 'name', 'title', 'phone', 'email', 'address', 'link',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
@ -155,7 +155,7 @@ class ContactAssignment(WebhooksMixin, ChangeLoggedModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ('content_type', 'object_id')
|
clone_fields = ('content_type', 'object_id', 'role', 'priority')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('priority', 'contact')
|
ordering = ('priority', 'contact')
|
||||||
|
@ -76,9 +76,9 @@ class Tenant(NetBoxModel):
|
|||||||
to='tenancy.ContactAssignment'
|
to='tenancy.ContactAssignment'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'group', 'description',
|
'group', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
@ -58,6 +58,8 @@ class TokenViewSet(NetBoxModelViewSet):
|
|||||||
# Workaround for schema generation (drf_yasg)
|
# Workaround for schema generation (drf_yasg)
|
||||||
if getattr(self, 'swagger_fake_view', False):
|
if getattr(self, 'swagger_fake_view', False):
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
if not self.request.user.is_authenticated:
|
||||||
|
return queryset.none()
|
||||||
if self.request.user.is_superuser:
|
if self.request.user.is_superuser:
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(user=self.request.user)
|
return queryset.filter(user=self.request.user)
|
||||||
@ -74,11 +76,11 @@ class TokenProvisionView(APIView):
|
|||||||
serializer.is_valid()
|
serializer.is_valid()
|
||||||
|
|
||||||
# Authenticate the user account based on the provided credentials
|
# Authenticate the user account based on the provided credentials
|
||||||
user = authenticate(
|
username = serializer.data.get('username')
|
||||||
request=request,
|
password = serializer.data.get('password')
|
||||||
username=serializer.data['username'],
|
if not username or not password:
|
||||||
password=serializer.data['password']
|
raise AuthenticationFailed("Username and password must be provided to provision a token.")
|
||||||
)
|
user = authenticate(request=request, username=username, password=password)
|
||||||
if user is None:
|
if user is None:
|
||||||
raise AuthenticationFailed("Invalid username/password")
|
raise AuthenticationFailed("Invalid username/password")
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ from django.http import HttpResponseRedirect
|
|||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.utils.http import url_has_allowed_host_and_scheme
|
||||||
from django.views.decorators.debug import sensitive_post_parameters
|
from django.views.decorators.debug import sensitive_post_parameters
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from social_core.backends.utils import load_backends
|
from social_core.backends.utils import load_backends
|
||||||
@ -92,7 +93,7 @@ class LoginView(View):
|
|||||||
data = request.POST if request.method == "POST" else request.GET
|
data = request.POST if request.method == "POST" else request.GET
|
||||||
redirect_url = data.get('next', settings.LOGIN_REDIRECT_URL)
|
redirect_url = data.get('next', settings.LOGIN_REDIRECT_URL)
|
||||||
|
|
||||||
if redirect_url and redirect_url.startswith('/'):
|
if redirect_url and url_has_allowed_host_and_scheme(redirect_url, allowed_hosts=None):
|
||||||
logger.debug(f"Redirecting user to {redirect_url}")
|
logger.debug(f"Redirecting user to {redirect_url}")
|
||||||
else:
|
else:
|
||||||
if redirect_url:
|
if redirect_url:
|
||||||
|
@ -99,6 +99,7 @@ class JSONField(_JSONField):
|
|||||||
if not self.help_text:
|
if not self.help_text:
|
||||||
self.help_text = 'Enter context data in <a href="https://json.org/">JSON</a> format.'
|
self.help_text = 'Enter context data in <a href="https://json.org/">JSON</a> format.'
|
||||||
self.widget.attrs['placeholder'] = ''
|
self.widget.attrs['placeholder'] = ''
|
||||||
|
self.widget.attrs['class'] = 'font-monospace'
|
||||||
|
|
||||||
def prepare_value(self, value):
|
def prepare_value(self, value):
|
||||||
if isinstance(value, InvalidJSONInput):
|
if isinstance(value, InvalidJSONInput):
|
||||||
|
@ -136,7 +136,7 @@ class ImportForm(BootstrapMixin, forms.Form):
|
|||||||
Generic form for creating an object from JSON/YAML data
|
Generic form for creating an object from JSON/YAML data
|
||||||
"""
|
"""
|
||||||
data = forms.CharField(
|
data = forms.CharField(
|
||||||
widget=forms.Textarea,
|
widget=forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||||
help_text="Enter object data in JSON or YAML format. Note: Only a single object/document is supported."
|
help_text="Enter object data in JSON or YAML format. Note: Only a single object/document is supported."
|
||||||
)
|
)
|
||||||
format = forms.ChoiceField(
|
format = forms.ChoiceField(
|
||||||
|
@ -86,8 +86,8 @@ def placeholder(value):
|
|||||||
"""
|
"""
|
||||||
if value not in ('', None):
|
if value not in ('', None):
|
||||||
return value
|
return value
|
||||||
placeholder = '<span class="text-muted">—</span>'
|
|
||||||
return mark_safe(placeholder)
|
return mark_safe('<span class="text-muted">—</span>')
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
@register.filter()
|
||||||
|
@ -109,9 +109,7 @@ def annotated_date(date_value):
|
|||||||
long_ts = date(date_value, 'DATETIME_FORMAT')
|
long_ts = date(date_value, 'DATETIME_FORMAT')
|
||||||
short_ts = date(date_value, 'SHORT_DATETIME_FORMAT')
|
short_ts = date(date_value, 'SHORT_DATETIME_FORMAT')
|
||||||
|
|
||||||
span = f'<span title="{long_ts}">{short_ts}</span>'
|
return mark_safe(f'<span title="{long_ts}">{short_ts}</span>')
|
||||||
|
|
||||||
return mark_safe(span)
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
|
@ -93,7 +93,7 @@ class VirtualMachineFilterForm(
|
|||||||
(None, ('q', 'tag')),
|
(None, ('q', 'tag')),
|
||||||
('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id', 'device_id')),
|
('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id', 'device_id')),
|
||||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||||
('Attriubtes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')),
|
('Attributes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')),
|
||||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||||
)
|
)
|
||||||
|
@ -153,9 +153,9 @@ class Cluster(NetBoxModel):
|
|||||||
to='tenancy.ContactAssignment'
|
to='tenancy.ContactAssignment'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'type', 'group', 'tenant', 'site',
|
'type', 'group', 'status', 'tenant', 'site',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
@ -299,9 +299,9 @@ class VirtualMachine(NetBoxModel, ConfigContextModel):
|
|||||||
|
|
||||||
objects = ConfigContextModelQuerySet.as_manager()
|
objects = ConfigContextModelQuerySet.as_manager()
|
||||||
|
|
||||||
clone_fields = [
|
clone_fields = (
|
||||||
'site', 'cluster', 'device', 'tenant', 'platform', 'status', 'role', 'vcpus', 'memory', 'disk',
|
'site', 'cluster', 'device', 'tenant', 'platform', 'status', 'role', 'vcpus', 'memory', 'disk',
|
||||||
]
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('_name', 'pk') # Name may be non-unique
|
ordering = ('_name', 'pk') # Name may be non-unique
|
||||||
|
@ -113,6 +113,8 @@ class WirelessLAN(WirelessAuthenticationBase, NetBoxModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
clone_fields = ('ssid', 'group', 'tenant', 'description')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('ssid', 'pk')
|
ordering = ('ssid', 'pk')
|
||||||
verbose_name = 'Wireless LAN'
|
verbose_name = 'Wireless LAN'
|
||||||
|
@ -25,7 +25,7 @@ netaddr==0.8.0
|
|||||||
Pillow==9.2.0
|
Pillow==9.2.0
|
||||||
psycopg2-binary==2.9.3
|
psycopg2-binary==2.9.3
|
||||||
PyYAML==6.0
|
PyYAML==6.0
|
||||||
sentry-sdk==1.9.0
|
sentry-sdk==1.9.2
|
||||||
social-auth-app-django==5.0.0
|
social-auth-app-django==5.0.0
|
||||||
social-auth-core==4.3.0
|
social-auth-core==4.3.0
|
||||||
svgwrite==1.4.3
|
svgwrite==1.4.3
|
||||||
@ -34,3 +34,6 @@ tzdata==2022.1
|
|||||||
|
|
||||||
# Workaround for #7401
|
# Workaround for #7401
|
||||||
jsonschema==3.2.0
|
jsonschema==3.2.0
|
||||||
|
|
||||||
|
# Workaround for #9986
|
||||||
|
pytz==2022.1
|
||||||
|
Loading…
Reference in New Issue
Block a user