Commit ac5f3142 by Őry Máté

Merge branch 'connect-via-client' into 'master'

Connect via Client support

fixes #222
parents dd87b7e7 19408a06
...@@ -368,7 +368,7 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE': ...@@ -368,7 +368,7 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
from shutilwhich import which from shutilwhich import which
from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
INSTALLED_APPS += ( INSTALLED_APPS += (
'djangosaml2', 'djangosaml2',
) )
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
...@@ -465,3 +465,6 @@ SESSION_COOKIE_NAME = "csessid%x" % (((getnode() // 139) ^ ...@@ -465,3 +465,6 @@ SESSION_COOKIE_NAME = "csessid%x" % (((getnode() // 139) ^
(getnode() % 983)) & 0xffff) (getnode() % 983)) & 0xffff)
MAX_NODE_RAM = get_env_variable("MAX_NODE_RAM", 1024) MAX_NODE_RAM = get_env_variable("MAX_NODE_RAM", 1024)
# Url to download the client: (e.g. http://circlecloud.org/client/download/)
CLIENT_DOWNLOAD_URL = get_env_variable('CLIENT_DOWNLOAD_URL', 'http://circlecloud.org/client/download/')
...@@ -383,6 +383,19 @@ $(function () { ...@@ -383,6 +383,19 @@ $(function () {
$('.notification-messages').load("/dashboard/notifications/"); $('.notification-messages').load("/dashboard/notifications/");
$('#notification-button a span[class*="badge-pulse"]').remove(); $('#notification-button a span[class*="badge-pulse"]').remove();
}); });
/* on the client confirmation button fire the clientInstalledAction */
$(document).on("click", "#client-check-button", function(event) {
var connectUri = $('#connect-uri').val();
clientInstalledAction(connectUri);
return false;
});
$("#dashboard-vm-details-connect-button").click(function(event) {
var connectUri = $(this).attr("href");
clientInstalledAction(connectUri);
return false;
});
}); });
function generateVmHTML(pk, name, host, icon, _status, fav, is_last) { function generateVmHTML(pk, name, host, icon, _status, fav, is_last) {
...@@ -589,6 +602,12 @@ function addModalConfirmation(func, data) { ...@@ -589,6 +602,12 @@ function addModalConfirmation(func, data) {
}); });
} }
function clientInstalledAction(location) {
setCookie('downloaded_client', true, 365 * 24 * 60 * 60, "/");
window.location.href = location;
$('#confirmation-modal').modal("hide");
}
// for AJAX calls // for AJAX calls
/** /**
* Getter for user cookies * Getter for user cookies
...@@ -611,6 +630,14 @@ function getCookie(name) { ...@@ -611,6 +630,14 @@ function getCookie(name) {
return cookieValue; return cookieValue;
} }
function setCookie(name, value, seconds, path) {
if (seconds!=null) {
var today = new Date();
var expire = new Date();
expire.setTime(today.getTime() + seconds);
}
document.cookie = name+"="+escape(value)+"; expires="+expire.toUTCString()+"; path="+path;
}
/* no js compatibility */ /* no js compatibility */
function noJS() { function noJS() {
......
...@@ -378,8 +378,14 @@ function checkNewActivity(runs) { ...@@ -378,8 +378,14 @@ function checkNewActivity(runs) {
} }
$("#vm-details-state span").html(data['human_readable_status'].toUpperCase()); $("#vm-details-state span").html(data['human_readable_status'].toUpperCase());
if(data['status'] == "RUNNING") { if(data['status'] == "RUNNING") {
if(data['connect_uri']) {
$("#dashboard-vm-details-connect-button").removeClass('disabled');
}
$("[data-target=#_console]").attr("data-toggle", "pill").attr("href", "#console").parent("li").removeClass("disabled"); $("[data-target=#_console]").attr("data-toggle", "pill").attr("href", "#console").parent("li").removeClass("disabled");
} else { } else {
if(data['connect_uri']) {
$("#dashboard-vm-details-connect-button").addClass('disabled');
}
$("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled"); $("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled");
} }
......
...@@ -115,7 +115,7 @@ $(function() { ...@@ -115,7 +115,7 @@ $(function() {
$('#confirmation-modal').modal("hide"); $('#confirmation-modal').modal("hide");
updateStatuses(1); updateStatuses(1);
/* if there are messages display them */ /* if there are messages display them */
if(data.messages && data.messages.length > 0) { if(data.messages && data.messages.length > 0) {
addMessage(data.messages.join("<br />"), "danger"); addMessage(data.messages.join("<br />"), "danger");
...@@ -123,7 +123,7 @@ $(function() { ...@@ -123,7 +123,7 @@ $(function() {
}, },
error: function(xhr, textStatus, error) { error: function(xhr, textStatus, error) {
$('#confirmation-modal').modal("hide"); $('#confirmation-modal').modal("hide");
if (xhr.status == 500) { if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger"); addMessage("500 Internal Server Error", "danger");
} else { } else {
...@@ -145,7 +145,7 @@ $(function() { ...@@ -145,7 +145,7 @@ $(function() {
// this didn't work ;; // this didn't work ;;
// var th = $("this").find("th"); // var th = $("this").find("th");
$(".table-sorting").hide(); $(".table-sorting").hide();
$(".vm-list-table thead th i").remove(); $(".vm-list-table thead th i").remove();
var icon_html = '<i class="fa fa-sort-' + (data.direction == "desc" ? "desc" : "asc") + ' pull-right"></i>'; var icon_html = '<i class="fa fa-sort-' + (data.direction == "desc" ? "desc" : "asc") + ' pull-right"></i>';
...@@ -160,13 +160,13 @@ $(function() { ...@@ -160,13 +160,13 @@ $(function() {
}); });
$(document).on("click", ".mass-migrate-node", function() { $(document).on("click", ".mass-migrate-node", function() {
$(this).find('input[type="radio"]').prop("checked", true); $(this).find('input[type="radio"]').prop("checked", true);
}); });
if(checkStatusUpdate() || $("#vm-list-table tbody tr").length >= 100) { if(checkStatusUpdate() || $("#vm-list-table tbody tr").length >= 100) {
updateStatuses(1); updateStatuses(1);
} }
}); });
...@@ -209,10 +209,10 @@ function updateStatuses(runs) { ...@@ -209,10 +209,10 @@ function updateStatuses(runs) {
$(this).remove(); $(this).remove();
} }
}); });
if(checkStatusUpdate()) { if(checkStatusUpdate()) {
setTimeout( setTimeout(
function() {updateStatuses(runs + 1)}, function() {updateStatuses(runs + 1)},
1000 + Math.exp(runs * 0.05) 1000 + Math.exp(runs * 0.05)
); );
} }
...@@ -239,7 +239,7 @@ function collectIds(rows) { ...@@ -239,7 +239,7 @@ function collectIds(rows) {
for(var i = 0; i < rows.length; i++) { for(var i = 0; i < rows.length; i++) {
ids.push(rows[i].vm); ids.push(rows[i].vm);
} }
return ids; return ids;
} }
function setRowColor(row) { function setRowColor(row) {
......
{% load i18n %}
<p>
{% blocktrans %}
To effortlessly connect to all kind of virtual machines you have to install the <strong>CIRCLE Client</strong>.
{% endblocktrans %}
</p>
<p class="text-info">
{% blocktrans %}
To install the <strong>CIRCLE Client</strong> click on the <strong>Download the Client</strong> button.
The button takes you to the installation detail page, where you can choose your operating system and start
the download or read more detailed information about the <strong>Client</strong>. The program can be installed on Windows XP (and above)
or Debian based Linux operating systems. To successfully install the client you have to have admin (root or elevated) rights.
After the installation complete clicking on the <strong>I have the Client installed</strong> button will launch the appropriate tool
designed for that connection with necessarily predefined configurations. This option will also save your answer and this prompt about
installation will not pop up again.
{% endblocktrans %}
</p>
<br>
<div class="pull-right">
<form method="POST" id="dashboard-client-check" action="">
{% csrf_token %}
<a class="btn btn-default" href="{% url "dashboard.views.detail" pk=instance.pk %}" data-dismiss="modal">{% trans "Cancel" %}</a>
<a class="btn btn-info" href="{{ client_download_url }}" traget="_blank">{% trans "Download the Client" %}</a>
<button data-dismiss="modal" id="client-check-button" type="submit" class="btn btn-success" title="{% trans "I downloaded and installed the client and I want to connect using it. This choice will be saved to your compuer" %}">
<i class="fa fa-external-link"></i> {% trans "I have the Client installed" %}
</button>
<input id="connect-uri" name="connect-uri" type="hidden" value="{% if instance.get_connect_uri %}{{ instance.get_connect_uri}}{% endif %}" />
<input name="vm" type="hidden" value="{% if instance.get_connect_uri %}{{ instance.pk}}{% endif %}" />
</form>
</div>
\ No newline at end of file
<a data-group-pk="{{ record.pk }}" <a data-group-pk="{{ record.pk }}"
class="btn btn-danger btn-xs real-link group-delete" class="btn btn-danger btn-xs real-link group-delete"
href="{% url "dashboard.views.delete-group" pk=record.pk %}?next={{ request.path }}"> href="{% url "dashboard.views.delete-group" pk=record.pk %}?next={{ request.path }}">
<i class="fa fa-trash-o"></i> <i class="fa fa-trash-o"></i>
</a> </a>
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
<div class="panel-heading"> <div class="panel-heading">
<div class="pull-right toolbar"> <div class="pull-right toolbar">
<div class="btn-group"> <div class="btn-group">
<a href="#index-graph-view" data-index-box="vm" class="btn btn-default btn-xs" <a href="#index-graph-view" data-index-box="vm" class="btn btn-default btn-xs"
data-container="body" data-container="body"
title="{% trans "summary view" %}"><i class="fa fa-dashboard"></i></a> title="{% trans "summary view" %}"><i class="fa fa-dashboard"></i></a>
<a href="#index-list-view" data-index-box="vm" class="btn btn-default btn-xs disabled" <a href="#index-list-view" data-index-box="vm" class="btn btn-default btn-xs disabled"
data-container="body" data-container="body"
title="{% trans "list view" %}"><i class="fa fa-list"></i></a> title="{% trans "list view" %}"><i class="fa fa-list"></i></a>
</div> </div>
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
<div class="row"> <div class="row">
<form action="{% url "dashboard.views.vm-list" %}" method="GET" id="dashboard-vm-search-form"> <form action="{% url "dashboard.views.vm-list" %}" method="GET" id="dashboard-vm-search-form">
<div class="col-sm-6 col-xs-6 input-group input-group-sm"> <div class="col-sm-6 col-xs-6 input-group input-group-sm">
<input id="dashboard-vm-search-input" type="text" class="form-control" name="s" <input id="dashboard-vm-search-input" type="text" class="form-control" name="s"
placeholder="{% trans "Search..." %}" /> placeholder="{% trans "Search..." %}" />
<div class="input-group-btn"> <div class="input-group-btn">
<button type="submit" class="form-control btn btn-primary"><i class="fa fa-search"></i></button> <button type="submit" class="form-control btn btn-primary"><i class="fa fa-search"></i></button>
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
{{ search_form.s }} {{ search_form.s }}
<div class="input-group-btn"> <div class="input-group-btn">
{{ search_form.stype }} {{ search_form.stype }}
<button type="submit" class="btn btn-primary input-tags"> <button type="submit" class="btn btn-primary input-tags">
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
</button> </button>
</div> </div>
......
...@@ -133,8 +133,21 @@ ...@@ -133,8 +133,21 @@
<i class="fa fa-copy" title="{% trans "Select all" %}"></i> <i class="fa fa-copy" title="{% trans "Select all" %}"></i>
</span> </span>
</div> </div>
{% endfor %} {% endfor %}
{% if instance.get_connect_uri %}
<div id="dashboard-vm-details-connect" class="operation-wrapper">
{% if client_download %}
<a id="dashboard-vm-details-connect-button" class="btn btn-xs btn-default operation " href="{{ instance.get_connect_uri}}" title="{% trans "Connect via the CIRCLE Client" %}">
<i class="fa fa-external-link"></i> {% trans "Connect" %}
</a>
<a href="{% url "dashboard.views.client-check" %}?vm={{ instance.pk }}">{% trans "Download client" %}</a>
{% else %}
<a id="dashboard-vm-details-connect-download-button" class="btn btn-xs btn-default operation " href="{% url "dashboard.views.client-check" %}?vm={{ instance.pk }}" title="{% trans "Download the CIRCLE Client" %}">
<i class="fa fa-external-link"></i> {% trans "Connect (download client)" %}
</a>
{% endif %}
</div>
{% endif %}
</div> </div>
<div class="col-md-8" id="vm-detail-pane"> <div class="col-md-8" id="vm-detail-pane">
<div class="panel panel-default" id="vm-detail-panel"> <div class="panel panel-default" id="vm-detail-panel">
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<strong>{% trans "Group actions" %}</strong> <strong>{% trans "Group actions" %}</strong>
<button id="vm-list-group-select-all" class="btn btn-info btn-xs">{% trans "Select all" %}</button> <button id="vm-list-group-select-all" class="btn btn-info btn-xs">{% trans "Select all" %}</button>
{% for o in ops %} {% for o in ops %}
<a href="{{ o.get_url }}" class="btn btn-xs btn-{{ o.effect }} mass-operation" <a href="{{ o.get_url }}" class="btn btn-xs btn-{{ o.effect }} mass-operation"
title="{{ o.name|capfirst }}" disabled> title="{{ o.name|capfirst }}" disabled>
<i class="fa fa-{{ o.icon }}"></i> <i class="fa fa-{{ o.icon }}"></i>
</a> </a>
...@@ -34,12 +34,12 @@ ...@@ -34,12 +34,12 @@
<div class="input-group-btn"> <div class="input-group-btn">
{{ search_form.stype }} {{ search_form.stype }}
</div> </div>
<label class="input-group-addon input-tags" title="{% trans "Include deleted VMs" %}" <label class="input-group-addon input-tags" title="{% trans "Include deleted VMs" %}"
id="vm-list-search-checkbox-span" data-container="body"> id="vm-list-search-checkbox-span" data-container="body">
{{ search_form.include_deleted }} {{ search_form.include_deleted }}
</label> </label>
<div class="input-group-btn"> <div class="input-group-btn">
<button type="submit" class="btn btn-primary input-tags"> <button type="submit" class="btn btn-primary input-tags">
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
</button> </button>
</div> </div>
......
...@@ -45,6 +45,7 @@ from .views import ( ...@@ -45,6 +45,7 @@ from .views import (
VmTraitsUpdate, VmRawDataUpdate, VmTraitsUpdate, VmRawDataUpdate,
GroupPermissionsView, GroupPermissionsView,
LeaseAclUpdateView, LeaseAclUpdateView,
ClientCheck,
) )
autocomplete_light.autodiscover() autocomplete_light.autodiscover()
...@@ -204,4 +205,6 @@ urlpatterns = patterns( ...@@ -204,4 +205,6 @@ urlpatterns = patterns(
name="dashboard.views.store-new-directory"), name="dashboard.views.store-new-directory"),
url(r"^store/refresh_toplist$", store_refresh_toplist, url(r"^store/refresh_toplist$", store_refresh_toplist,
name="dashboard.views.store-refresh-toplist"), name="dashboard.views.store-refresh-toplist"),
url(r"^client/check$", ClientCheck.as_view(),
name="dashboard.views.client-check"),
) )
...@@ -411,6 +411,9 @@ class VmDetailView(CheckedDetailView): ...@@ -411,6 +411,9 @@ class VmDetailView(CheckedDetailView):
context['can_change_resources'] = self.request.user.has_perm( context['can_change_resources'] = self.request.user.has_perm(
"vm.change_resources") "vm.change_resources")
# client info
context['client_download'] = self.request.COOKIES.get(
'downloaded_client')
# can link template # can link template
context['can_link_template'] = ( context['can_link_template'] = (
instance.template and instance.template.has_level(user, "operator") instance.template and instance.template.has_level(user, "operator")
...@@ -1531,6 +1534,37 @@ class GroupAclUpdateView(AclUpdateView): ...@@ -1531,6 +1534,37 @@ class GroupAclUpdateView(AclUpdateView):
return super(GroupAclUpdateView, self).get_object().profile return super(GroupAclUpdateView, self).get_object().profile
class ClientCheck(LoginRequiredMixin, TemplateView):
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/_modal.html']
else:
return ['dashboard/nojs-wrapper.html']
def get_context_data(self, *args, **kwargs):
context = super(ClientCheck, self).get_context_data(*args, **kwargs)
context.update({
'box_title': _('About CIRCLE Client'),
'ajax_title': False,
'client_download_url': settings.CLIENT_DOWNLOAD_URL,
'template': "dashboard/_client-check.html",
'instance': get_object_or_404(
Instance, pk=self.request.GET.get('vm')),
})
if not context['instance'].has_level(self.request.user, 'operator'):
raise PermissionDenied()
return context
def post(self, request, *args, **kwargs):
instance = get_object_or_404(Instance, pk=request.POST.get('vm'))
if not instance.has_level(request.user, 'operator'):
raise PermissionDenied()
response = HttpResponseRedirect(instance.get_absolute_url())
response.set_cookie('downloaded_client', 'True', 365 * 24 * 60 * 60)
return response
class TemplateChoose(LoginRequiredMixin, TemplateView): class TemplateChoose(LoginRequiredMixin, TemplateView):
def get_template_names(self): def get_template_names(self):
...@@ -2675,6 +2709,7 @@ def vm_activity(request, pk): ...@@ -2675,6 +2709,7 @@ def vm_activity(request, pk):
if not show_all: if not show_all:
activities = activities[:10] activities = activities[:10]
response['connect_uri'] = instance.get_connect_uri()
response['human_readable_status'] = instance.get_status_display() response['human_readable_status'] = instance.get_status_display()
response['status'] = instance.status response['status'] = instance.status
response['icon'] = instance.get_status_icon() response['icon'] = instance.get_status_icon()
......
...@@ -6,7 +6,7 @@ msgid "" ...@@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-09-03 12:57+0200\n" "POT-Creation-Date: 2014-09-03 18:49+0200\n"
"PO-Revision-Date: 2014-09-03 12:51+0200\n" "PO-Revision-Date: 2014-09-03 12:51+0200\n"
"Last-Translator: Mate Ory <ory.mate@ik.bme.hu>\n" "Last-Translator: Mate Ory <ory.mate@ik.bme.hu>\n"
"Language-Team: Hungarian <cloud@ik.bme.hu>\n" "Language-Team: Hungarian <cloud@ik.bme.hu>\n"
......
...@@ -607,9 +607,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -607,9 +607,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
port = self.get_connect_port(use_ipv6=use_ipv6) port = self.get_connect_port(use_ipv6=use_ipv6)
host = self.get_connect_host(use_ipv6=use_ipv6) host = self.get_connect_host(use_ipv6=use_ipv6)
proto = self.access_method proto = self.access_method
if proto == 'ssh': return ('circle:%(proto)s:cloud:%(pw)s:%(host)s:%(port)d' %
proto = 'sshterm'
return ('%(proto)s:cloud:%(pw)s:%(host)s:%(port)d' %
{'port': port, 'proto': proto, 'pw': self.pw, {'port': port, 'proto': proto, 'pw': self.pw,
'host': host}) 'host': host})
except: except:
......
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