diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html
index a597ee060..cd66cb105 100644
--- a/netbox/templates/virtualization/virtualmachine.html
+++ b/netbox/templates/virtualization/virtualmachine.html
@@ -51,6 +51,12 @@
Cluster |
diff --git a/netbox/templates/virtualization/virtualmachine_edit.html b/netbox/templates/virtualization/virtualmachine_edit.html
index 14acd13db..085f3cadf 100644
--- a/netbox/templates/virtualization/virtualmachine_edit.html
+++ b/netbox/templates/virtualization/virtualmachine_edit.html
@@ -6,6 +6,7 @@
Virtual Machine
{% render_field form.name %}
+ {% render_field form.status %}
{% render_field form.cluster_group %}
{% render_field form.cluster %}
{% render_field form.platform %}
diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py
index 42d3f237e..6849a5396 100644
--- a/netbox/virtualization/api/serializers.py
+++ b/netbox/virtualization/api/serializers.py
@@ -8,6 +8,7 @@ from dcim.models import Interface
from extras.api.customfields import CustomFieldModelSerializer
from tenancy.api.serializers import NestedTenantSerializer
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
+from virtualization.constants import STATUS_CHOICES
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@@ -82,6 +83,7 @@ class WritableClusterSerializer(CustomFieldModelSerializer):
#
class VirtualMachineSerializer(CustomFieldModelSerializer):
+ status = ChoiceFieldSerializer(choices=STATUS_CHOICES)
cluster = NestedClusterSerializer()
tenant = NestedTenantSerializer()
platform = NestedPlatformSerializer()
@@ -89,7 +91,8 @@ class VirtualMachineSerializer(CustomFieldModelSerializer):
class Meta:
model = VirtualMachine
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:
model = VirtualMachine
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',
]
diff --git a/netbox/virtualization/constants.py b/netbox/virtualization/constants.py
new file mode 100644
index 000000000..b05a541eb
--- /dev/null
+++ b/netbox/virtualization/constants.py
@@ -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',
+}
diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py
index 4efcd06e2..0cb06fa9c 100644
--- a/netbox/virtualization/filters.py
+++ b/netbox/virtualization/filters.py
@@ -7,6 +7,7 @@ from dcim.models import Platform
from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
+from .constants import STATUS_CHOICES
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@@ -55,6 +56,9 @@ class VirtualMachineFilter(CustomFieldFilterSet):
method='search',
label='Search',
)
+ status = django_filters.MultipleChoiceFilter(
+ choices=STATUS_CHOICES
+ )
cluster_group_id = NullableModelMultipleChoiceFilter(
name='cluster__group',
queryset=ClusterGroup.objects.all(),
diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py
index 8a1feaaea..7fc033d4c 100644
--- a/netbox/virtualization/forms.py
+++ b/netbox/virtualization/forms.py
@@ -12,10 +12,11 @@ from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFi
from tenancy.forms import TenancyForm
from tenancy.models import Tenant
from utilities.forms import (
- APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ChainedFieldsMixin,
- ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm, ConfirmationForm,
- ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea,
+ add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
+ ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,
+ ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea,
)
+from .constants import STATUS_CHOICES
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@@ -185,7 +186,9 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
class Meta:
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):
@@ -200,6 +203,11 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
class VirtualMachineCSVForm(forms.ModelForm):
+ status = CSVChoiceField(
+ choices=STATUS_CHOICES,
+ required=False,
+ help_text='Operational status of device'
+ )
cluster = forms.ModelChoiceField(
queryset=Cluster.objects.all(),
to_field_name='name',
@@ -229,11 +237,12 @@ class VirtualMachineCSVForm(forms.ModelForm):
class Meta:
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):
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)
tenant = forms.ModelChoiceField(queryset=Tenant.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']
+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):
model = VirtualMachine
q = forms.CharField(required=False, label='Search')
@@ -258,6 +274,7 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
label='Cluster'
)
+ status = forms.MultipleChoiceField(choices=vm_status_choices, required=False)
#
diff --git a/netbox/virtualization/migrations/0002_virtualmachine_add_status.py b/netbox/virtualization/migrations/0002_virtualmachine_add_status.py
new file mode 100644
index 000000000..5b03b6e33
--- /dev/null
+++ b/netbox/virtualization/migrations/0002_virtualmachine_add_status.py
@@ -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'),
+ ),
+ ]
diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py
index d1f949407..1501dabad 100644
--- a/netbox/virtualization/models.py
+++ b/netbox/virtualization/models.py
@@ -7,6 +7,7 @@ from django.utils.encoding import python_2_unicode_compatible
from extras.models import CustomFieldModel, CustomFieldValue
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,
unique=True
)
+ status = models.PositiveSmallIntegerField(
+ choices=STATUS_CHOICES,
+ default=STATUS_ACTIVE,
+ verbose_name='Status'
+ )
primary_ip4 = models.OneToOneField(
to='ipam.IPAddress',
on_delete=models.SET_NULL,
@@ -187,3 +193,6 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
def get_absolute_url(self):
return reverse('virtualization:virtualmachine', args=[self.pk])
+
+ def get_status_class(self):
+ return VM_STATUS_CLASSES[self.status]
diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py
index 59f3f5393..28bf1c12a 100644
--- a/netbox/virtualization/tables.py
+++ b/netbox/virtualization/tables.py
@@ -20,6 +20,10 @@ CLUSTERGROUP_ACTIONS = """
{% endif %}
"""
+VIRTUALMACHINE_STATUS = """
+{{ record.get_status_display }}
+"""
+
#
# Cluster types
@@ -79,12 +83,13 @@ class ClusterTable(BaseTable):
class VirtualMachineTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn()
+ status = tables.TemplateColumn(template_code=VIRTUALMACHINE_STATUS)
cluster = tables.LinkColumn('virtualization:cluster', args=[Accessor('cluster.pk')])
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
class Meta(BaseTable.Meta):
model = VirtualMachine
- fields = ('pk', 'name', 'cluster', 'tenant', 'vcpus', 'memory', 'disk')
+ fields = ('pk', 'name', 'status', 'cluster', 'tenant', 'vcpus', 'memory', 'disk')
#
|