mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-20 07:47:54 +01:00
Compare commits
1 Commits
feature
...
20923-conv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6362048788 |
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',
|
||||
'ColorAttr',
|
||||
'ChoiceAttr',
|
||||
'GenericForeignKeyAttr',
|
||||
'GPSCoordinatesAttr',
|
||||
'ImageAttr',
|
||||
'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):
|
||||
"""
|
||||
A physical or mailing address.
|
||||
|
||||
@@ -12,6 +12,7 @@ from utilities.views import get_viewname
|
||||
|
||||
__all__ = (
|
||||
'CommentsPanel',
|
||||
'ContextTablePanel',
|
||||
'JSONPanel',
|
||||
'NestedGroupObjectPanel',
|
||||
'ObjectAttributesPanel',
|
||||
@@ -339,3 +340,42 @@ class PluginContentPanel(Panel):
|
||||
def render(self, context):
|
||||
obj = context.get('object')
|
||||
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' %}
|
||||
{% 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' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block extra_controls %}
|
||||
@@ -11,36 +8,3 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% 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' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block extra_controls %}
|
||||
@@ -11,42 +8,3 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% 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>
|
||||
</li>
|
||||
{% 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' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
@@ -10,151 +6,3 @@
|
||||
<a href="{% url 'virtualization:virtualmachine_interfaces' pk=object.virtual_machine.pk %}">{{ object.virtual_machine }}</a>
|
||||
</li>
|
||||
{% 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
|
||||
|
||||
|
||||
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):
|
||||
name = attrs.TextAttr('name')
|
||||
status = attrs.ChoiceAttr('status')
|
||||
@@ -32,3 +42,35 @@ class VirtualMachineClusterPanel(panels.ObjectAttributesPanel):
|
||||
cluster = attrs.RelatedObjectAttr('cluster', linkify=True)
|
||||
cluster_type = attrs.RelatedObjectAttr('cluster.type', 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 ipam.models import IPAddress, VLANGroup
|
||||
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
||||
from ipam.ui.panels import FHRPGroupAssignmentsPanel
|
||||
from netbox.object_actions import (
|
||||
AddObject,
|
||||
BulkDelete,
|
||||
@@ -25,7 +26,14 @@ from netbox.object_actions import (
|
||||
EditObject,
|
||||
)
|
||||
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 utilities.query import count_related
|
||||
from utilities.query_functions import CollateAsChar
|
||||
@@ -54,6 +62,17 @@ class ClusterTypeListView(generic.ObjectListView):
|
||||
@register_model_view(ClusterType)
|
||||
class ClusterTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = ClusterType.objects.all()
|
||||
layout = layout.SimpleLayout(
|
||||
left_panels=[
|
||||
OrganizationalObjectPanel(),
|
||||
TagsPanel(),
|
||||
],
|
||||
right_panels=[
|
||||
RelatedObjectsPanel(),
|
||||
CustomFieldsPanel(),
|
||||
CommentsPanel(),
|
||||
],
|
||||
)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
@@ -121,6 +140,17 @@ class ClusterGroupListView(generic.ObjectListView):
|
||||
@register_model_view(ClusterGroup)
|
||||
class ClusterGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = ClusterGroup.objects.all()
|
||||
layout = layout.SimpleLayout(
|
||||
left_panels=[
|
||||
OrganizationalObjectPanel(),
|
||||
TagsPanel(),
|
||||
],
|
||||
right_panels=[
|
||||
RelatedObjectsPanel(),
|
||||
CustomFieldsPanel(),
|
||||
CommentsPanel(),
|
||||
],
|
||||
)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
@@ -202,6 +232,18 @@ class ClusterListView(generic.ObjectListView):
|
||||
@register_model_view(Cluster)
|
||||
class ClusterView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
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):
|
||||
return {
|
||||
@@ -507,6 +549,7 @@ class VirtualMachineBulkDeleteView(generic.BulkDeleteView):
|
||||
# VM interfaces
|
||||
#
|
||||
|
||||
|
||||
@register_model_view(VMInterface, 'list', path='', detail=False)
|
||||
class VMInterfaceListView(generic.ObjectListView):
|
||||
queryset = VMInterface.objects.all()
|
||||
@@ -518,6 +561,44 @@ class VMInterfaceListView(generic.ObjectListView):
|
||||
@register_model_view(VMInterface)
|
||||
class VMInterfaceView(generic.ObjectView):
|
||||
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):
|
||||
|
||||
@@ -623,6 +704,15 @@ class VirtualDiskListView(generic.ObjectListView):
|
||||
@register_model_view(VirtualDisk)
|
||||
class VirtualDiskView(generic.ObjectView):
|
||||
queryset = VirtualDisk.objects.all()
|
||||
layout = layout.SimpleLayout(
|
||||
left_panels=[
|
||||
panels.VirtualDiskPanel(),
|
||||
TagsPanel(),
|
||||
],
|
||||
right_panels=[
|
||||
CustomFieldsPanel(),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(VirtualDisk, 'add', detail=False)
|
||||
|
||||
Reference in New Issue
Block a user