mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-28 03:16:25 -06:00
Added a status field for virtual machines
This commit is contained in:
parent
d6637410a2
commit
0d8947bf36
@ -51,6 +51,12 @@
|
|||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ vm.name }}</td>
|
<td>{{ vm.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Status</td>
|
||||||
|
<td>
|
||||||
|
<span class="label label-{{ vm.get_status_class }}">{{ vm.get_status_display }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Cluster</td>
|
<td>Cluster</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
<div class="panel-heading"><strong>Virtual Machine</strong></div>
|
<div class="panel-heading"><strong>Virtual Machine</strong></div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% render_field form.name %}
|
{% render_field form.name %}
|
||||||
|
{% render_field form.status %}
|
||||||
{% render_field form.cluster_group %}
|
{% render_field form.cluster_group %}
|
||||||
{% render_field form.cluster %}
|
{% render_field form.cluster %}
|
||||||
{% render_field form.platform %}
|
{% render_field form.platform %}
|
||||||
|
@ -8,6 +8,7 @@ from dcim.models import Interface
|
|||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
from tenancy.api.serializers import NestedTenantSerializer
|
from tenancy.api.serializers import NestedTenantSerializer
|
||||||
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
|
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
|
||||||
|
from virtualization.constants import STATUS_CHOICES
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
|
|
||||||
|
|
||||||
@ -82,6 +83,7 @@ class WritableClusterSerializer(CustomFieldModelSerializer):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VirtualMachineSerializer(CustomFieldModelSerializer):
|
class VirtualMachineSerializer(CustomFieldModelSerializer):
|
||||||
|
status = ChoiceFieldSerializer(choices=STATUS_CHOICES)
|
||||||
cluster = NestedClusterSerializer()
|
cluster = NestedClusterSerializer()
|
||||||
tenant = NestedTenantSerializer()
|
tenant = NestedTenantSerializer()
|
||||||
platform = NestedPlatformSerializer()
|
platform = NestedPlatformSerializer()
|
||||||
@ -89,7 +91,8 @@ class VirtualMachineSerializer(CustomFieldModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'comments', 'custom_fields',
|
'id', 'name', 'status', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'comments',
|
||||||
|
'custom_fields',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -106,7 +109,8 @@ class WritableVirtualMachineSerializer(CustomFieldModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'comments', 'custom_fields',
|
'id', 'name', 'status', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'comments',
|
||||||
|
'custom_fields',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
19
netbox/virtualization/constants.py
Normal file
19
netbox/virtualization/constants.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
|
||||||
|
# VirtualMachine statuses (replicated from Device statuses)
|
||||||
|
STATUS_OFFLINE = 0
|
||||||
|
STATUS_ACTIVE = 1
|
||||||
|
STATUS_STAGED = 3
|
||||||
|
STATUS_CHOICES = [
|
||||||
|
[STATUS_ACTIVE, 'Active'],
|
||||||
|
[STATUS_OFFLINE, 'Offline'],
|
||||||
|
[STATUS_STAGED, 'Staged'],
|
||||||
|
]
|
||||||
|
|
||||||
|
# Bootstrap CSS classes for VirtualMachine statuses
|
||||||
|
VM_STATUS_CLASSES = {
|
||||||
|
0: 'warning',
|
||||||
|
1: 'success',
|
||||||
|
3: 'primary',
|
||||||
|
}
|
@ -7,6 +7,7 @@ from dcim.models import Platform
|
|||||||
from extras.filters import CustomFieldFilterSet
|
from extras.filters import CustomFieldFilterSet
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
|
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
|
||||||
|
from .constants import STATUS_CHOICES
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
|
|
||||||
|
|
||||||
@ -55,6 +56,9 @@ class VirtualMachineFilter(CustomFieldFilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
status = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=STATUS_CHOICES
|
||||||
|
)
|
||||||
cluster_group_id = NullableModelMultipleChoiceFilter(
|
cluster_group_id = NullableModelMultipleChoiceFilter(
|
||||||
name='cluster__group',
|
name='cluster__group',
|
||||||
queryset=ClusterGroup.objects.all(),
|
queryset=ClusterGroup.objects.all(),
|
||||||
|
@ -12,10 +12,11 @@ from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFi
|
|||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ChainedFieldsMixin,
|
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
||||||
ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm, ConfirmationForm,
|
ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,
|
||||||
ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea,
|
ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea,
|
||||||
)
|
)
|
||||||
|
from .constants import STATUS_CHOICES
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
|
|
||||||
|
|
||||||
@ -185,7 +186,9 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
fields = ['name', 'cluster_group', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
|
fields = [
|
||||||
|
'name', 'status', 'cluster_group', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -200,6 +203,11 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
|
|
||||||
|
|
||||||
class VirtualMachineCSVForm(forms.ModelForm):
|
class VirtualMachineCSVForm(forms.ModelForm):
|
||||||
|
status = CSVChoiceField(
|
||||||
|
choices=STATUS_CHOICES,
|
||||||
|
required=False,
|
||||||
|
help_text='Operational status of device'
|
||||||
|
)
|
||||||
cluster = forms.ModelChoiceField(
|
cluster = forms.ModelChoiceField(
|
||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
@ -229,11 +237,12 @@ class VirtualMachineCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
fields = ['name', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
|
fields = ['name', 'status', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
|
||||||
|
|
||||||
|
|
||||||
class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
|
status = forms.ChoiceField(choices=add_blank_choice(STATUS_CHOICES), required=False, initial='')
|
||||||
cluster = forms.ModelChoiceField(queryset=Cluster.objects.all(), required=False)
|
cluster = forms.ModelChoiceField(queryset=Cluster.objects.all(), required=False)
|
||||||
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False)
|
platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False)
|
||||||
@ -246,6 +255,13 @@ class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
nullable_fields = ['tenant', 'platform', 'vcpus', 'memory', 'disk']
|
nullable_fields = ['tenant', 'platform', 'vcpus', 'memory', 'disk']
|
||||||
|
|
||||||
|
|
||||||
|
def vm_status_choices():
|
||||||
|
status_counts = {}
|
||||||
|
for status in VirtualMachine.objects.values('status').annotate(count=Count('status')).order_by('status'):
|
||||||
|
status_counts[status['status']] = status['count']
|
||||||
|
return [(s[0], '{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in STATUS_CHOICES]
|
||||||
|
|
||||||
|
|
||||||
class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
q = forms.CharField(required=False, label='Search')
|
q = forms.CharField(required=False, label='Search')
|
||||||
@ -258,6 +274,7 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
|
queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
|
||||||
label='Cluster'
|
label='Cluster'
|
||||||
)
|
)
|
||||||
|
status = forms.MultipleChoiceField(choices=vm_status_choices, required=False)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.4 on 2017-09-14 17:49
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('virtualization', '0001_virtualization'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='virtualmachine',
|
||||||
|
name='status',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[[1, 'Active'], [0, 'Offline'], [3, 'Staged']], default=1, verbose_name='Status'),
|
||||||
|
),
|
||||||
|
]
|
@ -7,6 +7,7 @@ from django.utils.encoding import python_2_unicode_compatible
|
|||||||
|
|
||||||
from extras.models import CustomFieldModel, CustomFieldValue
|
from extras.models import CustomFieldModel, CustomFieldValue
|
||||||
from utilities.models import CreatedUpdatedModel
|
from utilities.models import CreatedUpdatedModel
|
||||||
|
from .constants import STATUS_ACTIVE, STATUS_CHOICES, VM_STATUS_CLASSES
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -139,6 +140,11 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
max_length=64,
|
max_length=64,
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
|
status = models.PositiveSmallIntegerField(
|
||||||
|
choices=STATUS_CHOICES,
|
||||||
|
default=STATUS_ACTIVE,
|
||||||
|
verbose_name='Status'
|
||||||
|
)
|
||||||
primary_ip4 = models.OneToOneField(
|
primary_ip4 = models.OneToOneField(
|
||||||
to='ipam.IPAddress',
|
to='ipam.IPAddress',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
@ -187,3 +193,6 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('virtualization:virtualmachine', args=[self.pk])
|
return reverse('virtualization:virtualmachine', args=[self.pk])
|
||||||
|
|
||||||
|
def get_status_class(self):
|
||||||
|
return VM_STATUS_CLASSES[self.status]
|
||||||
|
@ -20,6 +20,10 @@ CLUSTERGROUP_ACTIONS = """
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
VIRTUALMACHINE_STATUS = """
|
||||||
|
<span class="label label-{{ record.get_status_class }}">{{ record.get_status_display }}</span>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Cluster types
|
# Cluster types
|
||||||
@ -79,12 +83,13 @@ class ClusterTable(BaseTable):
|
|||||||
class VirtualMachineTable(BaseTable):
|
class VirtualMachineTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn()
|
name = tables.LinkColumn()
|
||||||
|
status = tables.TemplateColumn(template_code=VIRTUALMACHINE_STATUS)
|
||||||
cluster = tables.LinkColumn('virtualization:cluster', args=[Accessor('cluster.pk')])
|
cluster = tables.LinkColumn('virtualization:cluster', args=[Accessor('cluster.pk')])
|
||||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
fields = ('pk', 'name', 'cluster', 'tenant', 'vcpus', 'memory', 'disk')
|
fields = ('pk', 'name', 'status', 'cluster', 'tenant', 'vcpus', 'memory', 'disk')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
Loading…
Reference in New Issue
Block a user