netbox添加端口可视化

添加端口可视化

展示效果:

在这里插入图片描述

基于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. 状态颜色映射

状态条件颜色说明
Upenabled=Truecable 有值绿色 (#00c040)接口启用且已连接
Downenabled=Truecable=None灰色 (#E0E0E0)接口启用但未连接
Disabledenabled=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 %}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

avenjan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值