添加端口可视化
展示效果:

基于netbox-community v4.5.4
介绍:
在设备详情页面添加栏目,显示设备端口列表及连接状态,尤其交换机可直观展示端口使用情况(支持 i18n)
实现方法
在netbox/dcim/ui/panels.py中添加代码:
class DevicePortsPanel(panels.ObjectAttributesPanel):
template_name='dcim/device/attrs/port.html'
修改netbox/dcim/views.py代码:
layout = layout.SimpleLayout(
left_panels=[
panels.DeviceManagementPanel(),
panels.DevicePanel(),//移动设备面板
panels.VirtualChassisMembersPanel(),
CustomFieldsPanel(),
TagsPanel(),
CommentsPanel(),
ObjectsTablePanel(
model='dcim.VirtualDeviceContext',
filters={'device_id': lambda ctx: ctx['object'].pk},
actions=[
actions.AddObject('dcim.VirtualDeviceContext', url_params={'device': lambda ctx: ctx['object'].pk}),
],
),
],
right_panels=[
panels.PowerUtilizationPanel(),
ObjectsTablePanel(
model='ipam.Service',
title=_('Application Services'),
filters={'device_id': lambda ctx: ctx['object'].pk},
actions=[
actions.AddObject(
'ipam.Service',
url_params={
'parent_object_type': lambda ctx: ContentType.objects.get_for_model(ctx['object']).pk,
'parent': lambda ctx: ctx['object'].pk
}
),
],
),
ImageAttachmentsPanel(),
panels.DeviceDeviceTypePanel(),
panels.DevicePortsPanel(),//添加面板
panels.DeviceDimensionsPanel(),
TemplatePanel('dcim/panels/device_rack_elevations.html'),
],
)
创建新文件:netbox/templates/dcim/device/attrs/port.html
{% load i18n %}
<div class="card shadow-sm border-0">
<div class="card-header bg-primary bg-gradient text-white">
<strong class="fs-6">{% trans "Port Status Visualization" %}</strong>
</div>
<div class="card-body p-4">
{% with interfaces=object.interfaces.all|dictsort:"type" %}
{% if interfaces %}
{% regroup interfaces by type as type_groups %}
{% for group in type_groups %}
<div class="mb-3 mt-2 d-flex align-items-center">
<strong class="text-dark" style="font-size: 0.95rem;">{% trans "Interface Type" %}:
<span class="badge bg-light bg-gradient ms-2">{{ group.list.0.get_type_display }}</span></strong>
<span class="legend-badge" style="font-size: 0.75rem;">{{ group.list|length }} {% trans "Ports" %}</span>
</div>
<div class="port-panel mb-5">
{% for iface in group.list %}
<div class="port-unit" data-toggle="tooltip" title="{{ iface.name }} ({{ iface.get_type_display }})">
{% if 'sfp' in iface.type|lower or 'qsfp' in iface.type|lower or 'gbase-sr' in iface.type|lower or 'gbase-lr' in iface.type|lower or 'gbase-er' in iface.type|lower or '10gbase-sr' in iface.type|lower or '10gbase-lr' in iface.type|lower or '25gbase-sr' in iface.type|lower or '25gbase-lr' in iface.type|lower or '40gbase-sr4' in iface.type|lower or '40gbase-lr4' in iface.type|lower or '100gbase-sr4' in iface.type|lower or '100gbase-lr4' in iface.type|lower %}
{% if iface.enabled %}
{% if iface.cable %}
<svg class="port-icon optical up-glow" viewBox="0 0 1330 1024" xmlns="http://www.w3.org/2000/svg">
<rect x="30" y="30" width="1270" height="964" rx="40" fill="#00c040" stroke="#00c040"
stroke-width="35" />
<rect x="50" y="50" width="1230" height="924" rx="20" fill="#FFFFFF" stroke="#00c040"
stroke-width="2" />
<path
d="M146.923843 184.215181v106.479874h45.636535v84.516284h-49.022992v503.687055h447.915338V373.51811h-43.943307v-79.436598h38.871685V184.215181h-61.423874v-43.153134H214.104693v43.620788l-67.18085-0.467654z"
fill="#00c040" transform="translate(130, 60),scale(0.8)" />
<path
d="M748.648819 186.916283v106.487937h45.636535V377.912441h-49.022992v503.695118h447.915339V376.227276h-43.943307v-79.444662h38.871685V186.916283h-61.423874v-43.14507h-310.852536v43.612724l-67.18085-0.467654z"
fill="#00c040" transform="translate(130, 60),scale(0.8)" /> </svg>
{% else %}
<svg class="port-icon optical" viewBox="0 0 1330 1024" xmlns="http://www.w3.org/2000/svg">
<rect x="30" y="30" width="1270" height="964" rx="40" fill="#E0E0E0" stroke="#B0B0B0"
stroke-width="35" />
<rect x="50" y="50" width="1230" height="924" rx="20" fill="#FFFFFF" stroke="#D0D0D0"
stroke-width="2" />
<path
d="M146.923843 184.215181v106.479874h45.636535v84.516284h-49.022992v503.687055h447.915338V373.51811h-43.943307v-79.436598h38.871685V184.215181h-61.423874v-43.153134H214.104693v43.620788l-67.18085-0.467654z"
fill="#EAEBED" transform="translate(130, 60),scale(0.8)" />
<path
d="M748.648819 186.916283v106.487937h45.636535V377.912441h-49.022992v503.695118h447.915339V376.227276h-43.943307v-79.444662h38.871685V186.916283h-61.423874v-43.14507h-310.852536v43.612724l-67.18085-0.467654z"
fill="#EAEBED" transform="translate(130, 60),scale(0.8)" /> </svg>
{% endif %}
{% else %}
<svg class="port-icon optical disabled" viewBox="0 0 1330 1024" xmlns="http://www.w3.org/2000/svg">
<rect x="30" y="30" width="1270" height="964" rx="40" fill="#8c8c8c" stroke="#8c8c8c"
stroke-width="35" />
<rect x="50" y="50" width="1230" height="924" rx="20" fill="#FFFFFF" stroke="#8c8c8c"
stroke-width="2" />
<path d="M147 184v106h46v85h-49v504h448V374h-44v-79h39V184h-61v-43H214v44l-67 0z" fill="#FFFFFF"
transform="translate(130,60)scale(.8)" />
<path d="M749 187v106h46v85h-49v504h448V376h-44v-79h39V187h-61v-43H438v44l-67 0z" fill="#FFFFFF"
transform="translate(130,60)scale(.8)" /></svg>
{% endif %}
{% else %}
{% if iface.enabled %}
{% if iface.cable %}
<svg class="port-icon electrical up-glow" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path
d="M104 0h810c38 0 52 4 66 11a78 78 0 0132 32c7 14 11 28 11 66v805c0 38-4 52-11 66a78 78 0 01-32 32c-14 7-28 11-66 11H109c-38 0-52-4-66-11a78 78 0 01-32-32c-7-13-11-26-11-60V109c0-38 4-52 11-66a78 78 0 0132-32c13-7 26-11 60-11z"
fill="#00c040" />
<path
d="M109 43l-10 0c-21 0-28 2-36 6a35 35 0 00-15 15l-2 3c-3 7-5 16-5 37v805c0 28 2 37 6 46a35 35 0 0015 15l3 2c7 3 16 5 37 5h805c28 0 37-2 46-6a35 35 0 0015-15l2-3c3-7 5-16 5-37V109c0-28-2-37-6-46a35 35 0 00-15-15l-3-2c-7-3-16-5-37-5l-805 0z"
fill="#fff" />
<path
d="M896 128H128a21 21 0 00-21 21v533l0 3a21 21 0 0021 21h107v64l0 3a21 21 0 0021 21h64v85a21 21 0 0021 21h341l3 0a21 21 0 0021-21v-85h64l3 0a21 21 0 0021-21v-64h107a21 21 0 0021-21V149a21 21 0 00-21-21z"
fill="#00c040" />
<path
d="M875 171v490h-107l-3 0a21 21 0 00-21 21v64h-64l-3 0a21 21 0 00-21 21v85H363v-85l0-3a21 21 0 00-21-21h-64v-64l0-3a21 21 0 00-21-21H149V171h725z"
fill="#EAEBED" /></svg>
{% else %}
<svg class="port-icon electrical" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path
d="M104 0h810c38 0 52 4 66 11a78 78 0 0132 32c7 14 11 28 11 66v805c0 38-4 52-11 66a78 78 0 01-32 32c-14 7-28 11-66 11H109c-38 0-52-4-66-11a78 78 0 01-32-32c-7-13-11-26-11-60V109c0-38 4-52 11-66a78 78 0 0132-32c13-7 26-11 60-11z"
fill="#ADB1B9" />
<path
d="M109 43l-10 0c-21 0-28 2-36 6a35 35 0 00-15 15l-2 3c-3 7-5 16-5 37v805c0 28 2 37 6 46a35 35 0 0015 15l3 2c7 3 16 5 37 5h805c28 0 37-2 46-6a35 35 0 0015-15l2-3c3-7 5-16 5-37V109c0-28-2-37-6-46a35 35 0 00-15-15l-3-2c-7-3-16-5-37-5l-805 0z"
fill="#fff" />
<path
d="M896 128H128a21 21 0 00-21 21v533l0 3a21 21 0 0021 21h107v64l0 3a21 21 0 0021 21h64v85a21 21 0 0021 21h341l3 0a21 21 0 0021-21v-85h64l3 0a21 21 0 0021-21v-64h107a21 21 0 0021-21V149a21 21 0 00-21-21z"
fill="#ADB1B9" />
<path
d="M875 171v490h-107l-3 0a21 21 0 00-21 21v64h-64l-3 0a21 21 0 00-21 21v85H363v-85l0-3a21 21 0 00-21-21h-64v-64l0-3a21 21 0 00-21-21H149V171h725z"
fill="#fff" /></svg>
{% endif %}
{% else %}
<svg class="port-icon electrical disabled" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path
d="M104 0h810c38 0 52 4 66 11a78 78 0 0132 32c7 14 11 28 11 66v805c0 38-4 52-11 66a78 78 0 01-32 32c-14 7-28 11-66 11H109c-38 0-52-4-66-11a78 78 0 01-32-32c-7-13-11-26-11-60V109c0-38 4-52 11-66a78 78 0 0132-32c13-7 26-11 60-11z"
fill="#8c8c8c" />
<path
d="M109 43l-10 0c-21 0-28 2-36 6a35 35 0 00-15 15l-2 3c-3 7-5 16-5 37v805c0 28 2 37 6 46a35 35 0 0015 15l3 2c7 3 16 5 37 5h805c28 0 37-2 46-6a35 35 0 0015-15l2-3c3-7 5-16 5-37V109c0-28-2-37-6-46a35 35 0 00-15-15l-3-2c-7-3-16-5-37-5l-805 0z"
fill="#fff" />
<path
d="M896 128H128a21 21 0 00-21 21v533l0 3a21 21 0 0021 21h107v64l0 3a21 21 0 0021 21h64v85a21 21 0 0021 21h341l3 0a21 21 0 0021-21v-85h64l3 0a21 21 0 0021-21v-64h107a21 21 0 0021-21V149a21 21 0 00-21-21z"
fill="#8c8c8c" />
<path
d="M875 171v490h-107l-3 0a21 21 0 00-21 21v64h-64l-3 0a21 21 0 00-21 21v85H363v-85l0-3a21 21 0 00-21-21h-64v-64l0-3a21 21 0 00-21-21H149V171h725z"
fill="#EAEBED" /></svg>
{% endif %}
{% endif %}
<!--span class="port-label mt-1">{{ iface.name }}</span-->
</div>
{% endfor %}
</div>
{% endfor %}
<div class="legend-container">
<div class="legend-header">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="6" fill="#00c040" filter="url(#glow)"/>
<defs>
<filter id="glow" x="-2" y="-2" width="20" height="20">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
</svg>
<span class="legend-title">{% trans "Port status" %}</span>
</div>
<div class="d-flex gap-4 flex-wrap align-items-center">
<div class="legend-item">
<div class="legend-dot up"></div>
<span class="legend-text">{% trans "UP" %}</span>
</div>
<div class="legend-item">
<div class="legend-dot down"></div>
<span class="legend-text">{% trans "DOWN" %}</span>
</div>
</div>
<div class="legend-footer">
<span class="legend-hint">{% trans "Hover over the port to view detailed information" %}</span>
</div>
</div>
{% else %}
<p class="text-muted fst-italic">{% trans "No interfaces found." %}</p>
{% endif %}
{% endwith %}
</div>
</div>
<style>
/* 现代光影效果 */
.port-panel {
display: grid;
grid-template-columns: repeat(24, 1fr);
gap: 6px;
background: linear-gradient(145deg, #ffffff, #f8fafc);
padding: 16px;
border-radius: 16px;
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow:
0 10px 30px -5px rgba(0, 0, 0, 0.1),
inset 0 -2px 0 rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.port-panel:hover {
box-shadow:
0 20px 40px -10px rgba(0, 192, 64, 0.15),
inset 0 -2px 0 rgba(0, 0, 0, 0.05);
}
.port-unit {
display: flex;
flex-direction: column;
align-items: center;
min-width: 0;
position: relative;
}
.port-icon {
width: 28px;
height: 28px;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
}
/* 发光效果 - Up状态 */
.port-icon.up-glow {
filter:
drop-shadow(0 4px 8px rgba(0, 192, 64, 0.3))
drop-shadow(0 0 0 rgba(0, 192, 64, 0.6));
animation: pulse 2s infinite ease-in-out;
}
@keyframes pulse {
0% { filter: drop-shadow(0 4px 8px rgba(0, 192, 64, 0.3)); }
50% { filter: drop-shadow(0 8px 16px rgba(0, 192, 64, 0.5)); }
100% { filter: drop-shadow(0 4px 8px rgba(0, 192, 64, 0.3)); }
}
.port-icon.mini {
width: 20px;
height: 20px;
}
.port-icon:hover {
cursor: pointer;
transform: scale(1.15) translateY(-2px);
filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.2));
}
/* 图例容器美化 */
.legend-container {
background: linear-gradient(135deg, #ffffff, #f8fafc);
border-radius: 24px;
padding: 20px 24px;
box-shadow:
0 20px 40px -15px rgba(0, 0, 0, 0.2),
inset 0 1px 2px rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
margin-top: 20px;
}
.legend-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid rgba(0, 192, 64, 0.1);
}
.legend-title {
font-size: 0.9rem;
font-weight: 600;
color: #1e293b;
letter-spacing: 0.3px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: rgba(255, 255, 255, 0.7);
border-radius: 40px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02);
transition: all 0.2s ease;
border: 1px solid rgba(0, 0, 0, 0.02);
backdrop-filter: blur(5px);
}
.legend-item:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0, 192, 64, 0.1);
background: rgba(255, 255, 255, 0.95);
}
.legend-dot {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.legend-dot.up {
background: linear-gradient(145deg, #00c040, #00a034);
box-shadow: 0 0 12px rgba(0, 192, 64, 0.4);
}
.legend-dot.down {
background: #ffffff;
border: 2px solid #d0d0d0;
box-shadow: 0 0 12px rgba(0, 0, 0, 0.1);
}
.legend-dot.disabled {
background: linear-gradient(145deg, #8c8c8c, #787878);
}
.legend-text {
font-size: 0.85rem;
font-weight: 600;
color: #1e293b;
}
.legend-badge {
font-size: 0.7rem;
padding: 2px 8px;
background: rgba(0, 0, 0, 0.04);
border-radius: 20px;
color: #64748b;
letter-spacing: 0.2px;
}
.legend-footer {
margin-top: 16px;
padding-top: 12px;
border-top: 1px dashed rgba(0, 0, 0, 0.1);
}
.legend-hint {
font-size: 0.75rem;
color: #94a3b8;
display: flex;
align-items: center;
gap: 6px;
}
.legend-hint::before {
content: "💡";
font-size: 0.9rem;
opacity: 0.7;
}
/* 响应式布局优化 */
@media (max-width: 1920px) {
.port-panel {
grid-template-columns: repeat(32, 1fr);
}
}
@media (max-width: 1600px) {
.port-panel {
grid-template-columns: repeat(24, 1fr);
}
}
@media (max-width: 1200px) {
.port-panel {
grid-template-columns: repeat(16, 1fr);
}
}
@media (max-width: 992px) {
.port-panel {
grid-template-columns: repeat(12, 1fr);
}
}
@media (max-width: 768px) {
.port-panel {
grid-template-columns: repeat(8, 1fr);
}
.legend-item {
padding: 6px 12px;
}
}
/* 卡片整体美化 */
.card {
border: none;
background: linear-gradient(145deg, #ffffff, #f8fafc);
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 30px 60px -20px rgba(0, 192, 64, 0.2) !important;
}
.card-header strong {
font-weight: 600;
letter-spacing: 0.3px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.badge.bg-light {
background: rgba(255, 255, 255, 0.9) !important;
backdrop-filter: blur(5px);
border: 1px solid rgba(0, 0, 0, 0.05);
color: #1e293b !important;
font-weight: 500;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
}
/* 添加微妙的网格背景 */
.card-body {
position: relative;
overflow: hidden;
}
.card-body::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
linear-gradient(rgba(0, 192, 64, 0.02) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 192, 64, 0.02) 1px, transparent 1px);
background-size: 50px 50px;
pointer-events: none;
z-index: 0;
}
.card-body > * {
position: relative;
z-index: 1;
}
</style>
SVG:
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1330 1024">
<rect width="1270" height="964" x="30" y="30" fill="#E0E0E0" stroke="#B0B0B0" stroke-width="35" rx="40"/>
<rect width="1230" height="924" x="50" y="50" fill="#FFF" stroke="#D0D0D0" stroke-width="2" rx="20"/>
<path fill="#ADB1B9" d="M247.54 207.372v85.184h36.508v67.613H244.83v402.95h358.332V358.814h-35.154v-63.549h31.097v-87.893h-49.14V172.85h-248.68v34.896zm481.38 2.161v85.19h36.508v67.607H726.21v402.956h358.332V360.982h-35.154v-63.556h31.097v-87.893h-49.14v-34.516h-248.68v34.89z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<path fill="#ADB1B9" d="M103.595.043 914.603 0c38.037 0 51.84 3.968 65.749 11.392 13.91 7.445 24.81 18.347 32.256 32.256 7.424 13.91 11.392 27.712 11.392 65.75v805.205c0 38.037-3.968 51.84-11.392 65.749a77.55 77.55 0 0 1-32.256 32.256c-13.91 7.424-27.712 11.392-65.75 11.392H109.398c-38.037 0-51.84-3.968-65.749-11.392a77.55 77.55 0 0 1-32.256-32.256C4.352 967.168.427 954.112.042 920.405L0 109.397c0-38.037 3.968-51.84 11.392-65.749a77.55 77.55 0 0 1 32.256-32.256C56.832 4.352 69.888.427 103.595.042"/>
<path fill="#FFF" d="m109.397 42.667-9.706.085c-20.694.384-28.224 2.155-35.904 6.272C57.3 52.48 52.48 57.301 49.024 63.787l-1.621 3.306c-3.2 7.36-4.544 16.214-4.715 37.227l-.021 810.283c0 28.245 1.664 36.842 6.357 45.61 3.456 6.486 8.277 11.307 14.763 14.763l3.306 1.621c7.36 3.2 16.214 4.544 37.227 4.715l810.283.021c28.245 0 36.842-1.664 45.61-6.357 6.486-3.456 11.307-8.277 14.763-14.763l1.621-3.306c3.2-7.36 4.544-16.214 4.715-37.227l.021-810.283c0-28.245-1.664-36.842-6.357-45.61a34.9 34.9 0 0 0-14.763-14.763l-3.306-1.621c-7.36-3.2-16.214-4.544-37.227-4.715z"/>
<path fill="#ADB1B9" d="M896 128H128a21.333 21.333 0 0 0-21.333 21.333v533.334l.149 2.496A21.333 21.333 0 0 0 128 704h106.667v64l.149 2.496A21.333 21.333 0 0 0 256 789.333h64v85.334A21.333 21.333 0 0 0 341.333 896h341.334l2.496-.15A21.333 21.333 0 0 0 704 874.668v-85.334h64l2.496-.149A21.333 21.333 0 0 0 789.333 768v-64H896a21.333 21.333 0 0 0 21.333-21.333V149.333A21.333 21.333 0 0 0 896 128"/>
<path fill="#EAEBED" d="M874.667 170.667v490.666H768l-2.496.15a21.333 21.333 0 0 0-18.837 21.184v64h-64l-2.496.149A21.333 21.333 0 0 0 661.333 768v85.333H362.667V768l-.15-2.496a21.333 21.333 0 0 0-21.184-18.837h-64v-64l-.149-2.496A21.333 21.333 0 0 0 256 661.333H149.333V170.667z"/>
<path fill="#ADB1B9" d="M426.667 149.333A21.333 21.333 0 0 1 448 170.667v128a21.333 21.333 0 0 1-42.667 0v-128a21.333 21.333 0 0 1 21.334-21.334m-85.334 0a21.333 21.333 0 0 1 21.334 21.334v128a21.333 21.333 0 0 1-42.667 0v-128a21.333 21.333 0 0 1 21.333-21.334m170.667 0a21.333 21.333 0 0 1 21.333 21.334v128a21.333 21.333 0 0 1-42.666 0v-128A21.333 21.333 0 0 1 512 149.333m85.333 0a21.333 21.333 0 0 1 21.334 21.334v128a21.333 21.333 0 0 1-42.667 0v-128a21.333 21.333 0 0 1 21.333-21.334m85.334 0A21.333 21.333 0 0 1 704 170.667v128a21.333 21.333 0 0 1-42.667 0v-128a21.333 21.333 0 0 1 21.334-21.334"/>
</svg>
设计碎片:
代码字段信息介绍
这个端口状态可视化模板中,从NetBox提取了以下关键字段信息:
1. 接口对象字段 (iface)
| 字段 | 说明 | 示例值 |
iface.name | 接口名称 | GigabitEthernet0/1, eth0, xe-1/0/0 |
iface.type | 接口类型代码 | 1000base-t, 10gbase-sr, sfp+ |
iface.get_type_display | 接口类型显示名称 | 1000BASE-T (铜缆), 10GBASE-SR (光口) |
iface.enabled | 接口是否启用 | True (启用), False (禁用) |
iface.cable | 是否连接线缆 | 有值表示已连接, None表示未连接 |
2. 分组对象字段 (group)
| 字段 | 说明 | 示例值 |
group.list | 当前类型的所有接口列表 | 接口对象集合 |
group.list.0.get_type_display | 接口类型名称 | 1000BASE-T (铜缆) |
group.list|length | 当前类型的接口数量 | 24, 48, 4 |
3. 设备对象字段 (object)
| 字段 | 说明 | 示例值 |
object.interfaces.all | 设备的所有接口 | 接口查询集 |
4. 状态判断逻辑
python
# 光口判断条件
'sfp' in iface.type|lower # SFP/SFP+光模块
'qsfp' in iface.type|lower # QSFP/QSFP28光模块
'gbase-sr' in iface.type|lower # 短距光纤
'gbase-lr' in iface.type|lower # 长距光纤
'gbase-er' in iface.type|lower # 超长距光纤
'10gbase-sr' in iface.type|lower # 10G短距
'10gbase-lr' in iface.type|lower # 10G长距
'25gbase-sr' in iface.type|lower # 25G短距
'25gbase-lr' in iface.type|lower # 25G长距
'40gbase-sr4' in iface.type|lower # 40G短距
'40gbase-lr4' in iface.type|lower # 40G长距
'100gbase-sr4' in iface.type|lower # 100G短距
'100gbase-lr4' in iface.type|lower # 100G长距
5. 模板标签/过滤器
| 标签/过滤器 | 作用 | 示例 |
{% regroup %} | 按字段分组 | {% regroup interfaces by type as type_groups %} |
dictsort | 字典排序 | interfaces|dictsort:"type" |
lower | 转换为小写 | iface.type|lower |
length | 获取长度 | group.list|length |
6. 状态颜色映射
| 状态 | 条件 | 颜色 | 说明 |
| Up | enabled=True 且 cable 有值 | 绿色 (#00c040) | 接口启用且已连接 |
| Down | enabled=True 但 cable=None | 灰色 (#E0E0E0) | 接口启用但未连接 |
| Disabled | enabled=False | 深灰色 (#8c8c8c) | 接口禁用 |
7. 图标类型映射
| 接口类型 | 图标 | 判断条件 |
| 光口 | 双矩形图标 | 满足上述光口判断条件 |
| 电口 | 方块积木图标 | 不满足光口判断条件 |
使用示例
django
{# 显示接口名称 #}
<span class="port-label">{{ iface.name }}</span>
{# 显示接口类型 #}
<strong>{{ group.list.0.get_type_display }}</strong>
{# 显示端口数量 #}
<span class="legend-badge">{{ group.list|length }} Ports</span>
{# 判断接口状态 #}
{% if iface.enabled %}
{% if iface.cable %}
{# Up状态 - 已连接 #}
{% else %}
{# Down状态 - 未连接 #}
{% endif %}
{% else %}
{# Disabled状态 - 已禁用 #}
{% endif %}
1200

被折叠的 条评论
为什么被折叠?



