commit 2e888db44d81f4267f621d5f73560f13c218e9c3 Author: Dmitry Date: Fri Aug 19 09:00:26 2016 +0000 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18ad675 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.pyc +*.db +media/* +media/min/* +~*/migrations/000*.py +.idea/ diff --git a/Doc.txt b/Doc.txt new file mode 100644 index 0000000..92fb8be --- /dev/null +++ b/Doc.txt @@ -0,0 +1,65 @@ +// Формат общения NAS с базой +[ + { + "toa": 1, /* Тип события: + 0 - ничего не надо (пустое) + 1 - Активировать клиента (включить его) + 2 - Выключить клиента + 3 - Поставить заглушку + 4 - Открыть доступ в интернет + 5 - Закрыть доступ в интернет + 6 - Перечитать всю инфу (полная перезагрузка NAS) + 7 - Изменилась инфа об абоненте, переприменить его + */ + "id": 12, // ID объекта о котором событие (абонент там, или тариф) + "dt": "data" // Разная инфа, содержимое зависит от поля 'toa' + }, + { + "id": 13, + "toa": 3 + } +] + +// Формат передачи инфы об абонентах +{ + "subscribers": [ + { + "is_active": true, // Активен-ли абонент + "ip": 168558850, // Его ip + "tarif_id": 1, // id тарифа + "id": 2 // id абонента + }, + { + "is_active": true, + "ip": 168558850, + "tarif_id": 1, + "id": 2 + } + ], + "tariffs": [ + { + "tid": 1, // id тарифа + "amount": 0.0, // стоимость + "speedOut": 0.0, // Исходящая скорость + "speedIn": 0.0 // Входящая скорость + } + ] +} + + +------------------------------- +Состояние оплаты абонента определяется на основе присутствия у него тарифного плана, +если он есть - то значит всё оплачено (абонент его купил) и может пользоваться услугами. +Тарифный план имеет срок действия и стоимость. Его можно купить, как билет в интернет :) + + +ТАРИФНЫЙ ПЛАН С НАИМЕНЬШИМ ЧИСЛОМ ПРИОРИТЕТА ИМЕЕТ НАИВЫСШИЙ ПРИОРИТЕТ +В общем чем ближе приоритет к 0 тем он выше +0 - текущий тариф + + +Свою логику расчёта по тарифу можно добавить в файле tariff_app/custom_tariffs.py +Там надо добавить класс, наследованный от TariffBase и реализовать его абстрактные методы, +потом добавить этот класс в кортеж TARIFF_CHOICES указав: + код из 2х букв, сочетание должно быть уникальным + и ваш класс для своей логики расчёта тарифа diff --git a/abonapp/__init__.py b/abonapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/abonapp/admin.py b/abonapp/admin.py new file mode 100644 index 0000000..f62b3f7 --- /dev/null +++ b/abonapp/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin +import models + +admin.site.register(models.AbonGroup) +admin.site.register(models.Abon) +admin.site.register(models.InvoiceForPayment) +admin.site.register(models.AbonLog) +admin.site.register(models.AbonTariff) diff --git a/abonapp/forms.py b/abonapp/forms.py new file mode 100644 index 0000000..059c8bc --- /dev/null +++ b/abonapp/forms.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +from django import forms +from django.core.validators import RegexValidator +import models +from mydefs import ip_addr_regex + + +class AbonForm(forms.Form): + username = forms.CharField(max_length=127, required=False, widget=forms.TextInput(attrs={ + 'placeholder': u'Логин', + 'class': "form-control", + 'id': "login" + })) + fio = forms.CharField(max_length=256, widget=forms.TextInput(attrs={ + 'placeholder': u'ФИО', + 'class': "form-control", + 'id': "fio" + }), required=False) + ip_address = forms.GenericIPAddressField(protocol='IPv4', required=False, widget=forms.TextInput(attrs={ + 'pattern': ip_addr_regex, + 'placeholder': u'127.0.0.1', + 'class': "form-control", + 'id': "ip" + })) + + telephone = forms.CharField( + max_length=16, + validators=[RegexValidator(r'^\+[7,8,9,3]\d{10,11}$')], + widget=forms.TextInput(attrs={ + 'placeholder': u'+[7,8,9,3] и 10,11 цифр', + 'pattern': r'^\+[7,8,9,3]\d{10,11}$', + 'required': '', + 'class': 'form-control', + 'id': 'telephone' + }) + ) + is_active = forms.BooleanField( + required=False, + widget=forms.Select(attrs={'class': 'form-control', 'id': 'isactive'}) + ) + + group = forms.ModelChoiceField( + queryset=models.AbonGroup.objects.all(), + required=False, + widget=forms.Select(attrs={'class': 'form-control', 'id': 'grp'}) + ) + address = forms.CharField( + max_length=256, + required=False, + widget = forms.TextInput(attrs={'class': 'form-control', 'id': 'address'}) + ) + + +class AbonGroupForm(forms.ModelForm): + class Meta: + model = models.AbonGroup + fields = ['title', 'address'] + widgets = { + 'class': 'form-control' + } + + +class BuyTariff(forms.Form): + tariff = forms.ModelChoiceField( + queryset=models.Tariff.objects.all(), + required=True, + widget=forms.Select(attrs={'class': 'form-control', 'id': 'tariff'}) + ) diff --git a/abonapp/migrations/0001_initial.py b/abonapp/migrations/0001_initial.py new file mode 100644 index 0000000..6d70506 --- /dev/null +++ b/abonapp/migrations/0001_initial.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-06-28 23:51 +from __future__ import unicode_literals + +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('accounts_app', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ip_pool', '0001_initial'), + ('tariff_app', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Abon', + fields=[ + ('userprofile_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), + ('ballance', models.FloatField(default=0.0, validators=[django.core.validators.DecimalValidator])), + ('address', models.CharField(max_length=256)), + ], + options={ + 'db_table': 'abonent', + }, + bases=('accounts_app.userprofile',), + ), + migrations.CreateModel( + name='AbonGroup', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=127)), + ('address', models.CharField(blank=True, max_length=256, null=True)), + ], + options={ + 'db_table': 'abonent_groups', + }, + ), + migrations.CreateModel( + name='AbonLog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('amount', models.FloatField(default=0.0)), + ('comment', models.CharField(max_length=128)), + ('date', models.DateTimeField(auto_now_add=True)), + ('abon', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='abonapp.Abon')), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'abonent_log', + }, + ), + migrations.CreateModel( + name='AbonTariff', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tariff_priority', models.PositiveSmallIntegerField(default=0)), + ('time_start', models.DateTimeField(blank=True, default=None, null=True)), + ('abon', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='abonapp.Abon')), + ('tariff', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='linkto_tariff', to='tariff_app.Tariff')), + ], + options={ + 'ordering': ('tariff_priority',), + 'db_table': 'abonent_tariff', + }, + ), + migrations.CreateModel( + name='InvoiceForPayment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.BooleanField(default=False)), + ('amount', models.FloatField(default=0.0)), + ('comment', models.CharField(max_length=128)), + ('date_create', models.DateTimeField(auto_now_add=True)), + ('date_pay', models.DateTimeField(blank=True, null=True)), + ('abon', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='abonapp.Abon')), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('date_create',), + 'db_table': 'abonent_inv_pay', + }, + ), + migrations.AddField( + model_name='abon', + name='current_tariffs', + field=models.ManyToManyField(through='abonapp.AbonTariff', to='tariff_app.Tariff'), + ), + migrations.AddField( + model_name='abon', + name='group', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='abonapp.AbonGroup'), + ), + migrations.AddField( + model_name='abon', + name='ip_address', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ip_pool.IpPoolItem'), + ), + migrations.AlterUniqueTogether( + name='abontariff', + unique_together=set([('abon', 'tariff', 'tariff_priority')]), + ), + ] diff --git a/abonapp/migrations/__init__.py b/abonapp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/abonapp/models.py b/abonapp/models.py new file mode 100644 index 0000000..3ec1616 --- /dev/null +++ b/abonapp/models.py @@ -0,0 +1,349 @@ +# -*- coding: utf-8 -*- +from django.core.exceptions import MultipleObjectsReturned +from django.http import Http404 +from django.shortcuts import get_object_or_404 +from django.utils import timezone +from django.utils.datetime_safe import datetime +from agent import get_TransmitterClientKlass, Abonent, Tariff as AgentTariff +from ip_pool.models import IpPoolItem +from tariff_app.models import Tariff +from django.db import models +from djing import settings +from django.core.validators import DecimalValidator +from accounts_app.models import UserProfile + + +class LogicError(Exception): + def __init__(self, value): + self.value = value + def __unicode__(self): + return repr(self.value) + + +class AbonGroup(models.Model): + title = models.CharField(max_length=127) + address = models.CharField(max_length=256, blank=True, null=True) + + class Meta: + db_table = 'abonent_groups' + + def __unicode__(self): + return self.title + + +class AbonLog(models.Model): + abon = models.ForeignKey('Abon') + amount = models.FloatField(default=0.0) + author = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='+') + comment = models.CharField(max_length=128) + date = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = 'abonent_log' + + def __unicode__(self): + return self.comment + + +class AbonTariffManager(models.Manager): + + def update_priorities(self, abonent): + abon_tariff_list = AbonTariff.objects.filter(abon=abonent).order_by('tariff_priority') + + # Обновляем приоритеты, чтоб по порядку были + at_pr = 0 + for at in abon_tariff_list: + at.tariff_priority = at_pr + at_pr += 1 + at.save() + + +class AbonTariff(models.Model): + abon = models.ForeignKey('Abon') + tariff = models.ForeignKey(Tariff, related_name='linkto_tariff') + tariff_priority = models.PositiveSmallIntegerField(default=0) + + # время начала действия, остальные что не начали действие - NULL + time_start = models.DateTimeField(null=True, blank=True, default=None) + + objects = AbonTariffManager() + + def priority_up(self): + # ищем услугу с большим приоритетом(число приоритета меньше) + target_abtar = AbonTariff.objects.filter( + abon=self.abon, + tariff_priority__lt=self.tariff_priority + ).order_by('-tariff_priority')[:1] + if target_abtar.count() > 0: + target_abtar = target_abtar[0] + else: + return + + # Ищем текущий тариф абонента + active_abtar = AbonTariff.objects.filter( + abon=self.abon + )[:1] + if active_abtar.count() > 0: + active_abtar = active_abtar[0] + else: + return + + # Если услуга с которой хотим поменяться приоритетом является текущей то нельзя меняться + if active_abtar == target_abtar: + return + + # Swap приоритетов у текущего и найденного с меньшим tariff_priority (большим приоритетом) + tmp_prior = target_abtar.tariff_priority + target_abtar.tariff_priority = self.tariff_priority + target_abtar.save() + self.tariff_priority = tmp_prior + self.save() + + def priority_down(self): + # ищем услугу с меньшим приоритетом + target_abtar = AbonTariff.objects.filter( + abon=self.abon, + tariff_priority__gt=self.tariff_priority + )[:1] + if target_abtar.count() > 0: + target_abtar = target_abtar[0] + else: + # меньше нет, это самая последняя услуга + return + + # Swap приоритетов у текущего и найденного с большим tariff_priority (меньшим приоритетом) + tmp_pr = self.tariff_priority + self.tariff_priority = target_abtar.tariff_priority + target_abtar.tariff_priority = tmp_pr + target_abtar.save() + self.save() + + # Считает текущую стоимость услуг согласно выбранной для тарифа логики оплаты (см. в документации) + def calc_amount_service(self): + calc_obj = self.tariff.get_calc_type() + # calc_obj - instance of tariff_app.custom_tariffs.TariffBase + amount = calc_obj.calc_amount(self) + return round(amount, 2) + + # досрочно завершает услугу + def finish_and_activate_next_tariff(self, author): + + # выберем следующие по приоритету услуги + next_tarifs = AbonTariff.objects.filter(tariff_priority__gt = self.tariff_priority, abon=self.abon)[:1] + + if next_tarifs.count() < 1: + raise LogicError(u'У абонента нет следующих назначенных услуг') + + # 0й элемент это следующая подключаемая услуга + next_tarifs[0].time_start = timezone.now() + next_tarifs[0].save() + + # сколько денег стоят потраченные ресурсы + used_services = self.calc_amount_service() + + #теперь к текущему баллансу добавляем сумму не потраченных ресурсов, т.к. полная сумма тарифа списывается при покупке тарифа + ret_amount = self.tariff.amount - used_services + self.abon.ballance += ret_amount + self.abon.save() + + AbonLog.objects.create( + abon = self.abon, + amount = ret_amount, + author = author, + comment = u'Досрочное завершение услуги %s' % (self.tariff.title) + ) + + def __unicode__(self): + return "%d: '%s' - '%s'" % ( + self.tariff_priority, + self.tariff.title, + self.abon.get_short_name() + ) + + class Meta: + ordering = ('tariff_priority',) + db_table = 'abonent_tariff' + unique_together = (('abon', 'tariff', 'tariff_priority'),) + + +class Abon(UserProfile): + current_tariffs = models.ManyToManyField(Tariff, through=AbonTariff) + group = models.ForeignKey(AbonGroup, models.SET_NULL, blank=True, null=True) + ballance = models.FloatField(default=0.0, validators=[DecimalValidator]) + ip_address = models.OneToOneField(IpPoolItem, on_delete=models.SET_NULL, null=True, blank=True) + address = models.CharField(max_length=256) + + _act_tar_cache = None + + def active_tariff(self): + if self._act_tar_cache: + return self._act_tar_cache + + ats = AbonTariff.objects.filter(abon=self)[:1] + + if ats.count() > 0: + self._act_tar_cache = ats[0].tariff + return ats[0].tariff + else: + self._act_tar_cache = None + return + + def save_form(self, abonform_instance): + try: + cd = abonform_instance.cleaned_data + tel = cd['telephone'] + self.username = cd['username'] or tel[1:] + self.fio = cd['fio'] + self.telephone = tel + self.is_admin = False + self.ip_address = get_object_or_404(IpPoolItem, ip=cd['ip_address']) + self.is_active = True + self.group = cd['group'] + self.address = cd['address'] + except Http404: + raise LogicError(u'Введённый IP адрес не добавлен в ip pool') + except MultipleObjectsReturned: + raise LogicError(u'Введённый IP адрес не определён') + + class Meta: + db_table = 'abonent' + + def make_pay(self, curuser, how_match_to_pay=0.0): + AbonLog.objects.create( + abon = self, + amount = -how_match_to_pay, + author = curuser, + comment = u'Снятие со счёта средств' + ) + self.ballance -= how_match_to_pay + + def add_ballance(self, current_user, amount): + AbonLog.objects.create( + abon = self, + amount = amount, + author = current_user, + comment = u'Пополнение счёта через админку' + ) + self.ballance += amount + + def buy_tariff(self, tariff, author): + if self.ballance >= tariff.amount: + # денег достаточно, можно покупать + self.ballance -= tariff.amount + + # выбераем связь ТарифАбонент с самым низким приоритетом + abtrf = AbonTariff.objects.filter(abon=self).order_by('-tariff_priority')[:1] + abtrf = abtrf[0] if abtrf.count() > 0 else None + + # создаём новую связь с приоритетом ещё ниже + new_abtar = AbonTariff( + abon=self, + tariff=tariff, + tariff_priority=abtrf.tariff_priority+1 if abtrf else -1 + ) + + # Если это первая услуга в списке (фильтр по приоритету ничего не вернул) + if not abtrf: + # значит она сразу стаёт активной + new_abtar.time_start = timezone.now() + + new_abtar.save() + + # шлём сигнал о том что абонент купил первую услугу, а значит можно пользоваться инетом + # сигнал можно слать только после того как будет сохранён новый объект AbonTariff + if self.is_active and not abtrf: + tc = get_TransmitterClientKlass()() + act_tar = self.active_tariff() + agent_abon = Abonent( + self.id, + self.ip_address.int_ip(), + AgentTariff( + act_tar.id if act_tar else 0, + act_tar.speedIn if act_tar else 0.0, + act_tar.speedOut if act_tar else 0.0 + ) + ) + tc.signal_abon_refresh_info(agent_abon) + tc.signal_abon_open_inet(agent_abon) + + # Запись об этом в лог + AbonLog.objects.create( + abon = self, amount = -tariff.amount, + author = author, + comment = u'Покупка тарифного плана через админку, тариф "%s"' % tariff.title + ) + else: + raise LogicError(u'Недостаточно денег на счету абонента') + + # Пробует подключить новую услугу если пришло время + def activate_next_tariff(self, author): + ats = AbonTariff.objects.filter(abon=self).order_by('tariff_priority') + + nw = datetime.now(tz=timezone.get_current_timezone()) + + for at in ats: + # Если времени активации нет, то это ещё не активированный тариф + if not at.time_start: + return + + # время к началу месяца + to_start_month = datetime(nw.year, nw.month, 1, tzinfo=timezone.get_current_timezone()) + + # проверяем расстояние от Сегодня до начала этого месяца. + # И от заказа тарифа до начала этого месяца + if (nw - at.time_start) > (nw - to_start_month): + # Заказ из прошлого месяца, срок действия закончен + print u'Заказ из прошлого месяца, срок действия закончен' + + # выберем следующую по приоритету + #next_tarifs = AbonTariff.objects.filter(tariff_priority__gt = self.tariff_priority, abon=self.abon) + next_tarifs = filter(lambda tr: tr.tariff_priority > at.tariff_priority, ats)[:2] + + # и если что-нибудь вернулось то активируем, давая время начала действия + if next_tarifs.count() > 0: + next_tarifs[0].time_start = nw + next_tarifs[0].save() + + # завершаем текущую услугу. + at.delete() + + # Создаём лог о завершении услуги + AbonLog.objects.create( + abon = self, + amount = 0, + author = author, + comment = u'Завершение услуги по истечению срока действия' + ) + + +class InvoiceForPayment(models.Model): + abon = models.ForeignKey(Abon) + status = models.BooleanField(default=False) + amount = models.FloatField(default=0.0) + comment = models.CharField(max_length=128) + date_create = models.DateTimeField(auto_now_add=True) + date_pay = models.DateTimeField(blank=True, null=True) + author = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='+') + + def __unicode__(self): + return "%s -> %d $" % (self.abon.username, self.amount) + + def set_ok(self): + self.status = True + self.date_pay = datetime.now() + + def get_prev_invoice(self): + return self.objects.order + + class Meta: + ordering = ('date_create',) + db_table = 'abonent_inv_pay' + + +#def abon_save_signal(sender, instance, **kwargs): +# if not kwargs['created']: +# # if not create (change only) +# print "Kw1", instance.username, instance.is_active + + +#models.signals.post_save.connect(abon_save_signal, sender=Abon) diff --git a/abonapp/tests.py b/abonapp/tests.py new file mode 100644 index 0000000..82ce379 --- /dev/null +++ b/abonapp/tests.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +from django.shortcuts import get_object_or_404 +from django.test import TestCase +from models import Abon, AbonTariff +from tariff_app.models import Tariff + + +class AbonTariffTestCase(TestCase): + + def setUp(self): + abon1 = Abon.objects.create( + telephone='+79784653751', + fio=u'ФИО абона', + username='аго мучич' + ) + tarif1 = Tariff.objects.create( + title=u'Тариф 1', + speedIn=120.3, + speedOut=53, + amount=38 + ) + tarif2 = Tariff.objects.create( + title=u'Тариф 2', + speedIn=130.3, + speedOut=23, + amount=82 + ) + AbonTariff.objects.create( + abon=abon1, + tariff=tarif1, + tariff_priority=0 + ) + AbonTariff.objects.create( + abon=abon1, + tariff=tarif2, + tariff_priority=1 + ) + + def test_activate_next(self): + # возьмём абонента для опытов + abn = get_object_or_404(Abon, username=u'аго мучич') + + # берём купленные услуги + ats = AbonTariff.objects.filter(abon=abn) + for at in ats: + + # и пробуем назначить + at.activate_next_tariff() + + AbonTariff.objects.update_priorities(ats) diff --git a/abonapp/urls.py b/abonapp/urls.py new file mode 100644 index 0000000..e5c3e37 --- /dev/null +++ b/abonapp/urls.py @@ -0,0 +1,21 @@ +from django.conf.urls import url, include +import views + +urlpatterns = [ + + url(r'^$', views.grouplist, name='abongroup_list_link'), + url(r'^addgroup$', views.addgroup, name='addgroup_link'), + url(r'^delgroup', views.delgroup, name='people_delgroup_link'), + + url(r'^(?P\d+)/', include('abonapp.urls_abon')), + + url(r'^log$', views.log_page, name='abonapp_log_link'), + + url(r'^del$', views.delentity, name='abonapp_del_link'), + + url(r'^pay$', views.terminal_pay, name='abonapp_terminalpay_link'), + + # Api's + url(r'^api/abons$', views.abons) + +] diff --git a/abonapp/urls_abon.py b/abonapp/urls_abon.py new file mode 100644 index 0000000..dbbb62c --- /dev/null +++ b/abonapp/urls_abon.py @@ -0,0 +1,19 @@ +from django.conf.urls import url +import views + +urlpatterns = [ + url(r'^$', views.peoples, name='pays_people_list_link'), + url(r'^addabon$', views.addabon, name='addabon_link'), + url(r'^(?P\d+)$', views.abonhome, name='abonhome_link'), + + url(r'^(?P\d+)/services$', views.abon_services, name='abon_services_link'), + url(r'^(?P\d+)/amount', views.abonamount, name='abon_amount_link'), + url(r'^(?P\d+)/debts', views.invoice_for_payment, name='abon_debts_link'), + url(r'^(?P\d+)/pay_history', views.pay_history, name='abon_phistory_link'), + + + url(r'^(?P\d+)/addinvoice$', views.add_invoice, name='abonapp_addinvoice_link'), + url(r'^(?P\d+)/buy$', views.buy_tariff, name='abonapp_buy_tariff'), + url(r'^(?P\d+)/chpriority$', views.chpriority, name='abonapp_chpriority_tariff'), + url(r'^(?P\d+)/complete_service_(?P\d+)$', views.complete_service, name='abonapp_compl_srv'), +] diff --git a/abonapp/views.py b/abonapp/views.py new file mode 100644 index 0000000..1b34a4c --- /dev/null +++ b/abonapp/views.py @@ -0,0 +1,439 @@ +# -*- coding: utf-8 -*- +from json import dumps +from django.db import IntegrityError +from django.db.models import Count +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth.decorators import login_required +from django.utils import timezone +from accounts_app.models import UserProfile +from ip_pool.models import IpPoolItem +from tariff_app.models import Tariff +from django.template.context_processors import csrf +from django.http import HttpResponse, Http404 +from agent import get_TransmitterClientKlass, NetExcept +import forms +import models +import mydefs + + +@login_required +def peoples(request, gid): + peopleslist = models.Abon.objects.filter(group=gid) + + peopleslist = mydefs.pag_mn(request, peopleslist) + + return render(request, 'abonapp/peoples.html', { + 'peoples': peopleslist, + 'gid': gid + }) + + +@login_required +# @permission_required('abonapp.add_abongroup') +def addgroup(request): + warntext = '' + frm = forms.AbonGroupForm() + if request.method == 'POST': + frm = forms.AbonGroupForm(request.POST) + if frm.is_valid(): + frm.save() + return redirect('abongroup_list_link') + else: + warntext = u'Исправьте ошибки' + return render(request, 'abonapp/addGroup.html', { + 'csrf_token': csrf(request)['csrf_token'], + 'form': frm, + 'warntext': warntext + }) + + +@login_required +def grouplist(request): + groups = models.AbonGroup.objects.annotate(usercount=Count('abon')) + + groups = mydefs.pag_mn(request, groups) + + return render(request, 'abonapp/group_list.html', { + 'groups': groups + }) + + +@login_required +def delgroup(request): + agd = mydefs.safe_int(request.GET.get('id')) + get_object_or_404(models.AbonGroup, id=agd).delete() + return mydefs.res_success(request, 'abongroup_list_link') + + +@login_required +# @permission_required('abonapp.add_abon') +# @permission_required('abonapp.change_abon') +def addabon(request, gid): + warntext = '' + frm = None + try: + grp = get_object_or_404(models.AbonGroup, id=gid) + if request.method == 'POST': + frm = forms.AbonForm(request.POST) + if frm.is_valid(): + prf = models.Abon() + prf.group = grp + prf.save_form(frm) + prf.save() + return redirect('pays_people_list_link', gid) + else: + warntext = u'Некоторые поля заполнены не правильно, проверте ещё раз' + + except IntegrityError, e: + warntext = "%s: %s" % (warntext, e) + + except models.LogicError as e: + warntext = e.value + + if not frm: + frm = forms.AbonForm(initial={ + 'ip_address': IpPoolItem.objects.get_free_ip(), + 'group': grp + }) + + return render(request, 'abonapp/addAbon.html', { + 'warntext': warntext, + 'csrf_token': csrf(request)['csrf_token'], + 'form': frm, + 'gid': gid + }) + + +@login_required +# @permission_required('abonapp.delete_abon') +# @permission_required('abonapp.delete_abongroup') +def delentity(request): + typ = request.GET.get('t') + uid = request.GET.get('id') + + if typ == 'a': + abon = get_object_or_404(models.Abon, id=uid) + abon.delete() + return mydefs.res_success(request, 'pays_people_list_link') + elif typ == 'g': + get_object_or_404(models.AbonGroup, id=uid).delete() + return mydefs.res_success(request, 'pays_people_list_link') + + +@login_required +def abonamount(request, gid, uid): + warntext='' + if request.method == 'POST': + abonid = mydefs.safe_int(request.POST.get('abonid')) + if abonid == int(uid): + amnt = mydefs.safe_float(request.POST.get('amount')) + abon = get_object_or_404(models.Abon, id=abonid) + abon.add_ballance(request.user, amnt) + abon.save() + return redirect('abonhome_link', gid=gid, uid=uid) + else: + warntext = u'Не правильно выбран абонент как цель для пополнения' + return render(request, 'abonapp/abonamount.html', { + 'abon_id': int(uid), + 'gid': gid, + 'warntext': warntext + }) + + +@login_required +def invoice_for_payment(request, gid, uid): + #abon = get_object_or_404(models.Abon, id=uid) + invoices = models.InvoiceForPayment.objects.filter(abon_id=uid) + invoices = mydefs.pag_mn(request, invoices) + return render(request, 'abonapp/invoiceForPayment.html', { + 'invoices': invoices, + 'gid': gid, + 'abon_id': uid + }) + + +@login_required +def pay_history(request, gid, uid): + #abon = get_object_or_404(models.Abon, id=uid) + pay_history = models.AbonLog.objects.filter(abon_id=uid).order_by('-id') + pay_history = mydefs.pag_mn(request, pay_history) + + return render(request, 'abonapp/payHistory.html', { + 'pay_history': pay_history, + 'gid': gid, + 'abon_id': uid + }) + + +@login_required +def abon_services(request, gid, uid): + #abon = get_object_or_404(models.Abon, id=uid) + abon_tarifs = models.AbonTariff.objects.filter(abon_id=uid).order_by('tariff_priority') + + return render(request, 'abonapp/services.html', { + 'abon_id': uid, + 'abon_tarifs': abon_tarifs, + 'active_abontariff_id': abon_tarifs[0].id if abon_tarifs.count() > 0 else None, + 'gid': gid + }) + + +@login_required +def abonhome(request, gid, uid): + abon = get_object_or_404(models.Abon, id=uid) + warntext = '' + ballance = abon.ballance + frm = None + init_frm_dat = { + 'username': abon.username, + 'fio': abon.fio, + 'ip_address': abon.ip_address or u'Не назначен', + 'telephone': abon.telephone, + 'group': abon.group, + 'address': abon.address, + 'is_active': abon.is_active + } + + try: + if request.method == 'POST': + frm = forms.AbonForm(request.POST) + if frm.is_valid(): + cd = frm.cleaned_data + abon.username = cd['username'] + abon.fio = cd['fio'] + + ip_address = abon.ip_address + abon.ip_address = get_object_or_404(IpPoolItem, ip=cd['ip_address']) + + abon.telephone = cd['telephone'] + abon.group = cd['group'] + abon.address = cd['address'] + abisactive = abon.is_active + abon.is_active = 1 if cd['is_active'] else 0 + abon.save() + + # Если включили то шлём событие от этом + if cd['is_active'] and not abisactive: + tc = get_TransmitterClientKlass()() + tc.signal_abon_enable(abon) + + # Если выключили + elif not cd['is_active'] and abisactive: + tc = get_TransmitterClientKlass()() + tc.signal_abon_disable(abon) + + # Если изменили инфу, важную для NAS то говорим NAS'у перечитать инфу об абоненте + if abon.ip_address != ip_address: + tc = get_TransmitterClientKlass()() + tc.signal_abon_refresh_info(abon) + + #return redirect('abonhome_link', gid, uid) + else: + warntext = u'Не правильные значения, проверте поля и попробуйте ещё' + else: + frm = forms.AbonForm(initial=init_frm_dat) + except IntegrityError, e: + warntext = u'Проверте введённые вами значения, скорее всего такой ip уже у кого-то есть. А вообще: %s' % e + frm = forms.AbonForm(initial=init_frm_dat) + + except Http404: + warntext = u'Ip адрес не найден в списке IP адресов' + frm = forms.AbonForm(initial=init_frm_dat) + + except NetExcept as e: + warntext = e.value + + return render(request, 'abonapp/editAbon.html', { + 'warntext': warntext, + 'form': frm or forms.AbonForm(initial=init_frm_dat), + 'abon_id': uid, + 'ballance': ballance, + 'gid': gid + }) + + +def terminal_pay(request): + username = request.GET.get('username') + amount = mydefs.safe_float(request.GET.get('amount')) + + kernel_user = get_object_or_404(UserProfile, username='kernel') + abon = get_object_or_404(models.Abon, username=username) + + abon.add_ballance(kernel_user, amount) + + abon.save() + return HttpResponse('ok') + + +@login_required +# @permission_required('abonapp.add_invoiceforpayment') +def add_invoice(request, gid, uid): + uid = mydefs.safe_int(uid) + abon = get_object_or_404(models.Abon, id=uid) + + if request.method == 'POST': + curr_amount = mydefs.safe_int(request.POST.get('curr_amount')) + comment = request.POST.get('comment') + + newinv = models.InvoiceForPayment() + newinv.abon = abon + newinv.amount = curr_amount + newinv.comment = comment + + if request.POST.get('status') == u'on': + newinv.status = True + + newinv.author = request.user + newinv.save() + return redirect('abonhome_link', gid=gid, uid=uid) + else: + return render(request, 'abonapp/addInvoice.html', { + 'csrf_token': csrf(request)['csrf_token'], + 'abon': abon, + 'invcount': models.InvoiceForPayment.objects.filter(abon=abon).count(), + 'gid': gid + }) + + +@login_required +def buy_tariff(request, gid, uid): + warntext = '' + frm = None + try: + if request.method == 'POST': + frm = forms.BuyTariff(request.POST) + if frm.is_valid(): + cd = frm.cleaned_data + abon = get_object_or_404(models.Abon, id=uid) + abon.buy_tariff(cd['tariff'], request.user) + abon.save() + return redirect('abonhome_link', uid=uid) + else: + warntext = u'Что-то не так при покупке услуги, проверьте и попробуйте ещё' + else: + frm = forms.BuyTariff() + except models.LogicError as e: + warntext = e.value + + except NetExcept as e: + warntext = e.value + + return render(request, 'abonapp/buy_tariff.html', { + 'warntext': warntext, + 'form': frm or forms.BuyTariff(), + 'uid': uid, + 'gid': gid + }) + + +@login_required +def chpriority(request, gid, uid): + t = request.GET.get('t') + act = request.GET.get('a') + + current_abon_tariff = get_object_or_404(models.AbonTariff, id=t) + + if act == 'up': + current_abon_tariff.priority_up() + elif act == 'down': + current_abon_tariff.priority_down() + + return redirect('abonhome_link', gid=gid, uid=uid) + + +@login_required +def complete_service(request, gid, uid, srvid): + abtar = get_object_or_404(models.AbonTariff, id=srvid) + + if abtar.abon.id != int(uid): + return HttpResponse('

