
* Closes: #18588: Relabel Service model to Application Service Updates the `verbose_name` of the `Service` and `ServiceTemplate` models to "Application Service" and "Application Service Template" respectively. This serves as the foundational change for relabeling the model throughout the user interface to reduce ambiguity. To preserve backward compatibility for the REST and GraphQL APIs, the test suites have been updated to assert the stability of the original field and parameter names. This includes: * Using `filter_name_map` in the filterset test case to ensure API query parameters remain `service` and `service_id`. * Employing the GraphQL test suite's aliasing mechanism to ensure the public schema remains unchanged despite the underlying `verbose_name` modification. Subsequent commits will address UI-specific labels in navigation, tables, forms, and templates. * Rename to Application Services/Application Service Templates in nav menu * Rename ~service to ~'Application Service' in templates This was done for both the Service model and Service Template model appearances in templates where the word was hardcoded. * Change ~service to ~'application service' hardcoded strings in Python files * Update ~service to ~'application service' in docs
376 lines
20 KiB
HTML
376 lines
20 KiB
HTML
{% extends 'dcim/device/base.html' %}
|
|
{% load render_table from django_tables2 %}
|
|
{% load buttons %}
|
|
{% load static %}
|
|
{% load helpers %}
|
|
{% load plugins %}
|
|
{% load i18n %}
|
|
{% load l10n %}
|
|
{% load mptt %}
|
|
|
|
{% block content %}
|
|
<div class="row">
|
|
<div class="col col-12 col-xl-6">
|
|
<div class="card">
|
|
<h2 class="card-header">{% trans "Device" %}</h2>
|
|
<table class="table table-hover attr-table">
|
|
<tr>
|
|
<th scope="row">{% trans "Region" %}</th>
|
|
<td>{% nested_tree object.site.region %}</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Site" %}</th>
|
|
<td>{{ object.site|linkify }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Location" %}</th>
|
|
<td>{% nested_tree object.location %}</td>
|
|
</tr>
|
|
{% if object.virtual_chassis %}
|
|
<tr>
|
|
<th scope="row">{% trans "Virtual Chassis" %}</th>
|
|
<td>{{ object.virtual_chassis|linkify }}</td>
|
|
</tr>
|
|
{% endif %}
|
|
<tr>
|
|
<th scope="row">{% trans "Rack" %}</th>
|
|
<td class="d-flex justify-content-between align-items-start">
|
|
{% if object.rack %}
|
|
{{ object.rack|linkify }}
|
|
<a href="{{ object.rack.get_absolute_url }}?device={% firstof object.parent_bay.device.pk object.pk %}" class="btn btn-primary btn-sm d-print-none" title="{% trans "Highlight device in rack" %}">
|
|
<i class="mdi mdi-view-day-outline"></i>
|
|
</a>
|
|
{% else %}
|
|
{{ ''|placeholder }}
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Position" %}</th>
|
|
<td>
|
|
{% if object.parent_bay %}
|
|
{% with object.parent_bay.device as parent %}
|
|
{{ parent|linkify }} / {{ object.parent_bay }}
|
|
{% if parent.position %}
|
|
(U{{ parent.position|floatformat }} / {{ parent.get_face_display }})
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% elif object.rack and object.position %}
|
|
<span>U{{ object.position|floatformat }} / {{ object.get_face_display }}</span>
|
|
{% elif object.rack and object.device_type.u_height %}
|
|
<span class="badge text-bg-warning">{% trans "Not racked" %}</span>
|
|
{% else %}
|
|
{{ ''|placeholder }}
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "GPS Coordinates" %}</th>
|
|
<td class="position-relative">
|
|
{% if object.latitude and object.longitude %}
|
|
{% if config.MAPS_URL %}
|
|
<div class="position-absolute top-50 end-0 me-2 translate-middle-y d-print-none">
|
|
<a href="{{ config.MAPS_URL }}{{ object.latitude|unlocalize }},{{ object.longitude|unlocalize }}" target="_blank" class="btn btn-primary btn-sm">
|
|
<i class="mdi mdi-map-marker"></i> {% trans "Map" %}
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
<span>{{ object.latitude }}, {{ object.longitude }}</span>
|
|
{% else %}
|
|
{{ ''|placeholder }}
|
|
{% endif %}
|
|
</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 "Device Type" %}</th>
|
|
<td>
|
|
{{ object.device_type|linkify:"full_name" }} ({{ object.device_type.u_height|floatformat }}U)
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Description" %}</th>
|
|
<td>{{ object.description|placeholder }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Airflow" %}</th>
|
|
<td>
|
|
{{ object.get_airflow_display|placeholder }}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Serial Number" %}</th>
|
|
<td class="font-monospace">{{ object.serial|placeholder }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Asset Tag" %}</th>
|
|
<td class="font-monospace">{{ object.asset_tag|placeholder }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Config Template" %}</th>
|
|
<td>{{ object.config_template|linkify|placeholder }}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
{% if vc_members %}
|
|
<div class="card">
|
|
<h2 class="card-header">
|
|
{% trans "Virtual Chassis" %}
|
|
<div class="card-actions">
|
|
<a href="{{ object.virtual_chassis.get_absolute_url }}" class="btn btn-ghost-primary btn-sm">
|
|
<span class="mdi mdi-arrow-right-bold" aria-hidden="true"></span> {% trans "View Virtual Chassis" %}
|
|
</a>
|
|
</div>
|
|
</h2>
|
|
<table class="table table-hover attr-table">
|
|
<thead>
|
|
<tr class="border-bottom">
|
|
<th>{% trans "Device" %}</th>
|
|
<th>{% trans "Position" %}</th>
|
|
<th>{% trans "Master" %}</th>
|
|
<th>{% trans "Priority" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for vc_member in vc_members %}
|
|
<tr{% if vc_member == object %} class="table-primary"{% endif %}>
|
|
<td>{{ vc_member|linkify }}</td>
|
|
<td>{% badge vc_member.vc_position show_empty=True %}</td>
|
|
<td>
|
|
{% if object.virtual_chassis.master == vc_member %}
|
|
{% checkmark True %}
|
|
{% else %}
|
|
{{ ''|placeholder }}
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ vc_member.vc_priority|placeholder }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
{% include 'inc/panels/custom_fields.html' %}
|
|
{% include 'inc/panels/tags.html' %}
|
|
{% include 'inc/panels/comments.html' %}
|
|
<div class="card">
|
|
<h2 class="card-header">
|
|
{% trans "Virtual Device Contexts" %}
|
|
{% if perms.dcim.add_virtualdevicecontext %}
|
|
<div class="card-actions">
|
|
<a href="{% url 'dcim:virtualdevicecontext_add' %}?device={{ object.pk }}" class="btn btn-ghost-primary btn-sm">
|
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Create VDC" %}
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</h2>
|
|
{% htmx_table 'dcim:virtualdevicecontext_list' device_id=object.pk %}
|
|
</div>
|
|
{% plugin_left_page object %}
|
|
</div>
|
|
<div class="col col-12 col-xl-6">
|
|
<div class="card">
|
|
<h2 class="card-header">{% trans "Management" %}</h2>
|
|
<table class="table table-hover attr-table">
|
|
<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 "Role" %}</th>
|
|
<td>{{ object.role|linkify }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Platform" %}</th>
|
|
<td>{{ object.platform|linkify|placeholder }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Primary IPv4" %}</th>
|
|
<td>
|
|
{% if object.primary_ip4 %}
|
|
<a href="{{ object.primary_ip4.get_absolute_url }}" id="primary_ip4">{{ object.primary_ip4.address.ip }}</a>
|
|
{% if object.primary_ip4.nat_inside %}
|
|
({% trans "NAT for" %} <a href="{{ object.primary_ip4.nat_inside.get_absolute_url }}">{{ object.primary_ip4.nat_inside.address.ip }}</a>)
|
|
{% elif object.primary_ip4.nat_outside.exists %}
|
|
({% trans "NAT" %}: {% for nat in object.primary_ip4.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
|
|
{% endif %}
|
|
{% copy_content "primary_ip4" %}
|
|
{% else %}
|
|
{{ ''|placeholder }}
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Primary IPv6" %}</th>
|
|
<td>
|
|
{% if object.primary_ip6 %}
|
|
<a href="{{ object.primary_ip6.get_absolute_url }}" id="primary_ip6">{{ object.primary_ip6.address.ip }}</a>
|
|
{% if object.primary_ip6.nat_inside %}
|
|
({% trans "NAT for" %} <a href="{{ object.primary_ip6.nat_inside.get_absolute_url }}">{{ object.primary_ip6.nat_inside.address.ip }}</a>)
|
|
{% elif object.primary_ip6.nat_outside.exists %}
|
|
({% trans "NAT" %}: {% for nat in object.primary_ip6.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
|
|
{% endif %}
|
|
{% copy_content "primary_ip6" %}
|
|
{% else %}
|
|
{{ ''|placeholder }}
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Out-of-band IP</th>
|
|
<td>
|
|
{% if object.oob_ip %}
|
|
<a href="{{ object.oob_ip.get_absolute_url }}" id="oob_ip">{{ object.oob_ip.address.ip }}</a>
|
|
{% if object.oob_ip.nat_inside %}
|
|
({% trans "NAT for" %} <a href="{{ object.oob_ip.nat_inside.get_absolute_url }}">{{ object.oob_ip.nat_inside.address.ip }}</a>)
|
|
{% elif object.oob_ip.nat_outside.exists %}
|
|
({% trans "NAT" %}: {% for nat in object.oob_ip.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
|
|
{% endif %}
|
|
{% copy_content "oob_ip" %}
|
|
{% else %}
|
|
{{ ''|placeholder }}
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% if object.cluster %}
|
|
<tr>
|
|
<th>{% trans "Cluster" %}</th>
|
|
<td>
|
|
{% if object.cluster.group %}
|
|
{{ object.cluster.group|linkify }} /
|
|
{% endif %}
|
|
{{ object.cluster|linkify }}
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
</table>
|
|
</div>
|
|
{% if object.powerports.exists and object.poweroutlets.exists %}
|
|
<div class="card">
|
|
<h2 class="card-header">{% trans "Power Utilization" %}</h2>
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>{% trans "Input" %}</th>
|
|
<th>{% trans "Outlets" %}</th>
|
|
<th>{% trans "Allocated" %}</th>
|
|
<th>{% trans "Available" %}</th>
|
|
<th>{% trans "Utilization" %}</th>
|
|
</tr>
|
|
</thead>
|
|
{% for powerport in object.powerports.all %}
|
|
{% with utilization=powerport.get_power_draw powerfeed=powerport.connected_endpoints.0 %}
|
|
<tr>
|
|
<td>{{ powerport }}</td>
|
|
<td>{{ utilization.outlet_count }}</td>
|
|
<td>{{ utilization.allocated }}{% trans "VA" %}</td>
|
|
{% if powerfeed.available_power %}
|
|
<td>{{ powerfeed.available_power }}{% trans "VA" %}</td>
|
|
<td>{% utilization_graph utilization.allocated|percentage:powerfeed.available_power %}</td>
|
|
{% else %}
|
|
<td class="text-muted">—</td>
|
|
<td class="text-muted">—</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% for leg in utilization.legs %}
|
|
<tr>
|
|
<td style="padding-left: 20px">
|
|
{% trans "Leg" context "Leg of a power feed" %} {{ leg.name }}
|
|
</td>
|
|
<td>{{ leg.outlet_count }}</td>
|
|
<td>{{ leg.allocated }}</td>
|
|
{% if powerfeed.available_power %}
|
|
{% with phase_available=powerfeed.available_power|divide:3 %}
|
|
<td>{{ phase_available }}{% trans "VA" %}</td>
|
|
<td>{% utilization_graph leg.allocated|percentage:phase_available %}</td>
|
|
{% endwith %}
|
|
{% else %}
|
|
<td class="text-muted">—</td>
|
|
<td class="text-muted">—</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
{% endwith %}
|
|
{% endfor %}
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
<div class="card">
|
|
<h2 class="card-header">
|
|
{% trans "Application Services" %}
|
|
{% if perms.ipam.add_service %}
|
|
<div class="card-actions">
|
|
<a href="{% url 'ipam:service_add' %}?parent_object_type={{ object|content_type_id }}&parent={{ object.pk }}" class="btn btn-ghost-primary btn-sm">
|
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add an application service" %}
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</h2>
|
|
{% htmx_table 'ipam:service_list' device_id=object.pk %}
|
|
</div>
|
|
{% include 'inc/panels/image_attachments.html' %}
|
|
<div class="card">
|
|
<h2 class="card-header">{% trans "Dimensions" %}</h2>
|
|
<table class="table table-hover attr-table">
|
|
<tr>
|
|
<th scope="row">{% trans "Height" %}</th>
|
|
<td>
|
|
{{ object.device_type.u_height }}U
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">{% trans "Weight" %}</th>
|
|
<td>
|
|
{% if object.total_weight %}
|
|
{{ object.total_weight|floatformat }} {% trans "Kilograms" %}
|
|
({{ object.total_weight|kg_to_pounds|floatformat }} {% trans "Pounds" %})
|
|
{% else %}
|
|
{{ ''|placeholder }}
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
{% if object.rack and object.position %}
|
|
<div class="row" style="margin-bottom: 20px">
|
|
<div class="text-center">
|
|
<strong><a href="{% url 'dcim:rack' pk=object.rack.pk %}">{{ object.rack.name }}</a></strong>
|
|
{% if object.rack.role %}
|
|
<br /><span class="badge my-3" style="color: {{ object.rack.role.color|fgcolor }}; background-color: #{{ object.rack.role.color }}">{{ object.rack.role }}</span>
|
|
{% endif %}
|
|
{% if object.rack.facility_id %}
|
|
<br /><small class="text-muted">{{ object.rack.facility_id }}</small>
|
|
{% endif %}
|
|
</div>
|
|
<div class="col col-md-6 col-sm-6 col-xs-12 text-center">
|
|
<div style="margin-left: 30px">
|
|
<h2 class="h4">{% trans "Front" %}</h2>
|
|
{% include 'dcim/inc/rack_elevation.html' with object=object.rack face='front' extra_params=svg_extra %}
|
|
</div>
|
|
</div>
|
|
<div class="col col-md-6 col-sm-6 col-xs-12 text-center">
|
|
<div style="margin-left: 30px">
|
|
<h2 class="h4">{% trans "Rear" %}</h2>
|
|
{% include 'dcim/inc/rack_elevation.html' with object=object.rack face='rear' extra_params=svg_extra %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% plugin_right_page object %}
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col col-md-12">
|
|
{% plugin_full_width_page object %}
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|