diff --git a/circle/dashboard/static/dashboard/dashboard.css b/circle/dashboard/static/dashboard/dashboard.css index d33d8ce..30edfb7 100644 --- a/circle/dashboard/static/dashboard/dashboard.css +++ b/circle/dashboard/static/dashboard/dashboard.css @@ -684,7 +684,7 @@ textarea[name="list-new-namelist"] { .dashboard-vm-details-connect-command { /* for mobile view */ - margin-bottom: 20px; + padding-bottom: 20px; } #store-list-list { @@ -956,3 +956,11 @@ textarea[name="list-new-namelist"] { #vm-list-search-checkbox-span { cursor: pointer } + +#vm-details-resources-disk { + padding: 2px 5px 10px 5px; +} + +#vm-details-start-template-tour { + margin-right: 5px; +} diff --git a/circle/dashboard/static/dashboard/vm-details.js b/circle/dashboard/static/dashboard/vm-details.js index 3312ad2..d84c3ce 100644 --- a/circle/dashboard/static/dashboard/vm-details.js +++ b/circle/dashboard/static/dashboard/vm-details.js @@ -315,6 +315,24 @@ $(function() { if(Boolean($(this).data("disabled"))) return false; }); + $("#dashboard-tutorial-toggle").click(function() { + var box = $("#alert-new-template"); + var list = box.find("ol") + list.stop().slideToggle(function() { + var url = box.find("form").prop("action"); + var hidden = list.css("display") === "none"; + box.find("button i").prop("class", "fa fa-caret-" + (hidden ? "down" : "up")); + $.ajax({ + type: 'POST', + url: url, + data: {'hidden': hidden}, + headers: {"X-CSRFToken": getCookie('csrftoken')}, + success: function(re, textStatus, xhr) {} + }); + }); + return false; + }); + }); diff --git a/circle/dashboard/static/dashboard/vm-tour.js b/circle/dashboard/static/dashboard/vm-tour.js index 1641aba..6411522 100644 --- a/circle/dashboard/static/dashboard/vm-tour.js +++ b/circle/dashboard/static/dashboard/vm-tour.js @@ -1,5 +1,5 @@ $(function() { - $(".vm-details-start-template-tour").click(function() { + $("#vm-details-start-template-tour").click(function() { var intro = introJs(); intro.setOptions({ 'nextLabel': gettext("Next"), @@ -10,7 +10,7 @@ $(function() { intro.setOptions({ steps: [ { - element: document.querySelector("#vm-details-template-tour-button"), + element: document.querySelector("#vm-details-start-template-tour"), intro: "<p>" + gettext("Welcome to the template tutorial. In this quick tour, we gonna show you how to do the steps described above.") + "</p>" + "<p>" + gettext('For the next tour step press the "Next" button or the right arrow (or "Back" button/left arrow for the previous step).') + "</p>" + "<p>" + gettext("During the tour please don't try the functions because it may lead to graphical glitches, however " + @@ -27,19 +27,21 @@ $(function() { { element: document.querySelector('#vm-details-resources-form'), intro: '<p><strong>' + gettext("CPU priority") + ":</strong> " + gettext("higher is better") + "</p>" + - '<p><strong>' + gettext("CPU count") + ":</strong> " + gettext("number of CPU cores.") + "</p>" + - '<p><strong>' + gettext("RAM amount") + ":</strong> " + gettext("amount of RAM.") + "</p>", + '<p><strong>' + gettext("CPU count") + ":</strong> " + gettext("number of CPU cores.") + "</p>" + + '<p><strong>' + gettext("RAM amount") + ":</strong> " + gettext("amount of RAM.") + "</p>", + position: "top", }, { element: document.querySelector('#vm-details-resources-disk'), intro: gettext("You can add empty disks, download new ones and remove existing ones here."), + position: "top", }, { element: document.querySelector('a[href="#network"]'), intro: gettext('You can add new network interfaces or remove existing ones here.'), }, { - element: document.querySelector("#ops"), + element: document.querySelector('#ops a[class*="operation-deploy"]'), intro: gettext("Deploy the virtual machine."), }, { @@ -51,7 +53,7 @@ $(function() { intro: gettext("After you have connected to the virtual machine do your modifications then log off."), }, { - element: document.querySelector("#ops"), + element: document.querySelector('#ops a[class*="operation-save_as"]'), intro: gettext('Press the "Save as template" button and wait until the activity finishes.'), }, { diff --git a/circle/dashboard/templates/dashboard/vm-detail.html b/circle/dashboard/templates/dashboard/vm-detail.html index 5592e47..cda061e 100644 --- a/circle/dashboard/templates/dashboard/vm-detail.html +++ b/circle/dashboard/templates/dashboard/vm-detail.html @@ -6,14 +6,25 @@ {% block content %} {% if instance.is_base %} -<div class="alert alert-info alert-new-template"> - <strong>{% trans "This is the master vm of your new template" %}</strong> - <div id="vm-details-template-tour-button" class="pull-right"> - <a href="#" class="btn btn-default btn-lg pull-right vm-details-start-template-tour"> +<div class="alert alert-info alert-new-template" id="alert-new-template" style="position: relative;"> + <form action="{% url "dashboard.views.vm-toggle-tutorial" pk=instance.pk %}" + method="POST"> + {% csrf_token %} + <input name="hidden" type="hidden" + value="{{ hide_tutorial|yesno:"false,true" }}"/> + <button type="submit" + id="dashboard-tutorial-toggle" class="btn btn-sm pull-right btn-success"> + <i class="fa fa-caret-{% if hide_tutorial %}down{% else %}up{% endif %}"></i> + {% trans "Toggle tutorial panel" %} + </button> + + <a href="#" class="btn btn-default btn-sm pull-right" + id="vm-details-start-template-tour"> <i class="fa fa-play"></i> {% trans "Start template tutorial" %} </a> - </div> - <ol> + </form> + <strong>{% trans "This is the master vm of your new template" %}</strong> + <ol {% if hide_tutorial %}style="display: none;"{% endif %}> <li>{% trans "Modify the virtual machine to suit your needs <strong>(optional)</strong>" %} <ul> <li>{% trans "Change the description" %}</li> diff --git a/circle/dashboard/templates/dashboard/vm-detail/resources.html b/circle/dashboard/templates/dashboard/vm-detail/resources.html index e14cf6b..51e6d48 100644 --- a/circle/dashboard/templates/dashboard/vm-detail/resources.html +++ b/circle/dashboard/templates/dashboard/vm-detail/resources.html @@ -18,34 +18,28 @@ {% endif %} </form> - <hr /> - -<div class="row" id="vm-details-resources-disk"> - <div class="col-sm-11"> - <h3> - {% trans "Disks" %} - <div class="pull-right"> - <div id="disk-ops"> - {% include "dashboard/vm-detail/_disk-operations.html" %} - </div> +<div id="vm-details-resources-disk"> + <h3> + {% trans "Disks" %} + <div class="pull-right"> + <div id="disk-ops"> + {% include "dashboard/vm-detail/_disk-operations.html" %} </div> - </h3> - - <div class="row" id="vm-details-disk-add-for-form"></div> - - {% if not instance.disks.all %} - {% trans "No disks are added!" %} - {% endif %} - {% for d in instance.disks.all %} - <h4 class="list-group-item-heading dashboard-vm-details-network-h3"> - {% with long_remove=True %} - {% include "dashboard/_disk-list-element.html" %} - {% endwith %} - </h4> - {% endfor %} - </div> + </div> + </h3> + + {% if not instance.disks.all %} + {% trans "No disks are added." %} + {% endif %} + {% for d in instance.disks.all %} + <h4 class="list-group-item-heading dashboard-vm-details-network-h3"> + {% with long_remove=True %} + {% include "dashboard/_disk-list-element.html" %} + {% endwith %} + </h4> + {% endfor %} </div> {% if user.is_superuser %} diff --git a/circle/dashboard/urls.py b/circle/dashboard/urls.py index 00f5f69..1d2044c 100644 --- a/circle/dashboard/urls.py +++ b/circle/dashboard/urls.py @@ -45,6 +45,7 @@ from .views import ( VmTraitsUpdate, VmRawDataUpdate, GroupPermissionsView, LeaseAclUpdateView, + toggle_template_tutorial, ) autocomplete_light.autodiscover() @@ -97,6 +98,8 @@ urlpatterns = patterns( name='dashboard.views.vm-traits'), url(r'^vm/(?P<pk>\d+)/raw_data/$', VmRawDataUpdate.as_view(), name='dashboard.views.vm-raw-data'), + url(r'^vm/(?P<pk>\d+)/toggle_tutorial/$', toggle_template_tutorial, + name='dashboard.views.vm-toggle-tutorial'), url(r'^node/list/$', NodeList.as_view(), name='dashboard.views.node-list'), url(r'^node/(?P<pk>\d+)/$', NodeDetailView.as_view(), diff --git a/circle/dashboard/views.py b/circle/dashboard/views.py index 949e3ad..c5e07a6 100644 --- a/circle/dashboard/views.py +++ b/circle/dashboard/views.py @@ -361,13 +361,16 @@ class VmDetailView(CheckedDetailView): instance = context['instance'] user = self.request.user ops = get_operations(instance, user) + hide_tutorial = self.request.COOKIES.get( + "hide_tutorial_for_%s" % instance.pk) == "True" context.update({ 'graphite_enabled': settings.GRAPHITE_URL is not None, 'vnc_url': reverse_lazy("dashboard.views.detail-vnc", kwargs={'pk': self.object.pk}), 'ops': ops, 'op': {i.op: i for i in ops}, - 'connect_commands': user.profile.get_connect_commands(instance) + 'connect_commands': user.profile.get_connect_commands(instance), + 'hide_tutorial': hide_tutorial, }) # activity data @@ -3635,3 +3638,13 @@ def store_refresh_toplist(request): def absolute_url(url): return urljoin(settings.DJANGO_URL, url) + + +@login_required +def toggle_template_tutorial(request, pk): + hidden = request.POST.get("hidden", "").lower() == "true" + instance = get_object_or_404(Instance, pk=pk) + response = HttpResponseRedirect(instance.get_absolute_url()) + response.set_cookie( # for a week + "hide_tutorial_for_%s" % pk, hidden, 7 * 24 * 60 * 60) + return response