Commit e00e2679 by Kálmán Viktor

Merge branch 'feature-template-wizard' into 'master'

Feature Template Wizard

  pip install and migrate required

django-admin.py makemessages -d djangojs -l hu --settings=circle.settings.local --ignore=jsi18n/*
python manage.py compilejsi18n --settings=circle.settings.local -o dashboard/static/jsi18n -l hu
python manage.py compilejsi18n --settings=circle.settings.local -o dashboard/static/jsi18n -l en
parents be367e47 c9d678ce
...@@ -35,3 +35,6 @@ circle/*.pem ...@@ -35,3 +35,6 @@ circle/*.pem
# collected static files: # collected static files:
circle/static circle/static
circle/static_collected circle/static_collected
# jsi18n files
jsi18n
...@@ -253,6 +253,7 @@ THIRD_PARTY_APPS = ( ...@@ -253,6 +253,7 @@ THIRD_PARTY_APPS = (
'djcelery', 'djcelery',
'sizefield', 'sizefield',
'taggit', 'taggit',
'statici18n',
) )
# Apps specific for this project go here. # Apps specific for this project go here.
......
...@@ -27,8 +27,9 @@ from django.contrib.auth.forms import ( ...@@ -27,8 +27,9 @@ from django.contrib.auth.forms import (
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import ( from crispy_forms.layout import (
Layout, Div, BaseInput, Field, HTML, Submit, Fieldset, TEMPLATE_PACK Layout, Div, BaseInput, Field, HTML, Submit, Fieldset, TEMPLATE_PACK,
) )
from crispy_forms.utils import render_field from crispy_forms.utils import render_field
from django import forms from django import forms
from django.forms.widgets import TextInput from django.forms.widgets import TextInput
...@@ -44,9 +45,6 @@ from vm.models import ( ...@@ -44,9 +45,6 @@ from vm.models import (
) )
from .models import Profile from .models import Profile
VLANS = Vlan.objects.all()
DISKS = Disk.objects.exclude(type="qcow2-snap")
class VmCustomizeForm(forms.Form): class VmCustomizeForm(forms.Form):
name = forms.CharField() name = forms.CharField()
...@@ -479,35 +477,21 @@ class NodeForm(forms.ModelForm): ...@@ -479,35 +477,21 @@ class NodeForm(forms.ModelForm):
class TemplateForm(forms.ModelForm): class TemplateForm(forms.ModelForm):
networks = forms.ModelMultipleChoiceField( networks = forms.ModelMultipleChoiceField(
queryset=VLANS, required=False) queryset=None, required=False, label=_("Networks"))
system = forms.CharField(widget=forms.TextInput)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
parent = kwargs.pop("parent", None)
self.user = kwargs.pop("user", None) self.user = kwargs.pop("user", None)
super(TemplateForm, self).__init__(*args, **kwargs) super(TemplateForm, self).__init__(*args, **kwargs)
self.fields['networks'].queryset = Vlan.get_objects_with_level(
'user', self.user)
data = self.data.copy() data = self.data.copy()
data['owner'] = self.user.pk data['owner'] = self.user.pk
self.data = data self.data = data
if parent is not None: if self.instance.pk:
template = InstanceTemplate.objects.get(pk=parent) n = self.instance.interface_set.values_list("vlan", flat=True)
parent = template.__dict__
fields = ["system", "name", "num_cores", "boot_menu", "ram_size",
"priority", "access_method", "raw_data",
"arch", "description"]
for f in fields:
self.initial[f] = parent[f]
self.initial['lease'] = parent['lease_id']
self.initial['parent'] = template
self.initial['name'] = "Clone of %s" % self.initial['name']
self.for_networks = template
else:
self.for_networks = self.instance
if self.instance.pk or parent is not None:
n = self.for_networks.interface_set.values_list("vlan", flat=True)
self.initial['networks'] = n self.initial['networks'] = n
if not self.instance.pk and len(self.errors) < 1: if not self.instance.pk and len(self.errors) < 1:
...@@ -604,7 +588,7 @@ class TemplateForm(forms.ModelForm): ...@@ -604,7 +588,7 @@ class TemplateForm(forms.ModelForm):
Field('arch'), Field('arch'),
), ),
Fieldset( Fieldset(
"stuff", _("Virtual machine settings"),
Field('access_method'), Field('access_method'),
Field('boot_menu'), Field('boot_menu'),
Field('raw_data', **kwargs_raw_data), Field('raw_data', **kwargs_raw_data),
...@@ -614,7 +598,7 @@ class TemplateForm(forms.ModelForm): ...@@ -614,7 +598,7 @@ class TemplateForm(forms.ModelForm):
Field("system"), Field("system"),
), ),
Fieldset( Fieldset(
_("External"), _("External resources"),
Field("networks"), Field("networks"),
Field("lease"), Field("lease"),
Field("tags"), Field("tags"),
...@@ -626,6 +610,9 @@ class TemplateForm(forms.ModelForm): ...@@ -626,6 +610,9 @@ class TemplateForm(forms.ModelForm):
class Meta: class Meta:
model = InstanceTemplate model = InstanceTemplate
exclude = ('state', 'disks', ) exclude = ('state', 'disks', )
widgets = {
'system': forms.TextInput
}
class LeaseForm(forms.ModelForm): class LeaseForm(forms.ModelForm):
......
...@@ -35,14 +35,13 @@ from model_utils.models import TimeStampedModel ...@@ -35,14 +35,13 @@ from model_utils.models import TimeStampedModel
from model_utils.fields import StatusField from model_utils.fields import StatusField
from model_utils import Choices from model_utils import Choices
from vm.models import Instance
from acl.models import AclBase from acl.models import AclBase
logger = getLogger(__name__) logger = getLogger(__name__)
class Favourite(Model): class Favourite(Model):
instance = ForeignKey(Instance) instance = ForeignKey("vm.Instance")
user = ForeignKey(User) user = ForeignKey(User)
......
/* ===========================================================
# bootstrap-tour - v0.9.1
# http://bootstraptour.com
# ==============================================================
# Copyright 2012-2013 Ulrich Sossou
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
*/
.tour-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1100;background-color:#000;opacity:.8}.tour-step-backdrop{position:relative;z-index:1101;background:inherit}.tour-step-background{position:absolute;z-index:1100;background:inherit;border-radius:6px}.popover[class*=tour-]{z-index:1100}.popover[class*=tour-] .popover-navigation{padding:9px 14px}.popover[class*=tour-] .popover-navigation [data-role=end]{float:right}.popover[class*=tour-] .popover-navigation [data-role=prev],.popover[class*=tour-] .popover-navigation [data-role=next],.popover[class*=tour-] .popover-navigation [data-role=end]{cursor:pointer}.popover[class*=tour-] .popover-navigation [data-role=prev].disabled,.popover[class*=tour-] .popover-navigation [data-role=next].disabled,.popover[class*=tour-] .popover-navigation [data-role=end].disabled{cursor:default}.popover[class*=tour-].orphan{position:fixed;margin-top:0}.popover[class*=tour-].orphan .arrow{display:none}
\ No newline at end of file
...@@ -486,6 +486,55 @@ footer a, footer a:hover, footer a:visited { ...@@ -486,6 +486,55 @@ footer a, footer a:hover, footer a:visited {
margin-bottom: 20px; margin-bottom: 20px;
} }
.template-choose-list {
max-width: 600px;
}
.template-choose-list-element small {
display: none;
float: right;
padding-right: 50px;
}
.template-choose-list-element {
padding: 6px 10px;
cursor: pointer;
margin-bottom: 15px; /* bootstrap panel default is 20px */
}
.template-choose-list input[type="radio"] {
float: right;
}
/* template create vm help */
.alert-new-template {
background: #3071a9;
color: white;
font-size: 22px;
}
.alert-new-template ol {
margin-left: 25px;
}
/* bootstrap tour */
.tour-template {
max-width: 400px;
min-width: 270px;
font-size: 16px;
}
.tour-template {
text-align: justify;
}
.tour-template .popover-title {
font-weight: bold;
}
#vm-details-resources-form {
padding: 5px; /* it's nice this way in the tour */
}
.index-vm-list-name { .index-vm-list-name {
display: inline-block; display: inline-block;
max-width: 70%; max-width: 70%;
...@@ -546,6 +595,10 @@ footer a, footer a:hover, footer a:visited { ...@@ -546,6 +595,10 @@ footer a, footer a:hover, footer a:visited {
display: none; display: none;
} }
#ops {
padding: 15px 0 15px 15px;
}
#vm-access-table th:last-child, #vm-access-table td:last-child, #vm-access-table th:last-child, #vm-access-table td:last-child,
#template-access-table th:last-child, #template-access-table td:last-child { #template-access-table th:last-child, #template-access-table td:last-child {
text-align: center; text-align: center;
......
...@@ -33,6 +33,34 @@ $(function () { ...@@ -33,6 +33,34 @@ $(function () {
}); });
return false; return false;
}); });
$('.template-choose').click(function(e) {
$.ajax({
type: 'GET',
url: '/dashboard/template/choose/',
success: function(data) {
$('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
// check if user selected anything
$("#template-choose-next-button").click(function() {
var radio = $('input[type="radio"]:checked', "#template-choose-form").val();
if(!radio) {
$("#template-choose-alert").addClass("alert-warning")
.text(gettext("Select an option to proceed!"));
return false;
}
return true;
});
}
});
return false;
});
$('[href=#index-graph-view]').click(function (e) { $('[href=#index-graph-view]').click(function (e) {
var box = $(this).data('index-box'); var box = $(this).data('index-box');
$("#" + box + "-list-view").hide(); $("#" + box + "-list-view").hide();
...@@ -327,10 +355,10 @@ function massDeleteVm(data) { ...@@ -327,10 +355,10 @@ function massDeleteVm(data) {
type: 'POST', type: 'POST',
data: {'vms': data['data']['v']}, data: {'vms': data['data']['v']},
success: function(re, textStatus, xhr) { success: function(re, textStatus, xhr) {
for(var i=0; i< selected.length; i++) for(var i=0; i< data['data']['v'].length; i++)
$('.vm-list-table tbody tr').eq(data['data']['selected'][i]).fadeOut(500, function() { $('.vm-list-table tbody tr[data-vm-pk="' + data['data']['v'][i] + '"]').fadeOut(500, function() {
selected = [];
// reset group buttons // reset group buttons
selected = []
$('.vm-list-group-control a').attr('disabled', true); $('.vm-list-group-control a').attr('disabled', true);
$(this).remove(); $(this).remove();
}); });
......
$(function() {
$(".vm-details-start-template-tour").click(function() {
ttour = createTemplateTour();
ttour.init();
ttour.start();
});
});
function createTemplateTour() {
var ttour = new Tour({
storage: false,
name: "template",
template: "<div class='popover'>" +
"<div class='arrow'></div>" +
"<h3 class='popover-title'></h3>" +
"<div class='popover-content'></div>" +
"<div class='popover-navigation'>" +
"<div class='btn-group'>" +
"<button class='btn btn-sm btn-default' data-role='prev'>" +
'<i class="icon-chevron-left"></i> ' + gettext("Prev") + "</button> " +
"<button class='btn btn-sm btn-default' data-role='next'>" +
gettext("Next") + ' <i class="icon-chevron-right"></i></button> ' +
"<button class='btn btn-sm btn-default' data-role='pause-resume' data-pause-text='Pause' data-resume-text='Resume'>Pause</button> " +
"</div>" +
"<button class='btn btn-sm btn-default' data-role='end'>" +
gettext("End tour") + ' <i class="icon-flag-checkered"></i></button>' +
"</div>" +
"</div>",
});
ttour.addStep({
element: "#vm-details-template-tour-button",
title: gettext("Template Tutorial Tour"),
content: "<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 " +
"you can end the tour any time you want with the End Tour button!") + "</p>",
placement: "bottom",
backdrop: true,
});
ttour.addStep({
backdrop: true,
element: 'a[href="#home"]',
title: gettext("Home tab"),
content: gettext("In this tab you can tag your virtual machine and modify the name and description."),
placement: 'top',
onShow: function() {
$('a[href="#home"]').trigger("click");
},
});
ttour.addStep({
element: 'a[href="#resources"]',
title: gettext("Resources tab"),
backdrop: true,
placement: 'top',
content: gettext("On the resources tab you can edit the CPU/RAM options and add/remove disks!"),
onShow: function() {
$('a[href="#resources"]').trigger("click");
},
});
ttour.addStep({
element: '#vm-details-resources-form',
placement: 'top',
backdrop: true,
title: gettext("Resources"),
content: '<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>",
onShow: function() {
$('a[href="#resources"]').trigger("click");
},
});
ttour.addStep({
element: '#vm-details-resources-disk',
backdrop: true,
placement: 'top',
title: gettext("Disks"),
content: gettext("You can add empty disks, download new ones and remove existing ones here."),
onShow: function() {
$('a[href="#resources"]').trigger("click");
},
});
ttour.addStep({
element: 'a[href="#network"]',
backdrop: true,
placement: 'top',
title: gettext("Network tab"),
content: gettext('You can add new network interfaces or remove existing ones here.'),
onShow: function() {
$('a[href="#network"]').trigger("click");
},
});
ttour.addStep({
element: "#ops",
title: '<i class="icon-play"></i> ' + gettext("Deploy"),
placement: "left",
backdrop: true,
content: gettext("Deploy the virtual machine."),
});
ttour.addStep({
element: "#vm-info-pane",
title: gettext("Connect"),
placement: "top",
backdrop: true,
content: gettext("Use the connection string or connect with your choice of client!"),
});
ttour.addStep({
element: "#vm-info-pane",
placement: "top",
title: gettext("Customize the virtual machine"),
content: gettext("After you have connected to the virtual machine do your modifications then log off."),
});
ttour.addStep({
element: "#ops",
title: '<i class="icon-save"></i> ' + gettext("Save as"),
placement: "left",
backdrop: true,
content: gettext('Press the "Save as template" button and wait until the activity finishes.'),
});
ttour.addStep({
element: ".alert-new-template",
title: gettext("Finish"),
backdrop: true,
placement: "bottom",
content: gettext("This is the last message, if something is not clear you can do the the tour again!"),
});
return ttour;
}
...@@ -17,8 +17,10 @@ ...@@ -17,8 +17,10 @@
<script src="{{ STATIC_URL }}dashboard/js/jquery.knob.js"></script> <script src="{{ STATIC_URL }}dashboard/js/jquery.knob.js"></script>
<script src="{{ STATIC_URL}}dashboard/bootstrap-slider/bootstrap-slider.js"></script> <script src="{{ STATIC_URL}}dashboard/bootstrap-slider/bootstrap-slider.js"></script>
<link rel="stylesheet" href="{{ STATIC_URL }}dashboard/bootstrap-slider/slider.css"/> <link rel="stylesheet" href="{{ STATIC_URL }}dashboard/bootstrap-slider/slider.css"/>
<link href="{{ STATIC_URL }}dashboard/bootstrap-tour.min.css" rel="stylesheet">
<link href="{{ STATIC_URL }}dashboard/dashboard.css" rel="stylesheet"> <link href="{{ STATIC_URL }}dashboard/dashboard.css" rel="stylesheet">
<script src="{{ STATIC_URL }}dashboard/dashboard.js"></script> <script src="{{ STATIC_URL }}dashboard/dashboard.js"></script>
<script src="{{ STATIC_URL }}jsi18n/{{ LANGUAGE_CODE }}/djangojs.js"></script>
</head> </head>
<body> <body>
......
{% load i18n %}
<div class="alert alert-info" id="template-choose-alert">
{% trans "Customize an existing template or create a brand new one from scratch!" %}
</div>
<form action="{% url "dashboard.views.template-choose" %}" method="POST"
id="template-choose-form">
{% csrf_token %}
<div class="template-choose-list">
{% for t in templates %}
<div class="panel panel-default template-choose-list-element">
<input type="radio" name="parent" value="{{ t.pk }}"/>
{{ t.name }} - {{ t.system }}
<small>Cores: {{ t.num_cores }} RAM: {{ t.ram_size }}</small>
<div class="clearfix"></div>
</div>
{% endfor %}
<div class="panel panel-default template-choose-list-element">
<input type="radio" name="parent" value="base_vm"/>
{% trans "Create a new base VM without disk" %}
</div>
<button type="submit" id="template-choose-next-button" class="btn btn-success pull-right">{% trans "Next" %}</button>
<div class="clearfix"></div>
</div>
</form>
<script>
$(function() {
$(".template-choose-list-element").click(function() {
$("input", $(this)).prop("checked", true);
});
$(".template-choose-list-element").hover(
function() {
$("small", $(this)).stop().fadeIn(200);
},
function() {
$("small", $(this)).stop().fadeOut(200);
}
);
});
</script>
{% extends "dashboard/base.html" %}
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% block title-page %}{% trans "Create base VM" %}{% endblock %} {% if leases < 1 %}
<div class="alert alert-warning">
{% block content %} {% trans "You haven't created any leases yet, but you need one to create a template!" %}
<div class="row"> <a href="{% url "dashboard.views.lease-create" %}">{% trans "Create a new lease now." %}</a>
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<a class="pull-right btn btn-default btn-xs" href="{% url "dashboard.views.template-list" %}">{% trans "Back" %}</a>
<h3 class="no-margin"><i class="icon-desktop"></i> {% trans "Create base VM" %}</h3>
</div> </div>
<div class="panel-body"> {% endif %}
{% with form=form %}
{% with form=form %}
{% include "display-form-errors.html" %} {% include "display-form-errors.html" %}
{% endwith %} {% endwith %}
{% crispy form %} {% crispy form %}
</div>
</div>
</div>
</div>
<style> <style>
fieldset { fieldset {
...@@ -35,5 +26,3 @@ ...@@ -35,5 +26,3 @@
$("#hint_id_num_cores, #hint_id_priority, #hint_id_ram_size").hide(); $("#hint_id_num_cores, #hint_id_priority, #hint_id_ram_size").hide();
}); });
</script> </script>
{% endblock %}
...@@ -13,7 +13,9 @@ ...@@ -13,7 +13,9 @@
<br /> <br />
<div class="pull-right" style="margin-top: 15px;"> <div class="pull-right" style="margin-top: 15px;">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button>
<button id="confirmation-modal-button" type="button" class="btn btn-danger">{% trans "Delete" %}</button> <button id="confirmation-modal-button" type="button" class="btn btn-danger"
{% if disable_submit %}disabled{% endif %}
>{% trans "Delete" %}</button>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
......
...@@ -26,9 +26,12 @@ ...@@ -26,9 +26,12 @@
{% csrf_token %} {% csrf_token %}
<a class="btn btn-default">{% trans "Cancel" %}</a> <a class="btn btn-default">{% trans "Cancel" %}</a>
<input type="hidden" name="next" value="{{ request.GET.next }}"/> <input type="hidden" name="next" value="{{ request.GET.next }}"/>
<button class="btn btn-danger">{% trans "Yes" %}</button> <button class="btn btn-danger"
{% if disable_submit %}disabled{% endif %}
>{% trans "Yes" %}</button>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}
...@@ -22,15 +22,16 @@ ...@@ -22,15 +22,16 @@
<p> <p>
{% trans "You don't have any templates, however you can still start virtual machines and even save them as new templates!" %} {% trans "You don't have any templates, however you can still start virtual machines and even save them as new templates!" %}
</p> </p>
<p>
{% trans "The new button below creates a new base vm, please use only if necessary!" %}
</p>
</div> </div>
{% endfor %} {% endfor %}
<div href="#" class="list-group-item list-group-footer text-right"> <div href="#" class="list-group-item list-group-footer text-right">
<p> <p>
<a href="{% url "dashboard.views.template-list" %}" class="btn btn-primary btn-xs"><i class="icon-chevron-sign-right"></i> {% trans "show all" %}</a> <a href="{% url "dashboard.views.template-list" %}" class="btn btn-primary btn-xs">
<a href="{% url "dashboard.views.template-create" %}" class="btn btn-success btn-xs"><i class="icon-plus-sign"></i> {% trans "new" %}</a> <i class="icon-chevron-sign-right"></i> {% trans "show all" %}
</a>
<a href="{% url "dashboard.views.template-choose" %}" class="btn btn-success btn-xs template-choose">
<i class="icon-plus-sign"></i> {% trans "new" %}
</a>
</p> </p>
</div> </div>
</div> </div>
......
{% load i18n %} {% load i18n %}
<a href="{% url "dashboard.views.template-create" %}?parent={{ record.pk }}" id="template-list-clone-button" class="btn btn-default btn-xs" title="{% trans "Clone" %}">
<i class="icon-copy"></i>
</a>
<a href="{% url "dashboard.views.template-detail" pk=record.pk%}" id="template-list-edit-button" class="btn btn-default btn-xs" title="{% trans "Edit" %}"> <a href="{% url "dashboard.views.template-detail" pk=record.pk%}" id="template-list-edit-button" class="btn btn-default btn-xs" title="{% trans "Edit" %}">
<i class="icon-edit"></i> <i class="icon-edit"></i>
</a> </a>
......
...@@ -4,9 +4,40 @@ ...@@ -4,9 +4,40 @@
{% block title-page %}{{ instance.name }} | vm{% endblock %} {% block title-page %}{{ instance.name }} | vm{% endblock %}
{% block content %} {% 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">
<i class="icon-play"></i> {% trans "Start template tutorial" %}
</a>
</div>
<ol>
<li>{% trans "Modify the virtual machine to suit your needs <strong>(optional)</strong>" %}
<ul>
<li>{% trans "Change the name and description" %}</li>
<li>{% trans "Change the resources (CPU and RAM)" %}</li>
<li>{% trans "Attach or detach disks" %}</li>
<li>{% trans "Add or remove network interfaces" %}</li>
</ul>
</li>
<li>{% trans "Deploy the virtual machine" %}</li>
<li>{% trans "Connect to the machine" %}</li>
<li>{% trans "Do all the needed installations/customizations" %}</li>
<li>{% trans "Log off from the machine" %}</li>
<li>
{% trans "Press the Save as template button" %}
</li>
<li>
{% trans "Delete this virtual machine <strong>(optional)</strong>" %}
</li>
</ol>
</div>
{% endif %}
<div class="body-content"> <div class="body-content">
<div class="page-header"> <div class="page-header">
<div class="pull-right" style="padding-top: 15px;" id="ops"> <div class="pull-right" id="ops">
{% include "dashboard/vm-detail/_operations.html" %} {% include "dashboard/vm-detail/_operations.html" %}
</div> </div>
<h1> <h1>
...@@ -112,8 +143,10 @@ ...@@ -112,8 +143,10 @@
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
<script src="{{ STATIC_URL }}dashboard/bootstrap-tour.min.js"></script>
<script src="{{ STATIC_URL }}dashboard/vm-details.js"></script> <script src="{{ STATIC_URL }}dashboard/vm-details.js"></script>
<script src="{{ STATIC_URL }}dashboard/vm-common.js"></script> <script src="{{ STATIC_URL }}dashboard/vm-common.js"></script>
<script src="{{ STATIC_URL }}dashboard/vm-console.js"></script> <script src="{{ STATIC_URL }}dashboard/vm-console.js"></script>
<script src="{{ STATIC_URL }}dashboard/disk-list.js"></script> <script src="{{ STATIC_URL }}dashboard/disk-list.js"></script>
<script src="{{ STATIC_URL }}dashboard/vm-tour.js"></script>
{% endblock %} {% endblock %}
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
<hr /> <hr />
<div class="row"> <div class="row" id="vm-details-resources-disk">
<div class="col-sm-11"> <div class="col-sm-11">
<h3> <h3>
{% trans "Disks" %} {% trans "Disks" %}
......