Commit bc48c0ba by Bach Dániel

Merge remote-tracking branch 'origin/master' into issue-289

Conflicts:
	circle/dashboard/static/dashboard/dashboard.css
parents 2a45d4dc dc674ca8
......@@ -273,3 +273,4 @@ def register_operation(op_cls, op_id=None, target_cls=None):
setattr(target_cls, operation_registry_name, dict())
getattr(target_cls, operation_registry_name)[op_id] = op_cls
return op_cls
......@@ -524,11 +524,7 @@ class TemplateForm(forms.ModelForm):
value = field.widget.value_from_datadict(
self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, forms.FileField):
initial = self.initial.get(name, field.initial)
value = field.clean(value, initial)
else:
value = field.clean(value)
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
......@@ -544,13 +540,14 @@ class TemplateForm(forms.ModelForm):
else:
self.cleaned_data[name] = getattr(old, name)
if "req_traits" not in self.allowed_fields:
self.cleaned_data['req_traits'] = self.instance.req_traits.all()
def save(self, commit=True):
data = self.cleaned_data
self.instance.max_ram_size = data.get('ram_size')
instance = super(TemplateForm, self).save(commit=False)
if commit:
instance.save()
instance = super(TemplateForm, self).save(commit=True)
# create and/or delete InterfaceTemplates
networks = InterfaceTemplate.objects.filter(
......@@ -793,6 +790,48 @@ class VmCreateDiskForm(forms.Form):
return helper
class VmDiskResizeForm(forms.Form):
size = forms.CharField(
widget=FileSizeWidget, initial=(10 << 30), label=_('Size'),
help_text=_('Size to resize the disk in bytes or with units '
'like MB or GB.'))
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices')
self.disk = kwargs.pop('default')
super(VmDiskResizeForm, self).__init__(*args, **kwargs)
self.fields.insert(0, 'disk', forms.ModelChoiceField(
queryset=choices, initial=self.disk, required=True,
empty_label=None, label=_('Disk')))
if self.disk:
self.fields['disk'].widget = HiddenInput()
self.fields['size'].initial += self.disk.size
def clean(self):
cleaned_data = super(VmDiskResizeForm, self).clean()
size_in_bytes = self.cleaned_data.get("size")
disk = self.cleaned_data.get('disk')
if not size_in_bytes.isdigit() and len(size_in_bytes) > 0:
raise forms.ValidationError(_("Invalid format, you can use "
" GB or MB!"))
if int(size_in_bytes) < int(disk.size):
raise forms.ValidationError(_("Disk size must be greater than the "
"actual size."))
return cleaned_data
@property
def helper(self):
helper = FormHelper(self)
helper.form_tag = False
if self.disk:
helper.layout = Layout(
HTML(_("<label>Disk:</label> %s") % self.disk),
Field('disk'), Field('size'))
return helper
class VmDownloadDiskForm(forms.Form):
name = forms.CharField(max_length=100, label=_("Name"))
url = forms.CharField(label=_('URL'), validators=[URLValidator(), ])
......
......@@ -977,3 +977,33 @@ textarea[name="new_members"] {
#vm-info-pane {
margin-bottom: 20px;
}
.node-list-table tbody>tr>td, .node-list-table thead>tr>th {
vertical-align: middle;
}
.node-list-table thead>tr>th,
.node-list-table .enabled, .node-list-table .priority,
.node-list-table .overcommit, .node-list-table .number_of_VMs {
text-align: center;
}
.node-list-table-thin {
width: 10px;
}
.node-list-table-monitor {
width: 250px;
}
.graph-images img {
max-width: 100%;
}
#vm-list-table tbody td:nth-child(3) {
white-space: nowrap;
}
#vm-list-table td {
vertical-align: middle;
}
......@@ -397,6 +397,20 @@ $(function () {
clientInstalledAction(connectUri);
return false;
});
/* change graphs */
$(".graph-buttons a").click(function() {
var time = $(this).data("graph-time");
$(".graph-images img").each(function() {
var src = $(this).prop("src");
var new_src = src.substring(0, src.lastIndexOf("/") + 1) + time;
$(this).prop("src", new_src);
});
// change the buttons too
$(".graph-buttons a").removeClass("btn-primary").addClass("btn-default");
$(this).removeClass("btn-default").addClass("btn-primary");
return false;
});
});
function generateVmHTML(pk, name, host, icon, _status, fav, is_last) {
......
......@@ -19,8 +19,7 @@ from __future__ import absolute_import
from django.contrib.auth.models import Group, User
from django_tables2 import Table, A
from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
LinkColumn)
from django_tables2.columns import TemplateColumn, Column, LinkColumn
from vm.models import Node, InstanceTemplate, Lease
from django.utils.translation import ugettext_lazy as _
......@@ -40,8 +39,10 @@ class NodeListTable(Table):
attrs={'th': {'class': 'node-list-table-thin'}},
)
enabled = BooleanColumn(
get_status_display = Column(
verbose_name=_("Status"),
attrs={'th': {'class': 'node-list-table-thin'}},
order_by=("enabled", "schedule_enabled"),
)
name = TemplateColumn(
......@@ -66,20 +67,12 @@ class NodeListTable(Table):
orderable=False,
)
actions = TemplateColumn(
verbose_name=_("Actions"),
attrs={'th': {'class': 'node-list-table-thin'}},
template_code=('{% include "dashboard/node-list/column-'
'actions.html" with btn_size="btn-xs" %}'),
orderable=False,
)
class Meta:
model = Node
attrs = {'class': ('table table-bordered table-striped table-hover '
'node-list-table')}
fields = ('pk', 'name', 'host', 'enabled', 'priority', 'overcommit',
'number_of_VMs', )
fields = ('pk', 'name', 'host', 'get_status_display', 'priority',
'overcommit', 'number_of_VMs', )
class GroupListTable(Table):
......
......@@ -11,11 +11,17 @@
{% endif %}
{% else %}<span class="disk-list-disk-percentage" data-disk-pk="{{ d.pk }}">{{ d.get_download_percentage }}</span>%{% endif %}
{% if is_owner != False %}
<a href="{% url "dashboard.views.disk-remove" pk=d.pk %}?next={{ request.path }}"
data-disk-pk="{{ d.pk }}" class="btn btn-xs btn-danger pull-right disk-remove"
{% if not long_remove %}title="{% trans "Remove" %}"{% endif %}
>
<i class="fa fa-times"></i>{% if long_remove %} {% trans "Remove" %}{% endif %}
</a>
<a href="{% url "dashboard.views.disk-remove" pk=d.pk %}?next={{ request.path }}"
data-disk-pk="{{ d.pk }}" class="btn btn-xs btn-danger pull-right disk-remove"
{% if not long_remove %}title="{% trans "Remove" %}"{% endif %}>
<i class="fa fa-times"></i>{% if long_remove %} {% trans "Remove" %}{% endif %}
</a>
{% if op.resize_disk %}
<span class="operation-wrapper">
<a href="{{ op.resize_disk.get_url }}?disk={{d.pk}}" class="btn btn-xs btn-warning pull-right operation">
<i class="fa fa-arrows-alt"></i> {% trans "Resize" %}
</a>
</span>
{% endif %}
{% endif %}
<div style="clear: both;"></div>
{% for o in graph_time_options %}
<a class="btn btn-xs
btn-{% if graph_time == o.time %}primary{% else %}default{% endif %}"
href="?graph_time={{ o.time }}"
data-graph-time="{{ o.time }}">
{{ o.name }}
</a>
{% endfor %}
......@@ -18,6 +18,8 @@ Choose a compute node to migrate {{obj}} to.
<li class="panel panel-default"><div class="panel-body">
<label for="migrate-to-{{n.pk}}">
<strong>{{ n }}</strong>
<div class="label label-primary"><i class="fa {{n.get_status_icon}}"></i>
{{n.get_status_display}}</div>
{% if current == n.pk %}<div class="label label-info">{% trans "current" %}</div>{% endif %}
{% if selected == n.pk %}<div class="label label-success">{% trans "recommended" %}</div>{% endif %}
</label>
......
{% load i18n %}
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
{% if text %}
{{ text }}
{% else %}
{%blocktrans with object=object%}
Are you sure you want to flush <strong>{{ object }}</strong>?
{%endblocktrans%}
{% endif %}
<div class="pull-right">
<form action="{% url "dashboard.views.flush-node" pk=node.pk %}?next={{next}}" method="POST">
{% csrf_token %}
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button>
<input type="hidden" name="flush" value=""/>
<button class="btn btn-warning">{% trans "Yes" %}</button>
</form>
</div>
<div class="clearfix"></div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
......@@ -6,13 +6,12 @@
{% block content %}
<div class="body-content">
<div class="page-header">
<div class="pull-right" id="ops">
{% include "dashboard/vm-detail/_operations.html" %}
</div>
<div class="pull-right" style="padding-top: 15px;">
<a title="{% trans "Rename" %}" href="#" class="btn btn-default btn-xs node-details-rename-button"><i class="fa fa-pencil"></i></a>
<a title="{% trans "Flush" %}" data-node-pk="{{ node.pk }}" class="btn btn-default btn-xs real-link node-flush" href="{% url "dashboard.views.flush-node" pk=node.pk %}"><i class="fa fa-cloud-upload"></i></a>
<a title="{% trans "Enable" %}" style="display:{% if node.enabled %}none{% else %}inline-block{% endif %}" data-node-pk="{{ node.pk }}" class="btn btn-default btn-xs real-link node-enable" href="{% url "dashboard.views.status-node" pk=node.pk %}?next={{ request.path }}"><i class="fa fa-check"></i></a>
<a title="{% trans "Disable" %}" style="display:{% if not node.enabled %}none{% else %}inline-block{% endif %}" data-node-pk="{{ node.pk }}" class="btn btn-default btn-xs real-link node-enable" href="{% url "dashboard.views.status-node" pk=node.pk %}?next={{ request.path }}"><i class="fa fa-ban"></i></a>
<a title="{% trans "Delete" %}" data-node-pk="{{ node.pk }}" class="btn btn-default btn-xs real-link node-delete" href="{% url "dashboard.views.delete-node" pk=node.pk %}"><i class="fa fa-trash-o"></i></a>
<a title="{% trans "Help" %}" href="#" class="btn btn-default btn-xs node-details-help-button"><i class="fa fa-question"></i></a>
</div>
<h1>
<div id="node-details-rename">
......@@ -26,42 +25,34 @@
{{ node.name }}
</div>
</h1>
<div class="node-details-help js-hidden">
<ul style="list-style: none;">
<li>
<strong>{% trans "Rename" %}:</strong>
{% trans "Change the name of the node." %}
</li>
<li>
<strong>{% trans "Flush" %}:</strong>
{% trans "Disable node and move all instances to other one." %}
</li>
<li>
<strong>{% trans "Enable" %}:</strong>
{% trans "Enables node." %}
</li>
<li>
<strong>{% trans "Disable" %}:</strong>
{% trans "Disables node." %}
</li>
<li>
<strong>{% trans "Delete" %}:</strong>
{% trans "Remove node and it's host." %}
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col-md-2" id="node-info-pane">
<div id="node-info-data" class="big">
<span id="node-details-state" class="label
{% if node.state == 'ONLINE' %}label-success
{% elif node.state == 'MISSING' %}label-danger
{% elif node.state == 'DISABLED' %}label-warning
{% elif node.state == 'OFFLINE' %}label-warning{% endif %}">
{% if node.state == 'ACTIVE' %}label-success
{% elif node.state == 'PASSIVE' %}label-warning
{% else %}label-danger{% endif %}">
<i class="fa {{ node.get_status_icon }}"></i> {{ node.get_status_display|upper }}
</span>
</div>
<div>
{% if node.enabled %}
<span class="label label-success">{% trans "Enabled" %}</span>
{% if node.schedule_enabled %}
<span class="label label-success">{% trans "Schedule enabled" %}</span>
{% else %}
<span class="label label-warning">{% trans "Schedule disabled" %}</span>
{% endif %}
{% else %}
<span class="label label-warning">{% trans "Disabled" %}</span>
{% endif %}
{% if node.online %}
<span class="label label-success">{% trans "Online" %}</span>
{% else %}
<span class="label label-warning">{% trans "Offline" %}</span>
{% endif %}
</div>
</div>
<div class="col-md-10" id="node-detail-pane">
<div class="panel panel-default" id="node-detail-panel">
......
......@@ -30,15 +30,22 @@
</div>
<div class="col-md-8">
{% if graphite_enabled %}
<img src="{% url "dashboard.views.node-graph" node.pk "cpu" "6h" %}" style="width:100%"/>
<img src="{% url "dashboard.views.node-graph" node.pk "memory" "6h" %}" style="width:100%"/>
<img src="{% url "dashboard.views.node-graph" node.pk "network" "6h" %}" style="width:100%"/>
<div class="text-center graph-buttons">
{% include "dashboard/_graph-time-buttons.html" %}
</div>
<div class="graph-images text-center">
<img src="{% url "dashboard.views.node-graph" node.pk "cpu" graph_time %}"/>
<img src="{% url "dashboard.views.node-graph" node.pk "memory" graph_time %}"/>
<img src="{% url "dashboard.views.node-graph" node.pk "network" graph_time %}"/>
<img src="{% url "dashboard.views.node-graph" node.pk "vm" graph_time %}"/>
<img src="{% url "dashboard.views.node-graph" node.pk "alloc" graph_time %}"/>
</div>
{% endif %}
</div>
</div>
<style>
.form-group {
margin: 0px;
}
</div>
</style>
<style>
.form-group {
margin: 0px;
}
</style>
......@@ -21,25 +21,23 @@
</div>
</div>
<style>
.node-list-table tbody>tr>td, .node-list-table thead>tr>th {
vertical-align: middle;
}
.node-list-table thead>tr>th,
.node-list-table .enabled, .node-list-table .priority,
.node-list-table .overcommit, .node-list-table .number_of_VMs {
text-align: center;
}
.node-list-table-thin {
width: 10px;
}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right graph-buttons">
{% include "dashboard/_graph-time-buttons.html" %}
</div>
<h3 class="no-margin"><i class="fa fa-area-chart"></i> {% trans "Graphs" %}</h3>
</div>
<div class="text-center graph-images">
<img src="{% url "dashboard.views.node-list-graph" "alloc" graph_time %}"/>
<img src="{% url "dashboard.views.node-list-graph" "vm" graph_time %}"/>
</div>
</div>
</div><!-- -col-md-12 -->
</div><!-- .row -->
.node-list-table-monitor {
width: 250px;
}
</style>
{% endblock %}
{% block extra_js %}
......
{% load i18n %}
<div class="btn-group">
<button type="button" class="btn {{ btn_size }} btn-warning nojs-dropdown-toogle dropdown-toggle" data-toggle="dropdown">Action
<i class="fa fa-caret-down"></i>
</button>
<ul class="dropdown-menu nojs-dropdown-toogle" role="menu">
<li>
<a href="#" class="node-details-rename-button">
<i class="fa fa-pencil"></i> {% trans "Rename" %}
</a>
</li>
<li>
<a data-node-pk="{{ record.pk }}" class="real-link node-flush" href="{% url "dashboard.views.flush-node" pk=record.pk %}">
<i class="fa fa-cloud-upload"></i> {% trans "Flush" %}
</a>
</li>
<li>
<a style={% if record.enabled %}"display:none"{% else %}"display:block"{% endif %} data-node-pk="{{ record.pk }}" class="real-link node-enable" href="{% url "dashboard.views.status-node" pk=record.pk %}?next={{ request.path }}">
<i class="fa fa-check"></i> {% trans "Enable" %}
</a>
</li>
<li>
<a style={% if record.enabled %}"display:block"{% else %}"display:none"{% endif %} data-node-pk="{{ record.pk }}" class="real-link node-enable" href="{% url "dashboard.views.status-node" pk=record.pk %}?next={{ request.path }}">
<i class="fa fa-times"></i> {% trans "Disable" %}
</a>
</li>
<li>
<a data-node-pk="{{ record.pk }}" class="real-link node-delete" href="{% url "dashboard.views.delete-node" pk=record.pk %}?next={{ request.path }}">
<i class="fa fa-trash-o"></i> {% trans "Delete" %}
</a>
</li>
</ul>
</div>
......@@ -23,18 +23,15 @@
{% csrf_token %}
{{ form.name|as_crispy_field }}
<a {% if form.parent.value %}
href="{% url "dashboard.views.template-detail" pk=form.parent.value %}"
{% else %}
disabled %}
{% endif %}
class="btn btn-default pull-right" style="margin-top: 24px;">
{% trans "Visit" %}
<i class="fa fa-arrow-circle-right"></i>
</a>
<div style="width: 80%;">
{{ form.parent|as_crispy_field }}
</div>
<strong>{% trans "Parent template" %}:</strong>
{% if parent %}
<a href="{% url "dashboard.views.template-detail" pk=parent.pk %}">
{{ parent.name }}
</a>
{% else %}
-
{% endif %}
<fieldset class="resources-sliders">
<legend>{% trans "Resource configuration" %}</legend>
......
......@@ -7,6 +7,7 @@
<span class="timeline-icon{% if a.has_failed %} timeline-icon-failed{% endif %}">
<i class="fa {% if not a.finished %}fa-refresh fa-spin {% else %}fa-{{a.icon}}{% endif %}"></i>
</span>
{% spaceless %}
<strong{% if a.result %} title="{{ a.result|get_text:user }}"{% endif %}>
<a href="{{ a.get_absolute_url }}">
{% if a.times > 1 %}({{ a.times }}x){% endif %}
......@@ -16,7 +17,7 @@
- {{ a.percentage }}%
{% endif %}
</strong>
{% if a.times < 2%}{{ a.started|date:"Y-m-d H:i" }}{% endif %}{% if a.user %},
{% endspaceless %}{% if a.times < 2%} {{ a.started|date:"Y-m-d H:i" }}{% endif %}{% if a.user %},
<a class="no-style-link" href="{% url "dashboard.views.profile" username=a.user.username %}">
{% include "dashboard/_display-name.html" with user=a.user show_org=True %}
</a>
......
......@@ -137,9 +137,14 @@
</div>
<div class="col-md-8">
{% if graphite_enabled %}
<img src="{% url "dashboard.views.vm-graph" instance.pk "cpu" "6h" %}" style="width:100%"/>
<img src="{% url "dashboard.views.vm-graph" instance.pk "memory" "6h" %}" style="width:100%"/>
<img src="{% url "dashboard.views.vm-graph" instance.pk "network" "6h" %}" style="width:100%"/>
<div class="text-center graph-buttons">
{% include "dashboard/_graph-time-buttons.html" %}
</div>
<div class="graph-images text-center">
<img src="{% url "dashboard.views.vm-graph" instance.pk "cpu" graph_time %}"/>
<img src="{% url "dashboard.views.vm-graph" instance.pk "memory" graph_time %}"/>
<img src="{% url "dashboard.views.vm-graph" instance.pk "network" graph_time %}"/>
</div>
{% endif %}
</div>
</div>
......@@ -25,11 +25,11 @@ from .views import (
GroupDetailView, GroupList, IndexView,
InstanceActivityDetail, LeaseCreate, LeaseDelete, LeaseDetail,
MyPreferencesView, NodeAddTraitView, NodeCreate, NodeDelete,
NodeDetailView, NodeFlushView, NodeGraphView, NodeList, NodeStatus,
NodeDetailView, NodeList, NodeStatus,
NotificationView, PortDelete, TemplateAclUpdateView, TemplateCreate,
TemplateDelete, TemplateDetail, TemplateList, TransferOwnershipConfirmView,
TransferOwnershipView, vm_activity, VmCreate, VmDetailView,
VmDetailVncTokenView, VmGraphView, VmList,
VmDetailVncTokenView, VmList,
DiskRemoveView, get_disk_download_status, InterfaceDeleteView,
GroupRemoveUserView,
GroupRemoveFutureUserView,
......@@ -46,7 +46,10 @@ from .views import (
GroupPermissionsView,
LeaseAclUpdateView,
ClientCheck, TokenLogin,
VmGraphView, NodeGraphView, NodeListGraphView,
)
from .views.vm import vm_ops, vm_mass_ops
from .views.node import node_ops
autocomplete_light.autodiscover()
......@@ -74,8 +77,6 @@ urlpatterns = patterns(
name="dashboard.views.template-list"),
url(r"^template/delete/(?P<pk>\d+)/$", TemplateDelete.as_view(),
name="dashboard.views.template-delete"),
url(r'^vm/', include('dashboard.vm.urls')),
url(r'^vm/(?P<pk>\d+)/remove_port/(?P<rule>\d+)/$', PortDelete.as_view(),
name='dashboard.views.remove-port'),
url(r'^vm/(?P<pk>\d+)/$', VmDetailView.as_view(),
......@@ -110,8 +111,6 @@ urlpatterns = patterns(
name="dashboard.views.delete-node"),
url(r'^node/status/(?P<pk>\d+)/$', NodeStatus.as_view(),
name="dashboard.views.status-node"),
url(r'^node/flush/(?P<pk>\d+)/$', NodeFlushView.as_view(),
name="dashboard.views.flush-node"),
url(r'^node/create/$', NodeCreate.as_view(),
name='dashboard.views.node-create'),
......@@ -121,14 +120,18 @@ urlpatterns = patterns(
name="dashboard.views.delete-group"),
url(r'^group/list/$', GroupList.as_view(),
name='dashboard.views.group-list'),
url((r'^vm/(?P<pk>\d+)/graph/(?P<metric>cpu|memory|network)/'
url((r'^vm/(?P<pk>\d+)/graph/(?P<metric>[a-z]+)/'
r'(?P<time>[0-9]{1,2}[hdwy])$'),
VmGraphView.as_view(),
name='dashboard.views.vm-graph'),
url((r'^node/(?P<pk>\d+)/graph/(?P<metric>cpu|memory|network)/'
url((r'^node/(?P<pk>\d+)/graph/(?P<metric>[a-z]+)/'
r'(?P<time>[0-9]{1,2}[hdwy])$'),
NodeGraphView.as_view(),
name='dashboard.views.node-graph'),
url((r'^node/graph/(?P<metric>[a-z]+)/'
r'(?P<time>[0-9]{1,2}[hdwy])$'),
NodeListGraphView.as_view(),
name='dashboard.views.node-list-graph'),
url(r'^group/(?P<pk>\d+)/$', GroupDetailView.as_view(),
name='dashboard.views.group-detail'),
url(r'^group/(?P<pk>\d+)/update/$', GroupProfileUpdate.as_view(),
......@@ -210,3 +213,21 @@ urlpatterns = patterns(
url(r'^token-login/(?P<token>.*)/$', TokenLogin.as_view(),
name="dashboard.views.token-login"),
)
urlpatterns += patterns(
'',
*(url(r'^vm/(?P<pk>\d+)/op/%s/$' % op, v.as_view(), name=v.get_urlname())
for op, v in vm_ops.iteritems())
)
urlpatterns += patterns(
'',
*(url(r'^vm/mass_op/%s/$' % op, v.as_view(), name=v.get_urlname())
for op, v in vm_mass_ops.iteritems())
)
urlpatterns += patterns(
'',
*(url(r'^node/(?P<pk>\d+)/op/%s/$' % op, v.as_view(), name=v.get_urlname())
for op, v in node_ops.iteritems())
)
......@@ -11,3 +11,4 @@ from template import *
from user import *
from util import *
from vm import *
from graph import *
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, unicode_literals
import logging
import requests
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse, Http404
from django.utils.translation import ugettext_lazy as _
from django.views.generic import View
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from vm.models import Instance, Node
logger = logging.getLogger(__name__)
def register_graph(metric_cls, graph_name, graphview_cls):
if not hasattr(graphview_cls, 'metrics'):
graphview_cls.metrics = {}
graphview_cls.metrics[graph_name] = metric_cls
class GraphViewBase(LoginRequiredMixin, View):
def create_class(self, cls):
return type(str(cls.__name__ + 'Metric'), (cls, self.base), {})
def get(self, request, pk, metric, time, *args, **kwargs):
graphite_url = settings.GRAPHITE_URL
if graphite_url is None:
raise Http404()
try:
metric = self.metrics[metric]
except KeyError:
raise Http404()
try:
instance = self.get_object(request, pk)
except self.model.DoesNotExist:
raise Http404()
metric = self.create_class(metric)(instance)
return HttpResponse(metric.get_graph(graphite_url, time),
mimetype="image/png")
def get_object(self, request, pk):
instance = self.model.objects.get(id=pk)
if not instance.has_level(request.user, 'user'):
raise PermissionDenied()
return instance
class Metric(object):
cacti_style = True
derivative = False
scale_to_seconds = None
metric_name = None
title = None
label = None
def __init__(self, obj, metric_name=None):
self.obj = obj
self.metric_name = (
metric_name or self.metric_name or self.__class__.__name__.lower())
def get_metric_name(self):
return self.metric_name
def get_label(self):
return self.label or self.get_metric_name()
def get_title(self):
return self.title or self.get_metric_name()
def get_minmax(self):
return (None, None)
def get_target(self):
target = '%s.%s' % (self.obj.metric_prefix, self.get_metric_name())
if self.derivative:
target = 'nonNegativeDerivative(%s)' % target
if self.scale_to_seconds:
target = 'scaleToSeconds(%s, %d)' % (target, self.scale_to_seconds)
target = 'alias(%s, "%s")' % (target, self.get_label())
if self.cacti_style:
target = 'cactiStyle(%s)' % target
return target
def get_graph(self, graphite_url, time, width=500, height=200):
params = {'target': self.get_target(),
'from': '-%s' % time,
'title': self.get_title().encode('UTF-8'),
'width': width,
'height': height}
ymin, ymax = self.get_minmax()
if ymin is not None: