Commit dae0a707 by Bach Dániel

dashboard: improve node detail

Conflicts:
	circle/dashboard/static/dashboard/node-details.js
	circle/dashboard/urls.py
	circle/dashboard/views/node.py
parent d593d10c
var in_progress = false;
var activity_hash = 5;
$(function() { $(function() {
/* do we need to check for new activities */
if(decideActivityRefresh()) {
if(!in_progress) {
checkNewActivity(1);
in_progress = true;
}
}
$('a[href="#activity"]').click(function(){
$('a[href="#activity"] i').addClass('fa-spin');
if(!in_progress) {
checkNewActivity(1);
in_progress = true;
}
});
$('a.operation.btn').click(function(e) {
$.ajax({
type: 'GET',
url: $(this).attr('href'),
success: function(data) {
$('body').append(data);
$('#confirmation-modal').modal('show');
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('#confirmation-modal').remove();
});
}
});
return false;
});
/* 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();
...@@ -55,3 +89,54 @@ $(function() { ...@@ -55,3 +89,54 @@ $(function() {
}); });
}); });
function decideActivityRefresh() {
var check = false;
/* if something is still spinning */
if($('.timeline .activity i').hasClass('fa-spin'))
check = true;
return check;
}
function checkNewActivity(runs) {
var node = location.href.split('/'); node = node[node.length - 2];
$.ajax({
type: 'GET',
url: '/dashboard/node/' + node + '/activity/',
success: function(data) {
var new_activity_hash = (data['activities'] + "").hashCode();
if(new_activity_hash != activity_hash) {
$("#activity-refresh").html(data['activities']);
}
activity_hash = new_activity_hash;
$("[title]").tooltip();
if(runs > 0 && decideActivityRefresh()) {
setTimeout(
function() {checkNewActivity(runs + 1)},
1000 + Math.exp(runs * 0.05)
);
} else {
in_progress = false;
}
$('a[href="#activity"] i').removeClass('fa-spin');
},
error: function() {
in_progress = false;
}
});
}
String.prototype.hashCode = function() {
var hash = 0, i, chr, len;
if (this.length == 0) return hash;
for (i = 0, len = this.length; i < len; i++) {
chr = this.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
};
{% load i18n %} {% load i18n %}
{% load hro %} {% load hro %}
<div id="activity-timeline" class="timeline"> <div id="activity-timeline" class="timeline">
{% for a in activities %} {% for a in activities %}
<div class="activity" data-activity-id="{{ a.pk }}"> <div class="activity" data-activity-id="{{ a.pk }}">
<span class="timeline-icon{% if a.has_failed %} timeline-icon-failed{% endif %}"> <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-plus{% endif %}"></i> <i class="fa {% if not a.finished %}fa-refresh fa-spin {% else %}fa-plus{% endif %}"></i>
</span> </span>
<strong title="{{ a.result.get_admin_text }}"> <strong title="{{ a.result.get_admin_text }}">
{{ a.readable_name.get_admin_text|capfirst }} {{ a.readable_name.get_admin_text|capfirst }}
</strong> </strong>
{{ a.started|date:"Y-m-d H:i" }}, {{ a.user }} {{ a.started|date:"Y-m-d H:i" }}{% if a.user %}, {{ a.user }}{% endif %}
{% if a.children.count > 0 %} {% if a.children.count > 0 %}
<div class="sub-timeline"> <div class="sub-timeline">
{% for s in a.children.all %} {% for s in a.children.all %}
<div data-activity-id="{{ s.pk }}" <div data-activity-id="{{ s.pk }}"
class="sub-activity{% if s.has_failed %} sub-activity-failed{% endif %}" class="sub-activity{% if s.has_failed %} sub-activity-failed{% endif %}">
> <span title="{{ s.result.get_admin_text }}">
{{ s.readable_name|get_text:user }} {{ s.readable_name|get_text:user }}
&ndash; </span>
{% if s.finished %} &ndash;
{{ s.finished|time:"H:i:s" }} {% if s.finished %}
{% else %} {{ s.finished|time:"H:i:s" }}
<i class="fa fa-refresh fa-spin" class="sub-activity-loading-icon"></i> {% else %}
{% endif %} <i class="fa fa-refresh fa-spin" class="sub-activity-loading-icon"></i>
{% if s.has_failed %} {% endif %}
<div title="{{ s.result.get_admin_text }}" class="label label-danger">{% trans "failed" %}</div> {% if s.has_failed %}
{% endif %} <div class="label label-danger">{% trans "failed" %}</div>
</div> {% endif %}
{% endfor %} </div>
</div> {% endfor %}
{% endif %} </div>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
...@@ -2,6 +2,6 @@ ...@@ -2,6 +2,6 @@
<h3>{% trans "Activity" %}</h3> <h3>{% trans "Activity" %}</h3>
<div id="activity-timeline-wrapper"> <div id="activity-refresh">
{% include "dashboard/node-detail/_activity-timeline.html" %} {% include "dashboard/node-detail/_activity-timeline.html" %}
</div> </div>
...@@ -51,6 +51,7 @@ from .views import ( ...@@ -51,6 +51,7 @@ from .views import (
TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView, TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView,
TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView, TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView,
OpenSearchDescriptionView, OpenSearchDescriptionView,
NodeActivityView,
) )
from .views.vm import vm_ops, vm_mass_ops from .views.vm import vm_ops, vm_mass_ops
from .views.node import node_ops from .views.node import node_ops
...@@ -119,6 +120,7 @@ urlpatterns = patterns( ...@@ -119,6 +120,7 @@ urlpatterns = patterns(
name='dashboard.views.template-transfer-ownership-confirm'), name='dashboard.views.template-transfer-ownership-confirm'),
url(r'^node/delete/(?P<pk>\d+)/$', NodeDelete.as_view(), url(r'^node/delete/(?P<pk>\d+)/$', NodeDelete.as_view(),
name="dashboard.views.delete-node"), name="dashboard.views.delete-node"),
url(r'^node/(?P<pk>\d+)/activity/$', NodeActivityView.as_view()),
url(r'^node/create/$', NodeCreate.as_view(), url(r'^node/create/$', NodeCreate.as_view(),
name='dashboard.views.node-create'), name='dashboard.views.node-create'),
......
...@@ -27,8 +27,10 @@ from django.db.models import Count ...@@ -27,8 +27,10 @@ from django.db.models import Count
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.template import RequestContext
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic import DetailView, TemplateView from django.views.generic import DetailView, TemplateView, View
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
...@@ -279,3 +281,22 @@ class NodeAddTraitView(SuperuserRequiredMixin, DetailView): ...@@ -279,3 +281,22 @@ class NodeAddTraitView(SuperuserRequiredMixin, DetailView):
return redirect(self.get_success_url()) return redirect(self.get_success_url())
else: else:
return self.get(self, request, pk, *args, **kwargs) return self.get(self, request, pk, *args, **kwargs)
class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View):
def get(self, request, pk):
node = Node.objects.get(pk=pk)
activities = NodeActivity.objects.filter(
node=node, parent=None).order_by('-started').select_related()
response = {
'activities': render_to_string(
"dashboard/node-detail/_activity-timeline.html",
RequestContext(request, {'activities': activities}))
}
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
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