Commit f2794a01 by Őry Máté

Merge branch 'feature-node-traits'

closes #51
Conflicts:
	circle/dashboard/static/dashboard/node-details.js
	circle/dashboard/views.py
parents ebb9b2a5 2664addc
...@@ -20,7 +20,7 @@ from sizefield.widgets import FileSizeWidget ...@@ -20,7 +20,7 @@ from sizefield.widgets import FileSizeWidget
from firewall.models import Vlan, Host from firewall.models import Vlan, Host
from storage.models import Disk, DataStore from storage.models import Disk, DataStore
from vm.models import ( from vm.models import (
InstanceTemplate, Lease, InterfaceTemplate, Node, Instance InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance
) )
VLANS = Vlan.objects.all() VLANS = Vlan.objects.all()
...@@ -964,3 +964,30 @@ class WorkingBaseInput(BaseInput): ...@@ -964,3 +964,30 @@ class WorkingBaseInput(BaseInput):
self.input_type = input_type self.input_type = input_type
self.field_classes = "" # we need this for some reason self.field_classes = "" # we need this for some reason
super(WorkingBaseInput, self).__init__(name, value, **kwargs) super(WorkingBaseInput, self).__init__(name, value, **kwargs)
class TraitForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TraitForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper.layout = Layout(
Div(
Field('name', id="node-details-traits-input",
css_class="input-sm input-traits"),
Div(
HTML('<input type="submit" '
'class="btn btn-default btn-sm input-traits" '
'value="Add trait"/>',
),
css_class="input-group-btn",
),
css_class="input-group",
id="node-details-traits-form",
),
)
class Meta:
model = Trait
fields = ['name']
...@@ -191,31 +191,31 @@ body { ...@@ -191,31 +191,31 @@ body {
/* pass */ /* pass */
} }
#vm-details-tags-form { #vm-details-tags-form, #node-details-traits-form {
margin-top: 15px; margin-top: 15px;
max-width: 250px; max-width: 250px;
} }
.vm-details-remove-tag { .vm-details-remove-tag, .node-details-remove-trait {
color: white; color: white;
padding-left: 5px; padding-left: 5px;
} }
.vm-details-remove-tag:hover { .vm-details-remove-tag:hover, .node-details-remove-trait:hover {
cursor: pointer; cursor: pointer;
color: black; color: black;
text-decoration: none; text-decoration: none;
} }
/* small buttons for tags, copied from Bootstraps input-sm, bnt-sm */ /* small buttons for tags, copied from Bootstraps input-sm, bnt-sm */
.btn-tags { .btn-tags, .btn-traits {
padding: 3px 6px; padding: 3px 6px;
font-size: 11px; font-size: 11px;
line-height: 1.5; line-height: 1.5;
border-radius: 3px; border-radius: 3px;
} }
.input-tags { .input-tags, .input-tratis {
height: 22px; height: 22px;
padding: 2px 8px; padding: 2px 8px;
font-size: 11px; font-size: 11px;
......
/* rename */ /* rename */
$("#node-details-h1-name, .node-details-rename-button").click(function() { $("#node-details-h1-name, .node-details-rename-button").click(function() {
$("#node-details-h1-name").hide(); $("#node-details-h1-name").hide();
...@@ -62,4 +61,26 @@ function changeNodeStatus(data) { ...@@ -62,4 +61,26 @@ function changeNodeStatus(data) {
}); });
} }
// remove trait
$('.node-details-remove-trait').click(function() {
var to_remove = $(this).data("trait-pk");
var clicked = $(this);
$.ajax({
type: 'POST',
url: location.href,
headers: {"X-CSRFToken": getCookie('csrftoken')},
data: {'to_remove': to_remove},
success: function(re) {
if(re['message'].toLowerCase() == "success") {
$(clicked).closest(".label").fadeOut(500, function() {
$(this).remove();
});
}
},
error: function() {
addMessage(re['message'], 'danger');
}
});
return false;
});
...@@ -198,6 +198,7 @@ $(function() { ...@@ -198,6 +198,7 @@ $(function() {
} }
// refresh the given contents, parameter is the array of contents, in pair // refresh the given contents, parameter is the array of contents, in pair
function contentrefresh(elements,callbacks){ function contentrefresh(elements,callbacks){
for (var i = 0; i < elements.length; i+=2) { for (var i = 0; i < elements.length; i+=2) {
......
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block content %}
<style>
.help-block {
display:none;
}
.row {
margin-bottom: 15px;
}
</style>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin"><i class="icon-plus"></i> {% trans "Add Trait" %}</h3>
</div>
<div class="panel-body">
{% with form=form %}
{% include "display-form-errors.html" %}
{% endwith %}
{% crispy form %}
</div>
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %} {% extends "dashboard/base.html" %}
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<div class="body-content"> <div class="body-content">
<div class="page-header"> <div class="page-header">
......
{% load i18n %}
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<dl> <dl>
...@@ -6,6 +7,32 @@ ...@@ -6,6 +7,32 @@
<dt>Description:</dt> <dt>Description:</dt>
<dd><small>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc est libero, hendrerit at posuere sed, molestie congue quam. </small></dd> <dd><small>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc est libero, hendrerit at posuere sed, molestie congue quam. </small></dd>
</dl> </dl>
<div style="font-weight: bold;">{% trans "Traits" %}</div>
<div id="node-details-traits" style="margin-bottom: 20px;">
<div id="node-details-traits-list">
{% if node.traits.all %}
{% for t in node.traits.all %}
<div class="label label-success label-tag" style="display: inline-block">
{{ t }}
<a data-trait-pk="{{ t.pk }}" href="#" class="node-details-remove-trait"><i class="icon-remove"></i></a>
</div>
{% endfor %}
{% else %}
<small>{% trans "No trait added!" %}</small>
{% endif %}
</div>
{% load crispy_forms_tags %}
<style>
.row {
margin-bottom: 15px;
}
</style>
<form action="{% url "dashboard.views.node-addtrait" node.pk %}" method="POST">
{% csrf_token %}
{% crispy trait_form %}
</form>
</div><!-- id:node-details-traits -->
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
{% if graphite_enabled %} {% if graphite_enabled %}
...@@ -14,4 +41,10 @@ ...@@ -14,4 +41,10 @@
<img src="{% url "dashboard.views.node-graph" node.pk "network" "6h" %}" style="width:100%"/> <img src="{% url "dashboard.views.node-graph" node.pk "network" "6h" %}" style="width:100%"/>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<style>
.form-group {
margin: 0px;
}
</style>
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<dt>{% trans "Node name" %}:</dt><dd>{{ node.name }}</dd> <dt>{% trans "Node name" %}:</dt><dd>{{ node.name }}</dd>
<dt>{% trans "CPU cores" %}:</dt><dd>{{ node.num_cores }}</dd> <dt>{% trans "CPU cores" %}:</dt><dd>{{ node.num_cores }}</dd>
<dt>{% trans "RAM size" %}:</dt> <dd>{% widthratio node.ram_size 1048576 1 %} MB</dd> <dt>{% trans "RAM size" %}:</dt> <dd>{% widthratio node.ram_size 1048576 1 %} MB</dd>
<dt>{% trans "Architecture" %}:</td><dd>{{ node.arch }}</dd> <dt>{% trans "Architecture" %}:</dt><dd>{{ node.arch }}</dd>
<dt>{% trans "Host IP" %}:</dt><dd>{{ node.host.ipv4 }}</dd> <dt>{% trans "Host IP" %}:</dt><dd>{{ node.host.ipv4 }}</dd>
<dt>{% trans "Enabled" %}:</dt><dd>{{ node.enabled }}</dd> <dt>{% trans "Enabled" %}:</dt><dd>{{ node.enabled }}</dd>
<dt>{% trans "Host online" %}:</dt><dd> {{ node.online }}</dd> <dt>{% trans "Host online" %}:</dt><dd> {{ node.online }}</dd>
......
...@@ -2,14 +2,15 @@ from django.conf.urls import patterns, url ...@@ -2,14 +2,15 @@ from django.conf.urls import patterns, url
from vm.models import Instance from vm.models import Instance
from .views import ( from .views import (
IndexView, VmDetailView, VmList, VmCreate, TemplateDetail, AclUpdateView, AclUpdateView, DiskAddView, FavouriteView, GroupAclUpdateView, GroupDelete,
VmDelete, VmMassDelete, vm_activity, NodeList, NodeDetailView, PortDelete, GroupDetailView, GroupList, GroupUserDelete, IndexView, LeaseCreate,
TransferOwnershipView, TransferOwnershipConfirmView, NodeDelete, LeaseDelete, LeaseDetail, NodeAddTraitView, NodeCreate, NodeDelete,
TemplateList, LeaseDetail, NodeCreate, LeaseCreate, TemplateCreate, NodeDetailView, NodeGraphView, NodeList, NodeStatus, NotificationView,
FavouriteView, NodeStatus, GroupList, TemplateDelete, LeaseDelete, PortDelete, TemplateAclUpdateView, TemplateCreate, TemplateDelete,
VmGraphView, TemplateAclUpdateView, GroupDetailView, GroupDelete, TemplateDetail, TemplateList, TransferOwnershipConfirmView,
GroupAclUpdateView, GroupUserDelete, NotificationView, NodeGraphView, TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView,
VmMigrateView, VmDetailVncTokenView, VmRenewView, DiskAddView, VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, VmMigrateView,
VmRenewView,
) )
urlpatterns = patterns( urlpatterns = patterns(
...@@ -59,6 +60,8 @@ urlpatterns = patterns( ...@@ -59,6 +60,8 @@ urlpatterns = patterns(
url(r'^node/list/$', NodeList.as_view(), name='dashboard.views.node-list'), url(r'^node/list/$', NodeList.as_view(), name='dashboard.views.node-list'),
url(r'^node/(?P<pk>\d+)/$', NodeDetailView.as_view(), url(r'^node/(?P<pk>\d+)/$', NodeDetailView.as_view(),
name='dashboard.views.node-detail'), name='dashboard.views.node-detail'),
url(r'^node/(?P<pk>\d+)/add-trait/$', NodeAddTraitView.as_view(),
name='dashboard.views.node-addtrait'),
url(r'^tx/(?P<key>.*)/?$', TransferOwnershipConfirmView.as_view(), url(r'^tx/(?P<key>.*)/?$', TransferOwnershipConfirmView.as_view(),
name='dashboard.views.vm-transfer-ownership-confirm'), name='dashboard.views.vm-transfer-ownership-confirm'),
url(r'^node/delete/(?P<pk>\d+)/$', NodeDelete.as_view(), url(r'^node/delete/(?P<pk>\d+)/$', NodeDelete.as_view(),
......
...@@ -33,14 +33,15 @@ from braces.views import ( ...@@ -33,14 +33,15 @@ from braces.views import (
) )
from .forms import ( from .forms import (
VmCustomizeForm, TemplateForm, LeaseForm, NodeForm, HostForm, CircleAuthenticationForm, DiskAddForm, HostForm, LeaseForm, NodeForm,
DiskAddForm, CircleAuthenticationForm, TemplateForm, TraitForm, VmCustomizeForm,
) )
from .tables import (VmListTable, NodeListTable, NodeVmListTable, from .tables import (VmListTable, NodeListTable, NodeVmListTable,
TemplateListTable, LeaseListTable, GroupListTable,) TemplateListTable, LeaseListTable, GroupListTable,)
from vm.models import (Instance, InstanceTemplate, InterfaceTemplate, from vm.models import (
InstanceActivity, Node, instance_activity, Lease, Instance, instance_activity, InstanceActivity, InstanceTemplate, Interface,
Interface, NodeActivity) InterfaceTemplate, Lease, Node, NodeActivity, Trait,
)
from firewall.models import Vlan, Host, Rule from firewall.models import Vlan, Host, Rule
from dashboard.models import Favourite, Profile from dashboard.models import Favourite, Profile
...@@ -466,8 +467,12 @@ class VmDetailView(CheckedDetailView): ...@@ -466,8 +467,12 @@ class VmDetailView(CheckedDetailView):
class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
template_name = "dashboard/node-detail.html" template_name = "dashboard/node-detail.html"
model = Node model = Node
form = None
form_class = TraitForm
def get_context_data(self, **kwargs): def get_context_data(self, form=None, **kwargs):
if form is None:
form = self.form_class()
context = super(NodeDetailView, self).get_context_data(**kwargs) context = super(NodeDetailView, self).get_context_data(**kwargs)
instances = Instance.active.filter(node=self.object) instances = Instance.active.filter(node=self.object)
context['table'] = NodeVmListTable(instances) context['table'] = NodeVmListTable(instances)
...@@ -475,6 +480,7 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): ...@@ -475,6 +480,7 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
node=self.object, parent=None node=self.object, parent=None
).order_by('-started').select_related() ).order_by('-started').select_related()
context['activities'] = na context['activities'] = na
context['trait_form'] = form
context['graphite_enabled'] = ( context['graphite_enabled'] = (
NodeGraphView.get_graphite_url() is not None) NodeGraphView.get_graphite_url() is not None)
return context return context
...@@ -485,6 +491,8 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): ...@@ -485,6 +491,8 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
return self.__set_name(request) return self.__set_name(request)
if request.POST.get('change_status') is not None: if request.POST.get('change_status') is not None:
return self.__set_status(request) return self.__set_status(request)
if request.POST.get('to_remove'):
return self.__remove_trait(request)
return redirect(reverse_lazy("dashboard.views.node-detail", return redirect(reverse_lazy("dashboard.views.node-detail",
kwargs={'pk': self.get_object().pk})) kwargs={'pk': self.get_object().pk}))
...@@ -531,6 +539,23 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): ...@@ -531,6 +539,23 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
return redirect(reverse_lazy("dashboard.views.node-detail", return redirect(reverse_lazy("dashboard.views.node-detail",
kwargs={'pk': self.object.pk})) kwargs={'pk': self.object.pk}))
def __remove_trait(self, request):
try:
to_remove = request.POST.get('to_remove')
self.object = self.get_object()
self.object.traits.remove(to_remove)
message = u"Success"
except: # note this won't really happen
message = u"Not success"
if request.is_ajax():
return HttpResponse(
json.dumps({'message': message}),
content_type="application/json"
)
else:
return redirect(self.object.get_absolute_url())
class GroupDetailView(CheckedDetailView): class GroupDetailView(CheckedDetailView):
template_name = "dashboard/group-detail.html" template_name = "dashboard/group-detail.html"
...@@ -1269,6 +1294,39 @@ class NodeDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView): ...@@ -1269,6 +1294,39 @@ class NodeDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
return reverse_lazy('dashboard.index') return reverse_lazy('dashboard.index')
class NodeAddTraitView(SuperuserRequiredMixin, DetailView):
model = Node
template_name = "dashboard/node-add-trait.html"
def get_success_url(self):
next = self.request.GET.get('next')
if next:
return next
else:
return self.object.get_absolute_url()
def get_context_data(self, **kwargs):
self.object = self.get_object()
context = super(NodeAddTraitView, self).get_context_data(**kwargs)
context['form'] = (TraitForm(self.request.POST) if self.request.POST
else TraitForm())
return context
def post(self, request, pk, *args, **kwargs):
context = self.get_context_data(**kwargs)
form = context['form']
if form.is_valid():
node = self.object
n = form.cleaned_data['name']
trait, created = Trait.objects.get_or_create(name=n)
node.traits.add(trait)
success_message = _("Trait successfully added to node.")
messages.success(request, success_message)
return redirect(self.get_success_url())
else:
return self.get(self, request, pk, *args, **kwargs)
class NodeStatus(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): class NodeStatus(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
template_name = "dashboard/confirm/node-status.html" template_name = "dashboard/confirm/node-status.html"
model = Node model = Node
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment