Commit 6d44029c by Őry Máté

Merge branch 'master' into feature-pipeline

Conflicts:
	circle/dashboard/templates/dashboard/base.html
parents 43de3dcb 1ca7edbb
...@@ -108,9 +108,9 @@ $(function () { ...@@ -108,9 +108,9 @@ $(function () {
e.stopImmediatePropagation(); e.stopImmediatePropagation();
return false; return false;
}); });
$('[title]:not(.title-favourite)').tooltip(); $('body [title]:not(.title-favourite)').tooltip();
$('.title-favourite').tooltip({'placement': 'right'}); $('body .title-favourite').tooltip({'placement': 'right'});
$(':input[title]').tooltip({trigger: 'focus', placement: 'auto right'}); $('body :input[title]').tooltip({trigger: 'focus', placement: 'auto right'});
$(".knob").knob(); $(".knob").knob();
$('[data-toggle="pill"]').click(function() { $('[data-toggle="pill"]').click(function() {
...@@ -132,7 +132,7 @@ $(function () { ...@@ -132,7 +132,7 @@ $(function () {
$('.js-hidden').hide(); $('.js-hidden').hide();
/* favourite star */ /* favourite star */
$("#dashboard-vm-list").on('click', '.dashboard-vm-favourite', function(e) { $("#dashboard-vm-list, .page-header").on('click', '.dashboard-vm-favourite', function(e) {
var star = $(this).children("i"); var star = $(this).children("i");
var pk = $(this).data("vm"); var pk = $(this).data("vm");
if(star.hasClass("fa-star-o")) { if(star.hasClass("fa-star-o")) {
......
...@@ -5,6 +5,11 @@ ...@@ -5,6 +5,11 @@
{% block title-site %}Dashboard | CIRCLE{% endblock %} {% block title-site %}Dashboard | CIRCLE{% endblock %}
{% block extra_link %}
{% block extra_link_2 %}{% endblock %}
{% endblock %}
{% block navbar-brand %} {% block navbar-brand %}
<a class="navbar-brand" href="{% url "dashboard.index" %}" style="padding: 10px 15px;"> <a class="navbar-brand" href="{% url "dashboard.index" %}" style="padding: 10px 15px;">
{% include "branding.html" %} {% include "branding.html" %}
......
...@@ -4,6 +4,13 @@ ...@@ -4,6 +4,13 @@
{% block title-page %}{% trans "Index" %}{% endblock %} {% block title-page %}{% trans "Index" %}{% endblock %}
{% block extra_link_2 %}
<link rel="search"
type="application/opensearchdescription+xml"
href="{% url "dashboard.views.vm-opensearch" %}"
title="{% blocktrans with name=COMPANY_NAME %}{{name}} virtual machines{% endblocktrans %}" />
{% endblock %}
{% block content %} {% block content %}
<div class="body-content dashboard-index"> <div class="body-content dashboard-index">
<div class="row"> <div class="row">
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
<dd>{{ object.task_uuid|default:'n/a' }}</dd> <dd>{{ object.task_uuid|default:'n/a' }}</dd>
<dt>{% trans "status" %}</dt> <dt>{% trans "status" %}</dt>
<dd>{{ object.get_status_id }}</dd> <dd id="activity_status">{{ object.get_status_id }}</dd>
<dt>{% trans "result" %}</dt> <dt>{% trans "result" %}</dt>
......
...@@ -72,6 +72,13 @@ ...@@ -72,6 +72,13 @@
{{ instance.name }} {{ instance.name }}
</div> </div>
<small>{{ instance.primary_host.get_fqdn }}</small> <small>{{ instance.primary_host.get_fqdn }}</small>
<small class="dashboard-vm-favourite" style="line-height: 39.6px;" data-vm="{{ instance.pk }}">
{% if fav %}
<i class="fa fa-star text-primary title-favourite" title="{% trans "Unfavourite" %}"></i>
{% else %}
<i class="fa fa-star-o text-primary title-favourite" title="{% trans "Mark as favorite" %}"></i>
{% endif %}
</small>
</h1> </h1>
<div style="clear: both;"></div> <div style="clear: both;"></div>
</div> </div>
......
{% load i18n %}<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>{% blocktrans with name=COMPANY_NAME %}{{name}} virtual machines{% endblocktrans %}</ShortName>
<Url type="text/html"
template="{{ url }}?s={searchTerms}&amp;stype=shared" />
</OpenSearchDescription>
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from os import listdir
from os.path import isfile, isdir, join
import unittest
from django.conf import settings
from django.template import Template, Context, VariableDoesNotExist
from django.template.loader import find_template_loader
from django.core.urlresolvers import NoReverseMatch
class TemplateSyntaxTestCase(unittest.TestCase):
def test_templates(self):
"""Test all templates for syntax errors."""
for loader_name in settings.TEMPLATE_LOADERS:
print loader_name
loader = find_template_loader(loader_name)
self._test_dir(loader.get_template_sources(''))
def _test_dir(self, dir, path="/"):
for i in dir:
i = join(path, i)
if isfile(i):
self._test_template(join(path, i))
elif isdir(i):
print "%s:" % i
self._test_dir(listdir(i), i)
def _test_template(self, path):
print path
try:
Template(open(path).read()).render(Context({}))
except (NoReverseMatch, VariableDoesNotExist, KeyError, AttributeError,
ValueError, ) as e:
print e
...@@ -50,6 +50,7 @@ from .views import ( ...@@ -50,6 +50,7 @@ from .views import (
VmGraphView, NodeGraphView, NodeListGraphView, VmGraphView, NodeGraphView, NodeListGraphView,
TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView, TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView,
TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView, TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView,
OpenSearchDescriptionView,
) )
from .views.vm import vm_ops, vm_mass_ops from .views.vm import vm_ops, vm_mass_ops
from .views.node import node_ops from .views.node import node_ops
...@@ -221,6 +222,8 @@ urlpatterns = patterns( ...@@ -221,6 +222,8 @@ urlpatterns = patterns(
name="dashboard.views.client-check"), name="dashboard.views.client-check"),
url(r'^token-login/(?P<token>.*)/$', TokenLogin.as_view(), url(r'^token-login/(?P<token>.*)/$', TokenLogin.as_view(),
name="dashboard.views.token-login"), name="dashboard.views.token-login"),
url(r'^vm/opensearch.xml$', OpenSearchDescriptionView.as_view(),
name="dashboard.views.vm-opensearch"),
) )
urlpatterns += patterns( urlpatterns += patterns(
......
...@@ -19,6 +19,7 @@ from __future__ import unicode_literals, absolute_import ...@@ -19,6 +19,7 @@ from __future__ import unicode_literals, absolute_import
import logging import logging
from django.core.cache import get_cache from django.core.cache import get_cache
from django.core.urlresolvers import reverse
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.views.generic import TemplateView from django.views.generic import TemplateView
...@@ -121,3 +122,15 @@ class HelpView(TemplateView): ...@@ -121,3 +122,15 @@ class HelpView(TemplateView):
ctx.update({"saml": hasattr(settings, "SAML_CONFIG"), ctx.update({"saml": hasattr(settings, "SAML_CONFIG"),
"store": settings.STORE_URL}) "store": settings.STORE_URL})
return ctx return ctx
class OpenSearchDescriptionView(TemplateView):
template_name = "dashboard/vm-opensearch.xml"
content_type = "application/opensearchdescription+xml"
def get_context_data(self, **kwargs):
context = super(OpenSearchDescriptionView, self).get_context_data(
**kwargs)
context['url'] = self.request.build_absolute_uri(
reverse("dashboard.views.vm-list"))
return context
...@@ -115,6 +115,7 @@ class VmDetailView(GraphMixin, CheckedDetailView): ...@@ -115,6 +115,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
'op': {i.op: i for i in ops}, 'op': {i.op: i for i in ops},
'connect_commands': user.profile.get_connect_commands(instance), 'connect_commands': user.profile.get_connect_commands(instance),
'hide_tutorial': hide_tutorial, 'hide_tutorial': hide_tutorial,
'fav': instance.favourite_set.filter(user=user).exists(),
}) })
# activity data # activity data
......
...@@ -112,10 +112,12 @@ def pull(dir="~/circle/circle"): ...@@ -112,10 +112,12 @@ def pull(dir="~/circle/circle"):
@roles('portal') @roles('portal')
def update_portal(test=False): def update_portal(test=False, git=True):
"Update and restart portal+manager" "Update and restart portal+manager"
with _stopped("portal", "manager"): with _stopped("portal", "manager"):
if git:
pull() pull()
cleanup()
pip("circle", "~/circle/requirements.txt") pip("circle", "~/circle/requirements.txt")
bower() bower()
migrate() migrate()
...@@ -125,6 +127,12 @@ def update_portal(test=False): ...@@ -125,6 +127,12 @@ def update_portal(test=False):
@roles('portal') @roles('portal')
def build_portal():
"Update portal without pulling from git"
return update_portal(False, False)
@roles('portal')
def stop_portal(test=False): def stop_portal(test=False):
"Stop portal and manager" "Stop portal and manager"
_stop_services("portal", "manager") _stop_services("portal", "manager")
...@@ -136,10 +144,15 @@ def update_node(): ...@@ -136,10 +144,15 @@ def update_node():
with _stopped("node", "agentdriver", "monitor-client"): with _stopped("node", "agentdriver", "monitor-client"):
pull("~/vmdriver") pull("~/vmdriver")
pip("vmdriver", "~/vmdriver/requirements/production.txt") pip("vmdriver", "~/vmdriver/requirements/production.txt")
_cleanup("~/vmdriver")
pull("~/agentdriver") pull("~/agentdriver")
pip("agentdriver", "~/agentdriver/requirements.txt") pip("agentdriver", "~/agentdriver/requirements.txt")
_cleanup("~/agentdriver")
pull("~/monitor-client") pull("~/monitor-client")
pip("monitor-client", "~/monitor-client/requirements.txt") pip("monitor-client", "~/monitor-client/requirements.txt")
_cleanup("~/monitor-client")
@parallel @parallel
...@@ -161,6 +174,18 @@ def checkout(vmdriver="master", agent="master"): ...@@ -161,6 +174,18 @@ def checkout(vmdriver="master", agent="master"):
run("git checkout %s" % agent) run("git checkout %s" % agent)
@roles('portal')
def cleanup():
"Clean pyc files of portal"
_cleanup()
def _cleanup(dir="~/circle/circle"):
"Clean pyc files"
with cd("~/circle/circle"):
run("find -name '*.py[co]' -exec rm -f {} +")
def _stop_services(*services): def _stop_services(*services):
"Stop given services (warn only if not running)" "Stop given services (warn only if not running)"
with settings(warn_only=True): with settings(warn_only=True):
...@@ -189,3 +214,12 @@ def _stopped(*services): ...@@ -189,3 +214,12 @@ def _stopped(*services):
def _workon(name): def _workon(name):
return prefix("source ~/.virtualenvs/%s/bin/activate && " return prefix("source ~/.virtualenvs/%s/bin/activate && "
"source ~/.virtualenvs/%s/bin/postactivate" % (name, name)) "source ~/.virtualenvs/%s/bin/postactivate" % (name, name))
@roles('portal')
def install_bash_completion_script():
sudo("wget https://raw.githubusercontent.com/marcelor/fabric-bash-"
"autocompletion/48baf5735bafbb2be5be8787d2c2c04a44b6cdb0/fab "
"-O /etc/bash_completion.d/fab")
print("To have bash completion instantly, run\n"
" source /etc/bash_completion.d/fab")
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from string import ascii_letters
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -22,7 +23,7 @@ from django.utils.ipv6 import is_valid_ipv6_address ...@@ -22,7 +23,7 @@ from django.utils.ipv6 import is_valid_ipv6_address
from south.modelsinspector import add_introspection_rules from south.modelsinspector import add_introspection_rules
from django import forms from django import forms
from netaddr import (IPAddress, IPNetwork, AddrFormatError, ZEROFILL, from netaddr import (IPAddress, IPNetwork, AddrFormatError, ZEROFILL,
EUI, mac_unix) EUI, mac_unix, AddrConversionError)
import re import re
...@@ -31,7 +32,6 @@ domain_re = re.compile(r'^([A-Za-z0-9_-]\.?)+$') ...@@ -31,7 +32,6 @@ domain_re = re.compile(r'^([A-Za-z0-9_-]\.?)+$')
domain_wildcard_re = re.compile(r'^(\*\.)?([A-Za-z0-9_-]\.?)+$') domain_wildcard_re = re.compile(r'^(\*\.)?([A-Za-z0-9_-]\.?)+$')
ipv4_re = re.compile('^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$') ipv4_re = re.compile('^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
reverse_domain_re = re.compile(r'^(%\([abcd]\)d|[a-z0-9.-])+$') reverse_domain_re = re.compile(r'^(%\([abcd]\)d|[a-z0-9.-])+$')
ipv6_template_re = re.compile(r'^(%\([abcd]\)[dxX]|[A-Za-z0-9:-])+$')
class mac_custom(mac_unix): class mac_custom(mac_unix):
...@@ -246,15 +246,60 @@ def val_reverse_domain(value): ...@@ -246,15 +246,60 @@ def val_reverse_domain(value):
raise ValidationError(u'%s - invalid reverse domain name' % value) raise ValidationError(u'%s - invalid reverse domain name' % value)
def is_valid_ipv6_template(value):
"""Check whether the parameter is a valid ipv6 template."""
return ipv6_template_re.match(value) is not None
def val_ipv6_template(value): def val_ipv6_template(value):
"""Validate whether the parameter is a valid ipv6 template.""" """Validate whether the parameter is a valid ipv6 template.
if not is_valid_ipv6_template(value):
raise ValidationError(u'%s - invalid reverse ipv6 template' % value) Normal use:
>>> val_ipv6_template("123::%(a)d:%(b)d:%(c)d:%(d)d")
>>> val_ipv6_template("::%(a)x:%(b)x:%(c)d:%(d)d")
Don't have to use all bytes from the left (no a):
>>> val_ipv6_template("::%(b)x:%(c)d:%(d)d")
But have to use all ones to the right (a, but no b):
>>> val_ipv6_template("::%(a)x:%(c)d:%(d)d")
Traceback (most recent call last):
...
ValidationError: [u"template doesn't use parameter b"]
Detects valid templates building invalid ips:
>>> val_ipv6_template("xxx::%(a)d:%(b)d:%(c)d:%(d)d")
Traceback (most recent call last):
...
ValidationError: [u'template renders invalid IPv6 address']
Also IPv4-compatible addresses are invalid:
>>> val_ipv6_template("::%(a)02x%(b)02x:%(c)d:%(d)d")
Traceback (most recent call last):
...
ValidationError: [u'template results in IPv4 address']
"""
tpl = {ascii_letters[i]: 255 for i in range(4)}
try:
v6 = value % tpl
except:
raise ValidationError(_('%s: invalid template') % value)
used = False
for i in ascii_letters[:4]:
try:
value % {k: tpl[k] for k in tpl if k != i}
except KeyError:
used = True # ok, it misses this key
else:
if used:
raise ValidationError(
_("template doesn't use parameter %s") % i)
try:
v6 = IPAddress(v6, 6)
except:
raise ValidationError(_('template renders invalid IPv6 address'))
try:
v6.ipv4()
except (AddrConversionError, AddrFormatError):
pass # can't converted to ipv4 == it's real ipv6
else:
raise ValidationError(_('template results in IPv4 address'))
def is_valid_ipv4_address(value): def is_valid_ipv4_address(value):
...@@ -284,12 +329,3 @@ def val_mx(value): ...@@ -284,12 +329,3 @@ def val_mx(value):
domain_re.match(mx[1])): domain_re.match(mx[1])):
raise ValidationError(_("Bad MX address format. " raise ValidationError(_("Bad MX address format. "
"Should be: <priority>:<hostname>")) "Should be: <priority>:<hostname>"))
def convert_ipv4_to_ipv6(ipv6_template, ipv4):
"""Convert IPv4 address string to IPv6 address string."""
m = ipv4.words
return IPAddress(ipv6_template % {'a': int(m[0]),
'b': int(m[1]),
'c': int(m[2]),
'd': int(m[3])})
...@@ -17,9 +17,11 @@ ...@@ -17,9 +17,11 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from itertools import islice, ifilter from string import ascii_letters
from itertools import islice, ifilter, chain
from math import ceil
import logging import logging
from netaddr import IPSet, EUI, IPNetwork import random
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
...@@ -28,15 +30,17 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -28,15 +30,17 @@ from django.utils.translation import ugettext_lazy as _
from firewall.fields import (MACAddressField, val_alfanum, val_reverse_domain, from firewall.fields import (MACAddressField, val_alfanum, val_reverse_domain,
val_ipv6_template, val_domain, val_ipv4, val_ipv6_template, val_domain, val_ipv4,
val_domain_wildcard, val_domain_wildcard,
val_ipv6, val_mx, convert_ipv4_to_ipv6, val_ipv6, val_mx,
IPNetworkField, IPAddressField) IPNetworkField, IPAddressField)
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
import django.conf import django.conf
from django.db.models.signals import post_save, post_delete from django.db.models.signals import post_save, post_delete
import random from celery.exceptions import TimeoutError
from netaddr import IPSet, EUI, IPNetwork, IPAddress, ipv6_full
from common.models import method_cache, WorkerNotFound, HumanSortField from common.models import method_cache, WorkerNotFound, HumanSortField
from firewall.tasks.local_tasks import reloadtask from firewall.tasks.local_tasks import reloadtask
from firewall.tasks.remote_tasks import get_dhcp_clients
from .iptables import IptRule from .iptables import IptRule
from acl.models import AclBase from acl.models import AclBase
...@@ -360,9 +364,19 @@ class Vlan(AclBase, models.Model): ...@@ -360,9 +364,19 @@ class Vlan(AclBase, models.Model):
'address is: "%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa".'), 'address is: "%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa".'),
default="%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa") default="%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa")
ipv6_template = models.TextField( ipv6_template = models.TextField(
validators=[val_ipv6_template], blank=True,
verbose_name=_('ipv6 template'), help_text=_('Template for translating IPv4 addresses to IPv6. '
default="2001:738:2001:4031:%(b)d:%(c)d:%(d)d:0") 'Automatically generated hosts in dual-stack networks '
'will get this address. The template '
'can contain four tokens: "%(a)d", "%(b)d", '
'"%(c)d", and "%(d)d", representing the four bytes '
'of the IPv4 address, respectively, in decimal notation. '
'Moreover you can use any standard printf format '
'specification like %(a)02x to get the first byte as two '
'hexadecimal digits. Usual choices for mapping '
'198.51.100.0/24 to 2001:0DB8:1:1::/64 would be '
'"2001:db8:1:1:%(d)d::" and "2001:db8:1:1:%(d)02x00::".'),
validators=[val_ipv6_template], verbose_name=_('ipv6 template'))
dhcp_pool = models.TextField(blank=True, verbose_name=_('DHCP pool'), dhcp_pool = models.TextField(blank=True, verbose_name=_('DHCP pool'),
help_text=_( help_text=_(
'The address range of the DHCP pool: ' 'The address range of the DHCP pool: '
...@@ -377,6 +391,87 @@ class Vlan(AclBase, models.Model): ...@@ -377,6 +391,87 @@ class Vlan(AclBase, models.Model):
modified_at = models.DateTimeField(auto_now=True, modified_at = models.DateTimeField(auto_now=True,
verbose_name=_('modified at')) verbose_name=_('modified at'))
def clean(self):
super(Vlan, self).clean()
if self.ipv6_template:
if not self.network6:
raise ValidationError(
_("You cannot specify an IPv6 template if there is no "
"IPv6 network set."))
for i in (self.network4[1], self.network4[-1]):
i6 = self.convert_ipv4_to_ipv6(i)
if i6 not in self.network6:
raise ValidationError(
_("%(ip6)s (translated from %(ip4)s) is outside of "
"the IPv6 network.") % {"ip4": i, "ip6": i6})
if self.network6:
tpl, prefixlen = self._magic_ipv6_template(self.network4,
self.network6)
if not self.ipv6_template:
self.ipv6_template = tpl
if not self.host_ipv6_prefixlen:
self.host_ipv6_prefixlen = prefixlen
@staticmethod
def _host_bytes(prefixlen, maxbytes):
return int(ceil((maxbytes - prefixlen / 8.0)))
@staticmethod
def _append_hexa(s, v, lasthalf):
if lasthalf: # can use last half word
assert s[-1] == "0" or s[-1].endswith("00")
if s[-1].endswith("00"):
s[-1] = s[-1][:-2]
s[-1] += "%({})02x".format(v)
s[-1].lstrip("0")
else:
s.append("%({})02x00".format(v))
@classmethod
def _magic_ipv6_template(cls, network4, network6, verbose=None):
"""Offer a sensible ipv6_template value.
Based on prefix lengths the method magically selects verbose (decimal)
format:
>>> Vlan._magic_ipv6_template(IPNetwork("198.51.100.0/24"),
... IPNetwork("2001:0DB8:1:1::/64"))
('2001:db8:1:1:%(d)d::', 80)
However you can explicitly select non-verbose, i.e. hexa format:
>>> Vlan._magic_ipv6_template(IPNetwork("198.51.100.0/24"),
... IPNetwork("2001:0DB8:1:1::/64"), False)
('2001:db8:1:1:%(d)02x00::', 72)
"""
host4_bytes = cls._host_bytes(network4.prefixlen, 4)
host6_bytes = cls._host_bytes(network6.prefixlen, 16)
if host4_bytes > host6_bytes:
raise ValidationError(
_("IPv6 network is too small to map IPv4 addresses to it."))
letters = ascii_letters[4-host4_bytes:4]
remove = host6_bytes // 2
ipstr = network6.network.format(ipv6_full)
s = ipstr.split(":")[0:-remove]
if verbose is None: # use verbose format if net6 much wider
verbose = 2 * (host4_bytes + 1) < host6_bytes
if verbose:
for i in letters:
s.append("%({})d".format(i))
else:
remain = host6_bytes
for i in letters:
cls._append_hexa(s, i, remain % 2 == 1)
remain -= 1
if host6_bytes > host4_bytes:
s.append(":")
tpl = ":".join(s)
# compute prefix length
mask = int(IPAddress(tpl % {"a": 1, "b": 1, "c": 1, "d": 1}))
prefixlen = 128
while mask % 2 == 0:
mask /= 2
prefixlen -= 1
return (tpl, prefixlen)
def __unicode__(self): def __unicode__(self):
return "%s - %s" % ("managed" if self.managed else "unmanaged", return "%s - %s" % ("managed" if self.managed else "unmanaged",
self.name) self.name)
...@@ -402,7 +497,7 @@ class Vlan(AclBase, models.Model): ...@@ -402,7 +497,7 @@ class Vlan(AclBase, models.Model):
logger.debug("Found unused IPv4 address %s.", ipv4) logger.debug("Found unused IPv4 address %s.", ipv4)
ipv6 = None ipv6 = None
if self.network6 is not None: if self.network6 is not None:
ipv6 = convert_ipv4_to_ipv6(self.ipv6_template, ipv4) ipv6 = self.convert_ipv4_to_ipv6(ipv4)
if ipv6 in used_v6: if ipv6 in used_v6:
continue continue
else: else:
...@@ -411,6 +506,20 @@ class Vlan(AclBase, models.Model): ...@@ -411,6 +506,20 @@ class Vlan(AclBase, models.Model):
else: else:
raise ValidationError(_("All IP addresses are already in use.")) raise ValidationError(_("All IP addresses are already in use."))
def convert_ipv4_to_ipv6(self, ipv4):
"""Convert IPv4 address string to IPv6 address string."""
if isinstance(ipv4, basestring):
ipv4 = IPAddress(ipv4, 4)
nums = {ascii_letters[i]: int(ipv4.words[i]) for i in range(4)}
return IPAddress(self.ipv6_template % nums)
def get_dhcp_clients(self):
macs = set(i.mac for i in self.host_set.all())
return [{"mac": k, "ip": v["ip"], "hostname": v["hostname"]}
for k, v in chain(*(fw.get_dhcp_clients().iteritems()
for fw in Firewall.objects.all() if fw))
if v["interface"] == self.name and EUI(k) not in macs]
class VlanGroup(models.Model): class VlanGroup(models.Model):
""" """
...@@ -581,8 +690,7 @@ class Host(models.Model): ...@@ -581,8 +690,7 @@ class Host(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.id and self.ipv6 == "auto": if not self.id and self.ipv6 == "auto":
self.ipv6 = convert_ipv4_to_ipv6(self.vlan.ipv6_template, self.ipv6 = self.vlan.convert_ipv4_to_ipv6(self.ipv4)
self.ipv4)
self.full_clean() self.full_clean()
super(Host, self).save(*args, **kwargs) super(Host, self).save(*args, **kwargs)
...@@ -838,7 +946,7 @@ class Firewall(models.Model): ...@@ -838,7 +946,7 @@ class Firewall(models.Model):
return self.name return self.name
@method_cache(30) @method_cache(30)
def get_remote_queue_name(self, queue_id): def get_remote_queue_name(self, queue_id="firewall"):
"""Returns the name of the remote celery queue for this node. """Returns the name of the remote celery queue for this node.
Throws Exception if there is no worker on the queue. Throws Exception if there is no worker on the queue.
...@@ -851,6 +959,20 @@ class Firewall(models.Model): ...@@ -851,6 +959,20 @@ class Firewall(models.Model):
else: else:
raise WorkerNotFound() raise WorkerNotFound()
@method_cache(20)
def get_dhcp_clients(self):
try:
return get_dhcp_clients.apply_async(
queue=self.get_remote_queue_name(), expires=60).get(timeout=2)
except TimeoutError:
logger.info("get_dhcp_clients task timed out")
except IOError:
logger.exception("get_dhcp_clients failed. "
"maybe syslog isn't readble by firewall worker")
except:
logger.exception("get_dhcp_clients failed")
return {}
class Domain(models.Model): class Domain(models.Model):
name = models.CharField(max_length=40, validators=[val_domain], name = models.CharField(max_length=40, validators=[val_domain],
......
...@@ -62,5 +62,6 @@ def reload_blacklist(data): ...@@ -62,5 +62,6 @@ def reload_blacklist(data):
@celery.task(name='firewall.get_dhcp_clients') @celery.task(name='firewall.get_dhcp_clients')
def get_dhcp_clients(data): def get_dhcp_clients():
# {'00:21:5a:73:72:cd': {'interface': 'OFF', 'ip': None, 'hostname': None}}
pass pass
...@@ -78,6 +78,7 @@ class GetNewAddressTestCase(TestCase): ...@@ -78,6 +78,7 @@ class GetNewAddressTestCase(TestCase):
self.vlan = Vlan(vid=1, name='test', network4='10.0.0.0/29', self.vlan = Vlan(vid=1, name='test', network4='10.0.0.0/29',
network6='2001:738:2001:4031::/80', domain=d, network6='2001:738:2001:4031::/80', domain=d,
owner=self.u1) owner=self.u1)
self.vlan.clean()
self.vlan.save() self.vlan.save()
self.vlan.host_set.all().delete() self.vlan.host_set.all().delete()
for i in [1] + range(3, 6): for i in [1] + range(3, 6):
...@@ -85,6 +86,9 @@ class GetNewAddressTestCase(TestCase): ...@@ -85,6 +86,9 @@ class GetNewAddressTestCase(TestCase):
ipv4='10.0.0.%d' % i, vlan=self.vlan, ipv4='10.0.0.%d' % i, vlan=self.vlan,
owner=self.u1).save() owner=self.u1).save()
def tearDown(self):
self.vlan.delete()
def test_new_addr_w_empty_vlan(self): def test_new_addr_w_empty_vlan(self):
self.vlan.host_set.all().delete() self.vlan.host_set.all().delete()
self.vlan.get_new_address() self.vlan.get_new_address()
...@@ -96,12 +100,6 @@ class GetNewAddressTestCase(TestCase): ...@@ -96,12 +100,6 @@ class GetNewAddressTestCase(TestCase):
owner=self.u1).save() owner=self.u1).save()
self.assertRaises(ValidationError, self.vlan.get_new_address) self.assertRaises(ValidationError, self.vlan.get_new_address)
def test_all_addr_in_use_w_ipv6(self):
Host(hostname='h-x', mac='01:02:03:04:05:06',
ipv4='10.0.0.6', ipv6='2001:738:2001:4031:0:0:2:0',
vlan=self.vlan, owner=self.u1).save()
self.assertRaises(ValidationError, self.vlan.get_new_address)
def test_new_addr(self): def test_new_addr(self):
used_v4 = IPSet(self.vlan.host_set.values_list('ipv4', flat=True)) used_v4 = IPSet(self.vlan.host_set.values_list('ipv4', flat=True))
assert self.vlan.get_new_address()['ipv4'] not in used_v4 assert self.vlan.get_new_address()['ipv4'] not in used_v4
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -6,7 +6,7 @@ msgid "" ...@@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-10-20 12:09+0200\n" "POT-Creation-Date: 2014-11-14 13:43+0100\n"
"PO-Revision-Date: 2014-10-20 12:21+0200\n" "PO-Revision-Date: 2014-10-20 12:21+0200\n"
"Last-Translator: Mate Ory <ory.mate@ik.bme.hu>\n" "Last-Translator: Mate Ory <ory.mate@ik.bme.hu>\n"
"Language-Team: Hungarian <cloud@ik.bme.hu>\n" "Language-Team: Hungarian <cloud@ik.bme.hu>\n"
...@@ -17,7 +17,7 @@ msgstr "" ...@@ -17,7 +17,7 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 1.5\n" "X-Generator: Lokalize 1.5\n"
#: dashboard/static/dashboard/dashboard.js:68 #: dashboard/static/dashboard/dashboard.js:83
#: static_collected/all.047675ebf594.js:3443 #: static_collected/all.047675ebf594.js:3443
#: static_collected/all.0aecd87e873a.js:3443 #: static_collected/all.0aecd87e873a.js:3443
#: static_collected/all.0db607331718.js:3443 #: static_collected/all.0db607331718.js:3443
...@@ -34,13 +34,13 @@ msgstr "" ...@@ -34,13 +34,13 @@ msgstr ""
#: static_collected/dashboard/dashboard.be8725cd91bf.js:68 #: static_collected/dashboard/dashboard.be8725cd91bf.js:68
#: static_collected/dashboard/dashboard.e740d80401b2.js:68 #: static_collected/dashboard/dashboard.e740d80401b2.js:68
#: static_collected/dashboard/dashboard.fe0a2f126346.js:68 #: static_collected/dashboard/dashboard.fe0a2f126346.js:68
#: static_collected/dashboard/dashboard.js:68 #: static_collected/dashboard/dashboard.js:83
msgid "Select an option to proceed!" msgid "Select an option to proceed!"
msgstr "Válasszon a folytatáshoz." msgstr "Válasszon a folytatáshoz."
#: dashboard/static/dashboard/dashboard.js:259 #: dashboard/static/dashboard/dashboard.js:274
#: dashboard/static/dashboard/dashboard.js:307 #: dashboard/static/dashboard/dashboard.js:322
#: dashboard/static/dashboard/dashboard.js:317 #: dashboard/static/dashboard/dashboard.js:332
#: static_collected/all.047675ebf594.js:3633 #: static_collected/all.047675ebf594.js:3633
#: static_collected/all.047675ebf594.js:3681 #: static_collected/all.047675ebf594.js:3681
#: static_collected/all.047675ebf594.js:3691 #: static_collected/all.047675ebf594.js:3691
...@@ -88,9 +88,9 @@ msgstr "Válasszon a folytatáshoz." ...@@ -88,9 +88,9 @@ msgstr "Válasszon a folytatáshoz."
#: static_collected/dashboard/dashboard.fe0a2f126346.js:258 #: static_collected/dashboard/dashboard.fe0a2f126346.js:258
#: static_collected/dashboard/dashboard.fe0a2f126346.js:306 #: static_collected/dashboard/dashboard.fe0a2f126346.js:306
#: static_collected/dashboard/dashboard.fe0a2f126346.js:316 #: static_collected/dashboard/dashboard.fe0a2f126346.js:316
#: static_collected/dashboard/dashboard.js:259 #: static_collected/dashboard/dashboard.js:274
#: static_collected/dashboard/dashboard.js:307 #: static_collected/dashboard/dashboard.js:322
#: static_collected/dashboard/dashboard.js:317 #: static_collected/dashboard/dashboard.js:332
msgid "No result" msgid "No result"
msgstr "Nincs eredmény" msgstr "Nincs eredmény"
...@@ -129,10 +129,12 @@ msgid "Unknown error." ...@@ -129,10 +129,12 @@ msgid "Unknown error."
msgstr "Ismeretlen hiba." msgstr "Ismeretlen hiba."
#: dashboard/static/dashboard/template-list.js:103 #: dashboard/static/dashboard/template-list.js:103
#: static_collected/dashboard/template-list.js:103
msgid "Only the owners can delete the selected object." msgid "Only the owners can delete the selected object."
msgstr "Csak a tulajdonos törölheti a kiválasztott elemet." msgstr "Csak a tulajdonos törölheti a kiválasztott elemet."
#: dashboard/static/dashboard/template-list.js:105 #: dashboard/static/dashboard/template-list.js:105
#: static_collected/dashboard/template-list.js:105
msgid "An error occurred. (" msgid "An error occurred. ("
msgstr "Hiba történt. (" msgstr "Hiba történt. ("
...@@ -206,11 +208,12 @@ msgstr "Jelszó megjelenítése" ...@@ -206,11 +208,12 @@ msgstr "Jelszó megjelenítése"
#: static_collected/vm-detail.js:6391 #: static_collected/vm-detail.js:6391
#: static_collected/dashboard/vm-tour.1562cc89a659.js:22 #: static_collected/dashboard/vm-tour.1562cc89a659.js:22
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:22 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:22
#: static_collected/dashboard/vm-tour.js:22 #: static_collected/dashboard/vm-tour.js:6
msgid "Next" msgid "Next"
msgstr "Tovább" msgstr "Tovább"
#: dashboard/static/dashboard/vm-tour.js:7 #: dashboard/static/dashboard/vm-tour.js:7
#: static_collected/dashboard/vm-tour.js:7
msgid "Previous" msgid "Previous"
msgstr "Vissza" msgstr "Vissza"
...@@ -226,15 +229,17 @@ msgstr "Vissza" ...@@ -226,15 +229,17 @@ msgstr "Vissza"
#: static_collected/vm-detail.js:6395 #: static_collected/vm-detail.js:6395
#: static_collected/dashboard/vm-tour.1562cc89a659.js:26 #: static_collected/dashboard/vm-tour.1562cc89a659.js:26
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:26 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:26
#: static_collected/dashboard/vm-tour.js:26 #: static_collected/dashboard/vm-tour.js:8
msgid "End tour" msgid "End tour"
msgstr "Befejezés" msgstr "Befejezés"
#: dashboard/static/dashboard/vm-tour.js:9 #: dashboard/static/dashboard/vm-tour.js:9
#: static_collected/dashboard/vm-tour.js:9
msgid "Done" msgid "Done"
msgstr "Kész" msgstr "Kész"
#: dashboard/static/dashboard/vm-tour.js:56 #: dashboard/static/dashboard/vm-tour.js:56
#: static_collected/dashboard/vm-tour.js:56
msgid "" msgid ""
"Welcome to the template tutorial. In this quick tour, we are going to show " "Welcome to the template tutorial. In this quick tour, we are going to show "
"you how to do the steps described above." "you how to do the steps described above."
...@@ -254,7 +259,7 @@ msgstr "" ...@@ -254,7 +259,7 @@ msgstr ""
#: static_collected/vm-detail.js:6404 #: static_collected/vm-detail.js:6404
#: static_collected/dashboard/vm-tour.1562cc89a659.js:35 #: static_collected/dashboard/vm-tour.1562cc89a659.js:35
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:35 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:35
#: static_collected/dashboard/vm-tour.js:35 #: static_collected/dashboard/vm-tour.js:57
msgid "" msgid ""
"For the next tour step press the \"Next\" button or the right arrow (or " "For the next tour step press the \"Next\" button or the right arrow (or "
"\"Back\" button/left arrow for the previous step)." "\"Back\" button/left arrow for the previous step)."
...@@ -263,6 +268,7 @@ msgstr "" ...@@ -263,6 +268,7 @@ msgstr ""
"nyílbillentyűket." "nyílbillentyűket."
#: dashboard/static/dashboard/vm-tour.js:61 #: dashboard/static/dashboard/vm-tour.js:61
#: static_collected/dashboard/vm-tour.js:61
msgid "" msgid ""
"In this tab you can extend the expiration date of your virtual machine, add " "In this tab you can extend the expiration date of your virtual machine, add "
"tags and modify the name and description." "tags and modify the name and description."
...@@ -271,24 +277,26 @@ msgstr "" ...@@ -271,24 +277,26 @@ msgstr ""
"vagy módosíthatja a nevét, leírását." "vagy módosíthatja a nevét, leírását."
#: dashboard/static/dashboard/vm-tour.js:65 #: dashboard/static/dashboard/vm-tour.js:65
#: static_collected/dashboard/vm-tour.js:65
msgid "" msgid ""
"Please add a meaningful description to the virtual machine. Changing the " "Please add a meaningful description to the virtual machine. Changing the "
"name is also recommended, however you can choose a new name when saving the " "name is also recommended, however you can choose a new name when saving the "
"template." "template."
msgstr "" msgstr ""
"Kérjük, adjon meg egy informatív leírást. A név megváltoztatása is " "Kérjük, adjon meg egy informatív leírást. A név megváltoztatása is ajánlott, "
"ajánlott, azonban a mentéskor is van a sablon nevének " "azonban a mentéskor is van a sablon nevének megválasztására."
"megválasztására."
#: dashboard/static/dashboard/vm-tour.js:69 #: dashboard/static/dashboard/vm-tour.js:69
#: static_collected/dashboard/vm-tour.js:69
msgid "" msgid ""
"You can change the lease to extend the expiration date. This will be the " "You can change the lease to extend the expiration date. This will be the "
"lease of the new template." "lease of the new template."
msgstr "" msgstr ""
"Megváltoztathatja a bérleti módot is a lejárat bővítéséhez. Az gép " "Megváltoztathatja a bérleti módot is a lejárat bővítéséhez. Az gép bérleti "
"bérleti módját örökli majd a sablon is." "módját örökli majd a sablon is."
#: dashboard/static/dashboard/vm-tour.js:73 #: dashboard/static/dashboard/vm-tour.js:73
#: static_collected/dashboard/vm-tour.js:73
msgid "" msgid ""
"On the resources tab you can edit the CPU/RAM options and add/remove disks " "On the resources tab you can edit the CPU/RAM options and add/remove disks "
"if you have required permissions." "if you have required permissions."
...@@ -308,7 +316,7 @@ msgstr "" ...@@ -308,7 +316,7 @@ msgstr ""
#: static_collected/vm-detail.js:6438 #: static_collected/vm-detail.js:6438
#: static_collected/dashboard/vm-tour.1562cc89a659.js:69 #: static_collected/dashboard/vm-tour.1562cc89a659.js:69
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:69 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:69
#: static_collected/dashboard/vm-tour.js:69 #: static_collected/dashboard/vm-tour.js:81
msgid "CPU priority" msgid "CPU priority"
msgstr "CPU prioritás" msgstr "CPU prioritás"
...@@ -324,7 +332,7 @@ msgstr "CPU prioritás" ...@@ -324,7 +332,7 @@ msgstr "CPU prioritás"
#: static_collected/vm-detail.js:6438 #: static_collected/vm-detail.js:6438
#: static_collected/dashboard/vm-tour.1562cc89a659.js:69 #: static_collected/dashboard/vm-tour.1562cc89a659.js:69
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:69 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:69
#: static_collected/dashboard/vm-tour.js:69 #: static_collected/dashboard/vm-tour.js:82
msgid "higher is better" msgid "higher is better"
msgstr "a nagyobb érték a jobb" msgstr "a nagyobb érték a jobb"
...@@ -340,7 +348,7 @@ msgstr "a nagyobb érték a jobb" ...@@ -340,7 +348,7 @@ msgstr "a nagyobb érték a jobb"
#: static_collected/vm-detail.js:6439 #: static_collected/vm-detail.js:6439
#: static_collected/dashboard/vm-tour.1562cc89a659.js:70 #: static_collected/dashboard/vm-tour.1562cc89a659.js:70
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:70 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:70
#: static_collected/dashboard/vm-tour.js:70 #: static_collected/dashboard/vm-tour.js:83
msgid "CPU count" msgid "CPU count"
msgstr "CPU-k száma" msgstr "CPU-k száma"
...@@ -356,7 +364,7 @@ msgstr "CPU-k száma" ...@@ -356,7 +364,7 @@ msgstr "CPU-k száma"
#: static_collected/vm-detail.js:6439 #: static_collected/vm-detail.js:6439
#: static_collected/dashboard/vm-tour.1562cc89a659.js:70 #: static_collected/dashboard/vm-tour.1562cc89a659.js:70
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:70 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:70
#: static_collected/dashboard/vm-tour.js:70 #: static_collected/dashboard/vm-tour.js:84
msgid "number of CPU cores." msgid "number of CPU cores."
msgstr "A CPU-magok száma." msgstr "A CPU-magok száma."
...@@ -372,7 +380,7 @@ msgstr "A CPU-magok száma." ...@@ -372,7 +380,7 @@ msgstr "A CPU-magok száma."
#: static_collected/vm-detail.js:6440 #: static_collected/vm-detail.js:6440
#: static_collected/dashboard/vm-tour.1562cc89a659.js:71 #: static_collected/dashboard/vm-tour.1562cc89a659.js:71
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:71 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:71
#: static_collected/dashboard/vm-tour.js:71 #: static_collected/dashboard/vm-tour.js:85
msgid "RAM amount" msgid "RAM amount"
msgstr "RAM mennyiség" msgstr "RAM mennyiség"
...@@ -388,7 +396,7 @@ msgstr "RAM mennyiség" ...@@ -388,7 +396,7 @@ msgstr "RAM mennyiség"
#: static_collected/vm-detail.js:6440 #: static_collected/vm-detail.js:6440
#: static_collected/dashboard/vm-tour.1562cc89a659.js:71 #: static_collected/dashboard/vm-tour.1562cc89a659.js:71
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:71 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:71
#: static_collected/dashboard/vm-tour.js:71 #: static_collected/dashboard/vm-tour.js:86
msgid "amount of RAM." msgid "amount of RAM."
msgstr "a memória mennyisége." msgstr "a memória mennyisége."
...@@ -404,7 +412,7 @@ msgstr "a memória mennyisége." ...@@ -404,7 +412,7 @@ msgstr "a memória mennyisége."
#: static_collected/vm-detail.js:6451 #: static_collected/vm-detail.js:6451
#: static_collected/dashboard/vm-tour.1562cc89a659.js:82 #: static_collected/dashboard/vm-tour.1562cc89a659.js:82
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:82 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:82
#: static_collected/dashboard/vm-tour.js:82 #: static_collected/dashboard/vm-tour.js:96
msgid "" msgid ""
"You can add empty disks, download new ones and remove existing ones here." "You can add empty disks, download new ones and remove existing ones here."
msgstr "" msgstr ""
...@@ -423,7 +431,7 @@ msgstr "" ...@@ -423,7 +431,7 @@ msgstr ""
#: static_collected/vm-detail.js:6462 #: static_collected/vm-detail.js:6462
#: static_collected/dashboard/vm-tour.1562cc89a659.js:93 #: static_collected/dashboard/vm-tour.1562cc89a659.js:93
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:93 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:93
#: static_collected/dashboard/vm-tour.js:93 #: static_collected/dashboard/vm-tour.js:105
msgid "You can add new network interfaces or remove existing ones here." msgid "You can add new network interfaces or remove existing ones here."
msgstr "Hozzáadhat új hálózati interfészeket, vagy törölheti a meglévőket." msgstr "Hozzáadhat új hálózati interfészeket, vagy törölheti a meglévőket."
...@@ -439,17 +447,18 @@ msgstr "Hozzáadhat új hálózati interfészeket, vagy törölheti a meglévők ...@@ -439,17 +447,18 @@ msgstr "Hozzáadhat új hálózati interfészeket, vagy törölheti a meglévők
#: static_collected/vm-detail.js:6474 #: static_collected/vm-detail.js:6474
#: static_collected/dashboard/vm-tour.1562cc89a659.js:105 #: static_collected/dashboard/vm-tour.1562cc89a659.js:105
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:105 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:105
#: static_collected/dashboard/vm-tour.js:105 #: static_collected/dashboard/vm-tour.js:109
msgid "Deploy the virtual machine." msgid "Deploy the virtual machine."
msgstr "A virtuális gép elindítása." msgstr "A virtuális gép elindítása."
#: dashboard/static/dashboard/vm-tour.js:113 #: dashboard/static/dashboard/vm-tour.js:113
#: static_collected/dashboard/vm-tour.js:113
msgid "" msgid ""
"Use the CIRCLE client or the connection string to connect to the virtual " "Use the CIRCLE client or the connection string to connect to the virtual "
"machine." "machine."
msgstr "" msgstr ""
"Használja a CIRCLE klienst vagy a kapcsolódási adatokat a " "Használja a CIRCLE klienst vagy a kapcsolódási adatokat a virtuális géphez "
"virtuális géphez való csatlakozáshoz." "való csatlakozáshoz."
#: dashboard/static/dashboard/vm-tour.js:117 #: dashboard/static/dashboard/vm-tour.js:117
#: static_collected/vm-detail.09737c69abc3.js:5954 #: static_collected/vm-detail.09737c69abc3.js:5954
...@@ -463,7 +472,7 @@ msgstr "" ...@@ -463,7 +472,7 @@ msgstr ""
#: static_collected/vm-detail.js:6490 #: static_collected/vm-detail.js:6490
#: static_collected/dashboard/vm-tour.1562cc89a659.js:121 #: static_collected/dashboard/vm-tour.1562cc89a659.js:121
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:121 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:121
#: static_collected/dashboard/vm-tour.js:121 #: static_collected/dashboard/vm-tour.js:117
msgid "" msgid ""
"After you have connected to the virtual machine do your modifications then " "After you have connected to the virtual machine do your modifications then "
"log off." "log off."
...@@ -483,7 +492,7 @@ msgstr "" ...@@ -483,7 +492,7 @@ msgstr ""
#: static_collected/vm-detail.js:6498 #: static_collected/vm-detail.js:6498
#: static_collected/dashboard/vm-tour.1562cc89a659.js:129 #: static_collected/dashboard/vm-tour.1562cc89a659.js:129
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:129 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:129
#: static_collected/dashboard/vm-tour.js:129 #: static_collected/dashboard/vm-tour.js:121
msgid "" msgid ""
"Press the \"Save as template\" button and wait until the activity finishes." "Press the \"Save as template\" button and wait until the activity finishes."
msgstr "" msgstr ""
...@@ -491,12 +500,11 @@ msgstr "" ...@@ -491,12 +500,11 @@ msgstr ""
"elkészül." "elkészül."
#: dashboard/static/dashboard/vm-tour.js:125 #: dashboard/static/dashboard/vm-tour.js:125
#: static_collected/dashboard/vm-tour.js:125
msgid "" msgid ""
"This is the last message, if something is not clear you can do the the tour " "This is the last message, if something is not clear you can do the the tour "
"again." "again."
msgstr "" msgstr "A túra véget ért. Ha valami nem érthető, újrakezdheti az útmutatót."
"A túra véget ért. Ha valami nem érthető, újrakezdheti az "
"útmutatót."
#: network/static/js/host.js:10 static_collected/all.047675ebf594.js:5239 #: network/static/js/host.js:10 static_collected/all.047675ebf594.js:5239
#: static_collected/all.0aecd87e873a.js:5309 #: static_collected/all.0aecd87e873a.js:5309
...@@ -617,7 +625,6 @@ msgstr "Biztosan törli ezt az eszközt?" ...@@ -617,7 +625,6 @@ msgstr "Biztosan törli ezt az eszközt?"
#: static_collected/vm-detail.js:6389 #: static_collected/vm-detail.js:6389
#: static_collected/dashboard/vm-tour.1562cc89a659.js:20 #: static_collected/dashboard/vm-tour.1562cc89a659.js:20
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:20 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:20
#: static_collected/dashboard/vm-tour.js:20
msgid "Prev" msgid "Prev"
msgstr "Vissza" msgstr "Vissza"
...@@ -632,7 +639,6 @@ msgstr "Vissza" ...@@ -632,7 +639,6 @@ msgstr "Vissza"
#: static_collected/vm-detail.js:6402 #: static_collected/vm-detail.js:6402
#: static_collected/dashboard/vm-tour.1562cc89a659.js:33 #: static_collected/dashboard/vm-tour.1562cc89a659.js:33
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:33 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:33
#: static_collected/dashboard/vm-tour.js:33
msgid "Template Tutorial Tour" msgid "Template Tutorial Tour"
msgstr "Sablon-kalauz" msgstr "Sablon-kalauz"
...@@ -647,7 +653,6 @@ msgstr "Sablon-kalauz" ...@@ -647,7 +653,6 @@ msgstr "Sablon-kalauz"
#: static_collected/vm-detail.js:6403 #: static_collected/vm-detail.js:6403
#: static_collected/dashboard/vm-tour.1562cc89a659.js:34 #: static_collected/dashboard/vm-tour.1562cc89a659.js:34
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:34 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:34
#: static_collected/dashboard/vm-tour.js:34
msgid "" msgid ""
"Welcome to the template tutorial. In this quick tour, we gonna show you how " "Welcome to the template tutorial. In this quick tour, we gonna show you how "
"to do the steps described above." "to do the steps described above."
...@@ -665,7 +670,6 @@ msgstr "" ...@@ -665,7 +670,6 @@ msgstr ""
#: static_collected/vm-detail.js:6405 #: static_collected/vm-detail.js:6405
#: static_collected/dashboard/vm-tour.1562cc89a659.js:36 #: static_collected/dashboard/vm-tour.1562cc89a659.js:36
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:36 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:36
#: static_collected/dashboard/vm-tour.js:36
msgid "" msgid ""
"During the tour please don't try the functions because it may lead to " "During the tour please don't try the functions because it may lead to "
"graphical glitches, however " "graphical glitches, however "
...@@ -682,7 +686,6 @@ msgstr "A túra során még ne próbálja ki a bemutatott funkciókat." ...@@ -682,7 +686,6 @@ msgstr "A túra során még ne próbálja ki a bemutatott funkciókat."
#: static_collected/vm-detail.js:6414 #: static_collected/vm-detail.js:6414
#: static_collected/dashboard/vm-tour.1562cc89a659.js:45 #: static_collected/dashboard/vm-tour.1562cc89a659.js:45
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:45 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:45
#: static_collected/dashboard/vm-tour.js:45
msgid "Home tab" msgid "Home tab"
msgstr "Kezdőoldal" msgstr "Kezdőoldal"
...@@ -697,7 +700,6 @@ msgstr "Kezdőoldal" ...@@ -697,7 +700,6 @@ msgstr "Kezdőoldal"
#: static_collected/vm-detail.js:6415 #: static_collected/vm-detail.js:6415
#: static_collected/dashboard/vm-tour.1562cc89a659.js:46 #: static_collected/dashboard/vm-tour.1562cc89a659.js:46
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:46 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:46
#: static_collected/dashboard/vm-tour.js:46
msgid "" msgid ""
"In this tab you can tag your virtual machine and modify the name and " "In this tab you can tag your virtual machine and modify the name and "
"description." "description."
...@@ -716,7 +718,6 @@ msgstr "" ...@@ -716,7 +718,6 @@ msgstr ""
#: static_collected/vm-detail.js:6424 #: static_collected/vm-detail.js:6424
#: static_collected/dashboard/vm-tour.1562cc89a659.js:55 #: static_collected/dashboard/vm-tour.1562cc89a659.js:55
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:55 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:55
#: static_collected/dashboard/vm-tour.js:55
msgid "Resources tab" msgid "Resources tab"
msgstr "Erőforrások lap" msgstr "Erőforrások lap"
...@@ -731,7 +732,6 @@ msgstr "Erőforrások lap" ...@@ -731,7 +732,6 @@ msgstr "Erőforrások lap"
#: static_collected/vm-detail.js:6427 #: static_collected/vm-detail.js:6427
#: static_collected/dashboard/vm-tour.1562cc89a659.js:58 #: static_collected/dashboard/vm-tour.1562cc89a659.js:58
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:58 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:58
#: static_collected/dashboard/vm-tour.js:58
msgid "" msgid ""
"On the resources tab you can edit the CPU/RAM options and add/remove disks!" "On the resources tab you can edit the CPU/RAM options and add/remove disks!"
msgstr "" msgstr ""
...@@ -749,7 +749,6 @@ msgstr "" ...@@ -749,7 +749,6 @@ msgstr ""
#: static_collected/vm-detail.js:6437 #: static_collected/vm-detail.js:6437
#: static_collected/dashboard/vm-tour.1562cc89a659.js:68 #: static_collected/dashboard/vm-tour.1562cc89a659.js:68
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:68 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:68
#: static_collected/dashboard/vm-tour.js:68
msgid "Resources" msgid "Resources"
msgstr "Erőforrások" msgstr "Erőforrások"
...@@ -764,7 +763,6 @@ msgstr "Erőforrások" ...@@ -764,7 +763,6 @@ msgstr "Erőforrások"
#: static_collected/vm-detail.js:6450 #: static_collected/vm-detail.js:6450
#: static_collected/dashboard/vm-tour.1562cc89a659.js:81 #: static_collected/dashboard/vm-tour.1562cc89a659.js:81
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:81 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:81
#: static_collected/dashboard/vm-tour.js:81
msgid "Disks" msgid "Disks"
msgstr "Lemezek" msgstr "Lemezek"
...@@ -779,7 +777,6 @@ msgstr "Lemezek" ...@@ -779,7 +777,6 @@ msgstr "Lemezek"
#: static_collected/vm-detail.js:6461 #: static_collected/vm-detail.js:6461
#: static_collected/dashboard/vm-tour.1562cc89a659.js:92 #: static_collected/dashboard/vm-tour.1562cc89a659.js:92
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:92 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:92
#: static_collected/dashboard/vm-tour.js:92
msgid "Network tab" msgid "Network tab"
msgstr "Hálózat lap" msgstr "Hálózat lap"
...@@ -794,7 +791,6 @@ msgstr "Hálózat lap" ...@@ -794,7 +791,6 @@ msgstr "Hálózat lap"
#: static_collected/vm-detail.js:6471 #: static_collected/vm-detail.js:6471
#: static_collected/dashboard/vm-tour.1562cc89a659.js:102 #: static_collected/dashboard/vm-tour.1562cc89a659.js:102
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:102 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:102
#: static_collected/dashboard/vm-tour.js:102
msgid "Deploy" msgid "Deploy"
msgstr "Indítás" msgstr "Indítás"
...@@ -809,7 +805,6 @@ msgstr "Indítás" ...@@ -809,7 +805,6 @@ msgstr "Indítás"
#: static_collected/vm-detail.js:6479 #: static_collected/vm-detail.js:6479
#: static_collected/dashboard/vm-tour.1562cc89a659.js:110 #: static_collected/dashboard/vm-tour.1562cc89a659.js:110
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:110 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:110
#: static_collected/dashboard/vm-tour.js:110
msgid "Connect" msgid "Connect"
msgstr "Csatlakozás" msgstr "Csatlakozás"
...@@ -824,7 +819,6 @@ msgstr "Csatlakozás" ...@@ -824,7 +819,6 @@ msgstr "Csatlakozás"
#: static_collected/vm-detail.js:6482 #: static_collected/vm-detail.js:6482
#: static_collected/dashboard/vm-tour.1562cc89a659.js:113 #: static_collected/dashboard/vm-tour.1562cc89a659.js:113
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:113 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:113
#: static_collected/dashboard/vm-tour.js:113
msgid "Use the connection string or connect with your choice of client!" msgid "Use the connection string or connect with your choice of client!"
msgstr "Használja a megadott parancsot, vagy kedvenc kliensét." msgstr "Használja a megadott parancsot, vagy kedvenc kliensét."
...@@ -839,7 +833,6 @@ msgstr "Használja a megadott parancsot, vagy kedvenc kliensét." ...@@ -839,7 +833,6 @@ msgstr "Használja a megadott parancsot, vagy kedvenc kliensét."
#: static_collected/vm-detail.js:6489 #: static_collected/vm-detail.js:6489
#: static_collected/dashboard/vm-tour.1562cc89a659.js:120 #: static_collected/dashboard/vm-tour.1562cc89a659.js:120
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:120 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:120
#: static_collected/dashboard/vm-tour.js:120
msgid "Customize the virtual machine" msgid "Customize the virtual machine"
msgstr "Szabja testre a gépet" msgstr "Szabja testre a gépet"
...@@ -854,7 +847,6 @@ msgstr "Szabja testre a gépet" ...@@ -854,7 +847,6 @@ msgstr "Szabja testre a gépet"
#: static_collected/vm-detail.js:6495 #: static_collected/vm-detail.js:6495
#: static_collected/dashboard/vm-tour.1562cc89a659.js:126 #: static_collected/dashboard/vm-tour.1562cc89a659.js:126
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:126 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:126
#: static_collected/dashboard/vm-tour.js:126
msgid "Save as" msgid "Save as"
msgstr "Mentés sablonként" msgstr "Mentés sablonként"
...@@ -869,7 +861,6 @@ msgstr "Mentés sablonként" ...@@ -869,7 +861,6 @@ msgstr "Mentés sablonként"
#: static_collected/vm-detail.js:6504 #: static_collected/vm-detail.js:6504
#: static_collected/dashboard/vm-tour.1562cc89a659.js:135 #: static_collected/dashboard/vm-tour.1562cc89a659.js:135
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:135 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:135
#: static_collected/dashboard/vm-tour.js:135
msgid "Finish" msgid "Finish"
msgstr "Befejezés" msgstr "Befejezés"
...@@ -884,7 +875,6 @@ msgstr "Befejezés" ...@@ -884,7 +875,6 @@ msgstr "Befejezés"
#: static_collected/vm-detail.js:6507 #: static_collected/vm-detail.js:6507
#: static_collected/dashboard/vm-tour.1562cc89a659.js:138 #: static_collected/dashboard/vm-tour.1562cc89a659.js:138
#: static_collected/dashboard/vm-tour.7b4cf596f543.js:138 #: static_collected/dashboard/vm-tour.7b4cf596f543.js:138
#: static_collected/dashboard/vm-tour.js:138
msgid "" msgid ""
"This is the last message, if something is not clear you can do the the tour " "This is the last message, if something is not clear you can do the the tour "
"again!" "again!"
......
...@@ -15,13 +15,13 @@ ...@@ -15,13 +15,13 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.forms import ModelForm from django.forms import ModelForm, widgets
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput
from crispy_forms.bootstrap import FormActions from crispy_forms.bootstrap import FormActions, FieldWithButtons, StrictButton
from firewall.models import (Host, Vlan, Domain, Group, Record, BlacklistItem, from firewall.models import (Host, Vlan, Domain, Group, Record, BlacklistItem,
Rule, VlanGroup, SwitchPort) Rule, VlanGroup, SwitchPort)
...@@ -122,8 +122,12 @@ class HostForm(ModelForm): ...@@ -122,8 +122,12 @@ class HostForm(ModelForm):
Fieldset( Fieldset(
_('Network'), _('Network'),
'vlan', 'vlan',
'ipv4', FieldWithButtons('ipv4', StrictButton(
'ipv6', '<i class="fa fa-magic"></i>', css_id="ipv4-magic",
title=_("Generate random address."))),
FieldWithButtons('ipv6', StrictButton(
'<i class="fa fa-magic"></i>', css_id="ipv6-magic",
title=_("Generate IPv6 pair of IPv4 address."))),
'shared_ip', 'shared_ip',
'external_ipv4', 'external_ipv4',
), ),
...@@ -252,7 +256,9 @@ class VlanForm(ModelForm): ...@@ -252,7 +256,9 @@ class VlanForm(ModelForm):
Fieldset( Fieldset(
_('IPv6'), _('IPv6'),
'network6', 'network6',
'ipv6_template', FieldWithButtons('ipv6_template', StrictButton(
'<i class="fa fa-magic"></i>', css_id="ipv6-tpl-magic",
title=_("Generate sensible template."))),
'host_ipv6_prefixlen', 'host_ipv6_prefixlen',
), ),
Fieldset( Fieldset(
...@@ -277,6 +283,9 @@ class VlanForm(ModelForm): ...@@ -277,6 +283,9 @@ class VlanForm(ModelForm):
class Meta: class Meta:
model = Vlan model = Vlan
widgets = {
'ipv6_template': widgets.TextInput,
}
class VlanGroupForm(ModelForm): class VlanGroupForm(ModelForm):
......
...@@ -28,6 +28,49 @@ function getURLParameter(name) { ...@@ -28,6 +28,49 @@ function getURLParameter(name) {
); );
} }
function doBlink(id, count) {
if (count > 0) {
$(id).parent().delay(200).queue(function() {
$(this).delay(200).queue(function() {
$(this).removeClass("has-warning").dequeue();
doBlink(id, count-1);});
$(this).addClass("has-warning").dequeue()
});
}
}
$(function() { $(function() {
$("[title]").tooltip(); $("[title]").tooltip();
$("#ipv6-magic").click(function() {
$.ajax({url: window.location,
data: {ipv4: $("[name=ipv4]").val(),
vlan: $("[name=vlan]").val()},
success: function(data) {
$("[name=ipv6]").val(data.ipv6);
}});
});
$("#ipv4-magic").click(function() {
$.ajax({url: window.location,
data: {vlan: $("[name=vlan]").val()},
success: function(data) {
$("[name=ipv4]").val(data.ipv4);
if ($("[name=ipv6]").val() != data.ipv6) {
doBlink("[name=ipv6]", 3);
}
$("[name=ipv6]").val(data.ipv6);
}});
});
$("#ipv6-tpl-magic").click(function() {
$.ajax({url: window.location,
data: {network4: $("[name=network4]").val(),
network6: $("[name=network6]").val()},
success: function(data) {
$("[name=ipv6_template]").val(data.ipv6_template);
if ($("[name=host_ipv6_prefixlen]").val() != data.host_ipv6_prefixlen) {
doBlink("[name=host_ipv6_prefixlen]", 3);
}
$("[name=host_ipv6_prefixlen]").val(data.host_ipv6_prefixlen);
}});
});
}); });
...@@ -15,14 +15,30 @@ ...@@ -15,14 +15,30 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from netaddr import EUI
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.html import format_html
from django_tables2 import Table, A from django_tables2 import Table, A
from django_tables2.columns import LinkColumn, TemplateColumn from django_tables2.columns import LinkColumn, TemplateColumn, Column
from firewall.models import Host, Vlan, Domain, Group, Record, Rule, SwitchPort from firewall.models import Host, Vlan, Domain, Group, Record, Rule, SwitchPort
class MACColumn(Column):
def render(self, value):
if isinstance(value, basestring):
try:
value = EUI(value)
except:
return value
try:
return format_html('<abbr title="{0}">{1}</abbr>',
value.oui.registration().org, value)
except:
return value
class BlacklistItemTable(Table): class BlacklistItemTable(Table):
ipv4 = LinkColumn('network.blacklist', args=[A('pk')]) ipv4 = LinkColumn('network.blacklist', args=[A('pk')])
...@@ -55,9 +71,7 @@ class GroupTable(Table): ...@@ -55,9 +71,7 @@ class GroupTable(Table):
class HostTable(Table): class HostTable(Table):
hostname = LinkColumn('network.host', args=[A('pk')]) hostname = LinkColumn('network.host', args=[A('pk')])
mac = TemplateColumn( mac = MACColumn()
template_name="network/columns/mac.html"
)
class Meta: class Meta:
model = Host model = Host
...@@ -109,6 +123,20 @@ class SmallHostTable(Table): ...@@ -109,6 +123,20 @@ class SmallHostTable(Table):
attrs = {'class': 'table table-striped table-condensed'} attrs = {'class': 'table table-striped table-condensed'}
fields = ('hostname', 'ipv4') fields = ('hostname', 'ipv4')
order_by = ('vlan', 'hostname', ) order_by = ('vlan', 'hostname', )
empty_text = _("No hosts.")
class SmallDhcpTable(Table):
mac = MACColumn(verbose_name=_("MAC address"))
hostname = Column(verbose_name=_("hostname"))
ip = Column(verbose_name=_("requested IP"))
register = TemplateColumn(
template_name="network/columns/host-register.html",
attrs={"th": {"style": "display: none;"}})
class Meta:
attrs = {'class': 'table table-striped table-condensed'}
empty_text = _("No hosts.")
class RecordTable(Table): class RecordTable(Table):
......
{% load i18n %}
<a href="{% url "network.host_create" %}?vlan={{ object.pk }}&amp;mac={{ record.mac }}&amp;hostname={{ record.hostname }}&amp;ipv4={{ record.ip }}"
title="{% trans "register host" %}"><i class="fa fa-plus-circle"></i></a>
{% load i18n %}
<span title="{% blocktrans with vendor=record.hw_vendor|default:"n/a" %}Vendor: {{vendor}}{% endblocktrans %}">{{ record.mac }}</span>
...@@ -19,9 +19,16 @@ ...@@ -19,9 +19,16 @@
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<div class="page-header"> <div class="page-header">
<a href="{% url "network.host_create" %}?vlan={{vlan.pk}}" class="btn btn-success pull-right"><i class="fa fa-plus-circle"></i> {% trans "Create a new host" %}</a>
<h3>{% trans "Host list" %}</h3> <h3>{% trans "Host list" %}</h3>
</div> </div>
{% render_table host_list %} {% render_table host_list %}
<div class="page-header">
<h3>{% trans "Unregistered hosts" %}</h3>
</div>
{% render_table dhcp_list %}
<div class="page-header"> <div class="page-header">
<h3>{% trans "Manage access" %}</h3> <h3>{% trans "Manage access" %}</h3>
</div> </div>
......
...@@ -15,11 +15,13 @@ ...@@ -15,11 +15,13 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from netaddr import IPNetwork
from django.views.generic import (TemplateView, UpdateView, DeleteView, from django.views.generic import (TemplateView, UpdateView, DeleteView,
CreateView) CreateView)
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.shortcuts import render, redirect from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponse from django.http import HttpResponse, Http404
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
...@@ -29,7 +31,7 @@ from vm.models import Interface ...@@ -29,7 +31,7 @@ from vm.models import Interface
from .tables import (HostTable, VlanTable, SmallHostTable, DomainTable, from .tables import (HostTable, VlanTable, SmallHostTable, DomainTable,
GroupTable, RecordTable, BlacklistItemTable, RuleTable, GroupTable, RecordTable, BlacklistItemTable, RuleTable,
VlanGroupTable, SmallRuleTable, SmallGroupRuleTable, VlanGroupTable, SmallRuleTable, SmallGroupRuleTable,
SmallRecordTable, SwitchPortTable) SmallRecordTable, SwitchPortTable, SmallDhcpTable, )
from .forms import (HostForm, VlanForm, DomainForm, GroupForm, RecordForm, from .forms import (HostForm, VlanForm, DomainForm, GroupForm, RecordForm,
BlacklistItemForm, RuleForm, VlanGroupForm, SwitchPortForm) BlacklistItemForm, RuleForm, VlanGroupForm, SwitchPortForm)
...@@ -41,10 +43,36 @@ from braces.views import LoginRequiredMixin, SuperuserRequiredMixin ...@@ -41,10 +43,36 @@ from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
# from django.db.models import Q # from django.db.models import Q
from operator import itemgetter from operator import itemgetter
from itertools import chain from itertools import chain
import json
from dashboard.views import AclUpdateView from dashboard.views import AclUpdateView
from dashboard.forms import AclUserOrGroupAddForm from dashboard.forms import AclUserOrGroupAddForm
from django.utils import simplejson
try:
from django.http import JsonResponse
except ImportError:
class JsonResponse(HttpResponse):
"""JSON response for Django < 1.7
https://gist.github.com/philippeowagner/3179eb475fe1795d6515
"""
def __init__(self, content, mimetype='application/json',
status=None, content_type=None):
super(JsonResponse, self).__init__(
content=simplejson.dumps(content),
mimetype=mimetype,
status=status,
content_type=content_type)
class MagicMixin(object):
def get(self, *args, **kwargs):
if self.request.is_ajax():
result = self._get_ajax(*args, **kwargs)
return JsonResponse({k: unicode(result[k] or "") for k in result})
else:
return super(MagicMixin, self).get(*args, **kwargs)
class InitialOwnerMixin(FormMixin): class InitialOwnerMixin(FormMixin):
def get_initial(self): def get_initial(self):
...@@ -310,6 +338,25 @@ class GroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView): ...@@ -310,6 +338,25 @@ class GroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
return context return context
class HostMagicMixin(MagicMixin):
def _get_ajax(self, *args, **kwargs):
GET = self.request.GET
result = {}
vlan = get_object_or_404(Vlan.objects, pk=GET.get("vlan", ""))
if "ipv4" in GET:
try:
result["ipv6"] = vlan.convert_ipv4_to_ipv6(GET["ipv4"]) or ""
except:
result["ipv6"] = ""
else:
try:
result.update(vlan.get_new_address())
except ValidationError:
result["ipv4"] = ""
result["ipv6"] = ""
return result
class HostList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView): class HostList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
model = Host model = Host
table_class = HostTable table_class = HostTable
...@@ -331,15 +378,15 @@ class HostList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView): ...@@ -331,15 +378,15 @@ class HostList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
return data return data
class HostDetail(LoginRequiredMixin, SuperuserRequiredMixin, class HostDetail(HostMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, UpdateView): SuccessMessageMixin, UpdateView):
model = Host model = Host
template_name = "network/host-edit.html" template_name = "network/host-edit.html"
form_class = HostForm form_class = HostForm
success_message = _(u'Successfully modified host %(hostname)s!') success_message = _(u'Successfully modified host %(hostname)s!')
def get(self, request, *args, **kwargs): def _get_ajax(self, *args, **kwargs):
if request.is_ajax(): if "vlan" not in self.request.GET:
host = Host.objects.get(pk=kwargs['pk']) host = Host.objects.get(pk=kwargs['pk'])
host = { host = {
'hostname': host.hostname, 'hostname': host.hostname,
...@@ -347,9 +394,11 @@ class HostDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -347,9 +394,11 @@ class HostDetail(LoginRequiredMixin, SuperuserRequiredMixin,
'ipv6': str(host.ipv6), 'ipv6': str(host.ipv6),
'fqdn': host.get_fqdn() 'fqdn': host.get_fqdn()
} }
return HttpResponse(json.dumps(host), return host
content_type="application/json")
else: else:
return super(HostDetail, self)._get_ajax(*args, **kwargs)
def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
return super(HostDetail, self).get(request, *args, **kwargs) return super(HostDetail, self).get(request, *args, **kwargs)
...@@ -402,13 +451,29 @@ class HostDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -402,13 +451,29 @@ class HostDetail(LoginRequiredMixin, SuperuserRequiredMixin,
return reverse_lazy('network.host', kwargs=self.kwargs) return reverse_lazy('network.host', kwargs=self.kwargs)
class HostCreate(LoginRequiredMixin, SuperuserRequiredMixin, class HostCreate(HostMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, InitialOwnerMixin, CreateView): SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Host model = Host
template_name = "network/host-create.html" template_name = "network/host-create.html"
form_class = HostForm form_class = HostForm
success_message = _(u'Successfully created host %(hostname)s!') success_message = _(u'Successfully created host %(hostname)s!')
def get_initial(self):
initial = super(HostCreate, self).get_initial()
for i in ("vlan", "mac", "hostname"):
if i in self.request.GET and i not in self.request.POST:
initial[i] = self.request.GET[i]
if "vlan" in initial:
if not initial['vlan'].isnumeric():
raise Http404()
vlan = get_object_or_404(Vlan.objects, pk=initial['vlan'])
try:
initial.update(vlan.get_new_address())
except ValidationError as e:
messages.error(self.request, e.message)
return initial
class HostDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView): class HostDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
model = Host model = Host
...@@ -644,7 +709,21 @@ class VlanAclUpdateView(AclUpdateView): ...@@ -644,7 +709,21 @@ class VlanAclUpdateView(AclUpdateView):
model = Vlan model = Vlan
class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin, class VlanMagicMixin(MagicMixin):
def _get_ajax(self, *args, **kwargs):
GET = self.request.GET
result = {}
if "network4" in GET and "network6" in GET:
try:
result["ipv6_template"], result["host_ipv6_prefixlen"] = (
Vlan._magic_ipv6_template(IPNetwork(GET['network4']),
IPNetwork(GET['network6'])))
except:
result["ipv6_template"] = result["host_ipv6_prefixlen"] = ""
return result
class VlanDetail(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, UpdateView): SuccessMessageMixin, UpdateView):
model = Vlan model = Vlan
template_name = "network/vlan-edit.html" template_name = "network/vlan-edit.html"
...@@ -655,12 +734,8 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -655,12 +734,8 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin,
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(VlanDetail, self).get_context_data(**kwargs) context = super(VlanDetail, self).get_context_data(**kwargs)
context['host_list'] = SmallHostTable(self.object.host_set.all())
q = Host.objects.filter(interface__in=Interface.objects.filter( context['dhcp_list'] = SmallDhcpTable(self.object.get_dhcp_clients())
vlan=self.object
))
context['host_list'] = SmallHostTable(q)
context['vlan_vid'] = self.kwargs.get('vid') context['vlan_vid'] = self.kwargs.get('vid')
context['acl'] = AclUpdateView.get_acl_data( context['acl'] = AclUpdateView.get_acl_data(
self.object, self.request.user, 'network.vlan-acl') self.object, self.request.user, 'network.vlan-acl')
...@@ -670,7 +745,7 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -670,7 +745,7 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin,
success_url = reverse_lazy('network.vlan_list') success_url = reverse_lazy('network.vlan_list')
class VlanCreate(LoginRequiredMixin, SuperuserRequiredMixin, class VlanCreate(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, InitialOwnerMixin, CreateView): SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Vlan model = Vlan
template_name = "network/vlan-create.html" template_name = "network/vlan-create.html"
......
...@@ -769,8 +769,13 @@ class SaveAsTemplateOperation(InstanceOperation): ...@@ -769,8 +769,13 @@ class SaveAsTemplateOperation(InstanceOperation):
tmpl = InstanceTemplate(**params) tmpl = InstanceTemplate(**params)
tmpl.full_clean() # Avoiding database errors. tmpl.full_clean() # Avoiding database errors.
tmpl.save() tmpl.save()
# Copy traits from the VM instance
tmpl.req_traits.add(*self.instance.req_traits.all())
if clone: if clone:
tmpl.clone_acl(self.instance.template) tmpl.clone_acl(self.instance.template)
# Add permission for the original owner of the template
tmpl.set_level(self.instance.template.owner, 'owner')
tmpl.set_level(user, 'owner')
try: try:
tmpl.disks.add(*self.disks) tmpl.disks.add(*self.disks)
# create interface templates # create interface templates
......
...@@ -177,6 +177,7 @@ class InterfaceTestCase(TestCase): ...@@ -177,6 +177,7 @@ class InterfaceTestCase(TestCase):
d.save() d.save()
v = Vlan(vid=55, network4='127.0.0.1/8', v = Vlan(vid=55, network4='127.0.0.1/8',
network6='2001::1/32', domain=d) network6='2001::1/32', domain=d)
v.clean()
v.save() v.save()
Interface.create(i, v, managed=True, owner=owner) Interface.create(i, v, managed=True, owner=owner)
......
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