Commit a71972e7 by Bach Dániel

Merge branch 'feature-screenshot'

Conflicts:
	circle/dashboard/urls.py
parents c6347434 5abed6d9
...@@ -654,3 +654,12 @@ textarea[name="list-new-namelist"] { ...@@ -654,3 +654,12 @@ textarea[name="list-new-namelist"] {
80% { -webkit-transform: scale(1); } 80% { -webkit-transform: scale(1); }
100% { -webkit-transform: scale(1); } 100% { -webkit-transform: scale(1); }
} }
.btn-toolbar {
margin-bottom: 5px;
}
#vm-console-screenshot {
display: none;
}
...@@ -264,6 +264,31 @@ $(function() { ...@@ -264,6 +264,31 @@ $(function() {
return false; return false;
}); });
// screenshot
$("#getScreenshotButton").click(function() {
var vm = $(this).data("vm-pk");
var ct = $("#vm-console-screenshot");
$("i", this).addClass("icon-spinner icon-spin");
$(this).prop("disabled", true);
ct.slideDown();
var img = $("img", ct).prop("src", '/dashboard/vm/' + vm + '/screenshot/');
});
// if the image is loaded remove the spinning stuff
// note: this should not work if the image is cached, but it's not
// see: http://stackoverflow.com/a/3877079/1112653
$("#vm-console-screenshot img").load(function(e) {
$("#getScreenshotButton").prop("disabled", false)
.find("i").removeClass("icon-spinner icon-spin");
});
// screenshot close
$("#vm-console-screenshot button").click(function() {
$(this).parent("div").slideUp();
});
}); });
......
{% load i18n %} {% load i18n %}
<div class="btn-toolbar"> <div class="btn-toolbar">
<button id="sendCtrlAltDelButton" class="btn btn-danger small">{% trans "Send Ctrl+Alt+Del" %}</button> <button id="sendCtrlAltDelButton" class="btn btn-danger btn-sm">{% trans "Send Ctrl+Alt+Del" %}</button>
<button id="sendPasswordButton" class="btn btn-default small">{% trans "Type password" %}</button> <button id="sendPasswordButton" class="btn btn-default btn-sm">{% trans "Type password" %}</button>
<button id="getScreenshotButton" class="btn btn-info btn-sm pull-right" data-vm-pk="{{ instance.pk }}"><i class="icon-picture"></i> {% trans "Screenshot" %}</button>
</div> </div>
<div class="alert alert-info" id="noVNC_status"> <div class="alert alert-info" id="noVNC_status">
</div> </div>
<div id="vm-console-screenshot">
<button class="btn btn-danger btn-sm pull-right">{% trans "Close" %}</button>
<h3>{% trans "Screenshot" %}</h3>
<img />
<hr />
</div>
<canvas id="noVNC_canvas" width="640px" height="20px">Canvas not supported. <canvas id="noVNC_canvas" width="640px" height="20px">Canvas not supported.
</canvas> </canvas>
......
...@@ -34,6 +34,7 @@ from .views import ( ...@@ -34,6 +34,7 @@ from .views import (
GroupCreate, GroupCreate,
TemplateChoose, TemplateChoose,
UserCreationView, UserCreationView,
get_vm_screenshot
) )
urlpatterns = patterns( urlpatterns = patterns(
...@@ -84,6 +85,8 @@ urlpatterns = patterns( ...@@ -84,6 +85,8 @@ urlpatterns = patterns(
name='dashboard.views.vm-renew'), name='dashboard.views.vm-renew'),
url(r'^vm/activity/(?P<pk>\d+)/$', InstanceActivityDetail.as_view(), url(r'^vm/activity/(?P<pk>\d+)/$', InstanceActivityDetail.as_view(),
name='dashboard.views.vm-activity'), name='dashboard.views.vm-activity'),
url(r'^vm/(?P<pk>\d+)/screenshot/$', get_vm_screenshot,
name='dashboard.views.vm-get-screenshot'),
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(),
......
...@@ -1496,14 +1496,6 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView): ...@@ -1496,14 +1496,6 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
}) })
return self.render_to_response(context) return self.render_to_response(context)
def get_context_data(self, **kwargs):
context = super(NodeCreate, self).get_context_data(**kwargs)
# TODO acl
context.update({
})
return context
# TODO handle not ajax posts # TODO handle not ajax posts
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if not self.request.user.is_authenticated(): if not self.request.user.is_authenticated():
...@@ -1594,8 +1586,7 @@ class VmDelete(LoginRequiredMixin, DeleteView): ...@@ -1594,8 +1586,7 @@ class VmDelete(LoginRequiredMixin, DeleteView):
object = self.get_object() object = self.get_object()
if not object.has_level(self.request.user, 'owner'): if not object.has_level(self.request.user, 'owner'):
raise PermissionDenied() raise PermissionDenied()
# this is redundant now, but if we wanna add more to print
# we'll need this
context = super(VmDelete, self).get_context_data(**kwargs) context = super(VmDelete, self).get_context_data(**kwargs)
return context return context
...@@ -1634,12 +1625,6 @@ class NodeDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView): ...@@ -1634,12 +1625,6 @@ class NodeDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
else: else:
return ['dashboard/confirm/base-delete.html'] return ['dashboard/confirm/base-delete.html']
def get_context_data(self, **kwargs):
# this is redundant now, but if we wanna add more to print
# we'll need this
context = super(NodeDelete, self).get_context_data(**kwargs)
return context
# github.com/django/django/blob/master/django/views/generic/edit.py#L245 # github.com/django/django/blob/master/django/views/generic/edit.py#L245
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
object = self.get_object() object = self.get_object()
...@@ -2650,3 +2635,16 @@ class InterfaceDeleteView(DeleteView): ...@@ -2650,3 +2635,16 @@ class InterfaceDeleteView(DeleteView):
if redirect: if redirect:
return redirect return redirect
self.object.instance.get_absolute_url() self.object.instance.get_absolute_url()
@require_GET
def get_vm_screenshot(request, pk):
instance = get_object_or_404(Instance, pk=pk)
try:
image = instance.screenshot(instance=instance,
user=request.user).getvalue()
except:
# TODO handle this better
raise Http404()
return HttpResponse(image, mimetype="image/png")
...@@ -915,3 +915,9 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -915,3 +915,9 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
i.is_abortable_for_user = partial(i.is_abortable_for, i.is_abortable_for_user = partial(i.is_abortable_for,
user=user) user=user)
return acts return acts
def get_screenshot(self, timeout=5):
queue_name = self.get_remote_queue_name('vm')
return vm_tasks.screenshot.apply_async(args=[self.vm_name],
queue=queue_name
).get(timeout=timeout)
...@@ -543,3 +543,22 @@ class FlushOperation(NodeOperation): ...@@ -543,3 +543,22 @@ class FlushOperation(NodeOperation):
register_operation(FlushOperation) register_operation(FlushOperation)
class ScreenshotOperation(InstanceOperation):
activity_code_suffix = 'screenshot'
id = 'screenshot'
name = _("screenshot")
description = _("Get screenshot")
acl_level = "owner"
def check_precond(self):
super(ScreenshotOperation, self).check_precond()
if self.instance.status not in ['RUNNING']:
raise self.instance.WrongStateError(self.instance)
def _operation(self, instance, user):
return self.instance.get_screenshot(timeout=20)
register_operation(ScreenshotOperation)
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