mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-30 04:16:24 -06:00
Initial commit for Device History
This commit is contained in:
parent
1c38f705a7
commit
6a9ec035d6
@ -484,6 +484,12 @@ class InventoryItemFilter(DeviceComponentFilterSet):
|
||||
fields = ['name']
|
||||
|
||||
|
||||
class HistoryLogFilter(DeviceComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = ['name']
|
||||
|
||||
class ConsoleConnectionFilter(django_filters.FilterSet):
|
||||
site = django_filters.CharFilter(
|
||||
method='filter_site',
|
||||
|
@ -23,7 +23,7 @@ from .models import (
|
||||
Interface, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, InterfaceConnection, InterfaceTemplate,
|
||||
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES,
|
||||
RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole, Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD,
|
||||
SUBDEVICE_ROLE_PARENT, VIRTUAL_IFACE_TYPES
|
||||
SUBDEVICE_ROLE_PARENT, VIRTUAL_IFACE_TYPES, HistoryRole, HistoryLog
|
||||
)
|
||||
|
||||
|
||||
@ -1710,3 +1710,26 @@ class InventoryItemForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = ['name', 'manufacturer', 'part_id', 'serial']
|
||||
|
||||
|
||||
#
|
||||
# HistoryRole
|
||||
#
|
||||
|
||||
class HistoryRoleForm(BootstrapMixin, forms.ModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
model = HistoryRole
|
||||
fields = ['name', 'slug', 'color']
|
||||
|
||||
|
||||
#
|
||||
# HistoryLog
|
||||
#
|
||||
|
||||
class HistoryLogForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = HistoryLog
|
||||
fields = ['device', 'role', 'message']
|
||||
|
@ -1421,3 +1421,42 @@ class InventoryItem(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
#
|
||||
# History
|
||||
#
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class HistoryRole(models.Model):
|
||||
"""
|
||||
Grouping the history activity type
|
||||
"""
|
||||
name = models.CharField(max_length=50, unique=True)
|
||||
slug = models.SlugField(unique=True)
|
||||
color = ColorField()
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class HistoryLog(models.Model):
|
||||
"""
|
||||
A history is a free text form which gives capability to store any comment or change in time based view.
|
||||
"""
|
||||
time = models.DateTimeField(auto_now_add=True, editable=False)
|
||||
user = models.ForeignKey(User, related_name='history_log', blank=True, null=True, on_delete=models.SET_NULL)
|
||||
device = models.ForeignKey('Device', related_name='history_log', on_delete=models.CASCADE)
|
||||
|
||||
role = models.ForeignKey('HistoryRole', related_name='history_log', blank=True, null=True, on_delete=models.SET_NULL)
|
||||
message = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-time']
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
@ -6,7 +6,7 @@ from utilities.tables import BaseTable, SearchTable, ToggleColumn
|
||||
from .models import (
|
||||
ConsolePort, ConsolePortTemplate, ConsoleServerPortTemplate, Device, DeviceBayTemplate, DeviceRole, DeviceType,
|
||||
Interface, InterfaceTemplate, Manufacturer, Platform, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||
RackGroup, Region, Site,
|
||||
RackGroup, Region, Site, HistoryRole,
|
||||
)
|
||||
|
||||
|
||||
@ -70,6 +70,12 @@ DEVICEROLE_ACTIONS = """
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
HISTORYLOGROLE_ACTIONS = """
|
||||
{% if perms.dcim.change_historylogrole %}
|
||||
<a href="{% url 'dcim:historylogrole_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
MANUFACTURER_ACTIONS = """
|
||||
{% if perms.dcim.change_manufacturer %}
|
||||
<a href="{% url 'dcim:manufacturer_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
@ -498,3 +504,19 @@ class InterfaceConnectionTable(BaseTable):
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Interface
|
||||
fields = ('device_a', 'interface_a', 'device_b', 'interface_b')
|
||||
|
||||
#
|
||||
# HistoryLog roles
|
||||
#
|
||||
|
||||
class HistoryLogRoleTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.Column(verbose_name='Name')
|
||||
color = tables.TemplateColumn(COLOR_LABEL, verbose_name='Color')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
actions = tables.TemplateColumn(template_code=HISTORYLOGROLE_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||
verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = HistoryRole
|
||||
fields = ('pk', 'name', 'color', 'slug', 'actions')
|
||||
|
@ -113,6 +113,7 @@ urlpatterns = [
|
||||
url(r'^devices/(?P<pk>\d+)/edit/$', views.DeviceEditView.as_view(), name='device_edit'),
|
||||
url(r'^devices/(?P<pk>\d+)/delete/$', views.DeviceDeleteView.as_view(), name='device_delete'),
|
||||
url(r'^devices/(?P<pk>\d+)/inventory/$', views.device_inventory, name='device_inventory'),
|
||||
url(r'^devices/(?P<pk>\d+)/history-log/$', views.device_historylog, name='device_historylog'),
|
||||
url(r'^devices/(?P<pk>\d+)/lldp-neighbors/$', views.device_lldp_neighbors, name='device_lldp_neighbors'),
|
||||
url(r'^devices/(?P<pk>\d+)/ip-addresses/assign/$', views.ipaddress_assign, name='ipaddress_assign'),
|
||||
url(r'^devices/(?P<pk>\d+)/add-secret/$', secret_add, name='device_addsecret'),
|
||||
@ -178,6 +179,18 @@ urlpatterns = [
|
||||
url(r'^inventory-items/(?P<pk>\d+)/edit/$', views.InventoryItemEditView.as_view(), name='inventoryitem_edit'),
|
||||
url(r'^inventory-items/(?P<pk>\d+)/delete/$', views.InventoryItemDeleteView.as_view(), name='inventoryitem_delete'),
|
||||
|
||||
# HistoryLog
|
||||
url(r'^devices/(?P<device>\d+)/history-log/add/$', views.HistoryLogEditView.as_view(),
|
||||
name='historylog_add'),
|
||||
url(r'^history-log/(?P<pk>\d+)/edit/$', views.HistoryLogEditView.as_view(), name='historylog_edit'),
|
||||
url(r'^history-log/(?P<pk>\d+)/delete/$', views.HistoryLogDeleteView.as_view(), name='historylog_delete'),
|
||||
|
||||
# HistoryLogRoles
|
||||
url(r'^history-log-roles/$', views.HistoryLogRoleListView.as_view(), name='historylogrole_list'),
|
||||
url(r'^history-log-roles/add/$', views.HistoryLogRoleEditView.as_view(), name='historylogrole_add'),
|
||||
url(r'^history-log-roles/delete/$', views.HistoryLogRoleBulkDeleteView.as_view(), name='historylogrole_bulk_delete'),
|
||||
url(r'^history-log-roles/(?P<pk>\d+)/edit/$', views.HistoryLogRoleEditView.as_view(), name='historylogrole_edit'),
|
||||
|
||||
# Console/power/interface connections
|
||||
url(r'^console-connections/$', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'),
|
||||
url(r'^console-connections/import/$', views.ConsoleConnectionsBulkImportView.as_view(), name='console_connections_import'),
|
||||
|
@ -26,7 +26,7 @@ from .models import (
|
||||
CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
||||
DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
|
||||
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||
RackReservation, RackRole, Region, Site,
|
||||
RackReservation, RackRole, Region, Site, HistoryLog, HistoryRole,
|
||||
)
|
||||
|
||||
|
||||
@ -808,6 +808,17 @@ def device_inventory(request, pk):
|
||||
})
|
||||
|
||||
|
||||
def device_historylog(request, pk):
|
||||
|
||||
device = get_object_or_404(Device, pk=pk)
|
||||
historylog_items = HistoryLog.objects.filter(device=device)
|
||||
|
||||
return render(request, 'dcim/device_historylog.html', {
|
||||
'device': device,
|
||||
'historylog_items': historylog_items,
|
||||
})
|
||||
|
||||
|
||||
def device_lldp_neighbors(request, pk):
|
||||
|
||||
device = get_object_or_404(Device, pk=pk)
|
||||
@ -1611,3 +1622,49 @@ class InventoryItemEditView(PermissionRequiredMixin, ComponentEditView):
|
||||
class InventoryItemDeleteView(PermissionRequiredMixin, ComponentDeleteView):
|
||||
permission_required = 'dcim.delete_inventoryitem'
|
||||
model = InventoryItem
|
||||
|
||||
|
||||
#
|
||||
# HistoryLog
|
||||
#
|
||||
|
||||
class HistoryLogEditView(PermissionRequiredMixin, ComponentEditView):
|
||||
permission_required = 'dcim.change_historylog'
|
||||
model = HistoryLog
|
||||
form_class = forms.HistoryLogForm
|
||||
|
||||
def alter_obj(self, obj, request, url_args, url_kwargs):
|
||||
if 'device' in url_kwargs:
|
||||
obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
|
||||
obj.user = request.user
|
||||
return obj
|
||||
|
||||
|
||||
class HistoryLogDeleteView(PermissionRequiredMixin, ComponentDeleteView):
|
||||
permission_required = 'dcim.delete_historylog'
|
||||
model = HistoryLog
|
||||
|
||||
|
||||
#
|
||||
# HistoryLogRole
|
||||
#
|
||||
|
||||
class HistoryLogRoleListView(ObjectListView):
|
||||
queryset = HistoryRole.objects.annotate(historylog_count=Count('history_log'))
|
||||
table = tables.HistoryLogRoleTable
|
||||
template_name = 'dcim/historylogrole_list.html'
|
||||
|
||||
|
||||
class HistoryLogRoleEditView(PermissionRequiredMixin, ObjectEditView):
|
||||
permission_required = 'dcim.change_historylogrole'
|
||||
model = HistoryRole
|
||||
form_class = forms.HistoryRoleForm
|
||||
|
||||
def get_return_url(self, obj):
|
||||
return reverse('dcim:historylogrole_list')
|
||||
|
||||
|
||||
class HistoryLogRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_historylogrole'
|
||||
cls = HistoryRole
|
||||
default_return_url = 'dcim:historylogrole_list'
|
||||
|
46
netbox/templates/dcim/device_historylog.html
Normal file
46
netbox/templates/dcim/device_historylog.html
Normal file
@ -0,0 +1,46 @@
|
||||
{% extends '_base.html' %}
|
||||
|
||||
{% block title %}{{ device }} - Inventory{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'dcim/inc/device_header.html' with active_tab='historylog' %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>History Log</strong>
|
||||
</div>
|
||||
<table class="table table-hover table-condensed panel-body" id="hardware">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>User</th>
|
||||
<th>Role</th>
|
||||
<th>Message</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in historylog_items %}
|
||||
{% with template_name='dcim/inc/historylog.html' indent=0 %}
|
||||
{% include template_name %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if perms.dcim.add_historylog %}
|
||||
<a href="{% url 'dcim:historylog_add' device=device.pk %}" class="btn btn-success">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add History Log
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_historylogrole %}
|
||||
<a href="{% url 'dcim:historylogrole_list' %}" class="btn btn-success">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
History Log Roles
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
21
netbox/templates/dcim/historylogrole_list.html
Normal file
21
netbox/templates/dcim/historylogrole_list.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load helpers %}
|
||||
|
||||
{% block title %}History Role{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_historylogrole %}
|
||||
<a href="{% url 'dcim:historylogrole_add' %}" class="btn btn-primary">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a historylog role
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h1>History Roles</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:historylogrole_bulk_delete' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -48,4 +48,5 @@
|
||||
{% if device.status %}
|
||||
<li role="presentation"{% if active_tab == 'lldp-neighbors' %} class="active"{% endif %}><a href="{% url 'dcim:device_lldp_neighbors' pk=device.pk %}">LLDP Neighbors</a></li>
|
||||
{% endif %}
|
||||
<li role="presentation"{% if active_tab == 'historylog' %} class="active"{% endif %}><a href="{% url 'dcim:device_historylog' pk=device.pk %}">History Log</a></li>
|
||||
</ul>
|
||||
|
19
netbox/templates/dcim/inc/historylog.html
Normal file
19
netbox/templates/dcim/inc/historylog.html
Normal file
@ -0,0 +1,19 @@
|
||||
<tr>
|
||||
<td style="padding-left: {{ indent|add:5 }}px">{{ item.time }}</td>
|
||||
<td>{{ item.user }}</td>
|
||||
<td>{{ item.role }}</td>
|
||||
<td>{{ item.message }}</td>
|
||||
<td class="text-right">
|
||||
{% if perms.dcim.change_historylog_item %}
|
||||
<a href="{% url 'dcim:historylog_edit' pk=item.pk %}?return_url={% url 'dcim:device_historylog' pk=device.pk %}" class="btn btn-xs btn-warning"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_historylog_item %}
|
||||
<a href="{% url 'dcim:historylog_delete' pk=item.pk %}?return_url={% url 'dcim:device_historylog' pk=device.pk %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% for item in item.child_items.all %}
|
||||
{% with template_name='dcim/inc/historylog.html' indent=indent|add:20 %}
|
||||
{% include template_name %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
Loading…
Reference in New Issue
Block a user