mirror of
https://github.com/netbox-community/netbox.git
synced 2026-04-21 08:21:48 +02:00
Merge pull request #21496 from netbox-community/20923-convert-virtualization-views-to-new-ui-layout
Closes #20923: Migrate Virtualization object views to declarative layouts
This commit is contained in:
0
netbox/ipam/ui/__init__.py
Normal file
0
netbox/ipam/ui/__init__.py
Normal file
37
netbox/ipam/ui/panels.py
Normal file
37
netbox/ipam/ui/panels.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from netbox.ui import actions, panels
|
||||||
|
|
||||||
|
|
||||||
|
class FHRPGroupAssignmentsPanel(panels.ObjectPanel):
|
||||||
|
"""
|
||||||
|
A panel which lists all FHRP group assignments for a given object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
template_name = 'ipam/panels/fhrp_groups.html'
|
||||||
|
title = _('FHRP Groups')
|
||||||
|
actions = [
|
||||||
|
actions.AddObject(
|
||||||
|
'ipam.FHRPGroup',
|
||||||
|
url_params={
|
||||||
|
'return_url': lambda ctx: reverse(
|
||||||
|
'ipam:fhrpgroupassignment_add',
|
||||||
|
query={
|
||||||
|
'interface_type': ContentType.objects.get_for_model(ctx['object']).pk,
|
||||||
|
'interface_id': ctx['object'].pk,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
label=_('Create Group'),
|
||||||
|
),
|
||||||
|
actions.AddObject(
|
||||||
|
'ipam.FHRPGroupAssignment',
|
||||||
|
url_params={
|
||||||
|
'interface_type': lambda ctx: ContentType.objects.get_for_model(ctx['object']).pk,
|
||||||
|
'interface_id': lambda ctx: ctx['object'].pk,
|
||||||
|
},
|
||||||
|
label=_('Assign Group'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -10,6 +10,7 @@ __all__ = (
|
|||||||
'BooleanAttr',
|
'BooleanAttr',
|
||||||
'ColorAttr',
|
'ColorAttr',
|
||||||
'ChoiceAttr',
|
'ChoiceAttr',
|
||||||
|
'GenericForeignKeyAttr',
|
||||||
'GPSCoordinatesAttr',
|
'GPSCoordinatesAttr',
|
||||||
'ImageAttr',
|
'ImageAttr',
|
||||||
'NestedObjectAttr',
|
'NestedObjectAttr',
|
||||||
@@ -279,6 +280,32 @@ class NestedObjectAttr(ObjectAttribute):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GenericForeignKeyAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
An attribute representing a related generic relation object.
|
||||||
|
|
||||||
|
This attribute is similar to `RelatedObjectAttr` but uses the
|
||||||
|
ContentType of the related object to be displayed alongside the value.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
linkify (bool): If True, the rendered value will be hyperlinked
|
||||||
|
to the related object's detail view
|
||||||
|
"""
|
||||||
|
template_name = 'ui/attrs/generic_object.html'
|
||||||
|
|
||||||
|
def __init__(self, *args, linkify=None, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.linkify = linkify
|
||||||
|
|
||||||
|
def get_context(self, obj, context):
|
||||||
|
value = self.get_value(obj)
|
||||||
|
content_type = value._meta.verbose_name
|
||||||
|
return {
|
||||||
|
'content_type': content_type,
|
||||||
|
'linkify': self.linkify,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AddressAttr(ObjectAttribute):
|
class AddressAttr(ObjectAttribute):
|
||||||
"""
|
"""
|
||||||
A physical or mailing address.
|
A physical or mailing address.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from utilities.views import get_viewname
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CommentsPanel',
|
'CommentsPanel',
|
||||||
|
'ContextTablePanel',
|
||||||
'JSONPanel',
|
'JSONPanel',
|
||||||
'NestedGroupObjectPanel',
|
'NestedGroupObjectPanel',
|
||||||
'ObjectAttributesPanel',
|
'ObjectAttributesPanel',
|
||||||
@@ -339,3 +340,42 @@ class PluginContentPanel(Panel):
|
|||||||
def render(self, context):
|
def render(self, context):
|
||||||
obj = context.get('object')
|
obj = context.get('object')
|
||||||
return _get_registered_content(obj, self.method, context)
|
return _get_registered_content(obj, self.method, context)
|
||||||
|
|
||||||
|
|
||||||
|
class ContextTablePanel(ObjectPanel):
|
||||||
|
"""
|
||||||
|
A panel which renders a django-tables2/NetBoxTable instance provided
|
||||||
|
via the view's extra context.
|
||||||
|
|
||||||
|
This is useful when you already have a fully constructed table
|
||||||
|
(custom queryset, special columns, no list view) and just want to
|
||||||
|
render it inside a declarative layout panel.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
table (str | callable): Either the context key holding the table
|
||||||
|
(e.g. "vlan_table") or a callable which accepts the template
|
||||||
|
context and returns a table instance.
|
||||||
|
"""
|
||||||
|
template_name = 'ui/panels/context_table.html'
|
||||||
|
|
||||||
|
def __init__(self, table, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.table = table
|
||||||
|
|
||||||
|
def _resolve_table(self, context):
|
||||||
|
if callable(self.table):
|
||||||
|
return self.table(context)
|
||||||
|
return context.get(self.table)
|
||||||
|
|
||||||
|
def get_context(self, context):
|
||||||
|
table = self._resolve_table(context)
|
||||||
|
return {
|
||||||
|
**super().get_context(context),
|
||||||
|
'table': table,
|
||||||
|
}
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
table = self._resolve_table(context)
|
||||||
|
if table is None:
|
||||||
|
return ''
|
||||||
|
return super().render(context)
|
||||||
|
|||||||
47
netbox/templates/ipam/panels/fhrp_groups.html
Normal file
47
netbox/templates/ipam/panels/fhrp_groups.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{% extends "ui/panels/_base.html" %}
|
||||||
|
{% load perms %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block panel_content %}
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<thead>
|
||||||
|
<tr class="border-bottom">
|
||||||
|
<th>{% trans "Group" %}</th>
|
||||||
|
<th>{% trans "Protocol" %}</th>
|
||||||
|
<th>{% trans "Virtual IPs" %}</th>
|
||||||
|
<th>{% trans "Priority" %}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for assignment in object.fhrp_group_assignments.all %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ assignment.group|linkify:"group_id" }}</td>
|
||||||
|
<td>{{ assignment.group.get_protocol_display }}</td>
|
||||||
|
<td>
|
||||||
|
{% for ipaddress in assignment.group.ip_addresses.all %}
|
||||||
|
{{ ipaddress|linkify }}{% if not forloop.last %}<br />{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td>{{ assignment.priority }}</td>
|
||||||
|
<td class="text-end d-print-none">
|
||||||
|
{% if request.user|can_change:assignment %}
|
||||||
|
<a href="{% url 'ipam:fhrpgroupassignment_edit' pk=assignment.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-warning" title="{% trans "Edit" %}">
|
||||||
|
<i class="mdi mdi-pencil" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.user|can_delete:assignment %}
|
||||||
|
<a href="{% url 'ipam:fhrpgroupassignment_delete' pk=assignment.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-danger" title="{% trans "Delete" %}">
|
||||||
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-muted">{% trans "None" %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock panel_content %}
|
||||||
3
netbox/templates/ui/attrs/generic_object.html
Normal file
3
netbox/templates/ui/attrs/generic_object.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<span{% if name %} id="attr_{{ name }}"{% endif %}>
|
||||||
|
{% if linkify %}{{ value|linkify }}{% else %}{{ value }}{% endif %}{% if content_type %} ({{ content_type }}){% endif %}
|
||||||
|
</span>
|
||||||
6
netbox/templates/ui/panels/context_table.html
Normal file
6
netbox/templates/ui/panels/context_table.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% extends "ui/panels/_base.html" %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
|
{% block panel_content %}
|
||||||
|
{% render_table table 'inc/table.html' %}
|
||||||
|
{% endblock panel_content %}
|
||||||
@@ -1,95 +1 @@
|
|||||||
{% extends 'virtualization/cluster/base.html' %}
|
{% extends 'virtualization/cluster/base.html' %}
|
||||||
{% load helpers %}
|
|
||||||
{% load plugins %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col col-12 col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="card-header">{% trans "Cluster" %}</h2>
|
|
||||||
<table class="table table-hover attr-table">
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Name" %}</th>
|
|
||||||
<td>{{ object.name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Type" %}</th>
|
|
||||||
<td>{{ object.type|linkify }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Status" %}</th>
|
|
||||||
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Description" %}</th>
|
|
||||||
<td>{{ object.description|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Group" %}</th>
|
|
||||||
<td>{{ object.group|linkify|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Tenant" %}</th>
|
|
||||||
<td>
|
|
||||||
{% if object.tenant.group %}
|
|
||||||
{{ object.tenant.group|linkify }} /
|
|
||||||
{% endif %}
|
|
||||||
{{ object.tenant|linkify|placeholder }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Scope" %}</th>
|
|
||||||
{% if object.scope %}
|
|
||||||
<td>{{ object.scope|linkify }} ({% trans object.scope_type.name %})</td>
|
|
||||||
{% else %}
|
|
||||||
<td>{{ ''|placeholder }}</td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% include 'inc/panels/comments.html' %}
|
|
||||||
{% plugin_left_page object %}
|
|
||||||
</div>
|
|
||||||
<div class="col col-12 col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="card-header">{% trans "Allocated Resources" %}</h2>
|
|
||||||
<table class="table table-hover attr-table">
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><i class="mdi mdi-gauge"></i> {% trans "Virtual CPUs" %}</th>
|
|
||||||
<td>{{ vcpus_sum|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><i class="mdi mdi-chip"></i> {% trans "Memory" %}</th>
|
|
||||||
<td>
|
|
||||||
{% if memory_sum %}
|
|
||||||
<span title={{ memory_sum }}>{{ memory_sum|humanize_ram_megabytes }}</span>
|
|
||||||
{% else %}
|
|
||||||
{{ ''|placeholder }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><i class="mdi mdi-harddisk"></i> {% trans "Disk Space" %}</th>
|
|
||||||
<td>
|
|
||||||
{% if disk_sum %}
|
|
||||||
{{ disk_sum|humanize_disk_megabytes }}
|
|
||||||
{% else %}
|
|
||||||
{{ ''|placeholder }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% include 'inc/panels/related_objects.html' %}
|
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
|
||||||
{% include 'inc/panels/tags.html' %}
|
|
||||||
{% plugin_right_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
{% plugin_full_width_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
{% extends 'generic/object.html' %}
|
{% extends 'generic/object.html' %}
|
||||||
{% load helpers %}
|
|
||||||
{% load plugins %}
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
@@ -11,36 +8,3 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock extra_controls %}
|
{% endblock extra_controls %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col col-12 col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="card-header">{% trans "Cluster Group" %}</h2>
|
|
||||||
<table class="table table-hover attr-table">
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Name" %}</th>
|
|
||||||
<td>{{ object.name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Description" %}</th>
|
|
||||||
<td>{{ object.description|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% include 'inc/panels/tags.html' %}
|
|
||||||
{% plugin_left_page object %}
|
|
||||||
</div>
|
|
||||||
<div class="col col-12 col-md-6">
|
|
||||||
{% include 'inc/panels/related_objects.html' %}
|
|
||||||
{% include 'inc/panels/comments.html' %}
|
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
|
||||||
{% plugin_right_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
{% plugin_full_width_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
{% extends 'generic/object.html' %}
|
{% extends 'generic/object.html' %}
|
||||||
{% load helpers %}
|
|
||||||
{% load plugins %}
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
@@ -11,42 +8,3 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock extra_controls %}
|
{% endblock extra_controls %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col col-12 col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="card-header">{% trans "Cluster Type" %}</h2>
|
|
||||||
<table class="table table-hover attr-table">
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Name" %}</th>
|
|
||||||
<td>{{ object.name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Description" %}</th>
|
|
||||||
<td>{{ object.description|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Clusters" %}</th>
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'virtualization:cluster_list' %}?type_id={{ object.pk }}">{{ object.clusters.count }}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% include 'inc/panels/tags.html' %}
|
|
||||||
{% plugin_left_page object %}
|
|
||||||
</div>
|
|
||||||
<div class="col col-12 col-md-6">
|
|
||||||
{% include 'inc/panels/related_objects.html' %}
|
|
||||||
{% include 'inc/panels/comments.html' %}
|
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
|
||||||
{% plugin_right_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
{% plugin_full_width_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{% load helpers %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">{% trans "Allocated Resources" %}</h2>
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><i class="mdi mdi-gauge"></i> {% trans "Virtual CPUs" %}</th>
|
||||||
|
<td>{{ vcpus_sum|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><i class="mdi mdi-chip"></i> {% trans "Memory" %}</th>
|
||||||
|
<td>
|
||||||
|
{% if memory_sum %}
|
||||||
|
<span title={{ memory_sum }}>{{ memory_sum|humanize_ram_megabytes }}</span>
|
||||||
|
{% else %}
|
||||||
|
{{ ''|placeholder }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<i class="mdi mdi-harddisk"></i> {% trans "Disk Space" %}
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
{% if disk_sum %}
|
||||||
|
{{ disk_sum|humanize_disk_megabytes }}
|
||||||
|
{% else %}
|
||||||
|
{{ ''|placeholder }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
@@ -10,48 +10,3 @@
|
|||||||
<a href="{% url 'virtualization:virtualmachine_disks' pk=object.virtual_machine.pk %}">{{ object.virtual_machine }}</a>
|
<a href="{% url 'virtualization:virtualmachine_disks' pk=object.virtual_machine.pk %}">{{ object.virtual_machine }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col col-12 col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="card-header">{% trans "Virtual Disk" %}</h2>
|
|
||||||
<table class="table table-hover attr-table">
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Virtual Machine" %}</th>
|
|
||||||
<td>{{ object.virtual_machine|linkify }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Name" %}</th>
|
|
||||||
<td>{{ object.name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><i class="mdi mdi-harddisk"></i> {% trans "Size" %}</th>
|
|
||||||
<td>
|
|
||||||
{% if object.size %}
|
|
||||||
{{ object.size|humanize_disk_megabytes }}
|
|
||||||
{% else %}
|
|
||||||
{{ ''|placeholder }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Description" %}</th>
|
|
||||||
<td>{{ object.description|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% include 'inc/panels/tags.html' %}
|
|
||||||
{% plugin_left_page object %}
|
|
||||||
</div>
|
|
||||||
<div class="col col-12 col-md-6">
|
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
|
||||||
{% plugin_right_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
{% plugin_full_width_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{% load helpers %}
|
||||||
|
{{ value|humanize_disk_megabytes }}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
{% extends 'generic/object.html' %}
|
{% extends 'generic/object.html' %}
|
||||||
{% load helpers %}
|
|
||||||
{% load plugins %}
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
@@ -10,151 +6,3 @@
|
|||||||
<a href="{% url 'virtualization:virtualmachine_interfaces' pk=object.virtual_machine.pk %}">{{ object.virtual_machine }}</a>
|
<a href="{% url 'virtualization:virtualmachine_interfaces' pk=object.virtual_machine.pk %}">{{ object.virtual_machine }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col col-12 col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="card-header">{% trans "Interface" %}</h2>
|
|
||||||
<table class="table table-hover attr-table">
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Virtual Machine" %}</th>
|
|
||||||
<td>{{ object.virtual_machine|linkify }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Name" %}</th>
|
|
||||||
<td>{{ object.name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Enabled" %}</th>
|
|
||||||
<td>
|
|
||||||
{% if object.enabled %}
|
|
||||||
<span class="text-success"><i class="mdi mdi-check-bold"></i></span>
|
|
||||||
{% else %}
|
|
||||||
<span class="text-danger"><i class="mdi mdi-close"></i></span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Parent" %}</th>
|
|
||||||
<td>{{ object.parent|linkify|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Bridge" %}</th>
|
|
||||||
<td>{{ object.bridge|linkify|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Description" %}</th>
|
|
||||||
<td>{{ object.description|placeholder }} </td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "MTU" %}</th>
|
|
||||||
<td>{{ object.mtu|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "802.1Q Mode" %}</th>
|
|
||||||
<td>{{ object.get_mode_display|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
{% if object.mode == 'q-in-q' %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Q-in-Q SVLAN" %}</th>
|
|
||||||
<td>{{ object.qinq_svlan|linkify|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Tunnel" %}</th>
|
|
||||||
<td>{{ object.tunnel_termination.tunnel|linkify|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% include 'inc/panels/tags.html' %}
|
|
||||||
{% plugin_left_page object %}
|
|
||||||
</div>
|
|
||||||
<div class="col col-12 col-md-6">
|
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="card-header">{% trans "Addressing" %}</h2>
|
|
||||||
<table class="table table-hover attr-table">
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "MAC Address" %}</th>
|
|
||||||
<td>
|
|
||||||
{% if object.primary_mac_address %}
|
|
||||||
<span class="font-monospace">{{ object.primary_mac_address|linkify }}</span>
|
|
||||||
<span class="badge text-bg-primary">{% trans "Primary" %}</span>
|
|
||||||
{% else %}
|
|
||||||
{{ ''|placeholder }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "VRF" %}</th>
|
|
||||||
<td>{{ object.vrf|linkify|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "VLAN Translation" %}</th>
|
|
||||||
<td>{{ object.vlan_translation_policy|linkify|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% include 'ipam/inc/panels/fhrp_groups.html' %}
|
|
||||||
{% plugin_right_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="card-header">
|
|
||||||
{% trans "IP Addresses" %}
|
|
||||||
{% if perms.ipam.add_ipaddress %}
|
|
||||||
<div class="card-actions">
|
|
||||||
<a href="{% url 'ipam:ipaddress_add' %}?virtual_machine={{ object.virtual_machine.pk }}&vminterface={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-ghost-primary btn-sm">
|
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add IP Address" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</h2>
|
|
||||||
{% htmx_table 'ipam:ipaddress_list' vminterface_id=object.pk %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="card-header">
|
|
||||||
{% trans "MAC Addresses" %}
|
|
||||||
{% if perms.ipam.add_macaddress %}
|
|
||||||
<div class="card-actions">
|
|
||||||
<a href="{% url 'dcim:macaddress_add' %}?virtual_machine={{ object.device.pk }}&vminterface={{ object.pk }}&return_url={{ object.get_absolute_url }}"
|
|
||||||
class="btn btn-ghost-primary btn-sm">
|
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add MAC Address" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</h2>
|
|
||||||
{% htmx_table 'dcim:macaddress_list' vminterface_id=object.pk %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
{% include 'inc/panel_table.html' with table=vlan_table heading="VLANs" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if object.vlan_translation_policy %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
{% include 'inc/panel_table.html' with table=vlan_translation_table heading="VLAN Translation" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
{% include 'inc/panel_table.html' with table=child_interfaces_table heading="Child Interfaces" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
{% plugin_full_width_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@@ -3,6 +3,16 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from netbox.ui import attrs, panels
|
from netbox.ui import attrs, panels
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterPanel(panels.ObjectAttributesPanel):
|
||||||
|
name = attrs.TextAttr('name')
|
||||||
|
type = attrs.RelatedObjectAttr('type', linkify=True)
|
||||||
|
status = attrs.ChoiceAttr('status')
|
||||||
|
description = attrs.TextAttr('description')
|
||||||
|
group = attrs.RelatedObjectAttr('group', linkify=True)
|
||||||
|
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
|
||||||
|
scope = attrs.GenericForeignKeyAttr('scope', linkify=True)
|
||||||
|
|
||||||
|
|
||||||
class VirtualMachinePanel(panels.ObjectAttributesPanel):
|
class VirtualMachinePanel(panels.ObjectAttributesPanel):
|
||||||
name = attrs.TextAttr('name')
|
name = attrs.TextAttr('name')
|
||||||
status = attrs.ChoiceAttr('status')
|
status = attrs.ChoiceAttr('status')
|
||||||
@@ -32,3 +42,35 @@ class VirtualMachineClusterPanel(panels.ObjectAttributesPanel):
|
|||||||
cluster = attrs.RelatedObjectAttr('cluster', linkify=True)
|
cluster = attrs.RelatedObjectAttr('cluster', linkify=True)
|
||||||
cluster_type = attrs.RelatedObjectAttr('cluster.type', linkify=True)
|
cluster_type = attrs.RelatedObjectAttr('cluster.type', linkify=True)
|
||||||
device = attrs.RelatedObjectAttr('device', linkify=True)
|
device = attrs.RelatedObjectAttr('device', linkify=True)
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualDiskPanel(panels.ObjectAttributesPanel):
|
||||||
|
virtual_machine = attrs.RelatedObjectAttr('virtual_machine', linkify=True, label=_('Virtual Machine'))
|
||||||
|
name = attrs.TextAttr('name')
|
||||||
|
size = attrs.TemplatedAttr('size', template_name='virtualization/virtualdisk/attrs/size.html')
|
||||||
|
description = attrs.TextAttr('description')
|
||||||
|
|
||||||
|
|
||||||
|
class VMInterfacePanel(panels.ObjectAttributesPanel):
|
||||||
|
virtual_machine = attrs.RelatedObjectAttr('virtual_machine', linkify=True, label=_('Virtual Machine'))
|
||||||
|
name = attrs.TextAttr('name')
|
||||||
|
enabled = attrs.BooleanAttr('enabled')
|
||||||
|
parent = attrs.RelatedObjectAttr('parent_interface', linkify=True)
|
||||||
|
bridge = attrs.RelatedObjectAttr('bridge', linkify=True)
|
||||||
|
description = attrs.TextAttr('description')
|
||||||
|
mtu = attrs.TextAttr('mtu', label=_('MTU'))
|
||||||
|
mode = attrs.ChoiceAttr('mode', label=_('802.1Q Mode'))
|
||||||
|
qinq_svlan = attrs.RelatedObjectAttr('qinq_svlan', linkify=True, label=_('Q-in-Q SVLAN'))
|
||||||
|
tunnel_termination = attrs.RelatedObjectAttr('tunnel_termination.tunnel', linkify=True, label=_('Tunnel'))
|
||||||
|
|
||||||
|
|
||||||
|
class VMInterfaceAddressingPanel(panels.ObjectAttributesPanel):
|
||||||
|
title = _('Addressing')
|
||||||
|
|
||||||
|
primary_mac_address = attrs.TextAttr(
|
||||||
|
'primary_mac_address', label=_('MAC Address'), style='font-monospace', copy_button=True
|
||||||
|
)
|
||||||
|
vrf = attrs.RelatedObjectAttr('vrf', linkify=True, label=_('VRF'))
|
||||||
|
vlan_translation_policy = attrs.RelatedObjectAttr(
|
||||||
|
'vlan_translation_policy', linkify=True, label=_('VLAN Translation')
|
||||||
|
)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from extras.ui.panels import CustomFieldsPanel, ImageAttachmentsPanel, TagsPanel
|
|||||||
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
|
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
|
||||||
from ipam.models import IPAddress, VLANGroup
|
from ipam.models import IPAddress, VLANGroup
|
||||||
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
||||||
|
from ipam.ui.panels import FHRPGroupAssignmentsPanel
|
||||||
from netbox.object_actions import (
|
from netbox.object_actions import (
|
||||||
AddObject,
|
AddObject,
|
||||||
BulkDelete,
|
BulkDelete,
|
||||||
@@ -25,7 +26,14 @@ from netbox.object_actions import (
|
|||||||
EditObject,
|
EditObject,
|
||||||
)
|
)
|
||||||
from netbox.ui import actions, layout
|
from netbox.ui import actions, layout
|
||||||
from netbox.ui.panels import CommentsPanel, ObjectsTablePanel, TemplatePanel
|
from netbox.ui.panels import (
|
||||||
|
CommentsPanel,
|
||||||
|
ContextTablePanel,
|
||||||
|
ObjectsTablePanel,
|
||||||
|
OrganizationalObjectPanel,
|
||||||
|
RelatedObjectsPanel,
|
||||||
|
TemplatePanel,
|
||||||
|
)
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
from utilities.query_functions import CollateAsChar
|
from utilities.query_functions import CollateAsChar
|
||||||
@@ -54,6 +62,17 @@ class ClusterTypeListView(generic.ObjectListView):
|
|||||||
@register_model_view(ClusterType)
|
@register_model_view(ClusterType)
|
||||||
class ClusterTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
class ClusterTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = ClusterType.objects.all()
|
queryset = ClusterType.objects.all()
|
||||||
|
layout = layout.SimpleLayout(
|
||||||
|
left_panels=[
|
||||||
|
OrganizationalObjectPanel(),
|
||||||
|
TagsPanel(),
|
||||||
|
],
|
||||||
|
right_panels=[
|
||||||
|
RelatedObjectsPanel(),
|
||||||
|
CustomFieldsPanel(),
|
||||||
|
CommentsPanel(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
return {
|
return {
|
||||||
@@ -121,6 +140,17 @@ class ClusterGroupListView(generic.ObjectListView):
|
|||||||
@register_model_view(ClusterGroup)
|
@register_model_view(ClusterGroup)
|
||||||
class ClusterGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
class ClusterGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = ClusterGroup.objects.all()
|
queryset = ClusterGroup.objects.all()
|
||||||
|
layout = layout.SimpleLayout(
|
||||||
|
left_panels=[
|
||||||
|
OrganizationalObjectPanel(),
|
||||||
|
TagsPanel(),
|
||||||
|
],
|
||||||
|
right_panels=[
|
||||||
|
RelatedObjectsPanel(),
|
||||||
|
CustomFieldsPanel(),
|
||||||
|
CommentsPanel(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
return {
|
return {
|
||||||
@@ -202,6 +232,18 @@ class ClusterListView(generic.ObjectListView):
|
|||||||
@register_model_view(Cluster)
|
@register_model_view(Cluster)
|
||||||
class ClusterView(GetRelatedModelsMixin, generic.ObjectView):
|
class ClusterView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = Cluster.objects.all()
|
queryset = Cluster.objects.all()
|
||||||
|
layout = layout.SimpleLayout(
|
||||||
|
left_panels=[
|
||||||
|
panels.ClusterPanel(),
|
||||||
|
CommentsPanel(),
|
||||||
|
],
|
||||||
|
right_panels=[
|
||||||
|
TemplatePanel('virtualization/panels/cluster_resources.html'),
|
||||||
|
RelatedObjectsPanel(),
|
||||||
|
CustomFieldsPanel(),
|
||||||
|
TagsPanel(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
return {
|
return {
|
||||||
@@ -507,6 +549,7 @@ class VirtualMachineBulkDeleteView(generic.BulkDeleteView):
|
|||||||
# VM interfaces
|
# VM interfaces
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VMInterface, 'list', path='', detail=False)
|
@register_model_view(VMInterface, 'list', path='', detail=False)
|
||||||
class VMInterfaceListView(generic.ObjectListView):
|
class VMInterfaceListView(generic.ObjectListView):
|
||||||
queryset = VMInterface.objects.all()
|
queryset = VMInterface.objects.all()
|
||||||
@@ -518,6 +561,44 @@ class VMInterfaceListView(generic.ObjectListView):
|
|||||||
@register_model_view(VMInterface)
|
@register_model_view(VMInterface)
|
||||||
class VMInterfaceView(generic.ObjectView):
|
class VMInterfaceView(generic.ObjectView):
|
||||||
queryset = VMInterface.objects.all()
|
queryset = VMInterface.objects.all()
|
||||||
|
layout = layout.SimpleLayout(
|
||||||
|
left_panels=[
|
||||||
|
panels.VMInterfacePanel(),
|
||||||
|
TagsPanel(),
|
||||||
|
],
|
||||||
|
right_panels=[
|
||||||
|
CustomFieldsPanel(),
|
||||||
|
panels.VMInterfaceAddressingPanel(),
|
||||||
|
FHRPGroupAssignmentsPanel(),
|
||||||
|
],
|
||||||
|
bottom_panels=[
|
||||||
|
ObjectsTablePanel(
|
||||||
|
model='ipam.IPaddress',
|
||||||
|
filters={'vminterface_id': lambda ctx: ctx['object'].pk},
|
||||||
|
actions=[
|
||||||
|
actions.AddObject(
|
||||||
|
'ipam.IPaddress',
|
||||||
|
url_params={
|
||||||
|
'virtual_machine': lambda ctx: ctx['object'].virtual_machine.pk,
|
||||||
|
'vminterface': lambda ctx: ctx['object'].pk,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ObjectsTablePanel(
|
||||||
|
model='dcim.MACAddress',
|
||||||
|
filters={'vminterface_id': lambda ctx: ctx['object'].pk},
|
||||||
|
actions=[
|
||||||
|
actions.AddObject(
|
||||||
|
'dcim.MACAddress', url_params={'vminterface': lambda ctx: ctx['object'].pk}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ContextTablePanel('vlan_table', title=_('Assigned VLANs')),
|
||||||
|
ContextTablePanel('vlan_translation_table', title=_('VLAN Translation')),
|
||||||
|
ContextTablePanel('child_interfaces_table', title=_('Child Interfaces')),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
@@ -623,6 +704,15 @@ class VirtualDiskListView(generic.ObjectListView):
|
|||||||
@register_model_view(VirtualDisk)
|
@register_model_view(VirtualDisk)
|
||||||
class VirtualDiskView(generic.ObjectView):
|
class VirtualDiskView(generic.ObjectView):
|
||||||
queryset = VirtualDisk.objects.all()
|
queryset = VirtualDisk.objects.all()
|
||||||
|
layout = layout.SimpleLayout(
|
||||||
|
left_panels=[
|
||||||
|
panels.VirtualDiskPanel(),
|
||||||
|
TagsPanel(),
|
||||||
|
],
|
||||||
|
right_panels=[
|
||||||
|
CustomFieldsPanel(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VirtualDisk, 'add', detail=False)
|
@register_model_view(VirtualDisk, 'add', detail=False)
|
||||||
|
|||||||
Reference in New Issue
Block a user