uid not equal uid from service

') + + try: + if request.method == 'POST': + # досрочно завершаем услугу + try: + abtar.finish_and_activate_next_tariff(request.user) + # завершаем текущую услугу. + abtar.delete() + + except models.LogicError: + # Значит у абонента нет следующих услуг. Сигналим о закрытии инета в NAS + tc = get_TransmitterClientKlass()() + tc.signal_abon_close_inet(abtar.abon) + + # Переупорядочиваем приоритеты + models.AbonTariff.objects.update_priorities(abtar.abon) + + return redirect('abonhome_link', gid, uid) + + next_tariff = models.AbonTariff.objects.filter( + abon=abtar.abon, + tariff_priority__gt=abtar.tariff_priority + )[:1] + + if not abtar.time_start: + abtar.time_start = timezone.now() + abtar.save() + + time_use = timezone.now() - abtar.time_start + time_use = { + 'days': time_use.days, + 'hours': time_use.seconds / 3600, + 'minutes': time_use.seconds / 60 % 60 + } + return render(request, 'abonapp/complete_service.html', { + 'csrf_token': csrf(request)['csrf_token'], + 'abtar': abtar, + 'uid': uid, + 'next_tariff': next_tariff[0] if next_tariff.count() > 0 else None, + 'time_use': time_use, + 'gid': gid + }) + + except models.LogicError as e: + warntext = e.value + + except NetExcept as e: + warntext = e.value + + return render(request, 'abonapp/complete_service.html', { + 'csrf_token': csrf(request)['csrf_token'], + 'abtar': abtar, + 'uid': uid, + 'warntext': warntext + }) + + +@login_required +def log_page(request): + logs = models.AbonLog.objects.all() + + logs = mydefs.pag_mn(request, logs) + + return render(request, 'abonapp/log.html', { + 'logs': logs + }) + + +# API's + +def abons(request): + ablist = map(lambda abn: { + 'id': abn.id, + 'tarif_id': abn.active_tariff().id if abn.active_tariff() else 0, + 'ip': abn.ip_address.int_ip(), + 'is_active': abn.is_active + }, models.Abon.objects.all()) + + tarlist = map(lambda trf: { + 'id': trf.id, + 'speedIn': trf.speedIn, + 'speedOut': trf.speedOut + }, Tariff.objects.all()) + + data = { + 'subscribers': ablist, + 'tariffs': tarlist + } + del ablist, tarlist + return HttpResponse(dumps(data)) diff --git a/accounts_app/__init__.py b/accounts_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounts_app/admin.py b/accounts_app/admin.py new file mode 100644 index 0000000..a98979a --- /dev/null +++ b/accounts_app/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from models import UserProfile + +admin.site.register(UserProfile) diff --git a/accounts_app/migrations/0001_initial.py b/accounts_app/migrations/0001_initial.py new file mode 100644 index 0000000..a588f8f --- /dev/null +++ b/accounts_app/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-06-28 23:51 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0007_alter_validators_add_error_messages'), + ('photo_app', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(max_length=127, unique=True)), + ('fio', models.CharField(max_length=256)), + ('birth_day', models.DateField(auto_now_add=True)), + ('is_active', models.BooleanField(default=True)), + ('is_admin', models.BooleanField(default=False)), + ('telephone', models.CharField(max_length=16, unique=True, validators=[django.core.validators.RegexValidator(b'^\\+[7,8,9,3]\\d{10,11}$')], verbose_name=b'Telephone number')), + ('skype', models.CharField(blank=True, max_length=20)), + ('avatar', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='photo_app.Photo')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/accounts_app/migrations/__init__.py b/accounts_app/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounts_app/models.py b/accounts_app/models.py new file mode 100644 index 0000000..8c77cd1 --- /dev/null +++ b/accounts_app/models.py @@ -0,0 +1,107 @@ +# -*- coding:utf-8 -*- +from django.db import models +from djing.settings import DEFAULT_PICTURE +from photo_app.models import Photo +from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin +from django.core.validators import RegexValidator + + +class MyUserManager(BaseUserManager): + + def create_user(self, telephone, username, password=None): + """ + Creates and saves a User with the given email, date of + birth and password. + """ + if not telephone: + raise ValueError('Users must have an telephone number') + + user = self.model( + telephone=telephone, + username=username, + ) + + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, telephone, username, password): + """ + Creates and saves a superuser with the given email, date of + birth and password. + """ + user = self.create_user(telephone, + password=password, + username=username + ) + user.is_admin = True + user.is_superuser = True + user.save(using=self._db) + return user + + +class UserProfile(AbstractBaseUser, PermissionsMixin): + username = models.CharField(max_length=127, unique=True) + fio = models.CharField(max_length=256) + birth_day = models.DateField(auto_now_add=True) + is_active = models.BooleanField(default=True) + is_admin = models.BooleanField(default=False) + telephone = models.CharField( + max_length=16, + verbose_name='Telephone number', + unique=True, + validators=[RegexValidator(r'^\+[7,8,9,3]\d{10,11}$')] + ) + skype = models.CharField(max_length=20, blank=True) + avatar = models.ForeignKey(Photo, null=True, blank=True) + + USERNAME_FIELD = 'username' + REQUIRED_FIELDS = ['telephone'] + + def get_full_name(self): + if self.fio: + return "%s: %s" % (self.username, self.fio) + else: + return self.username + + def get_short_name(self): + return self.username or self.telephone + + + # Use UserManager to get the create_user method, etc. + objects = MyUserManager() + + @property + def is_staff(self): + "Is the user a member of staff?" + # Simplest possible answer: All admins are staff + return self.is_admin + + def get_big_ava(self): + if self.avatar: + return self.avatar.big() + else: + return DEFAULT_PICTURE + + def get_min_ava(self): + if self.avatar: + return self.avatar.min() + else: + return DEFAULT_PICTURE + + def __unicode__(self): + return self.username + + +#from django.db.models.signals import post_save + + +'''def create_custom_user(sender, instance, created, **kwargs): + if created: + values = {} + for field in sender._meta.local_fields: + values[field.attname] = getattr(instance, field.attname) + user = UserProfile(**values) + user.save()''' + +#post_save.connect(create_custom_user, User) diff --git a/accounts_app/tests.py b/accounts_app/tests.py new file mode 100644 index 0000000..501deb7 --- /dev/null +++ b/accounts_app/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/accounts_app/urls.py b/accounts_app/urls.py new file mode 100644 index 0000000..f683677 --- /dev/null +++ b/accounts_app/urls.py @@ -0,0 +1,27 @@ +# -*- coding:utf-8 -*- +from django.conf.urls import url +import views + +urlpatterns = [ + + url(r'^login/', views.to_signin, name='login_link'), + url(r'^logout/', views.sign_out, name='logout_link'), + + url(r'^me$', views.profile_show, name='profile'), + + url(r'^$', views.acc_list, name='accounts_list'), + + url(r'^add$', views.create_profile, name='create_profile_link'), + + url(r'^settings$', views.ch_info, name='settings_chinfo_link'), + url(r'^settings/change_ava$', views.ch_ava, name='settings_chava_link'), + + url(r'^(?P\d+)$', views.profile_show, name='other_profile'), + url(r'^(?P\d+)/perms$', views.perms, name='profile_perms_link'), + url(r'^(?P\d+)/chgroup$', views.chgroup, name='profile_chgroup_link'), + url(r'^(?P\d+)/del$', views.delete_profile, name='delete_profile_link'), + + url(r'^group/$', views.groups, name='profile_groups_list'), + url(r'^group/(?P\d+)$', views.group, name='profile_group_link') + +] \ No newline at end of file diff --git a/accounts_app/views.py b/accounts_app/views.py new file mode 100644 index 0000000..17136dc --- /dev/null +++ b/accounts_app/views.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- +from django.contrib.auth.decorators import login_required#, permission_required +from django.contrib.auth import authenticate, login, logout +from photo_app.models import Photo +from django.core.urlresolvers import NoReverseMatch +from models import UserProfile +from django.shortcuts import render, redirect, get_object_or_404 +from django.template.context_processors import csrf +from django.http import Http404 +from django.contrib.auth.models import Group, Permission +import mydefs + + +@login_required +def home(request): + return redirect('profile') + + +def to_signin(request): + nextl = request.GET.get('next') + + try: + if request.POST: + auser = authenticate(username=request.POST.get('login'), password=request.POST.get('password')) + if auser: + login(request, auser) + if nextl == 'None' or nextl == None or nextl == '': + return redirect('profile') + else: + return redirect(nextl) + else: + return render(request, 'accounts/login.html', { + 'csrf_token': csrf(request)['csrf_token'], + 'next': nextl, + 'errmsg': u'Неправильный логин или пароль, попробуйте ещё раз' + }) + except NoReverseMatch: + raise Http404(u"Destination page does not exist") + return render(request, 'accounts/login.html', { + 'csrf_token': csrf(request)['csrf_token'], + 'next': nextl + }) + + +def sign_out(request): + logout(request) + return redirect('login_link') + + +@login_required +def profile_show(request, id=0): + id = mydefs.safe_int(id) + + if id == 0: + usr = request.user + else: + usr = get_object_or_404(UserProfile, id=id) + + if request.method == 'POST': + usr.username = request.POST.get('username') + usr.fio = request.POST.get('fio') + usr.telephone = request.POST.get('telephone') + usr.is_active = request.POST.get('stat') + usr.is_admin = request.POST.get('is_admin') + usr.save() + return redirect('other_profile', id=id) + + return render(request, 'accounts/index.html', { + 'uid': id, + 'userprofile': usr + }) + + +@login_required +def chgroup(request, uid): + usr = get_object_or_404(UserProfile, id=uid) + usergroups = usr.groups.all() + othergroups = filter(lambda g: g not in usergroups, Group.objects.all()) + #Group.objects.exclude(user__in=usergroups) + + return render(request, 'accounts/profile_chgroup.html', { + 'uid': uid, + 'userprofile': usr, + 'allgroups': othergroups, + 'usergroups': usergroups + }) + + +@login_required +def ch_ava(request): + if request.method == 'POST': + user = request.user + if user.avatar: + user.avatar.delete() + photo = Photo() + photo.image = request.FILES.get('avatar') + photo.save() + user.avatar = photo + user.save() + request.user = user + + return render(request, 'accounts/settings/ch_info.html', { + 'user': request.user + }) + + +@login_required +def ch_info(request): + warntext='' + if request.method == 'POST': + user = request.user + user.username = request.POST.get('username') + user.fio = request.POST.get('fio') + user.email = request.POST.get('email') + user.telephone = request.POST.get('telephone') + user.skype = request.POST.get('skype') + + psw = request.POST.get('oldpasswd') + if psw != '': + if user.check_password(psw): + newpasswd = request.POST.get('newpasswd') + user.set_password(newpasswd) + else: + warntext = u'Неправильный пароль' + user.save() + request.user = user + + return render(request, 'accounts/settings/ch_info.html', { + 'user': request.user, + 'warntext': warntext + }) + + +@login_required +##@permission_required('accounts_app.add_userprofile') +def create_profile(request): + if request.method == 'POST': + username = request.POST.get('username') + + user = UserProfile() + user.username = username + user.fio = request.POST.get('fio') + user.email = request.POST.get('email') + user.telephone = request.POST.get('telephone') + user.skype = request.POST.get('skype') + user.is_admin = True + + passwd = request.POST.get('passwd') + conpasswd = request.POST.get('conpasswd') + if not passwd: + return render(request, 'accounts/create_acc.html',{ + 'warntext': u'Забыли указать пароль для нового аккаунта', + 'csrf_token': csrf(request)['csrf_token'], + 'newuser': user + }) + if not conpasswd: + return render(request, 'accounts/create_acc.html',{ + 'warntext': u'Забыли повторить пароль для нового аккаунта', + 'csrf_token': csrf(request)['csrf_token'], + 'newuser': user + }) + + if passwd == conpasswd: + user_qs = UserProfile.objects.filter(username=username)[:1] + if user_qs.count() == 0: + user.set_password(passwd) + user.save() + return redirect('accounts_list') + else: + return render(request, 'accounts/create_acc.html',{ + 'warntext': u'Пользователь с таким именем уже есть', + 'csrf_token': csrf(request)['csrf_token'], + 'newuser': user + }) + else: + return render(request, 'accounts/create_acc.html',{ + 'warntext': u'Пароли не совпадают, попробуйте ещё раз', + 'csrf_token': csrf(request)['csrf_token'], + 'newuser': user + }) + return render(request, 'accounts/create_acc.html', {'csrf_token': csrf(request)['csrf_token'],}) + + +@login_required +#@permission_required('accounts_app.del_userprofile') +def delete_profile(request, uid): + prf = get_object_or_404(UserProfile, id=uid) + prf.delete() + return redirect('accounts_list') + + +@login_required +def acc_list(request): + users = UserProfile.objects.filter(is_admin=True) + + users = mydefs.pag_mn(request, users) + + return render(request, 'accounts/acc_list.html', { + 'users': users + }) + + +@login_required +#@permission_required('accounts_app.change_userprofile') +def perms(request, id): + ingroups = filter(lambda x: x[0] == 'ingroups', request.POST.lists())[0][1] + id = mydefs.safe_int(id) + + profile = get_object_or_404(UserProfile, id=id) + profile.groups.clear() + + for group_id in ingroups: + gid = mydefs.safe_int(group_id) + profile.groups.add(gid) + profile.save() + + return redirect('other_profile', id) + + +@login_required +def groups(request): + grps = Group.objects.all() + + grps = mydefs.pag_mn(request, grps) + + return render(request, 'accounts/group_list.html', { + 'groups': grps + }) + + +@login_required +#@permission_required('auth.change_group') +def group(request, id): + id = mydefs.safe_int(id) + grp = get_object_or_404(Group, id=id) + + if request.method == 'POST': + group_rights = filter(lambda x: x[0] == 'group_rights', request.POST.lists())[0][1] + grp.permissions.clear() + for grr in group_rights: + rid = mydefs.safe_int(grr) + grp.permissions.add(rid) + grp.save() + return redirect('profile_group_link', id=id) + + grp_rights = grp.permissions.all() + all_rights = Permission.objects.exclude(group=grp) + + #prms = Permission.objects.all() + #for pr in prms: + # print u"%s | %s" % (pr.name, pr.codename) + + return render(request, 'accounts/group.html', { + 'csrf_token': csrf(request)['csrf_token'], + 'group': grp, + 'all_rights': all_rights, + 'grp_rights': grp_rights + }) diff --git a/agent.py b/agent.py new file mode 100644 index 0000000..82803fd --- /dev/null +++ b/agent.py @@ -0,0 +1,12 @@ +#!/bin/env python + +import os +from agent import main + + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") + + while True: + main(debug=True) + print "Exit from main, reload..." diff --git a/agent/__init__.py b/agent/__init__.py new file mode 100644 index 0000000..380a4e5 --- /dev/null +++ b/agent/__init__.py @@ -0,0 +1,3 @@ +from models import Abonent, Tariff +from main import main +from sslTransmitter import get_TransmitterClientKlass, NetExcept diff --git a/agent/db.py b/agent/db.py new file mode 100644 index 0000000..7416115 --- /dev/null +++ b/agent/db.py @@ -0,0 +1,27 @@ +# -*- coding:utf-8 -*- +import requests +from json import loads +from models import deserialize_tariffs, deserialize_abonents +import settings + + +def load_from_db(): + r = requests.get('%s://%s:%d/abons/api/abons' % ( + 'https' if settings.IS_USE_SSL else 'http', + settings.SERVER_IP, + settings.SERVER_PORT + ), verify=False) + try: + user_data = loads(r.text) + del r + # Получаем тарифы + tariffs = deserialize_tariffs(user_data) + + # Получаем пользователей + abons = deserialize_abonents(user_data, tariffs) + + return abons, tariffs + + except ValueError as e: + print 'Error:', e, r.text + return diff --git a/agent/firewall.py b/agent/firewall.py new file mode 100644 index 0000000..c9b2379 --- /dev/null +++ b/agent/firewall.py @@ -0,0 +1,48 @@ +# -*- coding:utf-8 -*- + + +class FirewallManager(object): + + f = r'/sbin/ipfw -q' + + # вызывает комманду shell + def exec_cmd(self, cmd): + print cmd + #os.execv(cmd, ['']) + + # ставит заглушку на абонента + def set_cap(self, user): + pass + + # Открывает доступ в интернет + def open_inet_door(self, user): + if not user.tariff: + print u'WARNING: User does not have a tariff' + return + cmd = r"%s table 12 add %s/32 %d && %s table 13 add %s/32 %d" % ( + self.f, user.ip_str(), user.tariff.tid, + self.f, user.ip_str(), user.tariff.tid+1000 + ) + self.exec_cmd(cmd) + + # Закрывает доступ в интернет + def close_inet_door(self, user): + cmd = r"%s table 12 del %s/32 && %s table 13 del %s/32" % ( + self.f, user.ip_str(), + self.f, user.ip_str() + ) + self.exec_cmd(cmd) + + # Создаёт тариф (пайпы, режущие скорость + def make_tariff(self, tariff): + cmd = r"make ipfw tariff :)" + self.exec_cmd(cmd) + + # Убирает тариф из фаервола + def destroy_tariff(self, tariff): + cmd = r"destroy ipfw tariff :)" + self.exec_cmd(cmd) + + def reset(self): + cmd = r"%s -f flush && %s table all flush" % (self.f, self.f) + self.exec_cmd(cmd) diff --git a/agent/ipfw.sh b/agent/ipfw.sh new file mode 100644 index 0000000..4e9c550 --- /dev/null +++ b/agent/ipfw.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +######################################################### +# ВАЖНО! Биллинг пока ограничен количеством тарифных планов +# не больше 1000 +######################################################### + + + + +f="/sbin/ipfw -q" + +lan=em1 # Clients +wan=em0 # Inet + + +${f} -f flush +${f} table all flush + +sysctl net.inet.ip.fw.one_pass=0 + +# dns +${f} table 100 add 8.8.8.8 # google public dns +${f} table 100 add 8.8.4.4 # google public dns2 +${f} table 100 add 77.88.8.8 # yandex base dns +${f} table 100 add 77.88.8.1 # yandex base dns2 + + +# ssh access +${f} add 50 allow tcp from any to me 22 +${f} add 51 allow tcp from me 22 to any + + +# loopback +${f} add 100 allow ip from any to any via lo0 + + +# в таблице 100 приоритетный траффик. +# это dns, платёжки.. +${f} add 500 allow ip from table\(100\) to any +${f} add 501 allow ip from any to table\(100\) + + + +# в таблице 10 разрешённые пользователи +# блокируем трафик всем кто не в ней +${f} add 1001 deny ip from not table\(10\) to any via $wan + +# если у абонентов есть внешние адреса (не через NAT) +#${f} add 1101 deny ip from any to not table\(10\) via $wan + + + + +# по 2 пайпа на тарифный план, на вход и выход +#${f} pipe 212 config bw 1152Kbit/s mask src-ip 0xffffffff noerror +#${f} pipe 213 config bw 1152Kbit/s mask dst-ip 0xffffffff noerror + +# добавляем пайпы в таблицу +${f} add 2001 pipe 212 ip from table\(10\) to any via $wan +${f} add 2002 pipe 213 ip from any to table\(11\) via $wan + +#---------------------- +# так добавляем абонентов чтоб резать скорость, надо указать номер их пайпа +#${f} table 10 add 10.0.172.138/32 212 +#${f} table 11 add 10.0.172.138/32 2212 +#---------------------- + + + + +# тут будем поджимать пользователей когда не хватает канала diff --git a/agent/main.py b/agent/main.py new file mode 100644 index 0000000..5d049ea --- /dev/null +++ b/agent/main.py @@ -0,0 +1,120 @@ +# -*- coding:utf-8 -*- +from sys import stdout +from db import load_from_db +from firewall import FirewallManager +from time import sleep +from sslTransmitter import TransmitServer + + +def filter_user_by_id(users, uid): + usrs = filter(lambda usr: usr.uid == uid, users) + if len(usrs) > 0: + return usrs[0] + else: + return + + +def main(debug=False): + users, tariffs = load_from_db() + frw = FirewallManager() + frw.reset() + + # Инициализация абонентов + if debug: + print u'Инициализация...' + # Открываем доступ в инет тем кто активен и у кого подключён тариф + for usr in filter(lambda usr: usr.is_active, users): + + # Доступ в интернет происходит по наличию подключённого тарифа + # если тарифа нет, то и инета нет + if usr.tariff: + # Открываем доступ в инет + frw.open_inet_door(usr) + + # Слушем в отдельном процессе сеть на предмет событий + ts = TransmitServer('127.0.0.1', 2134) + ts.start() + + if debug: + print u"Загружено %d абонентов" % len(users) + + while True: + + # Загружаем события для абонентов из сети (список объектов EventNAS из models) + events = ts.get_data() + # Проходим по появившимся событиям + for event in events: + #event.toa + #event.id + #event.dt + + # Смотрим тип события + toa = int(event.toa) + if toa == 0: continue + + # Включаем абонента + elif toa == 1: + usr = filter_user_by_id(users, event.id) + # Открываем доступ в инет + frw.open_inet_door(usr) + + # Выключаем абонента + elif toa == 2: + usr = filter_user_by_id(users, event.id) + # Выключаем интернет + frw.close_inet_door(usr) + + # Сообщение на заглушку + elif toa == 3: + usr = filter_user_by_id(users, event.id) + # Ставим заглушку + frw.set_cap(usr) + # Выключаем интернет + frw.close_inet_door(usr) + + # Открываем доступ в инет + elif toa == 4: + usr = filter_user_by_id(users, event.id) + frw.close_inet_door(usr) + frw.open_inet_door(usr) + + # Закрываем доступ в инет + elif toa == 5: + usr = filter_user_by_id(users, event.id) + frw.close_inet_door(usr) + + elif toa == 6: + # Сигнал на перезагрузку + # Выходим из main, выше она в цикле запустится ещё раз + return + + elif toa == 7: + # Сигнал о том что инфа об абоненте изменилась, надо перечитать + usr = filter_user_by_id(users, event.id) + usr.deserialize(event.dt, tariffs) + # если абонент активен, и куплен и активирован тариф то можно и в инет + if usr.is_active and usr.tariff is not None: + frw.close_inet_door(usr) + frw.open_inet_door(usr) + + elif toa == 8: + # Сигнал об изменении данных в тарифе + tariff = filter(lambda trf: trf.tid == event.id, tariffs) + if len(tariff) > 0: + tariff = tariff[0] + tariff.deserialize(event.dt) + + # Пересоздаём тариф + frw.destroy_tariff(tariff) + frw.make_tariff(tariff) + else: + print 'WARNING: не найден тариф для которого сигнал на изменение данных, пробуем перезагрузиться' + return + + # Очищаем очередь событий + ts.clear() + + # ждём время между итерациями проверки 10 сек... + sleep(10) + stdout.write('.') + stdout.flush() diff --git a/agent/models.py b/agent/models.py new file mode 100644 index 0000000..6b8d675 --- /dev/null +++ b/agent/models.py @@ -0,0 +1,143 @@ +# -*- coding:utf-8 -*- +import socket +import struct +from json import loads, dumps +from abc import ABCMeta, abstractmethod + + +class Serializer(object): + __metaclass__ = ABCMeta + + @abstractmethod + def _serializable_obj(self): + """Вернуть словарь для сериализации""" + + def serialize(self): + return dumps(self._serializable_obj()) + + @abstractmethod + def deserialize(self, *args): + """Надо обязательно этот метод реализовать, он много где используется. + Из JSON создать объект класса где реализуется метод""" + + +def serialize_tariffs(tariffs): + dt = map(lambda trf: trf._serializable_obj(), tariffs) + return dumps({'tariffs': dt}) + + +def deserialize_tariffs(dat): + dat = loads(dat) if type(dat) == str else dat + # Распаковываем из JSON массива dat['tariffs'] объекты через метод deserialize + return map(lambda tariff: Tariff().deserialize(tariff), dat['tariffs']) + + +def serialize_abonents(abonents): + dt = map(lambda abn: abn._serializable_obj(), abonents) + return dumps({'subscribers': dt}) + + +def deserialize_abonents(dat, tariffs): + dat = loads(dat) if type(dat) == str else dat + # Распаковываем из JSON массива dat['subscribers'] объекты через метод deserialize + return map(lambda abon: Abonent().deserialize(abon, tariffs), dat['subscribers']) + + +class Tariff(Serializer): + tid = 0 + speedIn = 0.0 + speedOut = 0.0 + + def __init__(self, tariff_id=None, speed_in=None, speed_out=None): + self.tid = tariff_id + self.speedOut = speed_out + self.speedIn = speed_in + + def is_active(self): + """возвращает активность тарифа. Если он не активен то пропустить""" + return True + + def _serializable_obj(self): + return { + 'id': self.tid, + 'speedIn': self.speedIn, + 'speedOut': self.speedOut + } + + def deserialize(self, dump): + inf = loads(dump) if type(dump) == str else dump + self.speedIn = float(inf['speedIn']) + self.speedOut = float(inf['speedOut']) + self.tid = int(inf['id']) + return self + + +class Abonent(Serializer): + uid = 0 + tariff = Tariff() + ip = 0xffffffff + + # Включён-ли абонент + is_active = True + + def __init__(self, uid=None, ip=None, tariff=None): + self.ip = ip + self.uid = uid + self.tariff = tariff + + def ip_str(self): + # int2ip, Example out '127.0.0.1' + return socket.inet_ntoa(struct.pack("!I", self.ip)) + + def _serializable_obj(self): + return { + 'id': self.uid, + 'is_active': self.is_active, + 'ip': self.ip, + 'tarif_id': self.tariff.tid if self.tariff else 0 + } + + def deserialize(self, dump, tariffs): + inf = loads(dump) if type(dump) == str else dump + self.uid = int(inf['id']) + self.is_active = bool(inf['is_active']) + self.ip = int(inf['ip']) + + tarif_id = int(inf['tarif_id']) + dbtrf = filter(lambda trf: trf.tid == tarif_id, tariffs) + if len(dbtrf) > 0: + self.tariff = dbtrf[0] + else: + self.tariff = None + return self + + +class EventNAS(Serializer): + + # Type Of Action + toa = 0 + + # id of object + id = 0 + + # extended data + dt = object() + + def __init__(self, type_action=None, obj_id=None, ext_data=None): + self.toa = type_action + self.id = obj_id + self.dt = ext_data + + def _serializable_obj(self): + if self.dt: + return {'toa': self.toa, 'id': self.id, 'dt': self.dt} + else: + return {'toa': self.toa, 'id': self.id} + + def deserialize(self, dump): + print dump + inf = loads(dump) if type(dump) == str else dump + self.toa = int(inf['toa']) + self.id = int(inf['id']) + self.dt = inf.get('dt') + return self diff --git a/agent/netflow/__init__.py b/agent/netflow/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/agent/netflow/netflow_handler.py b/agent/netflow/netflow_handler.py new file mode 100755 index 0000000..a59e00c --- /dev/null +++ b/agent/netflow/netflow_handler.py @@ -0,0 +1,49 @@ +#!/bin/env python2 +import sys +import socket +import struct +from re import sub + + +def ip2int(strip): + return struct.unpack("!I", socket.inet_aton(strip))[0] + + +def convert(query): + dat = sub(r'\s+', ' ', query.strip('\n')).split(' ') + + if len(dat) == 1: + return + + src_ip = ip2int(dat[0]) + dst_ip = ip2int(dat[1]) + proto = int(dat[2]) + src_port = int(dat[3]) + dst_port = int(dat[4]) + octets = int(dat[5]) + packets = int(dat[6]) + + sql = ",(%s,%s,%d,%d,%d,%d,%d)" % ( + hex(src_ip), hex(dst_ip), proto, src_port, dst_port, octets, packets + ) + return sql + + +if __name__=='__main__': + f=sys.stdin + print("INSERT INTO flowstat(`src_ip`, `dst_ip`, `proto`, `src_port`, `dst_port`, `octets`, `packets`) VALUES") + + # always none + f.readline() + + # first line + rs = convert(f.readline()) + # without first comma + print(rs[1:]) + + while True: + rs = convert(f.readline()) + if not rs: + break + print(rs) + f.close() diff --git a/agent/netflow/netflow_handler.sh b/agent/netflow/netflow_handler.sh new file mode 100755 index 0000000..3c1d83f --- /dev/null +++ b/agent/netflow/netflow_handler.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +DUMP_DIR="/var/db/flows" + +DUMP_FILE="$DUMP_DIR/$1" +PATH=/usr/local/sbin:/usr/local/bin:/usr/bin +CUR_DIR=`dirname $0` + + +flow-print -f3 < ${DUMP_FILE} | ${CUR_DIR}/netflow_handler.py \ +| mysql -uroot -p jungagent --password=ps diff --git a/agent/netflow/start_netflow.sh b/agent/netflow/start_netflow.sh new file mode 100755 index 0000000..d7fafba --- /dev/null +++ b/agent/netflow/start_netflow.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/home/dn/bin + +flow-capture -R /home/dn/bin/netflow_handler.py -w /var/db/flows -n1 -N0 0.0.0.0/0.0.0.0/8888 + +softflowd -v 5 -i wlp3s0 -n 127.0.0.1:8888 diff --git a/agent/settings.py b/agent/settings.py new file mode 100644 index 0000000..f09110c --- /dev/null +++ b/agent/settings.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + + +# Agent ip and port +SELF_IP = '127.0.0.1' +SELF_PORT = 2134 + +# Main server ip and port +SERVER_IP = '127.0.0.1' +SERVER_PORT = 8000 #8443 + +# Certificates +CERTFILE = "/etc/ssl/server.crt" +KEYFILE = "/etc/ssl/server.key" + +# Использовать-ли при передаче инфы между NAS и основным сервером SSL +IS_USE_SSL = False diff --git a/agent/sslTransmitter.py b/agent/sslTransmitter.py new file mode 100644 index 0000000..f25c029 --- /dev/null +++ b/agent/sslTransmitter.py @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- +import ssl +import socket +from multiprocessing import Process, Manager#, Lock +import settings +from models import EventNAS, Abonent, Tariff + + +class NetExcept(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + + +class SSLTransmitterServer(object): + bindsocket = None + + def connect(self, ip, port): + bindsocket = socket.socket() + bindsocket.bind((ip, port)) + bindsocket.listen(5) + self.bindsocket = bindsocket + + def _on_data_recive(self, v, data): + print "do_something:", data + #with lock: + v.append(EventNAS().deserialize(data)) + return False + + def _deal_with_client(self, connstream, v): + data = connstream.read() + while data: + if not self._on_data_recive(v, data): + break + data = connstream.read() + + def process(self, v): + while True: + newsocket, fromaddr = self.bindsocket.accept() + connstream = ssl.wrap_socket(newsocket, + server_side=True, + certfile=settings.CERTFILE, + keyfile=settings.KEYFILE) + try: + self._deal_with_client(connstream, v) + finally: + connstream.shutdown(socket.SHUT_RDWR) + connstream.close() + + +class PlainTransmitterServer(SSLTransmitterServer): + + def process(self, v): + while True: + newsocket, fromaddr = self.bindsocket.accept() + dat = newsocket.recv(0xffff) + if not dat: + break + self._on_data_recive(v, dat) + + +# Декоратор переводит классы абонента базы к объекту агента если надо. +# abonapp.models.Abon -> agent.Abonent +def agent_abon_typer(fn): + def wrapped(self, abon): + if isinstance(abon, Abonent): + fn(self, abon) + else: + abn = Abonent( + abon.id, + abon.ip_address.int_ip() if abon.ip_address else 0, + abon.active_tariff() + ) + fn(self, abn) + return wrapped + + +# Декоратор переводит классы тарифа базы к объекту агента если надо. +# tariff_app.models.Tariff -> agent.Tariff +def agent_tarif_typer(fn): + def wrapped(self, tariff): + if isinstance(tariff, Tariff): + fn(self, tariff) + else: + trf = Tariff( + tariff.id, + tariff.speedIn, + tariff.speedOut + ) + fn(self, trf) + return wrapped + + +class SSLTransmitterClient(object): + s = None + + def __init__(self, ip=None, port=None): + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # Require a certificate from the server. We used a self-signed certificate + # so here ca_certs must be the server certificate itself. + self.s = ssl.wrap_socket(s, + ca_certs=settings.CERTFILE, + cert_reqs=ssl.CERT_REQUIRED) + self.s.connect(( + ip or settings.SELF_IP, + port or settings.SELF_PORT + )) + except socket.error: + raise NetExcept(u'Ошибка подключения к NAS агенту') + + def write(self, d): + self.s.write(d) + + @agent_abon_typer + def signal_abon_enable(self, abon): + self.write( + EventNAS(1, abon.uid).serialize() + ) + + @agent_abon_typer + def signal_abon_disable(self, abon): + self.write( + EventNAS(2, abon.uid).serialize() + ) + + @agent_abon_typer + def signal_abon_set_cap(self, abon): + self.write( + EventNAS(3, abon.uid).serialize() + ) + + @agent_abon_typer + def signal_abon_open_inet(self, abon): + self.write( + EventNAS(4, abon.uid).serialize() + ) + + @agent_abon_typer + def signal_abon_close_inet(self, abon): + self.write( + EventNAS(5, abon.uid).serialize() + ) + + @agent_abon_typer + def signal_agent_reboot(self, abon): + self.write( + EventNAS(6, abon.uid).serialize() + ) + + @agent_abon_typer + def signal_abon_refresh_info(self, abon): + self.write( + EventNAS(7, abon.uid, abon._serializable_obj()).serialize() + ) + + @agent_tarif_typer + def signal_tarif_refresh_info(self, tariff): + self.write( + EventNAS(8, tariff.tid, tariff._serializable_obj()).serialize() + ) + + def __del__(self): + self.s.close() + + +class PlainTransmitterClient(SSLTransmitterClient): + + def __init__(self, ip=None, port=None): + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(( + ip or settings.SELF_IP, + port or settings.SELF_PORT + )) + self.s = s + except socket.error: + raise NetExcept(u'Ошибка подключения к NAS агенту') + + def write(self, d): + self.s.send(d) + + +def get_TransmitterClientKlass(): + if settings.IS_USE_SSL: + return SSLTransmitterClient + else: + return PlainTransmitterClient + + +def get_TransmitterServerKlass(): + if settings.IS_USE_SSL: + return SSLTransmitterServer + else: + return PlainTransmitterServer + + +def proc_entrypoint(obj, v, lock, ip, port): + srv = get_TransmitterServerKlass()() + srv.connect(ip, port) + srv.process(v) + + +class TransmitServer(object): + + def __init__(self, ip, port): + mngr = Manager() + self.v = mngr.list() + #self.lock = Lock() + self.p = Process(target=proc_entrypoint, args=(self, self.v, None, ip, port))#self.lock)) + + def get_data(self): + if len(self.v) > 0: + return list(self.v) + else: + return [] + + def clear(self): + del self.v[:] + + def start(self): + self.p.start() + + def __del__(self): + self.p.terminate() diff --git a/agent/tariff.py b/agent/tariff.py new file mode 100644 index 0000000..380474e --- /dev/null +++ b/agent/tariff.py @@ -0,0 +1 @@ +# -*- coding:utf-8 -*- diff --git a/bugs.txt b/bugs.txt new file mode 100644 index 0000000..1f78199 --- /dev/null +++ b/bugs.txt @@ -0,0 +1,11 @@ +- (GUI) Иконки возле кнопок не настроены, натыканы случайно + +Остановился на управлении купленными услугами(тарифами): + + нужен порядок выполнения очереди тарифов (модель зделана) + + Управление приоритетом тарифов (порядком выполнения) + - удаление ещё не активных купленных тарифов + + отображение и выделение активного тарифа + +Нужно на каждое изменение данных об абоненте слать сигнал об этом на NAS + +Фаервол допилить, там с пайпами не понятно, не знаю принцип действия diff --git a/cron.py b/cron.py new file mode 100644 index 0000000..1645f74 --- /dev/null +++ b/cron.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import os +import django + + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") + django.setup() + from abonapp.models import Abon, AbonTariff + + users = Abon.objects.all() + + for usr in users: + + usr.activate_next_tariff() + + AbonTariff.objects.update_priorities(usr) diff --git a/devapp/__init__.py b/devapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/devapp/admin.py b/devapp/admin.py new file mode 100644 index 0000000..19dff8d --- /dev/null +++ b/devapp/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +import models + +admin.site.register(models.Device) +admin.site.register(models.Port) +admin.site.register(models.PortStates) diff --git a/devapp/apps.py b/devapp/apps.py new file mode 100644 index 0000000..1ccacdf --- /dev/null +++ b/devapp/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class DevappConfig(AppConfig): + name = 'devapp' diff --git a/devapp/base_intr.py b/devapp/base_intr.py new file mode 100644 index 0000000..c0e9bac --- /dev/null +++ b/devapp/base_intr.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +from abc import ABCMeta, abstractmethod +from netsnmp import Session, VarList, Varbind + + +class DevBase(object): + __metaclass__ = ABCMeta + + @staticmethod + def description(): + """Возвращает текстовое описание""" + + @abstractmethod + def reboot(self): + """Перезагружает устройство""" + + @abstractmethod + def get_statistics(self): + """Получаем статистику""" + + @abstractmethod + def get_vlan(self): + """Получаем инфу о VLAN""" + + @abstractmethod + def get_ports(self): + """Получаем инфу о портах""" + + @abstractmethod + def toggle_port(self, port_num): + pass + + +class Port(object): + + def __init__(self, num, name, status, mac, speed): + self.num = int(num) + self.nm = name + self.st = status + self._mac = mac + self.sp = speed + + def mac(self): + m = self._mac + return "%x:%x:%x:%x:%x:%x" % (ord(m[0]), ord(m[1]), ord(m[2]), ord(m[3]), ord(m[4]), ord(m[5])) + + +class SNMPBaseWorker(object): + ses = None + + oids = { + 'reboot': '.1.3.6.1.4.1.2021.8.1.101.1', + 'get_ports': { + 'names': 'IF-MIB::ifDescr', + 'stats': 'IF-MIB::ifAdminStatus', + 'macs': 'IF-MIB::ifPhysAddress', + 'speeds': 'IF-MIB::ifHighSpeed' + }, + 'name': 'SNMPv2-SMI::mib-2.47.1.1.1.1.7.1', + 'toggle_port': '.1.3.6.1.2.1.2.2.1.7' + } + + def __init__(self, ip, passw='public', ver=2): + self.ses = Session(DestHost=ip, Version=ver, Community=passw) + + def _get_vars(self, oid): + vs = VarList(Varbind(oid)) + return self.ses.walk(vs) + + def _get_var(self, oid): + var = VarList(Varbind(oid)) + return self.ses.get(var) + + # Enable/Disable port + def toggle_port(self, port_num, status=True): + vs = VarList(Varbind( + tag="%s.%d" % (self.oids['toggle_port'], port_num), + val=1 if status else 2, + type='INTEGER' + )) + return self.ses.set(vs) + + def get_ports(self): + nams = self._get_vars(self.oids['get_ports']['names']) + stats = self._get_vars(self.oids['get_ports']['stats']) + macs = self._get_vars(self.oids['get_ports']['macs']) + speeds = self._get_vars(self.oids['get_ports']['speeds']) + res = [] + ln = len(nams) + for n in range(0, ln): + res.append(Port(n, nams[n], bool(stats[n]), macs[n], int(speeds[n]))) + return res + + def get_name(self): + return self._get_var(self.oids['name']) + + +# Example usage +if __name__ == '__main__': + wrk = SNMPBaseWorker('10.115.1.104', 'private', 2) + print(wrk.get_name()) + for pr in wrk.get_ports(): + assert isinstance(pr, Port) + print(pr.nm, pr.st, pr.mac(), pr.sp) + + # Enable 2 port + print wrk.toggle_port(2, True) + # Disable 2 port + print wrk.toggle_port(2, False) diff --git a/devapp/dev_types.py b/devapp/dev_types.py new file mode 100644 index 0000000..f80d7b2 --- /dev/null +++ b/devapp/dev_types.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from base_intr import DevBase, SNMPBaseWorker + + +class DLinkDevice(DevBase): + + @staticmethod + def description(): + return u"Свич D'Link" + + def reboot(self): + pass + + +DEVICE_TYPES = ( + ('Dl', DLinkDevice), +) + + +class SNMPDlinkWorker(SNMPBaseWorker): + oids = { + 'reboot': '.1.3.6.1.4.1.2021.8.1.101.1', + 'get_ports': { + 'names': 'IF-MIB::ifDescr', + 'stats': 'IF-MIB::ifAdminStatus', + 'macs': 'IF-MIB::ifPhysAddress', + 'speeds': 'IF-MIB::ifHighSpeed' + }, + 'name': 'SNMPv2-SMI::mib-2.47.1.1.1.1.7.1' + } diff --git a/devapp/forms.py b/devapp/forms.py new file mode 100644 index 0000000..36e857b --- /dev/null +++ b/devapp/forms.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from django import forms +import models +from mydefs import ip_addr_regex + + +class DeviceForm(forms.ModelForm): + class Meta: + model = models.Device + fields = '__all__' + widgets = { + 'ip_address': forms.TextInput(attrs={ + 'pattern': ip_addr_regex, + 'placeholder': '192.168.0.100', + 'required': True, + 'class': 'form-control', + 'id': 'ip_address' + }), + 'comment': forms.Textarea(attrs={ + 'required': True, + 'class': 'form-control', + 'id': 'comment' + }), + 'devtype': forms.Select(attrs={ + 'class': 'form-control', + 'id': 'devtype' + }), + 'man_passw': forms.PasswordInput(attrs={ + 'class': 'form-control', + 'id': 'man_passw' + }) + } diff --git a/devapp/migrations/0001_initial.py b/devapp/migrations/0001_initial.py new file mode 100644 index 0000000..143d4b4 --- /dev/null +++ b/devapp/migrations/0001_initial.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-06-28 23:51 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import mydefs + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Device', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ip_address', mydefs.MyGenericIPAddressField(max_length=8, protocol=b'IPv4')), + ('comment', models.CharField(max_length=256)), + ('devtype', models.CharField(choices=[(b'Dl', "\u0421\u0432\u0438\u0447 D'Link")], default=b'Dl', max_length=2)), + ('man_passw', models.CharField(blank=True, max_length=16, null=True)), + ], + ), + migrations.CreateModel( + name='Port', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('num', models.PositiveSmallIntegerField(default=0)), + ('speed', models.CharField(choices=[(b'h', b'100Mbps'), (b'k', b'1Gbps'), (b'd', b'10Gbps')], default=b'h', max_length=1)), + ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='devapp.Device')), + ], + ), + migrations.CreateModel( + name='PortStates', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('state_json_info', models.TextField()), + ('port', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='devapp.Port')), + ], + ), + migrations.AlterUniqueTogether( + name='port', + unique_together=set([('device', 'num')]), + ), + ] diff --git a/devapp/migrations/__init__.py b/devapp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/devapp/models.py b/devapp/models.py new file mode 100644 index 0000000..033a893 --- /dev/null +++ b/devapp/models.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +from django.db import models +from base_intr import DevBase +from mydefs import MyGenericIPAddressField, MyChoicesAdapter +from dev_types import DEVICE_TYPES + + +class _DeviceChoicesAdapter(MyChoicesAdapter): + + def __init__(self): + super(_DeviceChoicesAdapter, self).__init__(DEVICE_TYPES) + + +class Device(models.Model): + ip_address = MyGenericIPAddressField() + comment = models.CharField(max_length=256) + devtype = models.CharField(max_length=2, default=DEVICE_TYPES[0][0], choices=_DeviceChoicesAdapter()) + man_passw = models.CharField(max_length=16, null=True, blank=True) + #map_dot = models.ForeignKey() + + def get_abons(self): + pass + + def get_stat(self): + pass + + def get_manager_klass(self): + klasses = filter(lambda kl: kl[0] == self.devtype, DEVICE_TYPES) + if len(klasses) > 0: + res = klasses[0][1] + if issubclass(res, DevBase): + return res + return + + +class Port(models.Model): + PORT_SPEEDS = ( + ('h', '100Mbps'), + ('k', '1Gbps'), + ('d', '10Gbps') + ) + device = models.ForeignKey(Device) + num = models.PositiveSmallIntegerField(default=0) + speed = models.CharField(max_length=1, default=PORT_SPEEDS[0][0], choices=PORT_SPEEDS) + + class Meta: + unique_together = (('device', 'num')) + + +class PortStates(models.Model): + port = models.OneToOneField(Port) + state_json_info = models.TextField() diff --git a/devapp/tests.py b/devapp/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/devapp/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/devapp/urls.py b/devapp/urls.py new file mode 100644 index 0000000..be7fd11 --- /dev/null +++ b/devapp/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls import url +import views + +urlpatterns = [ + url(r'^$', views.devices, name='devs_link'), + url(r'^add$', views.dev, name='devs_add_link'), + url(r'^(?P\d+)$', views.devview, name='devs_view_link'), + url(r'^(?P\d+)/del$', views.devdel, name='devs_del_link'), + url(r'^(?P\d+)/edit$', views.dev, name='devs_edit_link'), +] diff --git a/devapp/views.py b/devapp/views.py new file mode 100644 index 0000000..64d3027 --- /dev/null +++ b/devapp/views.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +from django.contrib.auth.decorators import login_required +from django.shortcuts import render, redirect, get_object_or_404 +from models import Device +from mydefs import pag_mn, res_success, res_error +from forms import DeviceForm + + +@login_required +def devices(request): + devs = Device.objects.all() + devs = pag_mn(request, devs) + + return render(request, 'devapp/devices.html', { + 'devices': devs + }) + + +@login_required +def devdel(request, did): + try: + get_object_or_404(Device, id=did).delete() + return res_success(request, 'devs_link') + except: + return res_error(request, u'Неизвестная ошибка при удалении :(') + + +@login_required +def dev(request, devid=0): + warntext = '' + devinst = get_object_or_404(Device, id=devid) if devid != 0 else None + + if request.method == 'POST': + frm = DeviceForm(request.POST, instance=devinst) + if frm.is_valid(): + frm.save() + return redirect('devs_link') + else: + warntext = u'Ошибка в данных, проверте их ещё раз' + else: + frm = DeviceForm(instance=devinst) + + return render(request, 'devapp/dev.html', { + 'warntext': warntext, + 'form': frm, + 'devid': devid + }) + + +@login_required +def devview(request, did): + warntext = '' + + dev = get_object_or_404(Device, id=did) + + return render(request, 'devapp/ports.html', { + 'warntext': warntext, + 'dev': dev + }) diff --git a/djing/__init__.py b/djing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/djing/settings.py b/djing/settings.py new file mode 100644 index 0000000..c03ed57 --- /dev/null +++ b/djing/settings.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -* +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +from django.core.urlresolvers import reverse_lazy + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '$xvppe_5&iu4fgnj2h@eie6+w*n&m=60e7k_6ha5r4rgnfndz1' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'accounts_app', + 'photo_app', + 'privatemessage', + 'abonapp', + 'tariff_app', + 'ip_pool', + 'searchapp', + 'devapp', + 'mapapp', + 'statistics' +] + +MIDDLEWARE_CLASSES = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'djing.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'mydefs.context_processor_client_ipaddress' + ], + }, + }, +] + +WSGI_APPLICATION = 'djing.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.9/ref/settings/#databases + +DATABASES = { + 'default': { + #'ENGINE': 'django.db.backends.sqlite3', + #'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'jungagent', + 'USER': 'root', + 'PASSWORD': 'ps', + 'HOST': 'localhost' + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.9/topics/i18n/ + +LANGUAGE_CODE = 'ru-RU' + +TIME_ZONE = 'Europe/Simferopol' + +USE_I18N = True + +USE_L10N = False + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.9/howto/static-files/ + +STATIC_URL = '/static/' +if DEBUG: + STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),) + + +# Пример вывода: 16 сентября 2012 +DATE_FORMAT = 'd E Y' + + +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +MEDIA_URL = '/media/' + +DEFAULT_PICTURE = '/static/images/default-avatar.png' +AUTH_USER_MODEL = 'accounts_app.UserProfile' + +LOGIN_URL = reverse_lazy('login_link') +LOGIN_REDIRECT_URL = reverse_lazy('profile') +LOGOUT_URL = reverse_lazy('logout_link') diff --git a/djing/urls.py b/djing/urls.py new file mode 100644 index 0000000..a47314a --- /dev/null +++ b/djing/urls.py @@ -0,0 +1,26 @@ +from django.conf.urls import url, include +from django.contrib import admin +import settings +from views import home + +urlpatterns = [ + url(r'^$', home), + url(r'^accounts/', include('accounts_app.urls')), + url(r'^im/', include('privatemessage.urls')), + url(r'^abons/', include('abonapp.urls')), + url(r'^tarifs/', include('tariff_app.urls')), + url(r'^ip_pool/', include('ip_pool.urls')), + url(r'^search/', include('searchapp.urls')), + url(r'^dev/', include('devapp.urls')), + url(r'^map/', include('mapapp.urls')), + url(r'^statistic/', include('statistics.urls')), + url(r'^admin/', admin.site.urls), +] + + +if settings.DEBUG: + from django.conf.urls.static import static + from django.contrib.staticfiles.urls import staticfiles_urlpatterns + + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns += staticfiles_urlpatterns() diff --git a/djing/views.py b/djing/views.py new file mode 100644 index 0000000..1dfb701 --- /dev/null +++ b/djing/views.py @@ -0,0 +1,9 @@ +from django.shortcuts import redirect + + +def home(request): + return redirect('profile') + + +def finance_report(request): + pass diff --git a/djing/wsgi.py b/djing/wsgi.py new file mode 100644 index 0000000..fdbbb71 --- /dev/null +++ b/djing/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for djing project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") + +application = get_wsgi_application() diff --git a/install.sql b/install.sql new file mode 100644 index 0000000..5bc979e --- /dev/null +++ b/install.sql @@ -0,0 +1,17 @@ +create table flowstat ( + `id` int(10) AUTO_INCREMENT NOT NULL, + `src_ip` CHAR(8) NOT NULL, + `dst_ip` CHAR(8) NOT NULL, + `proto` smallint(2) unsigned NOT NULL DEFAULT 0, + `src_port` smallint(5) unsigned NOT NULL DEFAULT 0, + `dst_port` smallint(5) unsigned NOT NULL DEFAULT 0, + `octets` INT unsigned NOT NULL DEFAULT 0, + `packets` INT unsigned NOT NULL DEFAULT 0, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + + + +INSERT INTO flowstat(`src_ip`, `dst_ip`, `proto`, `src_port`, `dst_port`, `octets`, `packets`) VALUES +('c0a80201', 'c0a805ba', 6, 49150, 443, 5281, 13), +('c0a80201', 'c0a805ba', 6, 49150, 443, 5281, 13) diff --git a/ip_pool/__init__.py b/ip_pool/__init__.py new file mode 100644 index 0000000..8c4aa32 --- /dev/null +++ b/ip_pool/__init__.py @@ -0,0 +1 @@ +default_app_config = 'ip_pool.apps.IpPoolConfig' diff --git a/ip_pool/admin.py b/ip_pool/admin.py new file mode 100644 index 0000000..65ef72e --- /dev/null +++ b/ip_pool/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +import models + +admin.site.register(models.IpPoolItem) diff --git a/ip_pool/apps.py b/ip_pool/apps.py new file mode 100644 index 0000000..b5b1b03 --- /dev/null +++ b/ip_pool/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class IpPoolConfig(AppConfig): + name = 'ip_pool' + verbose_name = u"Ip pool" diff --git a/ip_pool/forms.py b/ip_pool/forms.py new file mode 100644 index 0000000..5395225 --- /dev/null +++ b/ip_pool/forms.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from django import forms +from mydefs import ip_addr_regex + + +class PoolForm(forms.Form): + start_ip = forms.GenericIPAddressField(protocol='IPv4', widget=forms.TextInput(attrs={ + 'pattern': ip_addr_regex, + 'placeholder': u'127.0.0.1', + 'id': 'start_ip', + 'class': 'form-control', + 'required': '' + }), required=True) + + end_ip = forms.GenericIPAddressField(protocol='IPv4', widget=forms.TextInput(attrs={ + 'pattern': ip_addr_regex, + 'placeholder': u'127.0.0.1', + 'id': 'end_ip', + 'class': 'form-control', + 'required': '' + }), required=True) diff --git a/ip_pool/migrations/0001_initial.py b/ip_pool/migrations/0001_initial.py new file mode 100644 index 0000000..05336f6 --- /dev/null +++ b/ip_pool/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-06-28 23:51 +from __future__ import unicode_literals + +from django.db import migrations, models +import mydefs + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='IpPoolItem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ip', mydefs.MyGenericIPAddressField(max_length=8, protocol=b'IPv4')), + ], + ), + ] diff --git a/ip_pool/migrations/__init__.py b/ip_pool/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ip_pool/models.py b/ip_pool/models.py new file mode 100644 index 0000000..b1342e4 --- /dev/null +++ b/ip_pool/models.py @@ -0,0 +1,60 @@ +from django.db import models, connection +from mydefs import ip2int, MyGenericIPAddressField + + +class IpPoolItemManager(models.Manager): + + def get_pools(self): + ips = self.raw(r'SELECT id, ip FROM ip_pool_ippoolitem ORDER BY id') + ips_len = len(list(ips)) + if ips_len < 1: + return + last_dg = ip2int(ips[0].ip) + start_pool = last_dg + res = list() + cnt = 0 + for ip in ips: + ipnt = ip2int(ip.ip) + if ipnt > last_dg + 1 or ipnt < last_dg - 1: + res.append((start_pool, last_dg, cnt)) + start_pool = ipnt + cnt = 0 + last_dg = ipnt + cnt += 1 + res.append((start_pool, last_dg, cnt)) + return res + + def add_pool(self, start_ip, end_ip): + start_ip = ip2int(start_ip) + end_ip = ip2int(end_ip) + + if (end_ip - start_ip) > 5000: + raise Exception(u'Not add over 5000 ip\'s') + + sql_strs = map(lambda tip: r"(%d)" % tip, range(start_ip, end_ip+1)) + sql = r'INSERT INTO ip_pool_ippoolitem (ip) VALUES %s' % r",".join(sql_strs) + print sql + + cursor = connection.cursor() + cursor.execute(sql) + + def get_free_ip(self): + sql = r'SELECT ip_pool_ippoolitem.id as id, ip_pool_ippoolitem.ip as ip FROM ip_pool_ippoolitem ' \ + r'LEFT JOIN abonent ON abonent.ip_address_id = ip_pool_ippoolitem.id WHERE ' \ + r'abonent.ip_address_id IS NULL LIMIT 1' + + rs = self.raw(sql) + rs_len = len(list(rs)) + return None if rs_len is 0 else rs[0] + + +class IpPoolItem(models.Model): + ip = MyGenericIPAddressField() + + objects = IpPoolItemManager() + + def int_ip(self): + return ip2int(self.ip) + + def __unicode__(self): + return self.ip diff --git a/ip_pool/tests.py b/ip_pool/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/ip_pool/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/ip_pool/urls.py b/ip_pool/urls.py new file mode 100644 index 0000000..5bfb07c --- /dev/null +++ b/ip_pool/urls.py @@ -0,0 +1,13 @@ +# -*- coding:utf-8 -*- +from django.conf.urls import url +import views + +urlpatterns = [ + + url(r'^$', views.home, name='pool_home_link'), + url(r'^range$', views.ips, name='pool_ips_link'), + url(r'^del$', views.del_pool, name='pool_ips_del_link'), + url(r'^add$', views.add_pool, name='pool_add_link'), + + url(r'^delip$', views.delip, name='pool_del_ip_link') +] \ No newline at end of file diff --git a/ip_pool/views.py b/ip_pool/views.py new file mode 100644 index 0000000..938fa19 --- /dev/null +++ b/ip_pool/views.py @@ -0,0 +1,76 @@ +from django.contrib.auth.decorators import login_required +from django.shortcuts import render, redirect, get_object_or_404 +from forms import PoolForm +from models import IpPoolItem +import mydefs + + +@login_required +def home(request): + pools = IpPoolItem.objects.get_pools() + print pools + + if pools: + pools = map(lambda ip: (mydefs.int2ip(ip[0]), mydefs.int2ip(ip[1]), ip[2]), pools) + pools = mydefs.pag_mn(request, pools) + + return render(request, 'ip_pool/index.html', { + 'pools': pools + }) + + +@login_required +def ips(request): + ip_start = request.GET.get('ips') + ip_end = request.GET.get('ipe') + + pool_ips = IpPoolItem.objects.filter(ip__gte=ip_start) + pool_ips = pool_ips.filter(ip__lte=ip_end) + + pool_ips = mydefs.pag_mn(request, pool_ips) + + return render(request, 'ip_pool/ips.html', { + 'pool_ips': pool_ips, + 'ips': ip_start, + 'ipe': ip_end + }) + + +@login_required +def del_pool(request): + ip_start = request.GET.get('ips') + ip_end = request.GET.get('ipe') + + pool_ips = IpPoolItem.objects.filter(ip__gte=ip_start) + pool_ips = pool_ips.filter(ip__lte=ip_end) + pool_ips = pool_ips.filter() + + pool_ips.delete() + + return mydefs.res_success(request, 'pool_home_link') + + +@login_required +def add_pool(request): + if request.method == 'POST': + frm = PoolForm(request.POST) + if frm.is_valid(): + cd = frm.cleaned_data + IpPoolItem.objects.add_pool(cd['start_ip'], cd['end_ip']) + return redirect('pool_home_link') + else: + warntext = u'Form is not valid' + else: + frm = PoolForm() + warntext = '' + return render(request, 'ip_pool/add_pool.html', { + 'form': frm, + 'warntext': warntext + }) + + +@login_required +def delip(request): + ipid = request.GET.get('id') + get_object_or_404(IpPoolItem, id=ipid).delete() + return mydefs.res_success(request, 'pool_home_link') diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..b3fc4dd --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/mapapp/__init__.py b/mapapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mapapp/admin.py b/mapapp/admin.py new file mode 100644 index 0000000..06bdd54 --- /dev/null +++ b/mapapp/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +import models + +admin.site.register(models.Dot) diff --git a/mapapp/apps.py b/mapapp/apps.py new file mode 100644 index 0000000..6bdb84f --- /dev/null +++ b/mapapp/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class MapappConfig(AppConfig): + name = 'mapapp' diff --git a/mapapp/migrations/0001_initial.py b/mapapp/migrations/0001_initial.py new file mode 100644 index 0000000..b42a6c3 --- /dev/null +++ b/mapapp/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-06-28 23:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Dot', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=64)), + ('posX', models.FloatField()), + ('posY', models.FloatField()), + ], + ), + ] diff --git a/mapapp/migrations/__init__.py b/mapapp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mapapp/models.py b/mapapp/models.py new file mode 100644 index 0000000..925f2f6 --- /dev/null +++ b/mapapp/models.py @@ -0,0 +1,10 @@ +from django.db import models + + +class Dot(models.Model): + title = models.CharField(max_length=64) + posX = models.FloatField() + posY = models.FloatField() + + def __unicode__(self): + return self.title diff --git a/mapapp/tests.py b/mapapp/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/mapapp/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/mapapp/urls.py b/mapapp/urls.py new file mode 100644 index 0000000..2309fbc --- /dev/null +++ b/mapapp/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url +import views + +urlpatterns = [ + url(r'^$', views.home, name='maps_home_link'), + url(r'^get_dots$', views.get_dots, name='maps_get_dots') +] diff --git a/mapapp/views.py b/mapapp/views.py new file mode 100644 index 0000000..d050cbd --- /dev/null +++ b/mapapp/views.py @@ -0,0 +1,18 @@ +from django.contrib.auth.decorators import login_required +from django.http import HttpResponse +from django.shortcuts import render +from models import Dot +from json import dumps + + +@login_required +def home(request): + return render(request, 'maps/index.html') + + + +def get_dots(r): + dots = Dot.objects.all() + return HttpResponse(dumps({ + 'dots': map(lambda d: (d.id, d.posX, d.posY, d.title), dots) + })) diff --git a/mydefs.py b/mydefs.py new file mode 100644 index 0000000..9660064 --- /dev/null +++ b/mydefs.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +from django.http import HttpResponse, Http404 +from json import dumps +from django.shortcuts import redirect +from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage +import socket +import struct +from django.db import models +from collections import Iterator + + +ip_addr_regex = r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' + + +def ip2int(addr): + return struct.unpack("!I", socket.inet_aton(addr))[0] + + +def int2ip(addr): + return socket.inet_ntoa(struct.pack("!I", addr)) + + +def safe_float(fl): + return 0.0 if fl is None or fl == '' else float(fl) + + +def safe_int(i): + return 0 if i is None or i == '' else int(i) + + +def res_success(request, redirect_to='/'): + if request.is_ajax(): + return HttpResponse(dumps({'errnum': 0})) + else: + return redirect(redirect_to) + + +def res_error(request, text): + if request.is_ajax(): + return HttpResponse(dumps({'errnum':1, 'errtext':text})) + else: + raise Http404(text) + + +# Pagination +def pag_mn(request, objs, count_per_page=10): + page = request.GET.get('p') + pgn = Paginator(objs, count_per_page) + try: + objs = pgn.page(page) + except PageNotAnInteger: + objs = pgn.page(1) + except EmptyPage: + objs = pgn.page(pgn.num_pages) + return objs + + +def context_processor_client_ipaddress(request): + ip = request.META.get('REMOTE_ADDR', '') or request.META.get('HTTP_X_FORWARDED_FOR', '') + return { + 'client_ipaddress': ip + } + #logmodels.debug('%s %s %s'%(request.user, request.path, ip)) + + +class MyGenericIPAddressField(models.GenericIPAddressField): + + description = "Int32 notation ip address" + + def __init__(self, protocol='IPv4', *args, **kwargs): + super(MyGenericIPAddressField, self).__init__(protocol=protocol, *args, **kwargs) + self.max_length = 8 + + def get_prep_value(self, value): + # strIp to Int + value = super(models.GenericIPAddressField, self).get_prep_value(value) + return ip2int(value) + + def to_python(self, addr): + return addr + + def get_internal_type(self): + return 'PositiveIntegerField' + + @staticmethod + def from_db_value(value, expression, connection, context): + return int2ip(value) + + +# Предназначен для Django CHOICES чтоб можно было передавать классы вместо просто описания поля, +# классы передавать для того чтоб по значению кода из базы понять какой класс нужно взять для нужной функциональности. +# Например по коду в базе вам нужно определять как считать тариф абонента, что реализовано в возвращаемом классе. +class MyChoicesAdapter(Iterator): + chs = tuple() + current_index = 0 + _max_index = 0 + + # На вход принимает кортеж кортежей, вложенный из 2х элементов: кода и класса, как: TARIFF_CHOICES + def __init__(self, choices): + self._max_index = len(choices) + self.chs = choices + + def next(self): + if self.current_index >= self._max_index: + raise StopIteration + else: + e = self.chs + ci = self.current_index + res = e[ci][0], e[ci][1].description() + self.current_index += 1 + return res diff --git a/photo_app/__init__.py b/photo_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/photo_app/admin.py b/photo_app/admin.py new file mode 100644 index 0000000..9d30a59 --- /dev/null +++ b/photo_app/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +import models + +admin.site.register(models.Photo) \ No newline at end of file diff --git a/photo_app/migrations/0001_initial.py b/photo_app/migrations/0001_initial.py new file mode 100644 index 0000000..00258c1 --- /dev/null +++ b/photo_app/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-06-28 23:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Photo', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image', models.ImageField(height_field=b'heigt', upload_to=b'', width_field=b'wdth')), + ('wdth', models.PositiveSmallIntegerField(blank=True, default=b'759', editable=False, null=True)), + ('heigt', models.PositiveSmallIntegerField(blank=True, editable=False, null=True)), + ], + ), + ] diff --git a/photo_app/migrations/__init__.py b/photo_app/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/photo_app/models.py b/photo_app/models.py new file mode 100644 index 0000000..9251c23 --- /dev/null +++ b/photo_app/models.py @@ -0,0 +1,69 @@ +# -*- coding:utf-8 -*- +from django.db import models +from djing.settings import BASE_DIR +import os +from PIL import Image +import time +import hashlib + + +class Photo(models.Model): + image = models.ImageField(width_field='wdth', height_field='heigt') + wdth = models.PositiveSmallIntegerField(null=True, blank=True, editable=False, default="759") + heigt = models.PositiveSmallIntegerField(null=True, blank=True, editable=False) + + def __unicode__(self): + return "{0}".format(self.image) + + def big(self): + return self.image.url + + def min(self): + pth = self.image.url.split('/')[-1:][0] + return "/media/min/%s" % pth + + def save(self, *args, **kwargs): + if not self.image: + return + + super(Photo, self).save() + + im = Image.open(self.image.path) + im.thumbnail((759, 759), Image.ANTIALIAS) + + hs = hashlib.md5(str(time.time())).hexdigest() + ext = self.image.path.split('.')[1:][0] + fname = "%s/media/%s.%s" % (BASE_DIR, hs, ext) + + im.save(fname) + os.remove(self.image.path) + self.image = "%s.%s" % (hs, ext) + super(Photo, self).save() + + + #class Meta: + # unique_together = (('image',),) + + +def resize_image(sender, instance, **kwargs): + if not kwargs['created']: + im = Image.open(instance.image.path) + im.thumbnail((200, 121), Image.ANTIALIAS) + pth = instance.image.path.split('/')[-1:][0] + fullpath = "%s/media/min/%s" % (BASE_DIR, pth) + im.save(fullpath) + + + +def post_delete_photo(sender, instance, **kwargs): + min_fname = instance.image.path.split('/')[-1:][0] + try: + os.remove('%s/media/min/%s' % (BASE_DIR, min_fname)) + os.remove(instance.image.path) + except OSError: + pass + + + +models.signals.post_save.connect(resize_image, sender=Photo) +models.signals.post_delete.connect(post_delete_photo, sender=Photo) diff --git a/photo_app/tests.py b/photo_app/tests.py new file mode 100644 index 0000000..501deb7 --- /dev/null +++ b/photo_app/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/photo_app/urls.py b/photo_app/urls.py new file mode 100644 index 0000000..3fa1f7b --- /dev/null +++ b/photo_app/urls.py @@ -0,0 +1,12 @@ +# -*- coding:utf-8 -*- +from django.conf.urls import patterns, url + +urlpatterns = [ + #url(r'^$', 'photo_albums', name='media_home_link'), + #url(r'^(?P\d+)$', 'photo_albums', name='media_home_link_by_id'), + #url(r'^album(?P\d+)$', 'photos', name='media_photos_link'), + + + #url(r'^newalbum/$', 'add_album', name='add_media_album_link'), + #url(r'^newphoto/$', 'add_photo_to_album', name='add_media_photo_link') +] diff --git a/photo_app/views.py b/photo_app/views.py new file mode 100644 index 0000000..380474e --- /dev/null +++ b/photo_app/views.py @@ -0,0 +1 @@ +# -*- coding:utf-8 -*- diff --git a/privatemessage/__init__.py b/privatemessage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/privatemessage/admin.py b/privatemessage/admin.py new file mode 100644 index 0000000..5e0689c --- /dev/null +++ b/privatemessage/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from models import PrivateMessages, Dialog + +admin.site.register(PrivateMessages) +admin.site.register(Dialog) diff --git a/privatemessage/context_processors.py b/privatemessage/context_processors.py new file mode 100644 index 0000000..25b8c81 --- /dev/null +++ b/privatemessage/context_processors.py @@ -0,0 +1,7 @@ +from models import PrivateMessages + + +def avail_messages(request): + return { + 'avail_messages_num': PrivateMessages.objects.get_my_messages(request) + } diff --git a/privatemessage/migrations/0001_initial.py b/privatemessage/migrations/0001_initial.py new file mode 100644 index 0000000..4efa2d2 --- /dev/null +++ b/privatemessage/migrations/0001_initial.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-06-28 23:51 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Dialog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=127)), + ('date_create', models.DateTimeField(auto_now_add=True)), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ('recepient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='PrivateMessages', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_send', models.DateTimeField(auto_now_add=True)), + ('text', models.TextField()), + ('is_viewed', models.BooleanField(default=False)), + ('dialog', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='privatemessage.Dialog')), + ], + ), + ] diff --git a/privatemessage/migrations/__init__.py b/privatemessage/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/privatemessage/models.py b/privatemessage/models.py new file mode 100644 index 0000000..d90b378 --- /dev/null +++ b/privatemessage/models.py @@ -0,0 +1,34 @@ +from django.db import models +from djing import settings + + +class MessagesManager(models.Manager): + + def get_my_messages(self, request): + if request.user.is_authenticated(): + num = self.filter(recepient=request.user, is_viewed=False).count() + else: + num = 0 + return int(num) + + +class Dialog(models.Model): + title = models.CharField(max_length=127) + owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='+') + recepient = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='+') + date_create = models.DateTimeField(auto_now_add=True) + + def __unicode__(self): + return self.title + + +class PrivateMessages(models.Model): + dialog = models.ForeignKey(Dialog) + date_send = models.DateTimeField(auto_now_add=True) + text = models.TextField() + is_viewed = models.BooleanField(default=False) + + objects = MessagesManager() + + def __unicode__(self): + return self.text diff --git a/privatemessage/tests.py b/privatemessage/tests.py new file mode 100644 index 0000000..04c4a6f --- /dev/null +++ b/privatemessage/tests.py @@ -0,0 +1,22 @@ +from django.test import TestCase +import models +from django.contrib.auth.models import User + + +class PaysTest(TestCase): + + def setUp(self): + self.msg = models.PrivateMessages.objects.create( + sender=User.objects.all()[0], + recepient=User.objects.all()[0], + text='test init text' + ) + + def tearDown(self): + models.PrivateMessages.objects.all().delete() + + def check_ret_msgs(self): + """check return messages""" + request = self.factory.get('/message/') + self.assertIsInstance(models.PrivateMessages.objects.get_my_messages(request), int, 'checking ret type') + self.assertGreater(models.PrivateMessages.objects.get_my_messages(request), 0, 'checking msg count') diff --git a/privatemessage/urls.py b/privatemessage/urls.py new file mode 100644 index 0000000..fbab5aa --- /dev/null +++ b/privatemessage/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import url +import views + +urlpatterns = [ + url(r'^$', views.home, name='privmsg_home'), + url(r'^delitem_(?P\d+)$', views.delitem, name='privmsg_delitem'), + url(r'^write', views.send_message, name='privmsg_send_message') +] diff --git a/privatemessage/views.py b/privatemessage/views.py new file mode 100644 index 0000000..67eee3c --- /dev/null +++ b/privatemessage/views.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +from django.contrib.auth.decorators import login_required +from django.shortcuts import render, redirect +from django.template.loader import render_to_string +from models import PrivateMessages +from django.http import HttpResponse +from json import dumps +from django.template.context_processors import csrf +import mydefs +from accounts_app.models import UserProfile + + +@login_required +def home(request): + msgs = PrivateMessages.objects.all() + return render(request, 'private_messages/index.html', { + 'msgs': msgs + }) + + +@login_required +def delitem(request, id=0): + r = {'errnum': 0,'errtext': u''} + try: + PrivateMessages.objects.get(id=id).delete() + except PrivateMessages.DoesNotExist: + r = { + 'errnum': 1, + 'errtext': u'Error while deleting, item does not exist' + } + return HttpResponse(dumps(r)) + + +@login_required +def send_message(request): + if request.method == 'GET': + return HttpResponse(render_to_string('private_messages/send_form.html',{ + 'csrf_token': csrf(request)['csrf_token'], + 'a': request.GET.get('a') + })) + elif request.method == 'POST': + try: + a = request.GET.get('a') + a = 0 if a is None or a == '' else int(a) + msg = PrivateMessages() + msg.sender = request.user + msg.recepient = UserProfile.objects.get(id=a) + msg.text = request.POST.get('msg_text') + msg.save() + return redirect('privmsg_home') + except UserProfile.DoesNotExist: + return mydefs.res_error(request, u'Адресат не найден') + else: + return mydefs.res_error(request, u'Ошибка типа запроса') diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d1c9c2b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Django==1.9 +requests +Pillow +MySQL-python diff --git a/searchapp/__init__.py b/searchapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/searchapp/admin.py b/searchapp/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/searchapp/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/searchapp/apps.py b/searchapp/apps.py new file mode 100644 index 0000000..6cc6464 --- /dev/null +++ b/searchapp/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class SearchappConfig(AppConfig): + name = 'searchapp' diff --git a/searchapp/migrations/__init__.py b/searchapp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/searchapp/models.py b/searchapp/models.py new file mode 100644 index 0000000..bd4b2ab --- /dev/null +++ b/searchapp/models.py @@ -0,0 +1,5 @@ +from __future__ import unicode_literals + +from django.db import models + +# Create your models here. diff --git a/searchapp/tests.py b/searchapp/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/searchapp/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/searchapp/urls.py b/searchapp/urls.py new file mode 100644 index 0000000..0838048 --- /dev/null +++ b/searchapp/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url +import views + +urlpatterns = [ + url(r'^$', views.home, name='search_home_link'), + +] diff --git a/searchapp/views.py b/searchapp/views.py new file mode 100644 index 0000000..0756bd3 --- /dev/null +++ b/searchapp/views.py @@ -0,0 +1,32 @@ +from django.db.models import Q +from django.shortcuts import render +from abonapp.models import Abon +from django.utils.html import escape +import re + + +def replace_without_case(orig, old, new): + return re.sub(old, new, orig, flags=re.IGNORECASE) + + +def home(request): + s = request.GET.get('s') + + if s: + abons = Abon.objects.filter( + Q(fio__icontains=s) | + Q(username__icontains=s) | + Q(telephone__icontains=s) + ) + else: + abons = [] + + for abn in abons: + abn.fio = replace_without_case(escape(abn.fio), s, "%s" % s) + abn.username = replace_without_case(escape(abn.username), s, "%s" % s) + abn.telephone = replace_without_case(escape(abn.telephone), s, "%s" % s) + + return render(request, 'searchapp/index.html', { + 'abons': abons, + 's': s + }) diff --git a/static/bad_ie.html b/static/bad_ie.html new file mode 100644 index 0000000..ca33cf1 --- /dev/null +++ b/static/bad_ie.html @@ -0,0 +1,11 @@ + + + + Старый браузер + + + + +

У вас старый ослик, обновитесь хотяб до IE10

+ + diff --git a/static/css/bootstrap-theme.min.css b/static/css/bootstrap-theme.min.css new file mode 100644 index 0000000..ac8dd55 --- /dev/null +++ b/static/css/bootstrap-theme.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} \ No newline at end of file diff --git a/static/css/bootstrap.min.css b/static/css/bootstrap.min.css new file mode 100644 index 0000000..28f154d --- /dev/null +++ b/static/css/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important;visibility:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/static/css/custom.css b/static/css/custom.css new file mode 100644 index 0000000..8365c48 --- /dev/null +++ b/static/css/custom.css @@ -0,0 +1,145 @@ +/* + * Base structure + */ + +/* Move down content because we have a fixed navbar that is 50px tall */ +body { + padding-top: 50px; +} + + +/* + * Global add-ons + */ + +.sub-header { + padding-bottom: 10px; + border-bottom: 1px solid #eee; +} + +/* + * Navbar + */ +.navbar-inverse { + background-image: none; +} + + +/* + * Sidebar + */ + +/* Hide for mobile, show later */ +.sidebar { + display: none; +} +@media (min-width: 768px) { + .sidebar { + position: fixed; + top: 39px; + bottom: 0; + left: 0; + z-index: 1000; + display: block; + padding: 20px; + overflow-x: hidden; + overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ + background-color: #303840; + border-right: 1px solid #eee; + } +} + +/* Sidebar navigation */ +.nav-sidebar { + margin-right: -21px; /* 20px padding + 1px border */ + margin-bottom: 20px; + margin-left: -20px; + border-top: 1px solid #576a7d; +} +.nav-sidebar > li > a { + padding-right: 20px; + padding-left: 20px; + color: #b3c3d2; +} +.nav>li>a:focus, .nav>li>a:hover { + background-color: #1f2429; + color: #b2bfcc; +} +.nav-sidebar > .active > a { + background-color: #44596b; +} + + + +.profile_img{ + color: aliceblue; + padding-bottom: 9px; +} +.main{ + margin-top: 10px; +} +.table-responsive thead{ + background-color: gainsboro; +} +td.btn-group{ + display: table-cell; +} +.table > tbody > tr > td { + vertical-align: middle; +} + +.modal-header.warning{ + background-color: #d2322d; + border-radius: 4px 4px 0 0; +} + + +.tab-pane { + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + border-bottom: 1px solid #ddd; + border-radius: 0px 0px 5px 5px; + padding: 10px; +} + + +/* search form */ +.stylish-input-group .input-group-addon{ + background: white !important; +} +.stylish-input-group .form-control{ + border-right:0; + box-shadow:0 0 0; + border-color:#ccc; +} +.stylish-input-group button{ + border:0; + background:transparent; +} + + +/*---------Switch----------*/ +.switch_ports { + border: 2px solid #636363; + border-radius: 7px; + box-shadow: 3px 3px 8px #2B2B2B; +} +.switch_ports h4 { + margin: 10px 0 3px 10px; +} +.port { + display: inline-block; + margin: 10px; + border: 1px solid #7B7B7B; + border-radius: 3px; + background-image: url(/static/img/icon-port-64x64-grey.png); + width: 64px; + height: 64px; +} +.port.giga { + background-color: #B5FFB5; +} +.port.kilo { + background-color: #FBEB79; +} +/*-------END-Switch--------*/ diff --git a/static/css/custom_login.css b/static/css/custom_login.css new file mode 100644 index 0000000..78e16fd --- /dev/null +++ b/static/css/custom_login.css @@ -0,0 +1,40 @@ +body { + padding-top: 40px; + padding-bottom: 40px; + background-color: #eee; +} + +.form-signin { + max-width: 330px; + padding: 15px; + margin: 0 auto; +} +.form-signin .form-signin-heading, +.form-signin .checkbox { + margin-bottom: 10px; +} +.form-signin .checkbox { + font-weight: normal; +} +.form-signin .form-control { + position: relative; + height: auto; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 10px; + font-size: 16px; +} +.form-signin .form-control:focus { + z-index: 2; +} +.form-signin input[type="email"] { + margin-bottom: -1px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.form-signin input[type="password"] { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} \ No newline at end of file diff --git a/static/fonts/glyphicons-halflings-regular.eot b/static/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000..b93a495 Binary files /dev/null and b/static/fonts/glyphicons-halflings-regular.eot differ diff --git a/static/fonts/glyphicons-halflings-regular.svg b/static/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..94fb549 --- /dev/null +++ b/static/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/fonts/glyphicons-halflings-regular.ttf b/static/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..1413fc6 Binary files /dev/null and b/static/fonts/glyphicons-halflings-regular.ttf differ diff --git a/static/fonts/glyphicons-halflings-regular.woff b/static/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..9e61285 Binary files /dev/null and b/static/fonts/glyphicons-halflings-regular.woff differ diff --git a/static/fonts/glyphicons-halflings-regular.woff2 b/static/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000..64539b5 Binary files /dev/null and b/static/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/static/img/0ZKOa52wPuc.jpg b/static/img/0ZKOa52wPuc.jpg new file mode 100644 index 0000000..7e26edb Binary files /dev/null and b/static/img/0ZKOa52wPuc.jpg differ diff --git a/static/img/favicon.ico b/static/img/favicon.ico new file mode 100644 index 0000000..b751e75 Binary files /dev/null and b/static/img/favicon.ico differ diff --git a/static/img/favicon_m.ico b/static/img/favicon_m.ico new file mode 100644 index 0000000..526d0a8 Binary files /dev/null and b/static/img/favicon_m.ico differ diff --git a/static/img/gmarkers/dev.png b/static/img/gmarkers/dev.png new file mode 100644 index 0000000..81ebf83 Binary files /dev/null and b/static/img/gmarkers/dev.png differ diff --git a/static/img/gmarkers/dev.xcf b/static/img/gmarkers/dev.xcf new file mode 100644 index 0000000..64f379f Binary files /dev/null and b/static/img/gmarkers/dev.xcf differ diff --git a/static/img/gmarkers/dev_bug.png b/static/img/gmarkers/dev_bug.png new file mode 100644 index 0000000..1c1e867 Binary files /dev/null and b/static/img/gmarkers/dev_bug.png differ diff --git a/static/img/gmarkers/dev_ok.png b/static/img/gmarkers/dev_ok.png new file mode 100644 index 0000000..b2639cf Binary files /dev/null and b/static/img/gmarkers/dev_ok.png differ diff --git a/static/img/gmarkers/flag_black.png b/static/img/gmarkers/flag_black.png new file mode 100644 index 0000000..0d98e16 Binary files /dev/null and b/static/img/gmarkers/flag_black.png differ diff --git a/static/img/icon-port-64x64-grey.png b/static/img/icon-port-64x64-grey.png new file mode 100644 index 0000000..3da8182 Binary files /dev/null and b/static/img/icon-port-64x64-grey.png differ diff --git a/static/img/kpkLhcH5R4E.jpg b/static/img/kpkLhcH5R4E.jpg new file mode 100644 index 0000000..f45991a Binary files /dev/null and b/static/img/kpkLhcH5R4E.jpg differ diff --git a/static/img/user_ava.gif b/static/img/user_ava.gif new file mode 100644 index 0000000..cc2079c Binary files /dev/null and b/static/img/user_ava.gif differ diff --git a/static/img/user_ava_min.gif b/static/img/user_ava_min.gif new file mode 100644 index 0000000..c73b656 Binary files /dev/null and b/static/img/user_ava_min.gif differ diff --git a/static/js/bootstrap.min.js b/static/js/bootstrap.min.js new file mode 100644 index 0000000..c6d3692 --- /dev/null +++ b/static/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.2",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.2",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.2",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.2",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('