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
/* ===========================================================
# 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.
*/
!function(a,b){var c,d;return d=b.document,c=function(){function c(c){this._options=a.extend({name:"tour",steps:[],container:"body",keyboard:!0,storage:b.localStorage,debug:!1,backdrop:!1,redirect:!0,orphan:!1,duration:!1,basePath:"",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'>&laquo; Prev</button> <button class='btn btn-sm btn-default' data-role='next'>Next &raquo;</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'>End tour</button> </div> </div>",afterSetState:function(){},afterGetState:function(){},afterRemoveState:function(){},onStart:function(){},onEnd:function(){},onShow:function(){},onShown:function(){},onHide:function(){},onHidden:function(){},onNext:function(){},onPrev:function(){},onPause:function(){},onResume:function(){}},c),this._force=!1,this._inited=!1,this.backdrop={overlay:null,$element:null,$background:null,backgroundShown:!1,overlayElementShown:!1}}return c.prototype.addSteps=function(a){var b,c,d;for(c=0,d=a.length;d>c;c++)b=a[c],this.addStep(b);return this},c.prototype.addStep=function(a){return this._options.steps.push(a),this},c.prototype.getStep=function(b){return null!=this._options.steps[b]?a.extend({id:"step-"+b,path:"",placement:"right",title:"",content:"<p></p>",next:b===this._options.steps.length-1?-1:b+1,prev:b-1,animation:!0,container:this._options.container,backdrop:this._options.backdrop,redirect:this._options.redirect,orphan:this._options.orphan,duration:this._options.duration,template:this._options.template,onShow:this._options.onShow,onShown:this._options.onShown,onHide:this._options.onHide,onHidden:this._options.onHidden,onNext:this._options.onNext,onPrev:this._options.onPrev,onPause:this._options.onPause,onResume:this._options.onResume},this._options.steps[b]):void 0},c.prototype.init=function(a){return this._force=a,this.ended()?(this._debug("Tour ended, init prevented."),this):(this.setCurrentStep(),this._initMouseNavigation(),this._initKeyboardNavigation(),this._onResize(function(a){return function(){return a.showStep(a._current)}}(this)),null!==this._current&&this.showStep(this._current),this._inited=!0,this)},c.prototype.start=function(a){var b;return null==a&&(a=!1),this._inited||this.init(a),null===this._current&&(b=this._makePromise(null!=this._options.onStart?this._options.onStart(this):void 0),this._callOnPromiseDone(b,this.showStep,0)),this},c.prototype.next=function(){var a;return a=this.hideStep(this._current),this._callOnPromiseDone(a,this._showNextStep)},c.prototype.prev=function(){var a;return a=this.hideStep(this._current),this._callOnPromiseDone(a,this._showPrevStep)},c.prototype.goTo=function(a){var b;return b=this.hideStep(this._current),this._callOnPromiseDone(b,this.showStep,a)},c.prototype.end=function(){var c,e;return c=function(c){return function(){return a(d).off("click.tour-"+c._options.name),a(d).off("keyup.tour-"+c._options.name),a(b).off("resize.tour-"+c._options.name),c._setState("end","yes"),c._inited=!1,c._force=!1,c._clearTimer(),null!=c._options.onEnd?c._options.onEnd(c):void 0}}(this),e=this.hideStep(this._current),this._callOnPromiseDone(e,c)},c.prototype.ended=function(){return!this._force&&!!this._getState("end")},c.prototype.restart=function(){return this._removeState("current_step"),this._removeState("end"),this.setCurrentStep(0),this.start()},c.prototype.pause=function(){var a;return a=this.getStep(this._current),a&&a.duration?(this._paused=!0,this._duration-=(new Date).getTime()-this._start,b.clearTimeout(this._timer),this._debug("Paused/Stopped step "+(this._current+1)+" timer ("+this._duration+" remaining)."),null!=a.onPause?a.onPause(this,this._duration):void 0):this},c.prototype.resume=function(){var a;return a=this.getStep(this._current),a&&a.duration?(this._paused=!1,this._start=(new Date).getTime(),this._duration=this._duration||a.duration,this._timer=b.setTimeout(function(a){return function(){return a._isLast()?a.next():a.end()}}(this),this._duration),this._debug("Started step "+(this._current+1)+" timer with duration "+this._duration),null!=a.onResume&&this._duration!==a.duration?a.onResume(this,this._duration):void 0):this},c.prototype.hideStep=function(b){var c,d,e;return(e=this.getStep(b))?(this._clearTimer(),d=this._makePromise(null!=e.onHide?e.onHide(this,b):void 0),c=function(c){return function(){var d;return d=a(e.element),d.data("bs.popover")||d.data("popover")||(d=a("body")),d.popover("destroy").removeClass("tour-"+c._options.name+"-element tour-"+c._options.name+"-"+b+"-element"),e.reflex&&d.css("cursor","").off("click.tour-"+c._options.name),e.backdrop&&c._hideBackdrop(),null!=e.onHidden?e.onHidden(c):void 0}}(this),this._callOnPromiseDone(d,c),d):void 0},c.prototype.showStep=function(a){var b,c,e,f;return this.ended()?(this._debug("Tour ended, showStep prevented."),this):(f=this.getStep(a))?(e=a<this._current,b=this._makePromise(null!=f.onShow?f.onShow(this,a):void 0),c=function(b){return function(){var c,g;if(b.setCurrentStep(a),g=function(){switch({}.toString.call(f.path)){case"[object Function]":return f.path();case"[object String]":return this._options.basePath+f.path;default:return f.path}}.call(b),c=[d.location.pathname,d.location.hash].join(""),b._isRedirect(g,c))return void b._redirect(f,g);if(b._isOrphan(f)){if(!f.orphan)return b._debug("Skip the orphan step "+(b._current+1)+". Orphan option is false and the element doesn't exist or is hidden."),void(e?b._showPrevStep():b._showNextStep());b._debug("Show the orphan step "+(b._current+1)+". Orphans option is true.")}return f.backdrop&&b._showBackdrop(b._isOrphan(f)?void 0:f.element),b._scrollIntoView(f.element,function(){return null!=f.element&&f.backdrop&&b._showOverlayElement(f.element),b._showPopover(f,a),null!=f.onShown&&f.onShown(b),b._debug("Step "+(b._current+1)+" of "+b._options.steps.length)}),f.duration?b.resume():void 0}}(this),this._callOnPromiseDone(b,c),b):void 0},c.prototype.getCurrentStep=function(){return this._current},c.prototype.setCurrentStep=function(a){return null!=a?(this._current=a,this._setState("current_step",a)):(this._current=this._getState("current_step"),this._current=null===this._current?null:parseInt(this._current,10)),this},c.prototype._setState=function(a,b){var c,d;if(this._options.storage){d=""+this._options.name+"_"+a;try{this._options.storage.setItem(d,b)}catch(e){c=e,c.code===DOMException.QUOTA_EXCEEDED_ERR&&this.debug("LocalStorage quota exceeded. State storage failed.")}return this._options.afterSetState(d,b)}return null==this._state&&(this._state={}),this._state[a]=b},c.prototype._removeState=function(a){var b;return this._options.storage?(b=""+this._options.name+"_"+a,this._options.storage.removeItem(b),this._options.afterRemoveState(b)):null!=this._state?delete this._state[a]:void 0},c.prototype._getState=function(a){var b,c;return this._options.storage?(b=""+this._options.name+"_"+a,c=this._options.storage.getItem(b)):null!=this._state&&(c=this._state[a]),(void 0===c||"null"===c)&&(c=null),this._options.afterGetState(a,c),c},c.prototype._showNextStep=function(){var a,b,c;return c=this.getStep(this._current),b=function(a){return function(){return a.showStep(c.next)}}(this),a=this._makePromise(null!=c.onNext?c.onNext(this):void 0),this._callOnPromiseDone(a,b)},c.prototype._showPrevStep=function(){var a,b,c;return c=this.getStep(this._current),b=function(a){return function(){return a.showStep(c.prev)}}(this),a=this._makePromise(null!=c.onPrev?c.onPrev(this):void 0),this._callOnPromiseDone(a,b)},c.prototype._debug=function(a){return this._options.debug?b.console.log("Bootstrap Tour '"+this._options.name+"' | "+a):void 0},c.prototype._isRedirect=function(a,b){return null!=a&&""!==a&&("[object RegExp]"===toString.call(a)&&!a.test(b)||"[object String]"===toString.call(a)&&a.replace(/\?.*$/,"").replace(/\/?$/,"")!==b.replace(/\/?$/,""))},c.prototype._redirect=function(b,c){return a.isFunction(b.redirect)?b.redirect.call(this,c):b.redirect===!0?(this._debug("Redirect to "+c),d.location.href=c):void 0},c.prototype._isOrphan=function(b){return null==b.element||!a(b.element).length||a(b.element).is(":hidden")&&"http://www.w3.org/2000/svg"!==a(b.element)[0].namespaceURI},c.prototype._isLast=function(){return this._current<this._options.steps.length-1},c.prototype._showPopover=function(b,c){var d,e,f,g,h,i;return i=a.extend({},this._options),f=a(a.isFunction(b.template)?b.template(c,b):b.template),e=f.find(".popover-navigation"),h=this._isOrphan(b),h&&(b.element="body",b.placement="top",f=f.addClass("orphan")),d=a(b.element),f.addClass("tour-"+this._options.name+" tour-"+this._options.name+"-"+c),d.addClass("tour-"+this._options.name+"-element tour-"+this._options.name+"-"+c+"-element"),b.options&&a.extend(i,b.options),b.reflex&&d.css("cursor","pointer").on("click.tour-"+this._options.name,function(a){return function(){return a._isLast()?a.next():a.end()}}(this)),b.prev<0&&e.find("[data-role='prev']").addClass("disabled"),b.next<0&&e.find("[data-role='next']").addClass("disabled"),b.duration||e.find("[data-role='pause-resume']").remove(),b.template=f.clone().wrap("<div>").parent().html(),d.popover({placement:b.placement,trigger:"manual",title:b.title,content:b.content,html:!0,animation:b.animation,container:b.container,template:b.template,selector:b.element}).popover("show"),g=d.data("bs.popover")?d.data("bs.popover").tip():d.data("popover").tip(),g.attr("id",b.id),this._reposition(g,b),h?this._center(g):void 0},c.prototype._reposition=function(b,c){var e,f,g,h,i,j,k;if(h=b[0].offsetWidth,f=b[0].offsetHeight,k=b.offset(),i=k.left,j=k.top,e=a(d).outerHeight()-k.top-b.outerHeight(),0>e&&(k.top=k.top+e),g=a("html").outerWidth()-k.left-b.outerWidth(),0>g&&(k.left=k.left+g),k.top<0&&(k.top=0),k.left<0&&(k.left=0),b.offset(k),"bottom"===c.placement||"top"===c.placement){if(i!==k.left)return this._replaceArrow(b,2*(k.left-i),h,"left")}else if(j!==k.top)return this._replaceArrow(b,2*(k.top-j),f,"top")},c.prototype._center=function(c){return c.css("top",a(b).outerHeight()/2-c.outerHeight()/2)},c.prototype._replaceArrow=function(a,b,c,d){return a.find(".arrow").css(d,b?50*(1-b/c)+"%":"")},c.prototype._scrollIntoView=function(c,d){var e,f,g,h,i,j;return e=a(c),e.length?(f=a(b),h=e.offset().top,j=f.height(),i=Math.max(0,h-j/2),this._debug("Scroll into view. ScrollTop: "+i+". Element offset: "+h+". Window height: "+j+"."),g=0,a("body,html").stop(!0,!0).animate({scrollTop:Math.ceil(i)},function(a){return function(){return 2===++g?(d(),a._debug("Scroll into view. Animation end element offset: "+e.offset().top+". Window height: "+f.height()+".")):void 0}}(this))):d()},c.prototype._onResize=function(c,d){return a(b).on("resize.tour-"+this._options.name,function(){return clearTimeout(d),d=setTimeout(c,100)})},c.prototype._initMouseNavigation=function(){var b;return b=this,a(d).off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']:not(.disabled)").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']:not(.disabled)").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']").on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']:not(.disabled)",function(a){return function(b){return b.preventDefault(),a.next()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']:not(.disabled)",function(a){return function(b){return b.preventDefault(),a.prev()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']",function(a){return function(b){return b.preventDefault(),a.end()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']",function(c){var d;return c.preventDefault(),d=a(this),d.text(d.data(b._paused?"pause-text":"resume-text")),b._paused?b.resume():b.pause()})},c.prototype._initKeyboardNavigation=function(){return this._options.keyboard?a(d).on("keyup.tour-"+this._options.name,function(a){return function(b){if(b.which)switch(b.which){case 39:return b.preventDefault(),a._isLast()?a.next():a.end();case 37:if(b.preventDefault(),a._current>0)return a.prev();break;case 27:return b.preventDefault(),a.end()}}}(this)):void 0},c.prototype._makePromise=function(b){return b&&a.isFunction(b.then)?b:null},c.prototype._callOnPromiseDone=function(a,b,c){return a?a.then(function(a){return function(){return b.call(a,c)}}(this)):b.call(this,c)},c.prototype._showBackdrop=function(){return this.backdrop.backgroundShown?void 0:(this.backdrop=a("<div/>",{"class":"tour-backdrop"}),this.backdrop.backgroundShown=!0,a("body").append(this.backdrop))},c.prototype._hideBackdrop=function(){return this._hideOverlayElement(),this._hideBackground()},c.prototype._hideBackground=function(){return this.backdrop.remove(),this.backdrop.overlay=null,this.backdrop.backgroundShown=!1},c.prototype._showOverlayElement=function(b){var c,d,e;return d=a(b),d&&0!==d.length&&!this.backdrop.overlayElementShown?(this.backdrop.overlayElementShown=!0,c=a("<div/>"),e=d.offset(),e.top=e.top,e.left=e.left,c.width(d.innerWidth()).height(d.innerHeight()).addClass("tour-step-background").offset(e),d.addClass("tour-step-backdrop"),a("body").append(c),this.backdrop.$element=d,this.backdrop.$background=c):void 0},c.prototype._hideOverlayElement=function(){return this.backdrop.overlayElementShown?(this.backdrop.$element.removeClass("tour-step-backdrop"),this.backdrop.$background.remove(),this.backdrop.$element=null,this.backdrop.$background=null,this.backdrop.overlayElementShown=!1):void 0},c.prototype._clearTimer=function(){return b.clearTimeout(this._timer),this._timer=null,this._duration=null},c}(),b.Tour=c}(jQuery,window);
\ 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" %}
......
...@@ -289,6 +289,7 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -289,6 +289,7 @@ class VmDetailTest(LoginMixin, TestCase):
tmpl = InstanceTemplate.objects.get(id=1) tmpl = InstanceTemplate.objects.get(id=1)
tmpl.set_level(self.u1, 'owner') tmpl.set_level(self.u1, 'owner')
tmpl.disks.get().set_level(self.u1, 'owner') tmpl.disks.get().set_level(self.u1, 'owner')
Vlan.objects.get(id=1).set_level(self.u1, 'user')
kwargs = tmpl.__dict__.copy() kwargs = tmpl.__dict__.copy()
kwargs.update(name='t1', lease=1, disks=1, raw_data='tst1') kwargs.update(name='t1', lease=1, disks=1, raw_data='tst1')
response = c.post('/dashboard/template/1/', kwargs) response = c.post('/dashboard/template/1/', kwargs)
...@@ -305,11 +306,21 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -305,11 +306,21 @@ class VmDetailTest(LoginMixin, TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(InstanceTemplate.objects.get(id=1).raw_data, 'tst2') self.assertEqual(InstanceTemplate.objects.get(id=1).raw_data, 'tst2')
def test_permitted_lease_delete(self): def test_permitted_lease_delete_w_template_using_it(self):
c = Client() c = Client()
self.login(c, 'superuser') self.login(c, 'superuser')
leases = Lease.objects.count() leases = Lease.objects.count()
response = c.post("/dashboard/lease/delete/1/") response = c.post("/dashboard/lease/delete/1/")
self.assertEqual(response.status_code, 400)
self.assertEqual(leases, Lease.objects.count())
def test_permitted_lease_delete_w_template_not_using_it(self):
c = Client()
self.login(c, 'superuser')
lease = Lease.objects.create(name="yay")
leases = Lease.objects.count()
response = c.post("/dashboard/lease/delete/%d/" % lease.pk)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(leases - 1, Lease.objects.count()) self.assertEqual(leases - 1, Lease.objects.count())
......
...@@ -30,6 +30,7 @@ from .views import ( ...@@ -30,6 +30,7 @@ from .views import (
TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView, TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView,
VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, VmMigrateView, VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, VmMigrateView,
VmRenewView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView, VmRenewView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView,
TemplateChoose,
) )
urlpatterns = patterns( urlpatterns = patterns(
...@@ -44,6 +45,8 @@ urlpatterns = patterns( ...@@ -44,6 +45,8 @@ urlpatterns = patterns(
url(r'^template/create/$', TemplateCreate.as_view(), url(r'^template/create/$', TemplateCreate.as_view(),
name="dashboard.views.template-create"), name="dashboard.views.template-create"),
url(r'^template/choose/$', TemplateChoose.as_view(),
name="dashboard.views.template-choose"),
url(r'template/(?P<pk>\d+)/acl/$', TemplateAclUpdateView.as_view(), url(r'template/(?P<pk>\d+)/acl/$', TemplateAclUpdateView.as_view(),
name='dashboard.views.template-acl'), name='dashboard.views.template-acl'),
url(r'^template/(?P<pk>\d+)/$', TemplateDetail.as_view(), url(r'^template/(?P<pk>\d+)/$', TemplateDetail.as_view(),
...@@ -52,6 +55,7 @@ urlpatterns = patterns( ...@@ -52,6 +55,7 @@ urlpatterns = patterns(
name="dashboard.views.template-list"), name="dashboard.views.template-list"),
url(r"^template/delete/(?P<pk>\d+)/$", TemplateDelete.as_view(), url(r"^template/delete/(?P<pk>\d+)/$", TemplateDelete.as_view(),
name="dashboard.views.template-delete"), name="dashboard.views.template-delete"),
url(r'^vm/(?P<pk>\d+)/op/', include('dashboard.vm.urls')), url(r'^vm/(?P<pk>\d+)/op/', include('dashboard.vm.urls')),
url(r'^vm/(?P<pk>\d+)/remove_port/(?P<rule>\d+)/$', PortDelete.as_view(), url(r'^vm/(?P<pk>\d+)/remove_port/(?P<rule>\d+)/$', PortDelete.as_view(),
name='dashboard.views.remove-port'), name='dashboard.views.remove-port'),
......
...@@ -852,22 +852,73 @@ class GroupAclUpdateView(AclUpdateView): ...@@ -852,22 +852,73 @@ class GroupAclUpdateView(AclUpdateView):
kwargs=self.kwargs)) kwargs=self.kwargs))
class TemplateChoose(TemplateView):
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/modal-wrapper.html']
else:
return ['dashboard/nojs-wrapper.html']
def get_context_data(self, *args, **kwargs):
context = super(TemplateChoose, self).get_context_data(*args, **kwargs)
templates = InstanceTemplate.get_objects_with_level("user",
self.request.user)
context.update({
'box_title': _('Choose template'),
'ajax_title': False,
'template': "dashboard/_template-choose.html",
'templates': templates.all(),
})
return context
def post(self, request, *args, **kwargs):
if not request.user.has_perm('vm.create_template'):
raise PermissionDenied()
template = request.POST.get("parent")
if template == "base_vm":
return redirect(reverse("dashboard.views.template-create"))
elif template is None:
messages.warning(request, _("Select an option to proceed!"))
return redirect(reverse("dashboard.views.template-choose"))
else:
template = get_object_or_404(InstanceTemplate, pk=template)
instance = Instance.create_from_template(
template=template, owner=request.user, is_base=True)
return redirect(instance.get_absolute_url())
class TemplateCreate(SuccessMessageMixin, CreateView): class TemplateCreate(SuccessMessageMixin, CreateView):
model = InstanceTemplate model = InstanceTemplate
form_class = TemplateForm form_class = TemplateForm
template_name = "dashboard/template-create.html"
success_message = _("Successfully created a new template!") def get_template_names(self):
if self.request.is_ajax():
pass
else:
return ['dashboard/nojs-wrapper.html']
def get_context_data(self, *args, **kwargs):
context = super(TemplateCreate, self).get_context_data(*args, **kwargs)
context.update({
'box_title': _("Create a new base VM"),
'template': "dashboard/_template-create.html",
'leases': Lease.objects.count()
})
return context
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
if not self.request.user.has_perm('vm.create_template'): if not self.request.user.has_perm('vm.create_template'):
raise PermissionDenied() raise PermissionDenied()
self.parent = self.request.GET.get("parent")
return super(TemplateCreate, self).get(*args, **kwargs) return super(TemplateCreate, self).get(*args, **kwargs)
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(TemplateCreate, self).get_form_kwargs() kwargs = super(TemplateCreate, self).get_form_kwargs()
kwargs['parent'] = getattr(self, "parent", None)
kwargs['user'] = self.request.user kwargs['user'] = self.request.user
return kwargs return kwargs
...@@ -880,24 +931,27 @@ class TemplateCreate(SuccessMessageMixin, CreateView): ...@@ -880,24 +931,27 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
return self.get(request, form, *args, **kwargs) return self.get(request, form, *args, **kwargs)
else: else:
post = form.cleaned_data post = form.cleaned_data
networks = self.__create_networks(post.pop("networks"),
networks = self.__create_networks(post.pop("networks")) request.user)
post.pop("parent")
post['max_ram_size'] = post['ram_size']
req_traits = post.pop("req_traits") req_traits = post.pop("req_traits")
tags = post.pop("tags") tags = post.pop("tags")
post['pw'] = User.objects.make_random_password() post['pw'] = User.objects.make_random_password()
post.pop("parent") post['is_base'] = True
post['max_ram_size'] = post['ram_size'] inst = Instance.create(params=post, disks=[],
inst = Instance.create(params=post, disks=[], networks=networks, networks=networks,
tags=tags, req_traits=req_traits) tags=tags, req_traits=req_traits)
messages.success(request, _("The template has been created, "
"you can now add disks to it!"))
return redirect("%s#resources" % inst.get_absolute_url()) return redirect("%s#resources" % inst.get_absolute_url())
return super(TemplateCreate, self).post(self, request, args, kwargs) return super(TemplateCreate, self).post(self, request, args, kwargs)
def __create_networks(self, vlans): def __create_networks(self, vlans, user):
networks = [] networks = []
for v in vlans: for v in vlans:
if not v.has_level(user, "user"):
raise PermissionDenied()
networks.append(InterfaceTemplate(vlan=v, managed=v.managed)) networks.append(InterfaceTemplate(vlan=v, managed=v.managed))
return networks return networks
...@@ -962,6 +1016,9 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -962,6 +1016,9 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
for disk in self.get_object().disks.all(): for disk in self.get_object().disks.all():
if not disk.has_level(request.user, 'user'): if not disk.has_level(request.user, 'user'):
raise PermissionDenied() raise PermissionDenied()
for network in self.get_object().interface_set.all():
if not network.vlan.has_level(request.user, "user"):
raise PermissionDenied()
return super(TemplateDetail, self).post(self, request, args, kwargs) return super(TemplateDetail, self).post(self, request, args, kwargs)
def get_form_kwargs(self): def get_form_kwargs(self):
...@@ -1711,9 +1768,26 @@ class LeaseDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView): ...@@ -1711,9 +1768,26 @@ class LeaseDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
else: else:
return ['dashboard/confirm/base-delete.html'] return ['dashboard/confirm/base-delete.html']
def get_context_data(self, *args, **kwargs):
c = super(LeaseDelete, self).get_context_data(*args, **kwargs)
lease = self.get_object()
templates = lease.instancetemplate_set
if templates.count() > 0:
text = _("You can't delete this lease because some templates "
"are still using it, modify these to proceed: ")
c['text'] = text + ", ".join("<strong>%s (#%d)</strong>"
"" % (o.name, o.pk)
for o in templates.all())
c['disable_submit'] = True
return c
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
object = self.get_object() object = self.get_object()
if (object.instancetemplate_set.count() > 0):
raise SuspiciousOperation()
object.delete() object.delete()
success_url = self.get_success_url() success_url = self.get_success_url()
success_message = _("Lease successfully deleted!") success_message = _("Lease successfully deleted!")
......
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-04-16 08:59+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: dashboard/static/dashboard/vm-tour.js:23
msgid "Prev"
msgstr "Előző"
#: dashboard/static/dashboard/vm-tour.js:25
msgid "Next"
msgstr "Következő"
#: dashboard/static/dashboard/vm-tour.js:29
msgid "End tour"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:36
msgid "Template Tutorial Tour"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:37
msgid ""
"Welcome to the template tutorial. In this quick tour, we gonna show you how "
"to do the steps described above."
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:38
msgid ""
"For the next tour step press the \"Next\" button or the right arrow (or "
"\"Back\" button/left arrow for the previous step)."
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:39
msgid ""
"During the tour please don't try the functions because it may lead to "
"graphical glitches, however "
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:48
msgid "Home tab"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:49
msgid ""
"In this tab you can tag your virtual machine and modify the description."
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:58
msgid "Resources tab"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:61
msgid ""
"On the resources tab you can edit the CPU/RAM options and add/remove disks!"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:71
msgid "Resources"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:72
msgid "CPU priority"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:72
msgid "higher (or lower?) is better"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:73
msgid "CPU count"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:73
msgid "number of CPU cores."
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:74
msgid "RAM amount"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:74
msgid "amount of RAM."
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:84
msgid "Disks"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:85
msgid ""
"You can add empty disks, download new ones and remove existing ones here."
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:95
msgid "Network tab"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:96
msgid "You can add new network interfaces or remove existing ones here."
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:105
msgid "Deploy"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:108
msgid "Deploy the virtual machine."
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:113
msgid "Connect"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:116
msgid "Use the connection string or connect with your choice of client!"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:123
msgid "Customize the virtual machine"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:124
msgid "After you have connected to the virtual do you modifications."
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:129
msgid "Save as"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:132
msgid ""
"Press the \"Save as template\" button and wait until the activity finishes."
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:138
msgid "Finisih"
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:141
msgid ""
"This is the last message, if something is not clear you can do the the tour "
"again!"
msgstr ""
#: network/static/js/host.js:10
msgid ""
"Are you sure you want to remove host group <strong>\"%(group)s\"</strong> "
"from <strong>\"%(host)s\"</strong>?"
msgstr ""
#: network/static/js/host.js:13
msgid "Are you sure you want to delete this rule?"
msgstr ""
#: network/static/js/host.js:20 network/static/js/switch-port.js:14
msgid "Cancel"
msgstr ""
#: network/static/js/host.js:25 network/static/js/switch-port.js:19
msgid "Remove"
msgstr ""
#: network/static/js/switch-port.js:8
msgid "Are you sure you want to delete this device?"
msgstr ""
// regexes
mac_re = /^([0-9a-fA-F]{2}(:|$)){6}$/;
alfanum_re = '^[A-Za-z0-9_-]+$';
domain_re = /^([A-Za-z0-9_-]\.?)+$/;
ipv4_re = /^[0-9]+\.([0-9]+)\.([0-9]+)\.([0-9]+)$/;
ipv6_re = /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i
reverse_domain_re = /^(%\([abcd]\)d|[a-z0-9.-])+$/;
// is this a new record?
var new_record = false;
// handles type change
// if we are currently creating a new one, call type_next()
$('#id_type').change(function() {
type = $(":selected", this).text();
resetForm();
resetName();
if(new_record) {
type_next();
new_record = false;
}
});
// handles hostname change
$('#id_host').change(function() {
host_id = $("#id_host :selected").val();
// if user selected "----" reset the inputs
if(!host_id) {
resetForm();
} else {
setNameAndAddress();
resetErrors();
}
});
// sets the name and address if necessary
function setNameAndAddress() {
var type = $("#id_type :selected").text();
host_id = $("#id_host :selected").val();
host_name = $("#id_host :selected").text();
// if A or AAAA record
if(type[0] === "A") {
promise = getHostData(host_id);
promise.success(function(data) {
hostname = document.getElementById("id_name");
hostname.disabled = true;
hostname.value = data.hostname;
addr = document.getElementById("id_address")
addr.disabled = true;
if(type === "A") {
addr.value = data.ipv4;
} else {
addr.value = data.ipv6;
}
});
}
// if CNAME
else if(type === "CNAME") {
promise = getHostData(host_id);
promise.success(function(data) {
addr = document.getElementById('id_address');
addr.disabled = true;
addr.value = data.fqdn;
});
}
// if MX
else if(type === "MX") {
if(!$('#id_address').val()) {
promise = getHostData(host_id);
promise.success(function(data) {
addr = document.getElementById('id_address');
addr.value = "10:" + data.fqdn;
});
}
}
}
// if we submit the form validate the form
$('#submit-id-submit').click(function() {
return validateForm();
});
// validates the form
// validation is like the one in firewall/model.py
function validateForm() {
type = $("#id_type :selected").text();
host = $('#id_host :selected').val();
messages = []
// if host is set
if(host && type[0] != "-") {
if(type === "CNAME") {
if(!$('#id_name').val()) {
messages.push({
'message': 'Name must be specified for ' +
'CNAME records if host is set!',
'id': 'name'
});
}
}
// if host is not set
} else if(!host && type[0] != "-") {
if(!$('#id_address').val()) {
messages.push({
'message': gettext('Address must be specified!'),
'id': 'address'
});
}
// address is set
else {
var addr = $('#id_address').val();
// ipv4
if(type === "A") {
if(!addr.match(ipv4_re)) {
text = gettext('%s - not an IPv4 address');
messages.push({
'message': interpolate(text, [addr]),
'id': 'address'
})
}
}
// ipv6
else if(type[0] === "A") {
if(!addr.match(ipv6_re)) {
text = gettext('%s - not an IPv6 address');
messages.push({
'message': interpolate(text, [addr]),
'id': 'address'
});
}
}
// MX
else if(type === "MX") {
mx = addr.split(':');
if(!(mx.length === 2 && mx[0].match("^[0-9]+$") && domain_re.test(mx[1]))) {
text = gettext('Bad MX address format. ' +
'Should be: <priority>:<hostname>')
messages.push({
'message': text,
'id': 'address'
});
}
}
// CNAME / NS / PTR / TXT
else if(['CNAME', 'NS', 'PTR', 'TXT'].indexOf(type) != -1) {
if(!domain_re.test(addr)) {
text = gettext('%s - invalid domain name');
messages.push({
'message': interpolate(text, [addr]),
'id': 'address'
});
}
}
// we doesn't really need this, but better safe than sorry
else {
messages.push({
'message': gettext('Unknown record type.'),
'id': 'type'
});
}
}
// we didn't choose a type
} else {
messages.push({
'message': gettext('You must choose a type'),
'id': 'type'
});
}
// check other inputs
// name
record_name = $('#id_name').val()
if(!record_name) {
//messages.push({
// 'message': gettext("You must specify a name!"),
// 'id': 'name'
//});
}
else if(!domain_re.test(record_name)) {
text = gettext('%s - invalid domain name'),
messages.push({
'message': interpolate(text, [record_name]),
'id': 'name'
});
}
// domain
if(!$('#id_domain :selected').val()) {
messages.push({
'message': gettext('You must choose a domain'),
'id': 'domain'
});
}
// owner
if(!$('#id_owner :selected').val()) {
messages.push({
'message': gettext('You must specify an owner!'),
'id': 'owner'
});
}
if(messages.length < 1) {
return true;
} else {
appendMessage('error', messages);
return false;
}
}
// makes the ajax call
function getHostData(pk) {
return $.ajax({
type: "GET",
url: "/network/hosts/" + pk + "/",
});
}
// enables fields, resets them and removes error messages
function resetForm() {
hostname = document.getElementById('id_name');
addr = document.getElementById('id_address');
hostname.disabled = false;
addr.disabled = false;
hostname.value = "";
addr.value = "";
resetErrors();
}
// removes all error messages / classes
function resetErrors() {
// reset invalid inputs too
$('div[id^="div_id_"][class*="has-error"]').each(function() {
$(this).removeClass('has-error');
});
// remove the error messages
$("#js_error").fadeOut();
}
// reset the hostname select
function resetName() {
$("#id_host option").filter(function() {
return $(this).text()[0] == "-";
}).prop('selected', true);
}
/*
* error creating function
*
* first it removes the current error message, then it iterates through
* all the given messages
*/
function appendMessage(type, messages, id) {
$('#js_error').remove();
resetErrors();
message = '<div id="js_error" style="display: none;" class="alert alert-danger"><ul>'
for(var i = 0;i < messages.length; i++) {
message += "<li>" +messages[i].message+ "</li>";
if(messages[i].id) {
$('#id_' + messages[i].id).closest('div[class="form-group"]').addClass("has-error");
}
}
message +='</ul></div>';
$('form').before(message);
$('html, body').animate({ scrollTop: 0}, 'slow', function() {
$('#js_error').fadeIn();
});
}
// remove error class from forms if we click on them
// it also removes the help-inline span that shouldn't really appear
$('* [id^="id_"]').focus(function() {
id = "#div_" + $(this).prop('id');
if($(id).hasClass('has-error')) {
$(id).removeClass('has-error');
$('span[id="error_1_' + $(this).attr('id') + '"]').remove();
}
});
// on page load
// if we are upadting
// - set the name and address
// else it's a new rule so we "create" the wizard thingy
$(function() {
// type is set, so it's an existing record
if($('#id_type :selected').val()) {
if($('#id_host :selected').val()) {
setNameAndAddress();
}
}
// else we are creaing a new
else {
domain = getURLParameter('domain');
if(domain != 'null' && domain.length > 0) {
new_record = false;
} else {
new_record = true;
// hide all input containers
$('div[id^="div_id_"]').hide();
// hide the save button
$('#submit-id-submit').hide();
//
$('#div_id_type .controls')
.addClass('input-group')
.append(
//' <a id="type_next" onclick="type_next()" class="btn btn-info">Next</a>'
'<span id="type_next" class="input-group-addon"><strong>' +
gettext('Specify a type!') +
'</strong></span>'
);
$('#div_id_type').fadeIn();
}
}
});
// if the user choose a type
function type_next() {
if($('#div_id_type :selected').val()) {
$('#div_id_type .controls').removeClass('input-group');
$('#type_next').remove();
$('div[id^="div_id_"]').fadeIn();
$('#submit-id-submit').fadeIn();
// this shouldn't be called ...
} else {
message = [{
'message': gettext('You must choose a type'),
'id': 'type'
}];
appendMessage('error', message);
}
return false;
}
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
</div><!-- .footer-container .container --> </div><!-- .footer-container .container -->
<script src="//code.jquery.com/jquery-latest.js"></script> <script src="//code.jquery.com/jquery-latest.js"></script>
<script src="{% url "network.js_catalog" %}"></script> <script src="{{ STATIC_URL }}jsi18n/{{ LANGUAGE_CODE }}/djangojs.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="{% static "js/bootbox.min.js" %}"></script> <script src="{% static "js/bootbox.min.js" %}"></script>
<script src="{% static "js/network.js" %}"></script> <script src="{% static "js/network.js" %}"></script>
......
...@@ -32,10 +32,6 @@ from .views import (IndexView, ...@@ -32,10 +32,6 @@ from .views import (IndexView,
remove_host_group, add_host_group, remove_host_group, add_host_group,
remove_switch_port_device, add_switch_port_device) remove_switch_port_device, add_switch_port_device)
js_info_dict = {
'packages': ('network', ),
}
urlpatterns = patterns( urlpatterns = patterns(
'', '',
url('^$', IndexView.as_view(), name='network.index'), url('^$', IndexView.as_view(), name='network.index'),
...@@ -109,8 +105,4 @@ urlpatterns = patterns( ...@@ -109,8 +105,4 @@ urlpatterns = patterns(
remove_switch_port_device, name='network.remove_switch_port_device'), remove_switch_port_device, name='network.remove_switch_port_device'),
url('^switchports/(?P<pk>\d+)/add/$', add_switch_port_device, url('^switchports/(?P<pk>\d+)/add/$', add_switch_port_device,
name='network.add_switch_port_device'), name='network.add_switch_port_device'),
# js gettext catalog
url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict,
name="network.js_catalog"),
) )
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Instance.is_base'
db.add_column(u'vm_instance', 'is_base',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Instance.is_base'
db.delete_column(u'vm_instance', 'is_base')
models = {
u'acl.level': {
'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Level'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
'weight': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
},
u'acl.objectlevel': {
'Meta': {'unique_together': "(('content_type', 'object_id', 'level'),)", 'object_name': 'ObjectLevel'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['acl.Level']"}),
'object_id': ('django.db.models.fields.IntegerField', [], {}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False'})
},
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'firewall.domain': {
'Meta': {'object_name': 'Domain'},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
'ttl': ('django.db.models.fields.IntegerField', [], {'default': '600'})
},
u'firewall.group': {
'Meta': {'object_name': 'Group'},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
u'firewall.host': {
'Meta': {'ordering': "('normalized_hostname', 'vlan')", 'unique_together': "(('hostname', 'vlan'),)", 'object_name': 'Host'},
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['firewall.Group']", 'null': 'True', 'blank': 'True'}),
'hostname': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ipv4': ('firewall.fields.IPAddressField', [], {'unique': 'True', 'max_length': '100'}),
'ipv6': ('firewall.fields.IPAddressField', [], {'max_length': '100', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
'location': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'mac': ('firewall.fields.MACAddressField', [], {'unique': 'True', 'max_length': '17'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'normalized_hostname': ('common.models.HumanSortField', [], {'default': "''", 'maximum_number_length': '4', 'max_length': '80', 'monitor': "'hostname'", 'blank': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
'pub_ipv4': ('firewall.fields.IPAddressField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'reverse': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
'shared_ip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Vlan']"})
},
u'firewall.vlan': {
'Meta': {'object_name': 'Vlan'},
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'dhcp_pool': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Domain']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ipv6_template': ('django.db.models.fields.TextField', [], {'default': "'2001:738:2001:4031:%(b)d:%(c)d:%(d)d:0'"}),
'managed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
'network4': ('firewall.fields.IPNetworkField', [], {'max_length': '100'}),
'network6': ('firewall.fields.IPNetworkField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'network_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'reverse_domain': ('django.db.models.fields.TextField', [], {'default': "'%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa'"}),
'snat_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
'snat_to': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['firewall.Vlan']", 'null': 'True', 'blank': 'True'}),
'vid': ('django.db.models.fields.IntegerField', [], {'unique': 'True'})
},
u'storage.datastore': {
'Meta': {'ordering': "[u'name']", 'object_name': 'DataStore'},
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'})
},
u'storage.disk': {
'Meta': {'ordering': "[u'name']", 'object_name': 'Disk'},
'base': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'derivatives'", 'null': 'True', 'to': u"orm['storage.Disk']"}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'datastore': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['storage.DataStore']"}),
'destroyed': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'dev_num': ('django.db.models.fields.CharField', [], {'default': "u'a'", 'max_length': '1'}),
'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'size': ('sizefield.models.FileSizeField', [], {'default': 'None', 'null': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '10'})
},
u'taggit.tag': {
'Meta': {'object_name': 'Tag'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
u'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
},
u'vm.instance': {
'Meta': {'ordering': "(u'pk',)", 'object_name': 'Instance'},
'access_method': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'active_since': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'arch': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'boot_menu': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'destroyed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'disks': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'instance_set'", 'symmetrical': 'False', 'to': u"orm['storage.Disk']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_base': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'lease': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.Lease']"}),
'max_ram_size': ('django.db.models.fields.IntegerField', [], {}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'instance_set'", 'null': 'True', 'to': u"orm['vm.Node']"}),
'num_cores': ('django.db.models.fields.IntegerField', [], {}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
'priority': ('django.db.models.fields.IntegerField', [], {}),
'pw': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'ram_size': ('django.db.models.fields.IntegerField', [], {}),
'raw_data': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'req_traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'}),
'status': ('model_utils.fields.StatusField', [], {'default': "u'NOSTATE'", 'max_length': '100', u'no_check_for_status': 'True'}),
'status_changed': ('model_utils.fields.MonitorField', [], {'default': 'datetime.datetime.now', u'monitor': "u'status'"}),
'system': ('django.db.models.fields.TextField', [], {}),
'template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'instance_set'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['vm.InstanceTemplate']"}),
'time_of_delete': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'time_of_suspend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'vnc_port': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'})
},
u'vm.instanceactivity': {
'Meta': {'ordering': "[u'-finished', u'-started', u'instance', u'-id']", 'object_name': 'InstanceActivity'},
'activity_code': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'finished': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'activity_log'", 'to': u"orm['vm.Instance']"}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': u"orm['vm.InstanceActivity']"}),
'result': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'resultant_state': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'succeeded': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'task_uuid': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
u'vm.instancetemplate': {
'Meta': {'ordering': "(u'name',)", 'object_name': 'InstanceTemplate'},
'access_method': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'arch': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'boot_menu': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'disks': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'template_set'", 'symmetrical': 'False', 'to': u"orm['storage.Disk']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'lease': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.Lease']"}),
'max_ram_size': ('django.db.models.fields.IntegerField', [], {}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'num_cores': ('django.db.models.fields.IntegerField', [], {}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.InstanceTemplate']", 'null': 'True', 'blank': 'True'}),
'priority': ('django.db.models.fields.IntegerField', [], {}),
'ram_size': ('django.db.models.fields.IntegerField', [], {}),
'raw_data': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'req_traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'}),
'system': ('django.db.models.fields.TextField', [], {})
},
u'vm.interface': {
'Meta': {'object_name': 'Interface'},
'host': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Host']", 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'interface_set'", 'to': u"orm['vm.Instance']"}),
'vlan': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'vm_interface'", 'to': u"orm['firewall.Vlan']"})
},
u'vm.interfacetemplate': {
'Meta': {'object_name': 'InterfaceTemplate'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'managed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'interface_set'", 'to': u"orm['vm.InstanceTemplate']"}),
'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Vlan']"})
},
u'vm.lease': {
'Meta': {'ordering': "[u'name']", 'object_name': 'Lease'},
'delete_interval_seconds': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'suspend_interval_seconds': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
},
u'vm.namedbaseresourceconfig': {
'Meta': {'object_name': 'NamedBaseResourceConfig'},
'arch': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'max_ram_size': ('django.db.models.fields.IntegerField', [], {}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'}),
'num_cores': ('django.db.models.fields.IntegerField', [], {}),
'priority': ('django.db.models.fields.IntegerField', [], {}),
'ram_size': ('django.db.models.fields.IntegerField', [], {})
},
u'vm.node': {
'Meta': {'ordering': "(u'-enabled', u'normalized_name')", 'object_name': 'Node'},
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'}),
'normalized_name': ('common.models.HumanSortField', [], {'default': "''", 'maximum_number_length': '4', 'max_length': '100', 'monitor': "u'name'", 'blank': 'True'}),
'overcommit': ('django.db.models.fields.FloatField', [], {'default': '1.0'}),
'priority': ('django.db.models.fields.IntegerField', [], {}),
'traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'})
},
u'vm.nodeactivity': {
'Meta': {'object_name': 'NodeActivity'},
'activity_code': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'finished': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'activity_log'", 'to': u"orm['vm.Node']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': u"orm['vm.NodeActivity']"}),
'result': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'succeeded': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'task_uuid': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
u'vm.trait': {
'Meta': {'object_name': 'Trait'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
}
}
complete_apps = ['vm']
\ No newline at end of file
...@@ -103,7 +103,8 @@ class VirtualMachineDescModel(BaseResourceConfigModel): ...@@ -103,7 +103,8 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
boot_menu = BooleanField(verbose_name=_('boot menu'), default=False, boot_menu = BooleanField(verbose_name=_('boot menu'), default=False,
help_text=_( help_text=_(
'Show boot device selection menu on boot.')) 'Show boot device selection menu on boot.'))
lease = ForeignKey(Lease, help_text=_("Preferred expiration periods.")) lease = ForeignKey(Lease, help_text=_("Preferred expiration periods."),
verbose_name=_("Lease"))
raw_data = TextField(verbose_name=_('raw_data'), blank=True, help_text=_( raw_data = TextField(verbose_name=_('raw_data'), blank=True, help_text=_(
'Additional libvirt domain parameters in XML format.')) 'Additional libvirt domain parameters in XML format.'))
req_traits = ManyToManyField(Trait, blank=True, req_traits = ManyToManyField(Trait, blank=True,
...@@ -238,6 +239,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -238,6 +239,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
vnc_port = IntegerField(blank=True, default=None, null=True, vnc_port = IntegerField(blank=True, default=None, null=True,
help_text=_("TCP port where VNC console listens."), help_text=_("TCP port where VNC console listens."),
unique=True, verbose_name=_('vnc_port')) unique=True, verbose_name=_('vnc_port'))
is_base = BooleanField(default=False)
owner = ForeignKey(User) owner = ForeignKey(User)
destroyed_at = DateTimeField(blank=True, null=True, destroyed_at = DateTimeField(blank=True, null=True,
help_text=_("The virtual machine's time of " help_text=_("The virtual machine's time of "
......
...@@ -3,14 +3,15 @@ anyjson==0.3.3 ...@@ -3,14 +3,15 @@ anyjson==0.3.3
billiard==3.3.0.17 billiard==3.3.0.17
bpython==0.12 bpython==0.12
celery==3.1.11 celery==3.1.11
Django==1.6.3
django-braces==1.4.0 django-braces==1.4.0
django-celery==3.1.10 django-celery==3.1.10
django-crispy-forms==1.4.0 django-crispy-forms==1.4.0
django-model-utils==2.0.3 django-model-utils==2.0.3
django-sizefield==0.4 django-sizefield==0.4
django-statici18n==1.1
django-tables2==0.15.0 django-tables2==0.15.0
django-taggit==0.12 django-taggit==0.12
Django==1.6.3
docutils==0.11 docutils==0.11
Jinja2==2.7.2 Jinja2==2.7.2
kombu==3.0.15 kombu==3.0.15
......
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