mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
Merge branch 'develop' into feature
This commit is contained in:
commit
ca59cd1eb8
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -17,7 +17,7 @@ body:
|
|||||||
What version of NetBox are you currently running? (If you don't have access to the most
|
What version of NetBox are you currently running? (If you don't have access to the most
|
||||||
recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/)
|
recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/)
|
||||||
before opening a bug report to see if your issue has already been addressed.)
|
before opening a bug report to see if your issue has already been addressed.)
|
||||||
placeholder: v3.0.5
|
placeholder: v3.0.6
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.0.5
|
placeholder: v3.0.6
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
@ -5,6 +5,13 @@ NetBox includes a `housekeeping` management command that should be run nightly.
|
|||||||
* Clearing expired authentication sessions from the database
|
* Clearing expired authentication sessions from the database
|
||||||
* Deleting changelog records older than the configured [retention time](../configuration/optional-settings.md#changelog_retention)
|
* Deleting changelog records older than the configured [retention time](../configuration/optional-settings.md#changelog_retention)
|
||||||
|
|
||||||
This command can be invoked directly, or by using the shell script provided at `/opt/netbox/contrib/netbox-housekeeping.sh`. This script can be copied into your cron scheduler's daily jobs directory (e.g. `/etc/cron.daily`) or referenced directly within the cron configuration file.
|
This command can be invoked directly, or by using the shell script provided at `/opt/netbox/contrib/netbox-housekeeping.sh`. This script can be linked from your cron scheduler's daily jobs directory (e.g. `/etc/cron.daily`) or referenced directly within the cron configuration file.
|
||||||
|
|
||||||
The `housekeeping` command can also be run manually at any time: Running the command outside of scheduled execution times will not interfere with its operation.
|
```shell
|
||||||
|
ln -s /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/netbox-housekeeping
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
On Debian-based systems, be sure to omit the `.sh` file extension when linking to the script from within a cron directory. Otherwise, the task may not run.
|
||||||
|
|
||||||
|
The `housekeeping` command can also be run manually at any time: Running the command outside scheduled execution times will not interfere with its operation.
|
||||||
|
@ -259,10 +259,10 @@ python3 manage.py createsuperuser
|
|||||||
|
|
||||||
NetBox includes a `housekeeping` management command that handles some recurring cleanup tasks, such as clearing out old sessions and expired change records. Although this command may be run manually, it is recommended to configure a scheduled job using the system's `cron` daemon or a similar utility.
|
NetBox includes a `housekeeping` management command that handles some recurring cleanup tasks, such as clearing out old sessions and expired change records. Although this command may be run manually, it is recommended to configure a scheduled job using the system's `cron` daemon or a similar utility.
|
||||||
|
|
||||||
A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be copied to your system's daily cron task directory, or included within the crontab directly. (If installing NetBox into a nonstandard path, be sure to update the system paths within this script first.)
|
A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be copied to or linked from your system's daily cron task directory, or included within the crontab directly. (If installing NetBox into a nonstandard path, be sure to update the system paths within this script first.)
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cp /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/
|
ln -s /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/netbox-housekeeping
|
||||||
```
|
```
|
||||||
|
|
||||||
See the [housekeeping documentation](../administration/housekeeping.md) for further details.
|
See the [housekeeping documentation](../administration/housekeeping.md) for further details.
|
||||||
|
@ -111,10 +111,10 @@ sudo systemctl restart netbox netbox-rq
|
|||||||
|
|
||||||
## Verify Housekeeping Scheduling
|
## Verify Housekeeping Scheduling
|
||||||
|
|
||||||
If upgrading from a release prior to NetBox v3.0, check that a cron task (or similar scheduled process) has been configured to run NetBox's nightly housekeeping command. A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be copied to your system's daily cron task directory, or included within the crontab directly. (If NetBox has been installed in a nonstandard path, be sure to update the system paths within this script first.)
|
If upgrading from a release prior to NetBox v3.0, check that a cron task (or similar scheduled process) has been configured to run NetBox's nightly housekeeping command. A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be linked from your system's daily cron task directory, or included within the crontab directly. (If NetBox has been installed in a nonstandard path, be sure to update the system paths within this script first.)
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cp /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/
|
ln -s /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/netbox-housekeeping
|
||||||
```
|
```
|
||||||
|
|
||||||
See the [housekeeping documentation](../administration/housekeeping.md) for further details.
|
See the [housekeeping documentation](../administration/housekeeping.md) for further details.
|
||||||
|
@ -1,5 +1,24 @@
|
|||||||
# NetBox v3.0
|
# NetBox v3.0
|
||||||
|
|
||||||
|
## v3.0.6 (2021-10-06)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#6850](https://github.com/netbox-community/netbox/issues/6850) - Default to current user when creating journal entries via REST API
|
||||||
|
* [#6955](https://github.com/netbox-community/netbox/issues/6955) - Include type, ID, and slug on object view
|
||||||
|
* [#7394](https://github.com/netbox-community/netbox/issues/7394) - Enable filtering cables by termination type & ID in REST API
|
||||||
|
* [#7462](https://github.com/netbox-community/netbox/issues/7462) - Include count of assigned virtual machines under platform view
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#7442](https://github.com/netbox-community/netbox/issues/7442) - Fix missing actions column on user-configured tables
|
||||||
|
* [#7446](https://github.com/netbox-community/netbox/issues/7446) - Fix exception when viewing a large number of child IPs within a prefix
|
||||||
|
* [#7455](https://github.com/netbox-community/netbox/issues/7455) - Fix site/provider network validation for circuit termination API serializer
|
||||||
|
* [#7459](https://github.com/netbox-community/netbox/issues/7459) - Pre-populate location data when adding a device to a rack
|
||||||
|
* [#7460](https://github.com/netbox-community/netbox/issues/7460) - Fix filtering connections by site ID
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v3.0.5 (2021-10-04)
|
## v3.0.5 (2021-10-04)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
@ -8,7 +27,6 @@
|
|||||||
* [#6423](https://github.com/netbox-community/netbox/issues/6423) - Cache rendered REST API specifications
|
* [#6423](https://github.com/netbox-community/netbox/issues/6423) - Cache rendered REST API specifications
|
||||||
* [#6708](https://github.com/netbox-community/netbox/issues/6708) - Add image attachment support for circuits, power panels
|
* [#6708](https://github.com/netbox-community/netbox/issues/6708) - Add image attachment support for circuits, power panels
|
||||||
* [#7387](https://github.com/netbox-community/netbox/issues/7387) - Enable arbitrary ordering of custom scripts
|
* [#7387](https://github.com/netbox-community/netbox/issues/7387) - Enable arbitrary ordering of custom scripts
|
||||||
* [#7427](https://github.com/netbox-community/netbox/issues/7427) - Don't select hidden rows when selecting all in a table
|
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
@ -23,6 +41,7 @@
|
|||||||
* [#7412](https://github.com/netbox-community/netbox/issues/7412) - Fix exception in UI when adding child device to device bay
|
* [#7412](https://github.com/netbox-community/netbox/issues/7412) - Fix exception in UI when adding child device to device bay
|
||||||
* [#7417](https://github.com/netbox-community/netbox/issues/7417) - Prevent exception when filtering objects list by invalid tag
|
* [#7417](https://github.com/netbox-community/netbox/issues/7417) - Prevent exception when filtering objects list by invalid tag
|
||||||
* [#7425](https://github.com/netbox-community/netbox/issues/7425) - Housekeeping command should honor zero verbosity
|
* [#7425](https://github.com/netbox-community/netbox/issues/7425) - Housekeeping command should honor zero verbosity
|
||||||
|
* [#7427](https://github.com/netbox-community/netbox/issues/7427) - Don't select hidden rows when selecting all in a table
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@ from rest_framework import serializers
|
|||||||
from circuits.choices import CircuitStatusChoices
|
from circuits.choices import CircuitStatusChoices
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
|
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
|
||||||
from dcim.api.serializers import CableTerminationSerializer, ConnectedEndpointSerializer
|
from dcim.api.serializers import CableTerminationSerializer
|
||||||
from netbox.api import ChoiceField
|
from netbox.api import ChoiceField
|
||||||
from netbox.api.serializers import (
|
from netbox.api.serializers import (
|
||||||
BaseModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer, WritableNestedSerializer
|
OrganizationalModelSerializer, PrimaryModelSerializer, ValidatedModelSerializer, WritableNestedSerializer
|
||||||
)
|
)
|
||||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||||
from .nested_serializers import *
|
from .nested_serializers import *
|
||||||
@ -90,11 +90,11 @@ class CircuitSerializer(PrimaryModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CircuitTerminationSerializer(BaseModelSerializer, CableTerminationSerializer):
|
class CircuitTerminationSerializer(ValidatedModelSerializer, CableTerminationSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
|
||||||
circuit = NestedCircuitSerializer()
|
circuit = NestedCircuitSerializer()
|
||||||
site = NestedSiteSerializer(required=False)
|
site = NestedSiteSerializer(required=False, allow_null=True)
|
||||||
provider_network = NestedProviderNetworkSerializer(required=False)
|
provider_network = NestedProviderNetworkSerializer(required=False, allow_null=True)
|
||||||
cable = NestedCableSerializer(read_only=True)
|
cable = NestedCableSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -136,14 +136,20 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
|
|||||||
SIDE_A = CircuitTerminationSideChoices.SIDE_A
|
SIDE_A = CircuitTerminationSideChoices.SIDE_A
|
||||||
SIDE_Z = CircuitTerminationSideChoices.SIDE_Z
|
SIDE_Z = CircuitTerminationSideChoices.SIDE_Z
|
||||||
|
|
||||||
|
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
|
||||||
|
circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
|
||||||
|
|
||||||
sites = (
|
sites = (
|
||||||
Site(name='Site 1', slug='site-1'),
|
Site(name='Site 1', slug='site-1'),
|
||||||
Site(name='Site 2', slug='site-2'),
|
Site(name='Site 2', slug='site-2'),
|
||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
|
provider_networks = (
|
||||||
circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
|
ProviderNetwork(provider=provider, name='Provider Network 1'),
|
||||||
|
ProviderNetwork(provider=provider, name='Provider Network 2'),
|
||||||
|
)
|
||||||
|
ProviderNetwork.objects.bulk_create(provider_networks)
|
||||||
|
|
||||||
circuits = (
|
circuits = (
|
||||||
Circuit(cid='Circuit 1', provider=provider, type=circuit_type),
|
Circuit(cid='Circuit 1', provider=provider, type=circuit_type),
|
||||||
@ -153,10 +159,10 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
|
|||||||
Circuit.objects.bulk_create(circuits)
|
Circuit.objects.bulk_create(circuits)
|
||||||
|
|
||||||
circuit_terminations = (
|
circuit_terminations = (
|
||||||
CircuitTermination(circuit=circuits[0], site=sites[0], term_side=SIDE_A),
|
CircuitTermination(circuit=circuits[0], term_side=SIDE_A, site=sites[0]),
|
||||||
CircuitTermination(circuit=circuits[0], site=sites[1], term_side=SIDE_Z),
|
CircuitTermination(circuit=circuits[0], term_side=SIDE_Z, provider_network=provider_networks[0]),
|
||||||
CircuitTermination(circuit=circuits[1], site=sites[0], term_side=SIDE_A),
|
CircuitTermination(circuit=circuits[1], term_side=SIDE_A, site=sites[1]),
|
||||||
CircuitTermination(circuit=circuits[1], site=sites[1], term_side=SIDE_Z),
|
CircuitTermination(circuit=circuits[1], term_side=SIDE_Z, provider_network=provider_networks[1]),
|
||||||
)
|
)
|
||||||
CircuitTermination.objects.bulk_create(circuit_terminations)
|
CircuitTermination.objects.bulk_create(circuit_terminations)
|
||||||
|
|
||||||
@ -164,13 +170,13 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
|
|||||||
{
|
{
|
||||||
'circuit': circuits[2].pk,
|
'circuit': circuits[2].pk,
|
||||||
'term_side': SIDE_A,
|
'term_side': SIDE_A,
|
||||||
'site': sites[1].pk,
|
'site': sites[0].pk,
|
||||||
'port_speed': 200000,
|
'port_speed': 200000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'circuit': circuits[2].pk,
|
'circuit': circuits[2].pk,
|
||||||
'term_side': SIDE_Z,
|
'term_side': SIDE_Z,
|
||||||
'site': sites[1].pk,
|
'provider_network': provider_networks[0].pk,
|
||||||
'port_speed': 200000,
|
'port_speed': 200000,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -10,14 +10,14 @@ from tenancy.filtersets import TenancyFilterSet
|
|||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter,
|
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter,
|
||||||
|
TreeNodeMultipleChoiceFilter,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CableFilterSet',
|
'CableFilterSet',
|
||||||
'CableTerminationFilterSet',
|
'CableTerminationFilterSet',
|
||||||
@ -1184,6 +1184,10 @@ class CableFilterSet(PrimaryModelFilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
termination_a_type = ContentTypeFilter()
|
||||||
|
termination_a_id = MultiValueNumberFilter()
|
||||||
|
termination_b_type = ContentTypeFilter()
|
||||||
|
termination_b_id = MultiValueNumberFilter()
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=CableTypeChoices
|
choices=CableTypeChoices
|
||||||
)
|
)
|
||||||
@ -1228,7 +1232,7 @@ class CableFilterSet(PrimaryModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Cable
|
model = Cable
|
||||||
fields = ['id', 'label', 'length', 'length_unit']
|
fields = ['id', 'label', 'length', 'length_unit', 'termination_a_id', 'termination_b_id']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -1243,73 +1247,6 @@ class CableFilterSet(PrimaryModelFilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class ConnectionFilterSet(BaseFilterSet):
|
|
||||||
|
|
||||||
def filter_site(self, queryset, name, value):
|
|
||||||
if not value.strip():
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(device__site__slug=value)
|
|
||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
|
||||||
if not value:
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(**{f'{name}__in': value})
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleConnectionFilterSet(ConnectionFilterSet):
|
|
||||||
site = django_filters.CharFilter(
|
|
||||||
method='filter_site',
|
|
||||||
label='Site (slug)',
|
|
||||||
)
|
|
||||||
device_id = MultiValueNumberFilter(
|
|
||||||
method='filter_device'
|
|
||||||
)
|
|
||||||
device = MultiValueCharFilter(
|
|
||||||
method='filter_device',
|
|
||||||
field_name='device__name'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ConsolePort
|
|
||||||
fields = ['name']
|
|
||||||
|
|
||||||
|
|
||||||
class PowerConnectionFilterSet(ConnectionFilterSet):
|
|
||||||
site = django_filters.CharFilter(
|
|
||||||
method='filter_site',
|
|
||||||
label='Site (slug)',
|
|
||||||
)
|
|
||||||
device_id = MultiValueNumberFilter(
|
|
||||||
method='filter_device'
|
|
||||||
)
|
|
||||||
device = MultiValueCharFilter(
|
|
||||||
method='filter_device',
|
|
||||||
field_name='device__name'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = PowerPort
|
|
||||||
fields = ['name']
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionFilterSet(ConnectionFilterSet):
|
|
||||||
site = django_filters.CharFilter(
|
|
||||||
method='filter_site',
|
|
||||||
label='Site (slug)',
|
|
||||||
)
|
|
||||||
device_id = MultiValueNumberFilter(
|
|
||||||
method='filter_device'
|
|
||||||
)
|
|
||||||
device = MultiValueCharFilter(
|
|
||||||
method='filter_device',
|
|
||||||
field_name='device__name'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Interface
|
|
||||||
fields = []
|
|
||||||
|
|
||||||
|
|
||||||
class PowerPanelFilterSet(PrimaryModelFilterSet):
|
class PowerPanelFilterSet(PrimaryModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
@ -1441,3 +1378,52 @@ class PowerFeedFilterSet(PrimaryModelFilterSet, CableTerminationFilterSet, PathE
|
|||||||
Q(comments__icontains=value)
|
Q(comments__icontains=value)
|
||||||
)
|
)
|
||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Connection filter sets
|
||||||
|
#
|
||||||
|
|
||||||
|
class ConnectionFilterSet(BaseFilterSet):
|
||||||
|
site_id = MultiValueNumberFilter(
|
||||||
|
method='filter_connections',
|
||||||
|
field_name='device__site_id'
|
||||||
|
)
|
||||||
|
site = MultiValueCharFilter(
|
||||||
|
method='filter_connections',
|
||||||
|
field_name='device__site__slug'
|
||||||
|
)
|
||||||
|
device_id = MultiValueNumberFilter(
|
||||||
|
method='filter_connections',
|
||||||
|
field_name='device_id'
|
||||||
|
)
|
||||||
|
device = MultiValueCharFilter(
|
||||||
|
method='filter_connections',
|
||||||
|
field_name='device__name'
|
||||||
|
)
|
||||||
|
|
||||||
|
def filter_connections(self, queryset, name, value):
|
||||||
|
if not value:
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(**{f'{name}__in': value})
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleConnectionFilterSet(ConnectionFilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ConsolePort
|
||||||
|
fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
|
class PowerConnectionFilterSet(ConnectionFilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PowerPort
|
||||||
|
fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceConnectionFilterSet(ConnectionFilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Interface
|
||||||
|
fields = []
|
||||||
|
@ -132,14 +132,18 @@ class RackElevationSVG:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation):
|
def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation):
|
||||||
|
link_url = '{}?{}'.format(
|
||||||
|
reverse('dcim:device_add'),
|
||||||
|
urlencode({
|
||||||
|
'site': rack.site.pk,
|
||||||
|
'location': rack.location.pk if rack.location else '',
|
||||||
|
'rack': rack.pk,
|
||||||
|
'face': face_id,
|
||||||
|
'position': id_
|
||||||
|
})
|
||||||
|
)
|
||||||
link = drawing.add(
|
link = drawing.add(
|
||||||
drawing.a(
|
drawing.a(href=link_url, target='_top')
|
||||||
href='{}?{}'.format(
|
|
||||||
reverse('dcim:device_add'),
|
|
||||||
urlencode({'rack': rack.pk, 'site': rack.site.pk, 'face': face_id, 'position': id_})
|
|
||||||
),
|
|
||||||
target='_top'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if reservation:
|
if reservation:
|
||||||
link.set_desc('{} — {} · {}'.format(
|
link.set_desc('{} — {} · {}'.format(
|
||||||
|
@ -2851,6 +2851,9 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
)
|
)
|
||||||
Interface.objects.bulk_create(interfaces)
|
Interface.objects.bulk_create(interfaces)
|
||||||
|
|
||||||
|
console_port = ConsolePort.objects.create(device=devices[0], name='Console Port 1')
|
||||||
|
console_server_port = ConsoleServerPort.objects.create(device=devices[0], name='Console Server Port 1')
|
||||||
|
|
||||||
# Cables
|
# Cables
|
||||||
Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=10, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
|
Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=10, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
|
||||||
Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=20, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
|
Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=20, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
|
||||||
@ -2858,6 +2861,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_PLANNED, color='f44336', length=40, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
|
Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_PLANNED, color='f44336', length=40, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
|
||||||
Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
|
Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
|
||||||
Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
|
Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
|
||||||
|
Cable(termination_a=console_port, termination_b=console_server_port, label='Cable 7').save()
|
||||||
|
|
||||||
def test_label(self):
|
def test_label(self):
|
||||||
params = {'label': ['Cable 1', 'Cable 2']}
|
params = {'label': ['Cable 1', 'Cable 2']}
|
||||||
@ -2877,7 +2881,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
|
|
||||||
def test_status(self):
|
def test_status(self):
|
||||||
params = {'status': [CableStatusChoices.STATUS_CONNECTED]}
|
params = {'status': [CableStatusChoices.STATUS_CONNECTED]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
params = {'status': [CableStatusChoices.STATUS_PLANNED]}
|
params = {'status': [CableStatusChoices.STATUS_PLANNED]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
@ -2888,30 +2892,44 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
def test_device(self):
|
def test_device(self):
|
||||||
devices = Device.objects.all()[:2]
|
devices = Device.objects.all()[:2]
|
||||||
params = {'device_id': [devices[0].pk, devices[1].pk]}
|
params = {'device_id': [devices[0].pk, devices[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
params = {'device': [devices[0].name, devices[1].name]}
|
params = {'device': [devices[0].name, devices[1].name]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
def test_rack(self):
|
def test_rack(self):
|
||||||
racks = Rack.objects.all()[:2]
|
racks = Rack.objects.all()[:2]
|
||||||
params = {'rack_id': [racks[0].pk, racks[1].pk]}
|
params = {'rack_id': [racks[0].pk, racks[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
params = {'rack': [racks[0].name, racks[1].name]}
|
params = {'rack': [racks[0].name, racks[1].name]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
|
||||||
def test_site(self):
|
def test_site(self):
|
||||||
site = Site.objects.all()[:2]
|
site = Site.objects.all()[:2]
|
||||||
params = {'site_id': [site[0].pk, site[1].pk]}
|
params = {'site_id': [site[0].pk, site[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
params = {'site': [site[0].slug, site[1].slug]}
|
params = {'site': [site[0].slug, site[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
|
||||||
def test_tenant(self):
|
def test_tenant(self):
|
||||||
tenant = Tenant.objects.all()[:2]
|
tenant = Tenant.objects.all()[:2]
|
||||||
params = {'tenant_id': [tenant[0].pk, tenant[1].pk]}
|
params = {'tenant_id': [tenant[0].pk, tenant[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
||||||
params = {'tenant': [tenant[0].slug, tenant[1].slug]}
|
params = {'tenant': [tenant[0].slug, tenant[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
||||||
|
|
||||||
|
def test_termination_types(self):
|
||||||
|
params = {'termination_a_type': 'dcim.consoleport'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
params = {'termination_b_type': 'dcim.consoleserverport'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_termination_ids(self):
|
||||||
|
interface_ids = Cable.objects.values_list('termination_a_id', flat=True)[:3]
|
||||||
|
params = {
|
||||||
|
'termination_a_type': 'dcim.interface',
|
||||||
|
'termination_a_id': list(interface_ids),
|
||||||
|
}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
|
|
||||||
class PowerPanelTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class PowerPanelTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
|
@ -1229,6 +1229,7 @@ class PlatformView(generic.ObjectView):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'devices_table': devices_table,
|
'devices_table': devices_table,
|
||||||
|
'virtualmachine_count': VirtualMachine.objects.filter(platform=instance).count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from drf_yasg.utils import swagger_serializer_method
|
from drf_yasg.utils import swagger_serializer_method
|
||||||
@ -30,6 +31,7 @@ __all__ = (
|
|||||||
'ExportTemplateSerializer',
|
'ExportTemplateSerializer',
|
||||||
'ImageAttachmentSerializer',
|
'ImageAttachmentSerializer',
|
||||||
'JobResultSerializer',
|
'JobResultSerializer',
|
||||||
|
'JournalEntrySerializer',
|
||||||
'ObjectChangeSerializer',
|
'ObjectChangeSerializer',
|
||||||
'ReportDetailSerializer',
|
'ReportDetailSerializer',
|
||||||
'ReportSerializer',
|
'ReportSerializer',
|
||||||
@ -192,6 +194,12 @@ class JournalEntrySerializer(ValidatedModelSerializer):
|
|||||||
queryset=ContentType.objects.all()
|
queryset=ContentType.objects.all()
|
||||||
)
|
)
|
||||||
assigned_object = serializers.SerializerMethodField(read_only=True)
|
assigned_object = serializers.SerializerMethodField(read_only=True)
|
||||||
|
created_by = serializers.PrimaryKeyRelatedField(
|
||||||
|
allow_null=True,
|
||||||
|
queryset=User.objects.all(),
|
||||||
|
required=False,
|
||||||
|
default=serializers.CurrentUserDefault()
|
||||||
|
)
|
||||||
kind = ChoiceField(
|
kind = ChoiceField(
|
||||||
choices=JournalEntryKindChoices,
|
choices=JournalEntryKindChoices,
|
||||||
required=False
|
required=False
|
||||||
|
@ -16,7 +16,7 @@ from django.core.validators import URLValidator
|
|||||||
# Environment setup
|
# Environment setup
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION = '3.0.6-dev'
|
VERSION = '3.0.7-dev'
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
HOSTNAME = platform.node()
|
HOSTNAME = platform.node()
|
||||||
|
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.
@ -73,16 +73,6 @@
|
|||||||
color: color-contrast($value);
|
color: color-contrast($value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use proper foreground color in the alert body. Note: this is applied to p, & small because
|
|
||||||
// we *don't* want to override the h1-h6 colors for alerts, since those are set to a color
|
|
||||||
// similar to the alert color.
|
|
||||||
.alert.alert-#{$color} {
|
|
||||||
p,
|
|
||||||
small {
|
|
||||||
color: color-contrast($value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure progress bars (utilization graph) in tables aren't too narrow to display the percentage.
|
// Ensure progress bars (utilization graph) in tables aren't too narrow to display the percentage.
|
||||||
@ -200,16 +190,21 @@ div#advanced-search-content div.card div.card-body div.col:not(:last-child) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
a {
|
td {
|
||||||
text-decoration: none;
|
a {
|
||||||
&:hover {
|
text-decoration: none;
|
||||||
text-decoration: underline;
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.table > :not(caption) > * > * {
|
th {
|
||||||
padding-right: $table-cell-padding-x-sm !important;
|
a, a:hover {
|
||||||
padding-left: $table-cell-padding-x-sm !important;
|
color: $body-color;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
font-size: $font-size-sm;
|
font-size: $font-size-sm;
|
||||||
@ -234,6 +229,11 @@ table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.table > :not(caption) > * > * {
|
||||||
|
padding-right: $table-cell-padding-x-sm !important;
|
||||||
|
padding-left: $table-cell-padding-x-sm !important;
|
||||||
|
}
|
||||||
|
|
||||||
&.object-list {
|
&.object-list {
|
||||||
th {
|
th {
|
||||||
font-size: $font-size-xs;
|
font-size: $font-size-xs;
|
||||||
|
@ -70,6 +70,7 @@ $spacing-s: $input-padding-x;
|
|||||||
span.arrow-down,
|
span.arrow-down,
|
||||||
span.arrow-up {
|
span.arrow-up {
|
||||||
border-color: currentColor;
|
border-color: currentColor;
|
||||||
|
color: $text-muted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Don't show the depth indicator outside of the menu.
|
// Don't show the depth indicator outside of the menu.
|
||||||
|
@ -7,6 +7,7 @@ $input-border-color: $gray-200;
|
|||||||
$theme-colors: map-merge(
|
$theme-colors: map-merge(
|
||||||
$theme-colors,
|
$theme-colors,
|
||||||
(
|
(
|
||||||
|
'primary': #337ab7,
|
||||||
'red': $red-500,
|
'red': $red-500,
|
||||||
'yellow': $yellow-500,
|
'yellow': $yellow-500,
|
||||||
'green': $green-500,
|
'green': $green-500,
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
--nbx-color-mode-toggle-color: #{$primary};
|
--nbx-color-mode-toggle-color: #{$primary};
|
||||||
--nbx-sidenav-link-color: #{$gray-800};
|
--nbx-sidenav-link-color: #{$gray-800};
|
||||||
--nbx-sidenav-pin-color: #{$orange};
|
--nbx-sidenav-pin-color: #{$orange};
|
||||||
--nbx-sidenav-parent-color: #{$gray-900};
|
--nbx-sidenav-parent-color: #{$gray-800};
|
||||||
--nbx-sidenav-group-color: #{$gray-800};
|
--nbx-sidenav-group-color: #{$gray-800};
|
||||||
|
|
||||||
&[data-netbox-color-mode='dark'] {
|
&[data-netbox-color-mode='dark'] {
|
||||||
@ -49,7 +49,7 @@
|
|||||||
--nbx-color-mode-toggle-color: #{$yellow-300};
|
--nbx-color-mode-toggle-color: #{$yellow-300};
|
||||||
--nbx-sidenav-link-color: #{$gray-200};
|
--nbx-sidenav-link-color: #{$gray-200};
|
||||||
--nbx-sidenav-pin-color: #{$yellow};
|
--nbx-sidenav-pin-color: #{$yellow};
|
||||||
--nbx-sidenav-parent-color: #{$gray-100};
|
--nbx-sidenav-parent-color: #{$gray-200};
|
||||||
--nbx-sidenav-group-color: #{$gray-600};
|
--nbx-sidenav-group-color: #{$gray-600};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
{% block title %}{{ obj.circuit.provider }} {{ obj.circuit }} - Side {{ form.term_side.value }}{% endblock %}
|
{% block title %}{{ obj.circuit.provider }} {{ obj.circuit }} - Side {{ form.term_side.value }}{% endblock %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Circuit Termination</h5>
|
<h5 class="offset-sm-3">Circuit Termination</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -53,9 +53,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Termination Details</h5>
|
<h5 class="offset-sm-3">Termination Details</h5>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,9 +17,7 @@
|
|||||||
<div class="row my-3">
|
<div class="row my-3">
|
||||||
<div class="col col-md-5">
|
<div class="col col-md-5">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header offset-sm-3">A Side</h5>
|
||||||
A Side
|
|
||||||
</h5>
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if termination_a.device %}
|
{% if termination_a.device %}
|
||||||
{# Device component #}
|
{# Device component #}
|
||||||
@ -100,9 +98,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col col-md-5">
|
<div class="col col-md-5">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header offset-sm-3">B Side</h5>
|
||||||
B Side
|
|
||||||
</h5>
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if tabs %}
|
{% if tabs %}
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
@ -154,7 +150,7 @@
|
|||||||
<div class="row my-3 justify-content-center">
|
<div class="row my-3 justify-content-center">
|
||||||
<div class="col col-md-8">
|
<div class="col col-md-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">Cable</h5>
|
<h5 class="card-header offset-sm-3">Cable</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% include 'dcim/inc/cable_form.html' %}
|
{% include 'dcim/inc/cable_form.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,111 +4,104 @@
|
|||||||
{% block form %}
|
{% block form %}
|
||||||
{% render_errors form %}
|
{% render_errors form %}
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Device</h5>
|
<h5 class="offset-sm-3">Device</h5>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.name %}
|
{% render_field form.name %}
|
||||||
{% render_field form.device_role %}
|
{% render_field form.device_role %}
|
||||||
{% render_field form.tags %}
|
{% render_field form.tags %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Hardware</h5>
|
<h5 class="offset-sm-3">Hardware</h5>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.manufacturer %}
|
{% render_field form.manufacturer %}
|
||||||
{% render_field form.device_type %}
|
{% render_field form.device_type %}
|
||||||
{% render_field form.serial %}
|
{% render_field form.serial %}
|
||||||
{% render_field form.asset_tag %}
|
{% render_field form.asset_tag %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Location</h5>
|
<h5 class="offset-sm-3">Location</h5>
|
||||||
|
</div>
|
||||||
|
{% render_field form.region %}
|
||||||
|
{% render_field form.site_group %}
|
||||||
|
{% render_field form.site %}
|
||||||
|
{% render_field form.location %}
|
||||||
|
{% render_field form.rack %}
|
||||||
|
|
||||||
|
{% if obj.device_type.is_child_device and obj.parent_bay %}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-sm-3 col-form-label">Parent Device</label>
|
||||||
|
<div class="col">
|
||||||
|
<input class="form-control" value="{{ obj.parent_bay.device }}" disabled />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.region %}
|
<div class="row mb-3">
|
||||||
{% render_field form.site_group %}
|
<label class="col-sm-3 col-form-label">Parent Bay</label>
|
||||||
{% render_field form.site %}
|
<div class="col">
|
||||||
{% render_field form.location %}
|
<div class="input-group">
|
||||||
{% render_field form.rack %}
|
<input class="form-control" value="{{ obj.parent_bay.name }}" disabled />
|
||||||
|
<a href="{% url 'dcim:devicebay_depopulate' pk=obj.parent_bay.pk %}" title="Regenerate Slug" class="btn btn-danger d-inline-flex align-items-center">
|
||||||
{% if obj.device_type.is_child_device and obj.parent_bay %}
|
<i class="mdi mdi-close-thick"></i> Remove
|
||||||
<div class="row mb-3">
|
</a>
|
||||||
<label class="col-sm-3 col-form-label">Parent Device</label>
|
|
||||||
<div class="col">
|
|
||||||
<input class="form-control" value="{{ obj.parent_bay.device }}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col-sm-3 col-form-label">Parent Bay</label>
|
|
||||||
<div class="col">
|
|
||||||
<div class="input-group">
|
|
||||||
<input class="form-control" value="{{ obj.parent_bay.name }}" disabled />
|
|
||||||
<a href="{% url 'dcim:devicebay_depopulate' pk=obj.parent_bay.pk %}" title="Regenerate Slug" class="btn btn-danger d-inline-flex align-items-center">
|
|
||||||
<i class="mdi mdi-close-thick"></i> Remove
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
</div>
|
||||||
{% render_field form.face %}
|
{% else %}
|
||||||
{% render_field form.position %}
|
{% render_field form.face %}
|
||||||
{% endif %}
|
{% render_field form.position %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Management</h5>
|
<h5 class="offset-sm-3">Management</h5>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.status %}
|
{% render_field form.status %}
|
||||||
{% render_field form.platform %}
|
{% render_field form.platform %}
|
||||||
{% if obj.pk %}
|
{% if obj.pk %}
|
||||||
{% render_field form.primary_ip4 %}
|
{% render_field form.primary_ip4 %}
|
||||||
{% render_field form.primary_ip6 %}
|
{% render_field form.primary_ip6 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Virtualization</h5>
|
<h5 class="offset-sm-3">Virtualization</h5>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.cluster_group %}
|
{% render_field form.cluster_group %}
|
||||||
{% render_field form.cluster %}
|
{% render_field form.cluster %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Tenancy</h5>
|
<h5 class="offset-sm-3">Tenancy</h5>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.tenant_group %}
|
{% render_field form.tenant_group %}
|
||||||
{% render_field form.tenant %}
|
{% render_field form.tenant %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
{% if form.custom_fields %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
</div>
|
</div>
|
||||||
{% render_custom_fields form %}
|
{% render_custom_fields form %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<h5 class="text-center">Local Config Context Data</h5>
|
<h5 class="text-center">Local Config Context Data</h5>
|
||||||
{% render_field form.local_context_data %}
|
{% render_field form.local_context_data %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group mb-5">
|
||||||
{% render_field form.comments label='Comments' %}
|
<h5 class="text-center">Comments</h5>
|
||||||
|
{% render_field form.comments %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% render_field form.tags %}
|
{% render_field form.tags %}
|
||||||
{% if form.custom_fields %}
|
{% if form.custom_fields %}
|
||||||
<hr />
|
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Interface</h5>
|
<h5 class="offset-sm-3">Interface</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -27,9 +27,8 @@
|
|||||||
{% render_field form.mgmt_only %}
|
{% render_field form.mgmt_only %}
|
||||||
{% render_field form.mark_connected %}
|
{% render_field form.mark_connected %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">802.1Q Switching</h5>
|
<h5 class="offset-sm-3">802.1Q Switching</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -40,8 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
{% if form.custom_fields %}
|
||||||
<hr />
|
<div class="field-group my-5">
|
||||||
<div class="field-group my-4">
|
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,6 +46,12 @@
|
|||||||
<a href="{% url 'dcim:device_list' %}?platform_id={{ object.pk }}">{{ devices_table.rows|length }}</a>
|
<a href="{% url 'dcim:device_list' %}?platform_id={{ object.pk }}">{{ devices_table.rows|length }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Virtual Machines</th>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'virtualization:virtualmachine_list' %}?platform_id={{ object.pk }}">{{ virtualmachine_count }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Rack</h5>
|
<h5 class="offset-sm-3">Rack</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -15,9 +15,8 @@
|
|||||||
{% render_field form.role %}
|
{% render_field form.role %}
|
||||||
{% render_field form.tags %}
|
{% render_field form.tags %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Inventory Control</h5>
|
<h5 class="offset-sm-3">Inventory Control</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -25,18 +24,16 @@
|
|||||||
{% render_field form.serial %}
|
{% render_field form.serial %}
|
||||||
{% render_field form.asset_tag %}
|
{% render_field form.asset_tag %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Tenancy</h5>
|
<h5 class="offset-sm-3">Tenancy</h5>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.tenant_group %}
|
{% render_field form.tenant_group %}
|
||||||
{% render_field form.tenant %}
|
{% render_field form.tenant %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Dimensions</h5>
|
<h5 class="offset-sm-3">Dimensions</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -45,34 +42,33 @@
|
|||||||
{% render_field form.u_height %}
|
{% render_field form.u_height %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col col-md-3 col-form-label text-lg-end">Outer Dimensions</label>
|
<label class="col col-md-3 col-form-label text-lg-end">Outer Dimensions</label>
|
||||||
<div class="col col-md-3">
|
<div class="col col-md-3 mb-1">
|
||||||
{{ form.outer_width }}
|
{{ form.outer_width }}
|
||||||
<div class="form-text">Width</div>
|
<div class="form-text">Width</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-3">
|
<div class="col col-md-3 mb-1">
|
||||||
{{ form.outer_depth }}
|
{{ form.outer_depth }}
|
||||||
<div class="form-text">Depth</div>
|
<div class="form-text">Depth</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-3">
|
<div class="col col-md-3 mb-1">
|
||||||
{{ form.outer_unit }}
|
{{ form.outer_unit }}
|
||||||
<div class="form-text">Unit</div>
|
<div class="form-text">Unit</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.desc_units %}
|
{% render_field form.desc_units %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
{% if form.custom_fields %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
</div>
|
</div>
|
||||||
{% render_custom_fields form %}
|
{% render_custom_fields form %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
{% render_field form.comments label='Comments' %}
|
<h5 class="text-center">Comments</h5>
|
||||||
|
{% render_field form.comments %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Virtual Chassis</h5>
|
<h5 class="offset-sm-3">Virtual Chassis</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -10,9 +10,8 @@
|
|||||||
{% render_field form.domain %}
|
{% render_field form.domain %}
|
||||||
{% render_field form.tags %}
|
{% render_field form.tags %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Member Devices</h5>
|
<h5 class="offset-sm-3">Member Devices</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -25,8 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
{% if form.custom_fields %}
|
||||||
<hr />
|
<div class="field-group my-5">
|
||||||
<div class="field-group my-4">
|
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ pk_form.pk }}
|
{{ pk_form.pk }}
|
||||||
{{ formset.management_form }}
|
{{ formset.management_form }}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Virtual Chassis</h5>
|
<h5 class="offset-sm-3">Virtual Chassis</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -20,16 +20,14 @@
|
|||||||
{% render_field vc_form.master %}
|
{% render_field vc_form.master %}
|
||||||
{% render_field vc_form.tags %}
|
{% render_field vc_form.tags %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
{% if vc_form.custom_fields %}
|
{% if vc_form.custom_fields %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
</div>
|
</div>
|
||||||
{% render_custom_fields vc_form %}
|
{% render_custom_fields vc_form %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="field-group mb-5">
|
<div class="field-group mb-5">
|
||||||
|
@ -8,6 +8,12 @@
|
|||||||
{% block header %}
|
{% block header %}
|
||||||
{# Breadcrumbs #}
|
{# Breadcrumbs #}
|
||||||
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
|
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
|
||||||
|
<div class="float-end">
|
||||||
|
<code class="text-muted" title="Object type and ID">
|
||||||
|
{{ object|meta:"app_label" }}.{{ object|meta:"model_name" }}:{{ object.pk }}
|
||||||
|
{% if object.slug %}({{ object.slug }}){% endif %}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<li class="breadcrumb-item"><a href="{% url object|viewname:'list' %}">{{ object|meta:'verbose_name_plural'|bettertitle }}</a></li>
|
<li class="breadcrumb-item"><a href="{% url object|viewname:'list' %}">{{ object|meta:'verbose_name_plural'|bettertitle }}</a></li>
|
||||||
|
@ -6,18 +6,6 @@
|
|||||||
{% if obj.pk %}Editing {{ obj_type }} {{ obj }}{% else %}Add a new {{ obj_type }}{% endif %}
|
{% if obj.pk %}Editing {{ obj_type }} {{ obj }}{% else %}Add a new {{ obj_type }}{% endif %}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
|
||||||
{% block controls %}
|
|
||||||
{% if obj and settings.DOCS_ROOT %}
|
|
||||||
<div class="controls">
|
|
||||||
<div class="control-group">
|
|
||||||
<a href="{{ obj|get_docs_url }}" target="_blank" class="btn btn-sm btn-outline-secondary" title="View model documentation">
|
|
||||||
<i class="mdi mdi-help-circle"></i> Help
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock controls %}
|
|
||||||
|
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
<ul class="nav nav-tabs px-3">
|
<ul class="nav nav-tabs px-3">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
@ -31,6 +19,16 @@
|
|||||||
{% block content-wrapper %}
|
{% block content-wrapper %}
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
|
<div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
|
||||||
|
|
||||||
|
{# Link to model documentation #}
|
||||||
|
{% if obj and settings.DOCS_ROOT %}
|
||||||
|
<div class="float-end">
|
||||||
|
<a href="{{ obj|get_docs_url }}" target="_blank" class="btn btn-sm btn-outline-secondary" title="View model documentation">
|
||||||
|
<i class="mdi mdi-help-circle"></i> Help
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<form action="" method="post" enctype="multipart/form-data" class="form-object-edit">
|
<form action="" method="post" enctype="multipart/form-data" class="form-object-edit">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for field in form.hidden_fields %}
|
{% for field in form.hidden_fields %}
|
||||||
@ -42,7 +40,7 @@
|
|||||||
|
|
||||||
{# Render grouped fields according to Form #}
|
{# Render grouped fields according to Form #}
|
||||||
{% for group, fields in form.Meta.fieldsets %}
|
{% for group, fields in form.Meta.fieldsets %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">{{ group }}</h5>
|
<h5 class="offset-sm-3">{{ group }}</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -50,14 +48,10 @@
|
|||||||
{% render_field form|getfield:name %}
|
{% render_field form|getfield:name %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% if not forloop.last %}
|
|
||||||
<hr />
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
{% if form.custom_fields %}
|
||||||
<hr />
|
<div class="field-group my-5">
|
||||||
<div class="field-group my-4">
|
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -66,15 +60,15 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if form.comments %}
|
{% if form.comments %}
|
||||||
<hr />
|
<div class="field-group my-5">
|
||||||
<div class="field-group my-4">
|
<h5 class="text-center">Comments</h5>
|
||||||
{% render_field form.comments label='Comments' %}
|
{% render_field form.comments %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{# Render all fields in a single group #}
|
{# Render all fields in a single group #}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
{% block form_fields %}{% render_form form %}{% endblock %}
|
{% block form_fields %}{% render_form form %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{{ content_type.model_class|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
|
||||||
|
|
||||||
{% block controls %}
|
{% block controls %}
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
@ -26,7 +28,7 @@
|
|||||||
{% block tab_items %}
|
{% block tab_items %}
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link active" id="object-list-tab" data-bs-toggle="tab" data-bs-target="#object-list" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
|
<button class="nav-link active" id="object-list-tab" data-bs-toggle="tab" data-bs-target="#object-list" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
|
||||||
{% block title %}{{ content_type.model_class|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
|
Records
|
||||||
{% badge table.page.paginator.count %}
|
{% badge table.page.paginator.count %}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
{% for section, items, icon in stats %}
|
{% for section, items, icon in stats %}
|
||||||
<div class="col col-sm-12 col-lg-6 col-xl-4 my-2 masonry-item">
|
<div class="col col-sm-12 col-lg-6 col-xl-4 my-2 masonry-item">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h6 class="card-header text-primary text-center">
|
<h6 class="card-header text-center">
|
||||||
<i class="mdi mdi-{{ icon }}"></i>
|
<i class="mdi mdi-{{ icon }}"></i>
|
||||||
<span class="ms-1">{{ section }}</span>
|
<span class="ms-1">{{ section }}</span>
|
||||||
</h6>
|
</h6>
|
||||||
@ -67,7 +67,7 @@
|
|||||||
<div class="row my-4 flex-grow-1 changelog-container">
|
<div class="row my-4 flex-grow-1 changelog-container">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h6 class="card-header text-primary text-center">
|
<h6 class="card-header text-center">
|
||||||
<i class="mdi mdi-clipboard-clock"></i>
|
<i class="mdi mdi-clipboard-clock"></i>
|
||||||
<span class="ms-1">Change Log</span>
|
<span class="ms-1">Change Log</span>
|
||||||
</h6>
|
</h6>
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control object-filter"
|
class="form-control object-filter"
|
||||||
placeholder="Filter"
|
placeholder="Quick find"
|
||||||
title="Filter text (regular expressions supported)"
|
title="Find in the results below (regular expressions supported)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">IP Addresses</h5>
|
<h5 class="offset-sm-3">IP Addresses</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -20,9 +20,8 @@
|
|||||||
{% render_field model_form.description %}
|
{% render_field model_form.description %}
|
||||||
{% render_field model_form.tags %}
|
{% render_field model_form.tags %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Tenancy</h5>
|
<h5 class="offset-sm-3">Tenancy</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -30,8 +29,7 @@
|
|||||||
{% render_field model_form.tenant %}
|
{% render_field model_form.tenant %}
|
||||||
</div>
|
</div>
|
||||||
{% if model_form.custom_fields %}
|
{% if model_form.custom_fields %}
|
||||||
<hr />
|
<div class="field-group my-5">
|
||||||
<div class="field-group my-4">
|
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
{% endblock tabs %}
|
{% endblock tabs %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">IP Address</h5>
|
<h5 class="offset-sm-3">IP Address</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -20,18 +20,16 @@
|
|||||||
{% render_field form.description %}
|
{% render_field form.description %}
|
||||||
{% render_field form.tags %}
|
{% render_field form.tags %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Tenancy</h5>
|
<h5 class="offset-sm-3">Tenancy</h5>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.tenant_group %}
|
{% render_field form.tenant_group %}
|
||||||
{% render_field form.tenant %}
|
{% render_field form.tenant %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Interface Assignment</h5>
|
<h5 class="offset-sm-3">Interface Assignment</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -81,9 +79,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">NAT IP (Inside)</h5>
|
<h5 class="offset-sm-3">NAT IP (Inside)</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -152,8 +149,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
{% if form.custom_fields %}
|
||||||
<hr />
|
<div class="field-group my-5">
|
||||||
<div class="field-group my-4">
|
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Service</h5>
|
<h5 class="offset-sm-3">Service</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -43,7 +43,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
{% if form.custom_fields %}
|
||||||
<hr />
|
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">VLAN</h5>
|
<h5 class="offset-sm-3">VLAN</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -15,18 +15,16 @@
|
|||||||
{% render_field form.description %}
|
{% render_field form.description %}
|
||||||
{% render_field form.tags %}
|
{% render_field form.tags %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Tenancy</h5>
|
<h5 class="offset-sm-3">Tenancy</h5>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.tenant_group %}
|
{% render_field form.tenant_group %}
|
||||||
{% render_field form.tenant %}
|
{% render_field form.tenant %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Assignment</h5>
|
<h5 class="offset-sm-3">Assignment</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -58,8 +56,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
{% if form.custom_fields %}
|
||||||
<hr />
|
<div class="field-group my-5">
|
||||||
<div class="field-group my-4">
|
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Sites</th>
|
<th scope="row">Tenants</th>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'tenancy:tenant_list' %}?group_id={{ object.pk }}">{{ tenants_table.rows|length }}</a>
|
<a href="{% url 'tenancy:tenant_list' %}?group_id={{ object.pk }}">{{ tenants_table.rows|length }}</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Interface</h5>
|
<h5 class="offset-sm-3">Interface</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -22,9 +22,8 @@
|
|||||||
{% render_field form.description %}
|
{% render_field form.description %}
|
||||||
{% render_field form.tags %}
|
{% render_field form.tags %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="field-group my-4">
|
<div class="field-group my-5">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">802.1Q Switching</h5>
|
<h5 class="offset-sm-3">802.1Q Switching</h5>
|
||||||
</div>
|
</div>
|
||||||
@ -35,8 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
{% if form.custom_fields %}
|
||||||
<hr />
|
<div class="field-group my-5">
|
||||||
<div class="field-group my-4">
|
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,14 +57,14 @@ class BaseTable(tables.Table):
|
|||||||
if user is not None and not isinstance(user, AnonymousUser):
|
if user is not None and not isinstance(user, AnonymousUser):
|
||||||
selected_columns = user.config.get(f"tables.{self.__class__.__name__}.columns")
|
selected_columns = user.config.get(f"tables.{self.__class__.__name__}.columns")
|
||||||
if selected_columns:
|
if selected_columns:
|
||||||
pk = self.base_columns.pop('pk', None)
|
|
||||||
actions = self.base_columns.pop('actions', None)
|
|
||||||
|
|
||||||
|
# Show only persistent or selected columns
|
||||||
for name, column in self.columns.items():
|
for name, column in self.columns.items():
|
||||||
if name in selected_columns:
|
if name in ['pk', 'actions', *selected_columns]:
|
||||||
self.columns.show(name)
|
self.columns.show(name)
|
||||||
else:
|
else:
|
||||||
self.columns.hide(name)
|
self.columns.hide(name)
|
||||||
|
|
||||||
# Rearrange the sequence to list selected columns first, followed by all remaining columns
|
# Rearrange the sequence to list selected columns first, followed by all remaining columns
|
||||||
# TODO: There's probably a more clever way to accomplish this
|
# TODO: There's probably a more clever way to accomplish this
|
||||||
self.sequence = [
|
self.sequence = [
|
||||||
@ -72,12 +72,14 @@ class BaseTable(tables.Table):
|
|||||||
*[c for c in self.columns.names() if c not in selected_columns]
|
*[c for c in self.columns.names() if c not in selected_columns]
|
||||||
]
|
]
|
||||||
|
|
||||||
# Always include PK and actions column, if defined on the table
|
# PK column should always come first
|
||||||
if pk:
|
if 'pk' in self.sequence:
|
||||||
self.base_columns['pk'] = pk
|
self.sequence.remove('pk')
|
||||||
self.sequence.insert(0, 'pk')
|
self.sequence.insert(0, 'pk')
|
||||||
if actions:
|
|
||||||
self.base_columns['actions'] = actions
|
# Actions column should always come last
|
||||||
|
if 'actions' in self.sequence:
|
||||||
|
self.sequence.remove('actions')
|
||||||
self.sequence.append('actions')
|
self.sequence.append('actions')
|
||||||
|
|
||||||
# Dynamically update the table's QuerySet to ensure related fields are pre-fetched
|
# Dynamically update the table's QuerySet to ensure related fields are pre-fetched
|
||||||
@ -128,7 +130,7 @@ class BaseTable(tables.Table):
|
|||||||
prefixes/IP addresses/etc., where some table rows may represent available address space.
|
prefixes/IP addresses/etc., where some table rows may represent available address space.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, '_objects_count'):
|
if not hasattr(self, '_objects_count'):
|
||||||
self._objects_count = sum(1 for obj in self.data if getattr(obj, 'pk'))
|
self._objects_count = sum(1 for obj in self.data if hasattr(obj, 'pk'))
|
||||||
return self._objects_count
|
return self._objects_count
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Django==3.2.7
|
Django==3.2.8
|
||||||
django-cors-headers==3.9.0
|
django-cors-headers==3.10.0
|
||||||
django-debug-toolbar==3.2.2
|
django-debug-toolbar==3.2.2
|
||||||
django-filter==21.1
|
django-filter==21.1
|
||||||
django-graphiql-debug-toolbar==0.2.0
|
django-graphiql-debug-toolbar==0.2.0
|
||||||
@ -8,14 +8,14 @@ django-pglocks==1.0.4
|
|||||||
django-prometheus==2.1.0
|
django-prometheus==2.1.0
|
||||||
django-redis==5.0.0
|
django-redis==5.0.0
|
||||||
django-rq==2.4.1
|
django-rq==2.4.1
|
||||||
django-tables2==2.4.0
|
django-tables2==2.4.1
|
||||||
django-taggit==1.5.1
|
django-taggit==1.5.1
|
||||||
django-timezone-field==4.2.1
|
django-timezone-field==4.2.1
|
||||||
djangorestframework==3.12.4
|
djangorestframework==3.12.4
|
||||||
drf-yasg[validation]==1.20.0
|
drf-yasg[validation]==1.20.0
|
||||||
graphene_django==2.15.0
|
graphene_django==2.15.0
|
||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
Jinja2==3.0.1
|
Jinja2==3.0.2
|
||||||
Markdown==3.3.4
|
Markdown==3.3.4
|
||||||
markdown-include==0.6.0
|
markdown-include==0.6.0
|
||||||
mkdocs-material==7.3.1
|
mkdocs-material==7.3.1
|
||||||
|
Loading…
Reference in New Issue
Block a user