commit
c2cabe0273
|
@ -17,15 +17,16 @@ body:
|
|||
How are you running NetBox? (For issues with the Docker image, please go to the
|
||||
[netbox-docker](https://github.com/netbox-community/netbox-docker) repo.)
|
||||
options:
|
||||
- Self-hosted
|
||||
- NetBox Cloud
|
||||
- NetBox Enterprise
|
||||
- Self-hosted
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: NetBox Version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v3.7.3
|
||||
placeholder: v3.7.4
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
@ -14,7 +14,7 @@ body:
|
|||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v3.7.3
|
||||
placeholder: v3.7.4
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
@ -84,4 +84,4 @@ jobs:
|
|||
run: coverage run --source="netbox/" netbox/manage.py test netbox/ --parallel
|
||||
|
||||
- name: Show coverage report
|
||||
run: coverage report --skip-covered --omit *migrations*
|
||||
run: coverage report --skip-covered --omit '*/migrations/*,*/tests/*'
|
||||
|
|
|
@ -101,7 +101,7 @@ markdown-include
|
|||
mkdocs-material
|
||||
|
||||
# Introspection for embedded code
|
||||
# https://github.com/mkdocstrings/mkdocstrings/blob/master/CHANGELOG.md
|
||||
# https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md
|
||||
mkdocstrings[python-legacy]
|
||||
|
||||
# Library for manipulating IP prefixes and addresses
|
||||
|
|
|
@ -384,7 +384,10 @@
|
|||
"8gfc-sfpp",
|
||||
"16gfc-sfpp",
|
||||
"32gfc-sfp28",
|
||||
"32gfc-sfpp",
|
||||
"64gfc-qsfpp",
|
||||
"64gfc-sfpdd",
|
||||
"64gfc-sfpp",
|
||||
"128gfc-qsfp28",
|
||||
"infiniband-sdr",
|
||||
"infiniband-ddr",
|
||||
|
|
|
@ -31,8 +31,7 @@ This section entails the installation and configuration of a local PostgreSQL da
|
|||
Once PostgreSQL has been installed, start the service and enable it to run at boot:
|
||||
|
||||
```no-highlight
|
||||
sudo systemctl start postgresql
|
||||
sudo systemctl enable postgresql
|
||||
sudo systemctl enable --now postgresql
|
||||
```
|
||||
|
||||
Before continuing, verify that you have installed PostgreSQL 12 or later:
|
||||
|
|
|
@ -14,8 +14,7 @@
|
|||
|
||||
```no-highlight
|
||||
sudo yum install -y redis
|
||||
sudo systemctl start redis
|
||||
sudo systemctl enable redis
|
||||
sudo systemctl enable --now redis
|
||||
```
|
||||
|
||||
Before continuing, verify that your installed version of Redis is at least v4.0:
|
||||
|
|
|
@ -27,8 +27,7 @@ sudo systemctl daemon-reload
|
|||
Then, start the `netbox` and `netbox-rq` services and enable them to initiate at boot time:
|
||||
|
||||
```no-highlight
|
||||
sudo systemctl start netbox netbox-rq
|
||||
sudo systemctl enable netbox netbox-rq
|
||||
sudo systemctl enable --now netbox netbox-rq
|
||||
```
|
||||
|
||||
You can use the command `systemctl status netbox` to verify that the WSGI service is running:
|
||||
|
|
|
@ -1,5 +1,30 @@
|
|||
# NetBox v3.7
|
||||
|
||||
## v3.7.4 (2024-03-13)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#14206](https://github.com/netbox-community/netbox/issues/14206) - Add additional FibreChannel SFP+ interface types
|
||||
* [#14366](https://github.com/netbox-community/netbox/issues/14366) - Enable custom links for config contexts & templates
|
||||
* [#15291](https://github.com/netbox-community/netbox/issues/15291) - Add tunnel termination buttons to VM interfaces table
|
||||
* [#15297](https://github.com/netbox-community/netbox/issues/15297) - Linkify platform column in device & virtual machine tables
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#13722](https://github.com/netbox-community/netbox/issues/13722) - Fix range expansion for comma-separated numerical values
|
||||
* [#14832](https://github.com/netbox-community/netbox/issues/14832) - Enable querying IP addresses for an FHRP group via GraphQL
|
||||
* [#15220](https://github.com/netbox-community/netbox/issues/15220) - Fix validation check when bulk editing the mask length of IP addresses
|
||||
* [#15232](https://github.com/netbox-community/netbox/issues/15232) - Permit user with sufficient permissions to assign an inventory item to a device type
|
||||
* [#15241](https://github.com/netbox-community/netbox/issues/15241) - Restore missing `display` field on VirtualDisk serialization in REST API
|
||||
* [#15243](https://github.com/netbox-community/netbox/issues/15243) - Correct representation of installed module when listing module bays using REST API brief mode
|
||||
* [#15316](https://github.com/netbox-community/netbox/issues/15316) - Fix selection of 3DES encryption for IKE & IPSec proposals
|
||||
* [#15322](https://github.com/netbox-community/netbox/issues/15322) - Add description field to YAML export for device & module types
|
||||
* [#15336](https://github.com/netbox-community/netbox/issues/15336) - Correct label for recurring scheduled jobs
|
||||
* [#15347](https://github.com/netbox-community/netbox/issues/15347) - Fix querying virtual machine contacts via GraphQL
|
||||
* [#15356](https://github.com/netbox-community/netbox/issues/15356) - Fix assignment of front & rear images to device types via REST API
|
||||
|
||||
---
|
||||
|
||||
## v3.7.3 (2024-02-21)
|
||||
|
||||
### Enhancements
|
||||
|
|
|
@ -414,11 +414,11 @@ class NestedFrontPortSerializer(WritableNestedSerializer):
|
|||
|
||||
class NestedModuleBaySerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebay-detail')
|
||||
module = NestedModuleSerializer(required=False, read_only=True, allow_null=True)
|
||||
installed_module = ModuleBayNestedModuleSerializer(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = models.ModuleBay
|
||||
fields = ['id', 'url', 'display', 'module', 'name']
|
||||
fields = ['id', 'url', 'display', 'installed_module', 'name']
|
||||
|
||||
|
||||
class NestedDeviceBaySerializer(WritableNestedSerializer):
|
||||
|
|
|
@ -326,8 +326,8 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
|
|||
airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False, allow_null=True)
|
||||
weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False, allow_null=True)
|
||||
device_count = serializers.IntegerField(read_only=True)
|
||||
front_image = serializers.URLField(allow_null=True, required=False)
|
||||
rear_image = serializers.URLField(allow_null=True, required=False)
|
||||
front_image = serializers.ImageField(required=False, allow_null=True)
|
||||
rear_image = serializers.ImageField(required=False, allow_null=True)
|
||||
|
||||
# Counter fields
|
||||
console_port_template_count = serializers.IntegerField(read_only=True)
|
||||
|
@ -1039,8 +1039,7 @@ class ModuleBaySerializer(NetBoxModelSerializer):
|
|||
model = ModuleBay
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'name', 'installed_module', 'label', 'position', 'description', 'tags',
|
||||
'custom_fields',
|
||||
'created', 'last_updated',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -889,7 +889,10 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||
TYPE_8GFC_SFP_PLUS = '8gfc-sfpp'
|
||||
TYPE_16GFC_SFP_PLUS = '16gfc-sfpp'
|
||||
TYPE_32GFC_SFP28 = '32gfc-sfp28'
|
||||
TYPE_32GFC_SFP_PLUS = '32gfc-sfpp'
|
||||
TYPE_64GFC_QSFP_PLUS = '64gfc-qsfpp'
|
||||
TYPE_64GFC_SFP_DD = '64gfc-sfpdd'
|
||||
TYPE_64GFC_SFP_PLUS = '64gfc-sfpp'
|
||||
TYPE_128GFC_QSFP28 = '128gfc-qsfp28'
|
||||
|
||||
# InfiniBand
|
||||
|
@ -1058,7 +1061,10 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||
(TYPE_8GFC_SFP_PLUS, 'SFP+ (8GFC)'),
|
||||
(TYPE_16GFC_SFP_PLUS, 'SFP+ (16GFC)'),
|
||||
(TYPE_32GFC_SFP28, 'SFP28 (32GFC)'),
|
||||
(TYPE_32GFC_SFP_PLUS, 'SFP+ (32GFC)'),
|
||||
(TYPE_64GFC_QSFP_PLUS, 'QSFP+ (64GFC)'),
|
||||
(TYPE_64GFC_SFP_DD, 'SFP-DD (64GFC)'),
|
||||
(TYPE_64GFC_SFP_PLUS, 'SFP+ (64GFC)'),
|
||||
(TYPE_128GFC_QSFP28, 'QSFP28 (128GFC)'),
|
||||
)
|
||||
),
|
||||
|
|
|
@ -229,15 +229,16 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
|||
'manufacturer': self.manufacturer.name,
|
||||
'model': self.model,
|
||||
'slug': self.slug,
|
||||
'description': self.description,
|
||||
'default_platform': self.default_platform.name if self.default_platform else None,
|
||||
'part_number': self.part_number,
|
||||
'u_height': float(self.u_height),
|
||||
'is_full_depth': self.is_full_depth,
|
||||
'subdevice_role': self.subdevice_role,
|
||||
'airflow': self.airflow,
|
||||
'comments': self.comments,
|
||||
'weight': float(self.weight) if self.weight is not None else None,
|
||||
'weight_unit': self.weight_unit,
|
||||
'comments': self.comments,
|
||||
}
|
||||
|
||||
# Component templates
|
||||
|
@ -415,9 +416,10 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
|||
'manufacturer': self.manufacturer.name,
|
||||
'model': self.model,
|
||||
'part_number': self.part_number,
|
||||
'comments': self.comments,
|
||||
'description': self.description,
|
||||
'weight': float(self.weight) if self.weight is not None else None,
|
||||
'weight_unit': self.weight_unit,
|
||||
'comments': self.comments,
|
||||
}
|
||||
|
||||
# Component templates
|
||||
|
|
|
@ -210,6 +210,10 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
|||
linkify=True,
|
||||
verbose_name=_('Type')
|
||||
)
|
||||
platform = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Platform')
|
||||
)
|
||||
primary_ip = tables.Column(
|
||||
linkify=True,
|
||||
order_by=('primary_ip4', 'primary_ip6'),
|
||||
|
@ -294,7 +298,7 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
|||
model = models.Device
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'role', 'manufacturer', 'device_type',
|
||||
'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'parent_device',
|
||||
'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'parent_device',
|
||||
'device_bay_position', 'position', 'face', 'latitude', 'longitude', 'airflow', 'primary_ip', 'primary_ip4',
|
||||
'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
|
||||
'config_template', 'comments', 'contacts', 'tags', 'created', 'last_updated',
|
||||
|
|
|
@ -1755,7 +1755,7 @@ class RearPortTest(APIViewTestCases.APIViewTestCase):
|
|||
|
||||
class ModuleBayTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ModuleBay
|
||||
brief_fields = ['display', 'id', 'module', 'name', 'url']
|
||||
brief_fields = ['display', 'id', 'installed_module', 'name', 'url']
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
|
|
|
@ -1079,7 +1079,7 @@ class DeviceTypeInventoryItemsView(DeviceTypeComponentsView):
|
|||
tab = ViewTab(
|
||||
label=_('Inventory Items'),
|
||||
badge=lambda obj: obj.inventory_item_template_count,
|
||||
permission='dcim.view_invenotryitemtemplate',
|
||||
permission='dcim.view_inventoryitemtemplate',
|
||||
weight=590,
|
||||
hide_if_empty=True
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@ from extras.models import ObjectChange
|
|||
__all__ = (
|
||||
'ChangelogMixin',
|
||||
'ConfigContextMixin',
|
||||
'ContactsMixin',
|
||||
'CustomFieldsMixin',
|
||||
'ImageAttachmentsMixin',
|
||||
'JournalEntriesMixin',
|
||||
|
|
|
@ -11,7 +11,7 @@ from extras.querysets import ConfigContextQuerySet
|
|||
from netbox.config import get_config
|
||||
from netbox.registry import registry
|
||||
from netbox.models import ChangeLoggedModel
|
||||
from netbox.models.features import CloningMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin
|
||||
from netbox.models.features import CloningMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin
|
||||
from utilities.jinja2 import ConfigTemplateLoader
|
||||
from utilities.utils import deepmerge
|
||||
|
||||
|
@ -26,7 +26,7 @@ __all__ = (
|
|||
# Config contexts
|
||||
#
|
||||
|
||||
class ConfigContext(SyncedDataMixin, CloningMixin, ChangeLoggedModel):
|
||||
class ConfigContext(SyncedDataMixin, CloningMixin, CustomLinksMixin, ChangeLoggedModel):
|
||||
"""
|
||||
A ConfigContext represents a set of arbitrary data available to any Device or VirtualMachine matching its assigned
|
||||
qualifiers (region, site, etc.). For example, the data stored in a ConfigContext assigned to site A and tenant B
|
||||
|
@ -210,7 +210,7 @@ class ConfigContextModel(models.Model):
|
|||
# Config templates
|
||||
#
|
||||
|
||||
class ConfigTemplate(SyncedDataMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
|
||||
class ConfigTemplate(SyncedDataMixin, CustomLinksMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
|
||||
name = models.CharField(
|
||||
verbose_name=_('name'),
|
||||
max_length=100
|
||||
|
|
|
@ -367,20 +367,6 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
|||
'primary_for_parent', _("Only IP addresses assigned to an interface can be designated as primary IPs.")
|
||||
)
|
||||
|
||||
# Do not allow assigning a network ID or broadcast address to an interface.
|
||||
if interface and (address := self.cleaned_data.get('address')):
|
||||
if address.ip == address.network:
|
||||
msg = _("{ip} is a network ID, which may not be assigned to an interface.").format(ip=address.ip)
|
||||
if address.version == 4 and address.prefixlen not in (31, 32):
|
||||
raise ValidationError(msg)
|
||||
if address.version == 6 and address.prefixlen not in (127, 128):
|
||||
raise ValidationError(msg)
|
||||
if address.version == 4 and address.ip == address.broadcast and address.prefixlen not in (31, 32):
|
||||
msg = _("{ip} is a broadcast address, which may not be assigned to an interface.").format(
|
||||
ip=address.ip
|
||||
)
|
||||
raise ValidationError(msg)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
ipaddress = super().save(*args, **kwargs)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import graphene
|
||||
|
||||
from ipam import filtersets, models
|
||||
from .mixins import IPAddressesMixin
|
||||
from netbox.graphql.scalars import BigInt
|
||||
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||
|
||||
|
@ -71,7 +72,7 @@ class AggregateType(NetBoxObjectType, BaseIPAddressFamilyType):
|
|||
filterset_class = filtersets.AggregateFilterSet
|
||||
|
||||
|
||||
class FHRPGroupType(NetBoxObjectType):
|
||||
class FHRPGroupType(NetBoxObjectType, IPAddressesMixin):
|
||||
|
||||
class Meta:
|
||||
model = models.FHRPGroup
|
||||
|
|
|
@ -844,6 +844,25 @@ class IPAddress(PrimaryModel):
|
|||
'address': _("Cannot create IP address with /0 mask.")
|
||||
})
|
||||
|
||||
# Do not allow assigning a network ID or broadcast address to an interface.
|
||||
if self.assigned_object:
|
||||
if self.address.ip == self.address.network:
|
||||
msg = _("{ip} is a network ID, which may not be assigned to an interface.").format(
|
||||
ip=self.address.ip
|
||||
)
|
||||
if self.address.version == 4 and self.address.prefixlen not in (31, 32):
|
||||
raise ValidationError(msg)
|
||||
if self.address.version == 6 and self.address.prefixlen not in (127, 128):
|
||||
raise ValidationError(msg)
|
||||
if (
|
||||
self.address.version == 4 and self.address.ip == self.address.broadcast and
|
||||
self.address.prefixlen not in (31, 32)
|
||||
):
|
||||
msg = _("{ip} is a broadcast address, which may not be assigned to an interface.").format(
|
||||
ip=self.address.ip
|
||||
)
|
||||
raise ValidationError(msg)
|
||||
|
||||
# Enforce unique IP space (if applicable)
|
||||
if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique):
|
||||
duplicate_ips = self.get_duplicates()
|
||||
|
|
|
@ -28,7 +28,7 @@ from netbox.plugins import PluginConfig
|
|||
# Environment setup
|
||||
#
|
||||
|
||||
VERSION = '3.7.3'
|
||||
VERSION = '3.7.4'
|
||||
|
||||
# Hostname
|
||||
HOSTNAME = platform.node()
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
<td>
|
||||
{{ object.scheduled|annotated_date|placeholder }}
|
||||
{% if object.interval %}
|
||||
({% blocktrans with interval=object.interval %}every {{ interval }} seconds{% endblocktrans %})
|
||||
({% blocktrans with interval=object.interval %}every {{ interval }} minutes{% endblocktrans %})
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -51,36 +51,43 @@ def parse_alphanumeric_range(string):
|
|||
'0-3,a-d' => [0, 1, 2, 3, a, b, c, d]
|
||||
"""
|
||||
values = []
|
||||
for dash_range in string.split(','):
|
||||
for value in string.split(','):
|
||||
if '-' not in value:
|
||||
# Item is not a range
|
||||
values.append(value)
|
||||
continue
|
||||
|
||||
# Find the range's beginning & end values
|
||||
try:
|
||||
begin, end = dash_range.split('-')
|
||||
begin, end = value.split('-')
|
||||
vals = begin + end
|
||||
# Break out of loop if there's an invalid pattern to return an error
|
||||
if (not (vals.isdigit() or vals.isalpha())) or (vals.isalpha() and not (vals.isupper() or vals.islower())):
|
||||
return []
|
||||
except ValueError:
|
||||
begin, end = dash_range, dash_range
|
||||
raise forms.ValidationError(_('Range "{value}" is invalid.').format(value=value))
|
||||
|
||||
# Numeric range
|
||||
if begin.isdigit() and end.isdigit():
|
||||
if int(begin) >= int(end):
|
||||
raise forms.ValidationError(_('Range "{value}" is invalid.').format(value=dash_range))
|
||||
|
||||
raise forms.ValidationError(
|
||||
_('Invalid range: Ending value ({end}) must be greater than beginning value ({begin}).').format(
|
||||
begin=begin, end=end
|
||||
)
|
||||
)
|
||||
for n in list(range(int(begin), int(end) + 1)):
|
||||
values.append(n)
|
||||
|
||||
# Alphanumeric range
|
||||
else:
|
||||
# Value-based
|
||||
if begin == end:
|
||||
values.append(begin)
|
||||
# Range-based
|
||||
else:
|
||||
# Not a valid range (more than a single character)
|
||||
if not len(begin) == len(end) == 1:
|
||||
raise forms.ValidationError(_('Range "{value}" is invalid.').format(value=dash_range))
|
||||
# Not a valid range (more than a single character)
|
||||
if not len(begin) == len(end) == 1:
|
||||
raise forms.ValidationError(_('Range "{value}" is invalid.').format(value=value))
|
||||
if ord(begin) >= ord(end):
|
||||
raise forms.ValidationError(_('Range "{value}" is invalid.').format(value=value))
|
||||
for n in list(range(ord(begin), ord(end) + 1)):
|
||||
values.append(chr(n))
|
||||
|
||||
if ord(begin) >= ord(end):
|
||||
raise forms.ValidationError(_('Range "{value}" is invalid.').format(value=dash_range))
|
||||
|
||||
for n in list(range(ord(begin), ord(end) + 1)):
|
||||
values.append(chr(n))
|
||||
return values
|
||||
|
||||
|
||||
|
|
|
@ -191,7 +191,16 @@ class ExpandAlphanumeric(TestCase):
|
|||
|
||||
self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
|
||||
|
||||
def test_set(self):
|
||||
def test_set_numeric(self):
|
||||
input = 'r[1,2]a'
|
||||
output = sorted([
|
||||
'r1a',
|
||||
'r2a',
|
||||
])
|
||||
|
||||
self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output)
|
||||
|
||||
def test_set_alpha(self):
|
||||
input = '[r,t]1a'
|
||||
output = sorted([
|
||||
'r1a',
|
||||
|
|
|
@ -172,6 +172,6 @@ class VirtualDiskSerializer(NetBoxModelSerializer):
|
|||
class Meta:
|
||||
model = VirtualDisk
|
||||
fields = [
|
||||
'id', 'url', 'virtual_machine', 'name', 'description', 'size', 'tags', 'custom_fields', 'created',
|
||||
'last_updated',
|
||||
'id', 'url', 'display', 'virtual_machine', 'name', 'description', 'size', 'tags', 'custom_fields',
|
||||
'created', 'last_updated',
|
||||
]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from dcim.graphql.types import ComponentObjectType
|
||||
from extras.graphql.mixins import ConfigContextMixin
|
||||
from extras.graphql.mixins import ConfigContextMixin, ContactsMixin
|
||||
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
|
||||
from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType
|
||||
from virtualization import filtersets, models
|
||||
|
@ -38,7 +38,7 @@ class ClusterTypeType(OrganizationalObjectType):
|
|||
filterset_class = filtersets.ClusterTypeFilterSet
|
||||
|
||||
|
||||
class VirtualMachineType(ConfigContextMixin, NetBoxObjectType):
|
||||
class VirtualMachineType(ConfigContextMixin, ContactsMixin, NetBoxObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.VirtualMachine
|
||||
|
|
|
@ -33,6 +33,15 @@ VMINTERFACE_BUTTONS = """
|
|||
</ul>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if perms.vpn.add_tunnel and not record.tunnel_termination %}
|
||||
<a href="{% url 'vpn:tunnel_add' %}?termination1_type=virtualization.virtualmachine&termination1_parent={{ record.virtual_machine.pk }}&termination1_termination={{ record.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" title="Create a tunnel" class="btn btn-success btn-sm">
|
||||
<i class="mdi mdi-tunnel-outline" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% elif perms.vpn.delete_tunneltermination and record.tunnel_termination %}
|
||||
<a href="{% url 'vpn:tunneltermination_delete' pk=record.tunnel_termination.pk %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" title="Remove tunnel" class="btn btn-danger btn-sm">
|
||||
<i class="mdi mdi-tunnel-outline" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
|
||||
|
@ -64,6 +73,10 @@ class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable)
|
|||
role = columns.ColoredLabelColumn(
|
||||
verbose_name=_('Role'),
|
||||
)
|
||||
platform = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Platform')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
|
@ -97,9 +110,9 @@ class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable)
|
|||
class Meta(NetBoxTable.Meta):
|
||||
model = VirtualMachine
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'tenant_group', 'platform',
|
||||
'vcpus', 'memory', 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'description', 'comments',
|
||||
'config_template', 'contacts', 'tags', 'created', 'last_updated',
|
||||
'pk', 'id', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'tenant_group', 'vcpus',
|
||||
'memory', 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'description', 'comments', 'config_template',
|
||||
'contacts', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip',
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -124,7 +124,7 @@ class EncryptionAlgorithmChoices(ChoiceSet):
|
|||
(ENCRYPTION_AES256_CBC, '256-bit AES (CBC)'),
|
||||
(ENCRYPTION_AES256_GCM, '256-bit AES (GCM)'),
|
||||
(ENCRYPTION_3DES, '3DES'),
|
||||
(ENCRYPTION_3DES, 'DES'),
|
||||
(ENCRYPTION_DES, 'DES'),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
bleach==6.1.0
|
||||
Django==4.2.10
|
||||
Django==4.2.11
|
||||
django-cors-headers==4.3.1
|
||||
django-debug-toolbar==4.3.0
|
||||
django-filter==23.5
|
||||
django-filter==24.1
|
||||
django-graphiql-debug-toolbar==0.2.0
|
||||
django-mptt==0.14.0
|
||||
django-pglocks==1.0.4
|
||||
|
@ -15,14 +15,14 @@ django-tables2==2.7.0
|
|||
django-timezone-field==6.1.0
|
||||
djangorestframework==3.14.0
|
||||
drf-spectacular==0.27.1
|
||||
drf-spectacular-sidecar==2024.2.1
|
||||
drf-spectacular-sidecar==2024.3.4
|
||||
feedparser==6.0.11
|
||||
graphene-django==3.0.0
|
||||
gunicorn==21.2.0
|
||||
Jinja2==3.1.3
|
||||
Markdown==3.5.2
|
||||
mkdocs-material==9.5.10
|
||||
mkdocstrings[python-legacy]==0.24.0
|
||||
mkdocs-material==9.5.13
|
||||
mkdocstrings[python-legacy]==0.24.1
|
||||
netaddr==1.2.1
|
||||
Pillow==10.2.0
|
||||
psycopg[binary,pool]==3.1.18
|
||||
|
|
Loading…
Reference in New Issue