Commit db560b90 by Őry Máté

Merge branch 'master' into feature-mass-ops

parents 9f4783eb 106f963d
......@@ -368,9 +368,9 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
from shutilwhich import which
from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
# INSTALLED_APPS += ( # needed only for testing djangosaml2
# 'djangosaml',
# )
INSTALLED_APPS += (
'djangosaml2',
)
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'djangosaml2.backends.Saml2Backend',
......
......@@ -634,12 +634,8 @@ class LeaseForm(forms.ModelForm):
Field('name'),
Field("suspend_interval_seconds", type="hidden", value="0"),
Field("delete_interval_seconds", type="hidden", value="0"),
HTML(string_concat("<label>", _("Suspend in"), "</label>")),
Div(
Div(
HTML(_("Suspend in")),
css_class="input-group-addon",
style="width: 100px;",
),
NumberField("suspend_hours", css_class="form-control"),
Div(
HTML(_("hours")),
......@@ -662,12 +658,8 @@ class LeaseForm(forms.ModelForm):
),
css_class="input-group interval-input",
),
HTML(string_concat("<label>", _("Delete in"), "</label>")),
Div(
Div(
HTML(_("Delete in")),
css_class="input-group-addon",
style="width: 100px;",
),
NumberField("delete_hours", css_class="form-control"),
Div(
HTML(_("hours")),
......@@ -691,7 +683,7 @@ class LeaseForm(forms.ModelForm):
css_class="input-group interval-input",
)
)
helper.add_input(Submit("submit", "Save changes"))
helper.add_input(Submit("submit", _("Save changes")))
return helper
class Meta:
......@@ -703,6 +695,8 @@ class VmRenewForm(forms.Form):
force = forms.BooleanField(required=False, label=_(
"Set expiration times even if they are shorter than "
"the current value."))
save = forms.BooleanField(required=False, label=_(
"Save selected lease."))
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices')
......@@ -714,6 +708,32 @@ class VmRenewForm(forms.Form):
empty_label=None, label=_('Length')))
if len(choices) < 2:
self.fields['lease'].widget = HiddenInput()
self.fields['save'].widget = HiddenInput()
@property
def helper(self):
helper = FormHelper(self)
helper.form_tag = False
return helper
class VmStateChangeForm(forms.Form):
interrupt = forms.BooleanField(required=False, label=_(
"Forcibly interrupt all running activities."),
help_text=_("Set all activities to finished state, "
"but don't interrupt any tasks."))
new_state = forms.ChoiceField(Instance.STATUS, label=_(
"New status"))
def __init__(self, *args, **kwargs):
show_interrupt = kwargs.pop('show_interrupt')
status = kwargs.pop('status')
super(VmStateChangeForm, self).__init__(*args, **kwargs)
if not show_interrupt:
self.fields['interrupt'].widget = HiddenInput()
self.fields['new_state'].initial = status
@property
def helper(self):
......@@ -1141,9 +1161,9 @@ class VmResourcesForm(forms.ModelForm):
vm_search_choices = (
(0, _("owned")),
(1, _("shared")),
(2, _("all")),
("owned", _("owned")),
("shared", _("shared")),
("all", _("all")),
)
......@@ -1162,5 +1182,5 @@ class VmListSearchForm(forms.Form):
# set initial value, otherwise it would be overwritten by request.GET
if not self.data.get("stype"):
data = self.data.copy()
data['stype'] = 2
data['stype'] = "all"
self.data = data
......@@ -262,7 +262,7 @@ $(function () {
$("#dashboard-vm-search-form").submit(function() {
var vm_list_items = $("#dashboard-vm-list .list-group-item");
if(vm_list_items.length == 1) {
if(vm_list_items.length == 1 && vm_list_items.first().prop("href")) {
window.location.href = vm_list_items.first().prop("href");
return false;
}
......
var ctrlDown, shiftDown = false;
var ctrlKey = 17;
var shiftKey = 16;
var selected = [];
$(function() {
$(document).keydown(function(e) {
if (e.keyCode == ctrlKey) ctrlDown = true;
if (e.keyCode == shiftKey) shiftDown = true;
}).keyup(function(e) {
if (e.keyCode == ctrlKey) ctrlDown = false;
if (e.keyCode == shiftKey) shiftDown = false;
});
$('.group-list-table tbody').find('tr').mousedown(function() {
var retval = true;
if (ctrlDown) {
setRowColor($(this));
if(!$(this).hasClass('group-list-selected')) {
selected.splice(selected.indexOf($(this).index()), 1);
} else {
selected.push($(this).index());
}
retval = false;
} else if(shiftDown) {
if(selected.length > 0) {
start = selected[selected.length - 1] + 1;
end = $(this).index();
if(start > end) {
var tmp = start - 1; start = end; end = tmp - 1;
}
for(var i = start; i <= end; i++) {
if(selected.indexOf(i) < 0) {
selected.push(i);
setRowColor($('.group-list-table tbody tr').eq(i));
}
}
}
retval = false;
} else {
$('.group-list-selected').removeClass('group-list-selected');
$(this).addClass('group-list-selected');
selected = [$(this).index()];
}
// reset btn disables
$('.group-list-table tbody tr .btn').attr('disabled', false);
// show/hide group controls
if(selected.length > 1) {
$('.group-list-group-control a').attr('disabled', false);
for(var i = 0; i < selected.length; i++) {
$('.group-list-table tbody tr').eq(selected[i]).find('.btn').attr('disabled', true);
}
} else {
$('.group-list-group-control a').attr('disabled', true);
}
return retval;
});
$('#group-list-group-migrate').click(function() {
console.log(collectIds(selected));
});
$('tbody a').mousedown(function(e) {
// parent tr doesn't get selected when clicked
e.stopPropagation();
});
$('tbody a').click(function(e) {
// browser doesn't jump to top when clicked the buttons
if(!$(this).hasClass('real-link')) {
return false;
}
});
/* rename */
$("#group-list-rename-button, .group-details-rename-button").click(function() {
$("#group-list-column-name", $(this).closest("tr")).hide();
......@@ -113,51 +34,4 @@ $(function() {
return false;
});
/* group actions */
/* select all */
$('#group-list-group-select-all').click(function() {
$('.group-list-table tbody tr').each(function() {
var index = $(this).index();
if(selected.indexOf(index) < 0) {
selected.push(index);
$(this).addClass('group-list-selected');
}
});
if(selected.length > 0)
$('.group-list-group-control a').attr('disabled', false);
return false;
});
/* mass vm delete */
$('#group-list-group-delete').click(function() {
addModalConfirmation(massDeleteVm,
{
'url': '/dashboard/group/mass-delete/',
'data': {
'selected': selected,
'v': collectIds(selected)
}
}
);
return false;
});
});
function collectIds(rows) {
var ids = [];
for(var i = 0; i < rows.length; i++) {
var div = $('td:first-child div', $('.group-list-table tbody tr').eq(rows[i]));
ids.push(div.prop('id').replace('node-', ''));
}
return ids;
}
function setRowColor(row) {
if(!row.hasClass('group-list-selected')) {
row.addClass('group-list-selected');
} else {
row.removeClass('group-list-selected');
}
}
......@@ -7,7 +7,6 @@
{% csrf_token %}
{{ vm_create_form.template }}
{{ vm_create_form.customized }}
<div class="row">
<div class="col-sm-12">
......@@ -23,6 +22,8 @@
</div>
</div>
{% if perms.vm.set_resources %}
{{ vm_create_form.customized }}
<div class="row">
<div class="col-sm-10">
<div class="form-group">
......@@ -85,6 +86,7 @@
</div><!-- .no-js-hidden -->
</div><!-- .col-sm-8 -->
</div><!-- .row -->
{% endif %}
</form>
<script>
......
......@@ -6,63 +6,24 @@
{% block content %}
<div class="alert alert-info">
Tip #1: you can select multiple vm instances while holding down the <strong>CTRL</strong> key!
</div>
<div class="alert alert-info">
Tip #2: if you want to select multiple instances by one click select an instance then hold down <strong>SHIFT</strong> key and select another one!
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin"><i class="fa fa-group"></i> Your groups</h3>
</div>
<div class="panel-body group-list-group-control">
<p>
<strong>Group actions</strong>
<button id="group-list-group-select-all" class="btn btn-info btn-xs">Select all</button>
<a id="group-list-group-delete" disabled href="#" class="btn btn-danger btn-xs"><i class="fa fa-times"></i> Discard</a>
</p>
</div>
<div class="panel-body">
<div id="table_container">
<div id="rendered_table" class="panel-body">
{% render_table table %}
</div>
</div>
</div><!-- .panel-body -->
</div>
</div>
</div>
<style>
.popover {
max-width: 600px;
}
.group-list-selected, .group-list-selected td {
background-color: #e8e8e8 !important;
}
.group-list-selected:hover, .group-list-selected:hover td {
background-color: #d0d0d0 !important;
}
.group-list-selected td:first-child {
font-weight: bold;
}
.group-list-table-thin {
width: 10px;
}
.group-list-table-admin {
width: 130px;
}
</style>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/group-list.js"></script>
<script src="{{ STATIC_URL}}dashboard/group-list.js"></script>
{% endblock %}
<a data-group-pk="{{ record.pk }}" class="btn btn-danger btn-xs real-link group-delete" href="{% url "dashboard.views.delete-group" pk=record.pk %}?next={{ request.path }}"><i class="fa fa-trash"></i></a>
<a data-group-pk="{{ record.pk }}"
class="btn btn-danger btn-xs real-link group-delete"
href="{% url "dashboard.views.delete-group" pk=record.pk %}?next={{ request.path }}">
<i class="fa fa-trash-o"></i>
</a>
......@@ -3,8 +3,10 @@
<div class="panel-heading">
<div class="pull-right toolbar">
<div class="btn-group">
<a href="#index-graph-view" data-index-box="node" class="btn btn-default btn-xs"><i class="fa fa-dashboard"></i></a>
<a href="#index-list-view" data-index-box="node" class="btn btn-default btn-xs disabled"><i class="fa fa-list"></i></a>
<a href="#index-graph-view" data-index-box="node" class="btn btn-default btn-xs"
data-container="body"><i class="fa fa-dashboard"></i></a>
<a href="#index-list-view" data-index-box="node" class="btn btn-default btn-xs disabled"
data-container="body"><i class="fa fa-list"></i></a>
</div>
<span class="btn btn-default btn-xs infobtn" title="{% trans "List of compute nodes, also called worker nodes or hypervisors, which run the virtual machines." %}"><i class="fa fa-info-circle"></i></span>
......
......@@ -3,10 +3,12 @@
<div class="panel-heading">
<div class="pull-right toolbar">
<div class="btn-group">
<a href="#index-graph-view" data-index-box="vm" class="btn
btn-default btn-xs" title="{% trans "summary view" %}"><i class="fa fa-dashboard"></i></a>
<a href="#index-list-view" data-index-box="vm" class="btn
btn-default btn-xs disabled" title="{% trans "list view" %}"><i class="fa fa-list"></i></a>
<a href="#index-graph-view" data-index-box="vm" class="btn btn-default btn-xs"
data-container="body"
title="{% trans "summary view" %}"><i class="fa fa-dashboard"></i></a>
<a href="#index-list-view" data-index-box="vm" class="btn btn-default btn-xs disabled"
data-container="body"
title="{% trans "list view" %}"><i class="fa fa-list"></i></a>
</div>
<span class="btn btn-default btn-xs infobtn" title="{% trans "List of your current virtual machines. Favourited ones are ahead of others." %}"><i class="fa fa-info-circle"></i></span>
</div>
......
......@@ -7,7 +7,7 @@
<span class="operation operation-{{op.op}} btn btn-default disabled btn-xs">
{% else %}
<a href="{{op.get_url}}" class="operation operation-{{op.op}} btn
btn-{{op.effect}} btn-xs" title="{{op.name|capfirst}}: {{op.description|truncatewords:20}}">
btn-{{op.effect}} btn-xs" title="{{op.name|capfirst}}: {{op.description|truncatewords:15}}">
{% endif %}
<i class="fa fa-{{op.icon}}"></i>
<span{% if not op.is_preferred %} class="sr-only"{% endif %}>{{op.name}}</span>
......
......@@ -67,7 +67,7 @@ from .forms import (
CircleAuthenticationForm, HostForm, LeaseForm, MyProfileForm,
NodeForm, TemplateForm, TraitForm, VmCustomizeForm, GroupCreateForm,
UserCreationForm, GroupProfileUpdateForm, UnsubscribeForm,
VmSaveForm, UserKeyForm, VmRenewForm,
VmSaveForm, UserKeyForm, VmRenewForm, VmStateChangeForm,
CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm,
TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm,
VmResourcesForm, VmAddInterfaceForm, VmListSearchForm
......@@ -936,6 +936,24 @@ class VmRenewView(FormOperationMixin, TokenOperationView, VmOperationView):
return extra
class VmStateChangeView(FormOperationMixin, VmOperationView):
op = 'emergency_change_state'
icon = 'legal'
effect = 'danger'
show_in_toolbar = True
form_class = VmStateChangeForm
wait_for_result = 0.5
def get_form_kwargs(self):
inst = self.get_op().instance
active_activities = InstanceActivity.objects.filter(
finished__isnull=True, instance=inst)
show_interrupt = active_activities.exists()
val = super(VmStateChangeView, self).get_form_kwargs()
val.update({'show_interrupt': show_interrupt, 'status': inst.status})
return val
vm_ops = OrderedDict([
('deploy', VmOperationView.factory(
op='deploy', icon='play', effect='success')),
......@@ -956,8 +974,7 @@ vm_ops = OrderedDict([
op='shut_off', icon='ban', effect='warning')),
('recover', VmOperationView.factory(
op='recover', icon='medkit', effect='warning')),
('nostate', VmOperationView.factory(
op='emergency_change_state', icon='legal', effect='danger')),
('nostate', VmStateChangeView),
('destroy', VmOperationView.factory(
extra_bases=[TokenOperationView],
op='destroy', icon='times', effect='danger')),
......@@ -1764,10 +1781,10 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
def create_default_queryset(self):
cleaned_data = self.search_form.cleaned_data
stype = cleaned_data.get('stype', 2)
superuser = stype == 2
shared = stype == 1
level = "owner" if stype == 0 else "user"
stype = cleaned_data.get('stype', "all")
superuser = stype == "all"
shared = stype == "shared"
level = "owner" if stype == "owned" else "user"
queryset = Instance.get_objects_with_level(
level, self.request.user,
group_also=shared, disregard_superuser=not superuser,
......@@ -2048,8 +2065,10 @@ class VmCreate(LoginRequiredMixin, TemplateView):
if not template.has_level(request.user, 'user'):
raise PermissionDenied()
instances = [Instance.create_from_template(
template=template, owner=user)]
args = {"template": template, "owner": user}
if "name" in request.POST:
args["name"] = request.POST.get("name")
instances = [Instance.create_from_template(**args)]
return self.__deploy(request, instances)
def __create_customized(self, request, *args, **kwargs):
......@@ -2075,6 +2094,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
'num_cores': post['cpu_count'],
'ram_size': post['ram_size'],
'priority': post['cpu_priority'],
'max_ram_size': post['ram_size'],
}
networks = [InterfaceTemplate(vlan=l, managed=l.managed)
for l in post['networks']]
......
......@@ -203,12 +203,6 @@ class Rule(models.Model):
elif self.firewall_id:
return 'INPUT' if self.direction == 'in' else 'OUTPUT'
def get_dport_sport(self):
if self.direction == 'in':
return self.dport, self.sport
else:
return self.sport, self.dport
def get_ipt_rules(self, host=None):
# action
action = 'LOG_ACC' if self.action == 'accept' else 'LOG_DROP'
......@@ -235,9 +229,6 @@ class Rule(models.Model):
if vlan and not vlan.managed:
return retval
# src and dst ports
dport, sport = self.get_dport_sport()
# process foreign vlans
for foreign_vlan in self.foreign_network.vlans.all():
if not foreign_vlan.managed:
......@@ -246,7 +237,7 @@ class Rule(models.Model):
r = IptRule(priority=self.weight, action=action,
proto=self.proto, extra=self.extra,
comment='Rule #%s' % self.pk,
src=src, dst=dst, dport=dport, sport=sport)
src=src, dst=dst, dport=self.dport, sport=self.sport)
chain_name = self.get_chain_name(local=vlan, remote=foreign_vlan)
retval[chain_name] = r
......
......@@ -745,7 +745,7 @@ class RenewOperation(InstanceOperation):
required_perms = ()
concurrency_check = False
def _operation(self, activity, lease=None, force=False):
def _operation(self, activity, lease=None, force=False, save=False):
suspend, delete = self.instance.get_renew_times(lease)
if (not force and suspend and self.instance.time_of_suspend and
suspend < self.instance.time_of_suspend):
......@@ -759,6 +759,8 @@ class RenewOperation(InstanceOperation):
"in its delete time get earlier than before."))
self.instance.time_of_suspend = suspend
self.instance.time_of_delete = delete
if save:
self.instance.lease = lease
self.instance.save()
activity.result = create_readable(ugettext_noop(
"Renewed to suspend at %(suspend)s and destroy at %(delete)s."),
......@@ -779,9 +781,17 @@ class ChangeStateOperation(InstanceOperation):
"resources.")
acl_level = "owner"
required_perms = ('vm.emergency_change_state', )
concurrency_check = False
def _operation(self, user, activity, new_state="NOSTATE"):
def _operation(self, user, activity, new_state="NOSTATE", interrupt=False):
activity.resultant_state = new_state
if interrupt:
msg_txt = ugettext_noop("Activity is forcibly interrupted.")
message = create_readable(msg_txt, msg_txt)
for i in InstanceActivity.objects.filter(
finished__isnull=True, instance=self.instance):
i.finish(False, result=message)
logger.error('Forced finishing activity %s', i)
register_operation(ChangeStateOperation)
......
......@@ -86,7 +86,7 @@ def agent_started(vm, version=None):
if version and version != settings.AGENT_VERSION:
try:
update_agent(vm, instance, act)
update_agent(instance, act)
except TimeoutError:
pass
else:
......@@ -134,9 +134,9 @@ def agent_stopped(vm):
pass
def update_agent(instance, vm, act=None):
def update_agent(instance, act=None):
if act:
act.sub_activity(
act = act.sub_activity(
'update',
readable_name=create_readable(
ugettext_noop('update to %(version)s'),
......@@ -150,5 +150,6 @@ def update_agent(instance, vm, act=None):
version=settings.AGENT_VERSION))
with act:
queue = instance.get_remote_queue_name("agent")
update.apply_async(queue=queue,
args=(vm, create_agent_tar())).get(timeout=10)
update.apply_async(
queue=queue,
args=(instance.vm_name, create_agent_tar())).get(timeout=10)
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