From 5816bf02048fe9163ed61e318579f78af4f11a82 Mon Sep 17 00:00:00 2001 From: Bach Dániel <bd@ik.bme.hu> Date: Sat, 18 Oct 2014 18:34:34 +0200 Subject: [PATCH] dashboard: add RemovePortOperation --- circle/dashboard/forms.py | 31 ++++++++++++++++++++++++++++++- circle/dashboard/templates/dashboard/vm-detail/network.html | 4 ++-- circle/dashboard/views/vm.py | 29 +++++++++++++++++++++++++++++ circle/firewall/models.py | 9 ++++++++- circle/vm/operations.py | 19 +++++++++++++++++++ 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/circle/dashboard/forms.py b/circle/dashboard/forms.py index 4000354..e524474 100644 --- a/circle/dashboard/forms.py +++ b/circle/dashboard/forms.py @@ -40,7 +40,7 @@ from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm from django.forms.widgets import TextInput, HiddenInput from django.template import Context from django.template.loader import render_to_string -from django.utils.html import escape +from django.utils.html import escape, format_html from django.utils.translation import ugettext_lazy as _ from sizefield.widgets import FileSizeWidget from django.core.urlresolvers import reverse_lazy @@ -935,6 +935,35 @@ class VmDeployForm(OperationForm): "(blank allows scheduling automatically)."))) +class VmPortRemoveForm(OperationForm): + def __init__(self, *args, **kwargs): + choices = kwargs.pop('choices') + self.rule = kwargs.pop('default') + + super(VmPortRemoveForm, self).__init__(*args, **kwargs) + + self.fields.insert(0, 'rule', forms.ModelChoiceField( + queryset=choices, initial=self.rule, required=True, + empty_label=None, label=_('Port'))) + if self.rule: + self.fields['rule'].widget = HiddenInput() + + @property + def helper(self): + helper = super(VmPortRemoveForm, self).helper + if self.rule: + helper.layout = Layout( + AnyTag( + "div", + HTML(format_html(_("<label>Port:</label> {0}/{1}"), + escape(self.rule.dport), escape(self.rule.proto))), + css_class="form-group", + ), + Field("rule"), + ) + return helper + + class CircleAuthenticationForm(AuthenticationForm): # fields: username, password diff --git a/circle/dashboard/templates/dashboard/vm-detail/network.html b/circle/dashboard/templates/dashboard/vm-detail/network.html index 9e49384..d4d63b8 100644 --- a/circle/dashboard/templates/dashboard/vm-detail/network.html +++ b/circle/dashboard/templates/dashboard/vm-detail/network.html @@ -78,7 +78,7 @@ {{ l.private }}/{{ l.proto }} </td> <td> - <a href="{% url "dashboard.views.remove-port" pk=instance.pk rule=l.ipv4.pk %}" class="btn btn-link btn-xs vm-details-remove-port" data-rule="{{ l.ipv4.pk }}" title="{% trans "Remove" %}"><i class="fa fa-times"><span class="sr-only">{% trans "Remove" %}</span></i></a> + <a href="{{ op.remove_port.get_url }}?rule={{ l.ipv4.pk }}" class="btn btn-link btn-xs vm-details-remove-port" data-rule="{{ l.ipv4.pk }}" title="{% trans "Remove" %}"><i class="fa fa-times"><span class="sr-only">{% trans "Remove" %}</span></i></a> </td> </tr> {% endif %} @@ -110,7 +110,7 @@ {{ l.private }}/{{ l.proto }} </td> <td> - <a href="{% url "dashboard.views.remove-port" pk=instance.pk rule=l.ipv4.pk %}" class="btn btn-link btn-xs vm-details-remove-port" data-rule="{{ l.ipv6.pk }}" title="{% trans "Remove" %}"><i class="fa fa-times"><span class="sr-only">{% trans "Remove" %}</span></i></a> + <a href="{{ op.remove_port.get_url }}?rule={{ l.ipv4.pk }}" class="btn btn-link btn-xs vm-details-remove-port" data-rule="{{ l.ipv6.pk }}" title="{% trans "Remove" %}"><i class="fa fa-times"><span class="sr-only">{% trans "Remove" %}</span></i></a> </td> </tr> {% endif %} diff --git a/circle/dashboard/views/vm.py b/circle/dashboard/views/vm.py index bbfa2cb..24ac55d 100644 --- a/circle/dashboard/views/vm.py +++ b/circle/dashboard/views/vm.py @@ -62,6 +62,7 @@ from ..forms import ( VmRenewForm, VmStateChangeForm, VmListSearchForm, VmCustomizeForm, TransferOwnershipForm, VmDiskResizeForm, RedeployForm, VmDiskRemoveForm, VmMigrateForm, VmDeployForm, + VmPortRemoveForm, ) from ..models import Favourite, Profile @@ -450,6 +451,33 @@ class VmMigrateView(FormOperationMixin, VmOperationView): return val +class VmPortRemoveView(FormOperationMixin, VmOperationView): + + op = 'remove_port' + show_in_toolbar = False + with_reload = True + icon = 'times' + effect = "danger" + form_class = VmPortRemoveForm + + def get_form_kwargs(self): + instance = self.get_op().instance + choices = Rule.portforwards().filter( + host__interface__instance=instance) + rule_pk = self.request.GET.get('rule') + if rule_pk: + try: + default = choices.get(pk=rule_pk) + except (ValueError, Rule.DoesNotExist): + raise Http404() + else: + default = None + + val = super(VmPortRemoveView, self).get_form_kwargs() + val.update({'choices': choices, 'default': default}) + return val + + class VmSaveView(FormOperationMixin, VmOperationView): op = 'save_as_template' @@ -683,6 +711,7 @@ vm_ops = OrderedDict([ op='remove_disk', form_class=VmDiskRemoveForm, icon='times', effect="danger")), ('add_interface', VmAddInterfaceView), + ('remove_port', VmPortRemoveView), ('renew', VmRenewView), ('resources_change', VmResourcesChangeView), ('password_reset', VmOperationView.factory( diff --git a/circle/firewall/models.py b/circle/firewall/models.py index cfa4a99..2a7e6ba 100644 --- a/circle/firewall/models.py +++ b/circle/firewall/models.py @@ -243,6 +243,13 @@ class Rule(models.Model): return retval + @classmethod + def portforwards(cls, host=None): + qs = cls.objects.filter(dport__isnull=False, direction='in') + if host is not None: + qs = qs.filter(host=host) + return qs + class Meta: verbose_name = _("rule") verbose_name_plural = _("rules") @@ -762,7 +769,7 @@ class Host(models.Model): Return a list of ports with forwarding rules set. """ retval = [] - for rule in self.rules.filter(dport__isnull=False, direction='in'): + for rule in Rule.portforwards(host=self): forward = { 'proto': rule.proto, 'private': rule.dport, diff --git a/circle/vm/operations.py b/circle/vm/operations.py index cfad163..9db81b6 100644 --- a/circle/vm/operations.py +++ b/circle/vm/operations.py @@ -606,6 +606,25 @@ class RemoveInterfaceOperation(InstanceOperation): @register_operation +class RemovePortOperation(InstanceOperation): + id = 'remove_port' + name = _("close port") + description = _("Close the specified port.") + concurrency_check = False + required_perms = () + accept_states = () + + def _operation(self, activity, rule): + interface = rule.host.interface_set.get() + if interface.instance != self.instance: + raise PermissionDenied() + activity.readable_name = create_readable( + ugettext_noop("close %(proto)s/%(port)d on %(host)s"), + proto=rule.proto, port=rule.dport, host=rule.host) + rule.delete() + + +@register_operation class RemoveDiskOperation(InstanceOperation): id = 'remove_disk' name = _("remove disk") -- libgit2 0.26.0