Browse Source

Merge branch 'devel'

devel
Dmitry 9 years ago
parent
commit
9de01b2340
  1. 12
      abonapp/forms.py
  2. 64
      abonapp/locale/ru/LC_MESSAGES/django.po
  3. 7
      abonapp/migrations/0001_initial.py
  4. 21
      abonapp/migrations/0019_abon_ip_address.py
  5. 25
      abonapp/migrations/0020_auto_20170517_1655.py
  6. 33
      abonapp/models.py
  7. 1
      abonapp/templates/abonapp/buy_tariff.html
  8. 49
      abonapp/templates/abonapp/charts.html
  9. 49
      abonapp/templates/abonapp/editAbon.html
  10. 18
      abonapp/templates/abonapp/ext.htm
  11. 3
      abonapp/templates/abonapp/modal_abonamount.html
  12. 45
      abonapp/templates/abonapp/modal_extra_field.html
  13. 1
      abonapp/templates/abonapp/passport_view.html
  14. 39
      abonapp/templates/abonapp/peoples.html
  15. 3
      abonapp/tests.py
  16. 2
      abonapp/urls.py
  17. 4
      abonapp/urls_abon.py
  18. 208
      abonapp/views.py
  19. 9
      agent/core.py
  20. 15
      agent/mod_mikrotik.py
  21. 6
      agent/netflow/netflow_handler.sh
  22. 2
      agent/netflow/start_netflow.sh
  23. 125
      agent/netflow/to_mysql.c
  24. 66
      agent/netflow/to_mysql.py
  25. 1
      bugs.txt
  26. 4
      clientsideapp/templates/clientsideapp/ext.html
  27. 4
      clientsideapp/views.py
  28. 3
      cron.py
  29. 2
      devapp/dev_types.py
  30. 2
      devapp/forms.py
  31. 19
      dhcp_lever.py
  32. 1
      djing/settings_example.py
  33. 1
      djing/urls.py
  34. 1
      ip_pool/__init__.py
  35. 6
      ip_pool/admin.py
  36. 6
      ip_pool/apps.py
  37. 22
      ip_pool/forms.py
  38. 23
      ip_pool/migrations/0001_initial.py
  39. 0
      ip_pool/migrations/__init__.py
  40. 59
      ip_pool/models.py
  41. 48
      ip_pool/templates/ip_pool/add_pool.html
  42. 73
      ip_pool/templates/ip_pool/index.html
  43. 59
      ip_pool/templates/ip_pool/ips.html
  44. 15
      ip_pool/urls.py
  45. 81
      ip_pool/views.py
  46. 17
      mydefs.py
  47. 16
      searchapp/views.py
  48. 4
      static/css/all.min.css
  49. 8
      static/css/custom.css
  50. 31
      static/js/all.min.js
  51. 2
      static/js/bootstrap-datetimepicker.min.js
  52. 562
      static/js/datetime_with_moment.min.js
  53. 20
      static/js/my.js
  54. 6
      statistics/admin.py
  55. 53
      statistics/fields.py
  56. 18
      statistics/migrations/0002_delete_statelem.py
  57. 64
      statistics/models.py
  58. 12
      systemd_units/djing_rotate.service
  59. 11
      systemd_units/djing_rotate.timer
  60. 15
      taskapp/templates/taskapp/add_edit_task.html
  61. 7
      templates/404.html
  62. 15
      templates/base.html
  63. 4
      templates/message_block.html

12
abonapp/forms.py

@ -124,3 +124,15 @@ class PassportForm(forms.ModelForm):
initials = {
'date_of_acceptance': datetime(year=2014, month=6, day=1)
}
class ExtraFieldForm(forms.ModelForm):
class Meta:
model = models.ExtraFieldsModel
fields = '__all__'
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'field_type': forms.Select(attrs={'class': 'form-control'}),
'data': forms.TextInput(attrs={'class': 'form-control'})
}

64
abonapp/locale/ru/LC_MESSAGES/django.po

@ -619,14 +619,8 @@ msgstr "Абонент успешно изменён"
#: abonapp/views.py:258
#, python-format
msgid "Ip address already exist. %s"
msgstr ""
"Проверте введённые вами значения, скорее всего такой ip уже у кого-то есть. "
"А вообще: %s"
#: abonapp/views.py:266
msgid "Ip address not found"
msgstr "Указанный вами ip отсутствует в ip pool"
msgid "Ip address already exist"
msgstr "Такой ip уже у кого-то есть"
#: abonapp/views.py:268
msgid "User has not have password, and cannot login"
@ -721,6 +715,12 @@ msgstr "Для абонента не найдены паспортные дан
msgid "currency"
msgstr "руб"
msgid "Charts"
msgstr "Графики"
msgid "Group what you want doesn't exist"
msgstr "Указанная вами группа не найдена"
msgid "User device was not found"
msgstr "Пользовательское устройство не найдено"
@ -747,3 +747,51 @@ msgstr "Действует до"
msgid "Do"
msgstr "Действия"
msgid "Last traffic"
msgstr "Последний траффик"
msgid "Extra field successfully created"
msgstr "Динамичесое поле добавлено успешно"
msgid "Extra field successfully deleted"
msgstr "Динамическое поле успешно удалено"
msgid "Extra field does not exist"
msgstr "Поле не найдено"
msgid "Extra fields"
msgstr "Динамические записи"
msgid "Add extra field"
msgstr "Добавить новое динамическое поле"
msgid "Add"
msgstr "Добавить"
msgid "Field title"
msgstr "Название поля"
msgid "Field type"
msgstr "Тип динамического поля"
msgid "Field content"
msgstr "Содержимое динамического поля"
msgid "Extra fields has been saved"
msgstr "Динамические поля сохранены"
msgid "One or more extra fields has not been saved"
msgstr "Поле или одно из полей не найдено"
msgid "ok ping, %d/%d loses"
msgstr "пингуется, %d/%d"
msgid "no ping, %d/%d loses"
msgstr "не пингуется, %d/%d"
msgid "no ping"
msgstr "не пингуется"
msgid "ping ok"
msgstr "пингуется"

7
abonapp/migrations/0001_initial.py

@ -13,7 +13,6 @@ class Migration(migrations.Migration):
dependencies = [
('accounts_app', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('ip_pool', '0001_initial'),
('tariff_app', '0001_initial'),
]
@ -102,12 +101,6 @@ class Migration(migrations.Migration):
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={('abon', 'tariff', 'tariff_priority')},

21
abonapp/migrations/0019_abon_ip_address.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-05-10 23:17
from __future__ import unicode_literals
from django.db import migrations
import mydefs
class Migration(migrations.Migration):
dependencies = [
('abonapp', '0018_auto_20170418_1236'),
]
operations = [
migrations.AddField(
model_name='abon',
name='ip_address',
field=mydefs.MyGenericIPAddressField(blank=True, max_length=8, null=True, protocol='ipv4'),
),
]

25
abonapp/migrations/0020_auto_20170517_1655.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-05-17 13:55
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('abonapp', '0019_abon_ip_address'),
]
operations = [
migrations.AddField(
model_name='extrafieldsmodel',
name='title',
field=models.CharField(default='no title', max_length=16),
),
migrations.AlterField(
model_name='extrafieldsmodel',
name='field_type',
field=models.CharField(choices=[('int', 'Цифровое поле'), ('str', 'Текстовое поле'), ('dbl', 'Дробное с плавающей точкой'), ('ipa', 'IP Адрес')], default='str', max_length=3),
),
]

33
abonapp/models.py

@ -6,14 +6,10 @@ from django.db import models
from django.core import validators
from django.utils.translation import ugettext as _
from agent import Transmitter, AbonStruct, TariffStruct, NasFailedResult, NasNetworkError
from ip_pool.models import IpPoolItem
from tariff_app.models import Tariff
from accounts_app.models import UserProfile
from .fields import MACAddressField
class LogicError(Exception):
pass
from mydefs import MyGenericIPAddressField, ip2int, LogicError, ip_addr_regex
class AbonGroup(models.Model):
@ -165,12 +161,24 @@ class ExtraFieldsModel(models.Model):
DYNAMIC_FIELD_TYPES = (
('int', _('Digital field')),
('str', _('Text field')),
('dbl', _('Floating field'))
('dbl', _('Floating field')),
('ipa', _('Ip Address'))
)
field_type = models.CharField(max_length=3, choices=DYNAMIC_FIELD_TYPES)
title = models.CharField(max_length=16, default='no title')
field_type = models.CharField(max_length=3, choices=DYNAMIC_FIELD_TYPES, default='str')
data = models.CharField(max_length=64, null=True, blank=True)
def get_regexp(self):
if self.field_type == 'int':
return r'^[+-]?\d+$'
elif self.field_type == 'dbl':
return r'^[-+]?\d+[,.]\d+$'
elif self.field_type == 'str':
return r'^[a-zA-ZА-Яа-я0-9]+$'
elif self.field_type == 'ipa':
return ip_addr_regex
def clean(self):
d = self.data
if self.field_type == 'int':
@ -207,7 +215,7 @@ 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)
ip_address = models.OneToOneField(IpPoolItem, on_delete=models.SET_NULL, null=True, blank=True)
ip_address = MyGenericIPAddressField(blank=True, null=True)
description = models.TextField(null=True, blank=True)
street = models.ForeignKey(AbonStreet, on_delete=models.SET_NULL, null=True, blank=True)
house = models.CharField(max_length=12, null=True, blank=True)
@ -321,7 +329,7 @@ class Abon(UserProfile):
# создаём абонента из структуры агента
def build_agent_struct(self):
if self.ip_address:
user_ip = self.ip_address.int_ip()
user_ip = ip2int(self.ip_address)
else:
return
inst_tariff = self.active_tariff()
@ -331,6 +339,13 @@ class Abon(UserProfile):
agent_trf = TariffStruct()
return AbonStruct(self.pk, user_ip, agent_trf, bool(self.is_active))
def save(self, *args, **kwargs):
# проверяем не-ли у кого такого-же ip
if Abon.objects.filter(ip_address=self.ip_address).exclude(pk=self.pk).count() > 0:
self.is_bad_ip = True
raise LogicError(_('Ip address already exist'))
super(Abon, self).save(*args, **kwargs)
class AbonDevice(models.Model):
abon = models.ForeignKey(Abon)

1
abonapp/templates/abonapp/buy_tariff.html

@ -38,7 +38,6 @@
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>
<input type="text" class="form-control" name="deadline" id="id_deadline" value="{{ tariffs.0.calc_deadline|date:"Y-m-d" }}">
<script type="text/javascript" src="/static/js/datetime_with_moment.min.js"></script>
<script type="text/javascript">
$(function () {
$('#id_deadline').datetimepicker({

49
abonapp/templates/abonapp/charts.html

@ -0,0 +1,49 @@
{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">График использования</h3>
</div>
<div class="panel-body">
{% if charts_data %}
<div id="chrt"></div>
<script type="text/javascript">
$(document).ready(function ($) {
new Chartist.Line('#chrt', {
series: [
{
name: 'График траффика',
data: [
{{ charts_data }}
]
}
]
}, {
height: '600px',
showArea: true,
showLine: false,
showPoint: false,
high: {{ high }},
axisX: {
type: Chartist.FixedScaleAxis,
divisor: 12,
labelInterpolationFnc: function (value) {
return moment(value).format('HH:mm:ss');
}
}
});
});
</script>
{% else %}
<h2>Траффик не найден</h2>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

49
abonapp/templates/abonapp/editAbon.html

@ -32,7 +32,7 @@
</div>
</div>
<div class="form-group-sm">
<div class="form-group-sm{% if is_bad_ip %} has-error{% endif %}">
<label for="id_ip" class="col-sm-4 control-label">{% trans 'Ip Address' %}</label>
<div class="col-sm-8">
<input type="text" value="{{ ip|default:'' }}" class="form-control" name="ip" placeholder="{% trans 'Not assigned' %}" pattern="^(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]?)$"{% if abon.opt82 %} disabled{% endif %}/>
@ -123,6 +123,16 @@
</div>
</div>
{% if ip %}
<div class="form-group-sm">
<div class="col-sm-offset-4 col-sm-8 btn-group btn-group-sm">
<a href="{% url 'abonapp:ping' %}" class="btn btn-sm btn-default btn-cmd" title="Ping" data-param="{{ ip }}">
<span class="glyphicon glyphicon-check"></span> Ping
</a>
</div>
</div>
{% endif %}
</form>
</div>
</div>
@ -143,7 +153,7 @@
</div>
<div class="form-group-sm">
<label for="id_ip" class="col-sm-2 control-label">{% trans 'Port' %}</label>
<label for="id_port" class="col-sm-2 control-label">{% trans 'Port' %}</label>
<div class="col-sm-10">
{{ tech_form.port }}{{ tech_form.port.errors }}
</div>
@ -165,19 +175,43 @@
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Доп поля</h3>
<h3 class="panel-title">{% trans 'Extra fields' %}</h3>
</div>
<div class="panel-body">
<form role="form" class="form-horizontal" action="#" method="post">{% csrf_token %}
<form role="form" class="form-horizontal" action="{% url 'abonapp:extra_field_edit' abon_group.pk abon.pk %}" method="post">{% csrf_token %}
{% for ef in abon.extra_fields.all %}
<div class="form-group-sm">
<label for="id_ip" class="col-sm-2 control-label">{% trans 'Ip Address' %}</label>
<label class="col-sm-2 control-label">{{ ef.title }}</label>
<div class="col-sm-10">
<input type="text" value="{{ ip|default:'' }}" class="form-control" name="ip" placeholder="192.168.0.101" pattern="^(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]?)$"/>
<div class="input-group input-group-sm">
<input type="text" value="{{ ef.data|default:_('Not assigned') }}" class="form-control" pattern="{{ ef.get_regexp }}" name="ex">
<input type="hidden" value="{{ ef.pk }}" name="ed">
<span class="input-group-btn">
<a href="{% url 'abonapp:extra_field_delete' abon_group.pk abon.pk ef.pk %}" class="btn btn-danger" title="{% trans 'Delete' %}">
<span class="glyphicon glyphicon-remove"></span>
</a>
</span>
</div>
</div>
</div>
{% empty %}
<h3>{% trans 'Extra field does not exist' %}</h3>
{% endfor %}
<div class="form-group-sm">
<div class="col-sm-offset-2 col-sm-10 btn-group btn-group-sm">
<a href="{% url 'abonapp:extra_field' abon_group.pk abon.pk %}" class="btn btn-success btn-modal" title="{% trans 'Add extra field' %}">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add' %}
</a>
<button class="btn btn-primary" type="submit">
<span class="glyphicon glyphicon-edit"></span> {% trans 'Save' %}
</button>
</div>
</div>
</form>
</div>
@ -186,4 +220,3 @@
</div>
{% endblock %}

18
abonapp/templates/abonapp/ext.htm

@ -5,7 +5,7 @@
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'abonapp:group_list' %}">{% trans 'User groups' %}</a></li>
<li><a href="{% url 'abonapp:people_list' abon_group.id %}">{{ abon_group.title }}</a></li>
<li><a href="{% url 'abonapp:people_list' abon_group.pk %}">{{ abon_group.title }}</a></li>
<li class="active">{{ abon.fio }}</li>
</ol>
@ -21,32 +21,38 @@
<ul class="nav nav-tabs">
{% url 'abonapp:abon_home' abon_group.id abon.id as abon_home %}
{% url 'abonapp:abon_home' abon_group.pk abon.pk as abon_home %}
<li{% if abon_home == request.path %} class="active"{% endif %}>
<a href="{{ abon_home }}">{% trans 'Sub information' %}</a>
</li>
{% url 'abonapp:abon_services' abon_group.id abon.id as abserv %}
{% url 'abonapp:abon_services' abon_group.pk abon.pk as abserv %}
<li{% if abserv == request.path %} class="active"{% endif %}>
<a href="{{ abserv }}">{% trans 'Services' %}</a>
</li>
{% if perms.abonapp.can_view_passport %}
{% url 'abonapp:passport_view' abon_group.id abon.id as passport_view_url %}
{% url 'abonapp:passport_view' abon_group.pk abon.pk as passport_view_url %}
<li{% if passport_view_url == request.path %} class="active"{% endif %}>
<a href="{{ passport_view_url }}">{% trans 'Passport information' %}</a>
</li>
{% endif %}
{% url 'abonapp:abon_phistory' abon_group.id abon.id as abphist %}
{% url 'abonapp:abon_phistory' abon_group.pk abon.pk as abphist %}
<li{% if abphist == request.path %} class="active"{% endif %}>
<a href="{{ abphist }}">{% trans 'Payments' %}</a>
</li>
{% url 'abonapp:task_log' abon_group.id abon.id as abtasklog %}
{% url 'abonapp:task_log' abon_group.pk abon.pk as abtasklog %}
<li{% if abtasklog == request.path %} class="active"{% endif %}>
<a href="{{ abtasklog }}">{% trans 'History of tasks' %}</a>
</li>
{% url 'abonapp:charts' abon_group.pk abon.pk as abtasklog %}
<li{% if abtasklog == request.path %} class="active"{% endif %}>
<a href="{{ abtasklog }}">{% trans 'Charts' %}</a>
</li>
</ul>
<div class="tab-content">

3
abonapp/templates/abonapp/modal_abonamount.html

@ -12,8 +12,7 @@
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-ruble"></span></span>
<input id="amount" type="text" name="amount" placeholder="0.0" class="form-control" required
pattern="\d+">
<input id="amount" type="text" name="amount" placeholder="0.0" class="form-control" required>
</div>
</div>
<input type="hidden" name="abonid" value="{{ abon.id }}"><br>

45
abonapp/templates/abonapp/modal_extra_field.html

@ -0,0 +1,45 @@
{% load i18n %}
<form role="form" action="{% url 'abonapp:extra_field' gid abon.pk %}" method="post"> {% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-plus"></span> {% trans 'Add extra field' %}</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="id_title">{% trans 'Field title' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-text-background"></span></span>
{{ frm.title }}{{ frm.title.errors }}
</div>
</div>
<div class="form-group">
<label for="id_field_type">{% trans 'Field type' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-ice-lolly"></span></span>
{{ frm.field_type }}{{ frm.field_type.errors }}
</div>
</div>
<div class="form-group">
<label for="id_data">{% trans 'Field content' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-paperclip"></span></span>
{{ frm.data }}{{ frm.data.errors }}
</div>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-success">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add' %}
</button>
<button type="reset" class="btn btn-default">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %}
</button>
</div>
</div>
</form>

1
abonapp/templates/abonapp/passport_view.html

@ -34,7 +34,6 @@
<div class="col-sm-9">
{{ frm.date_of_acceptance }}{{ frm.date_of_acceptance.errors }}
</div>
<script type="text/javascript" src="/static/js/datetime_with_moment.min.js"></script>
<script type="text/javascript">
$(function () {
$('#id_date_of_acceptance').datetimepicker({

39
abonapp/templates/abonapp/peoples.html

@ -24,6 +24,7 @@
</a>
{% if order_by == 'username' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th>{% trans 'Last traffic' %}</th>
<th>
<a href="{% url 'abonapp:people_list' abon_group.pk %}?order_by=ip_address&dir={{ dir|default:"down" }}">
{% trans 'Ip address' %}
@ -49,7 +50,7 @@
{% if order_by == 'house' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th width="150">{% trans 'Telephone' %}</th>
<th width="150">{% trans 'Service' %}</th>
<!--<th width="150">{% trans 'Service' %}</th>-->
<th width="50">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?order_by=ballance&dir={{ dir|default:"down" }}">
{% trans 'Ballance' %}
@ -66,22 +67,32 @@
{% else %}
<tr class="danger">
{% endif %}
<td><a href="{% url 'abonapp:abon_home' human.group.pk human.pk %}">{{ human.username }}</a></td>
<td>
<a href="{% url 'abonapp:abon_home' human.group.pk human.pk %}">{{ human.username }}</a>
{% if human.is_online %}
<span class="glyphicon glyphicon-ok text-success"></span>
{% endif %}
</td>
<td>
{% if human.traf %}
{{ human.traf.cur_time|date:"H:i" }}
{% endif %}
</td>
<td>{{ human.ip_address|default:_('Not assigned') }}</td>
<td>{{ human.fio }}</td>
<td>{{ human.street|default:_('Not assigned') }}</td>
<td>{{ human.house|default:_('Not assigned') }}</td>
<td><a href="tel:{{ human.telephone }}">{{ human.telephone }}</a></td>
<td>
{% if human.active_tariff %}
{% if perms.tariff_app.change_tariff %}
<a href="{% url 'tarifs:edit' human.active_tariff.pk %}">{{ human.active_tariff.title }}</a>
{% else %}
{{ human.active_tariff.title }}
{% endif %}
{% else %}&mdash;&mdash;&mdash;
{% endif %}
</td>
<!--<td>
{ % if human.active_tariff %}
{ % if perms.tariff_app.change_tariff %}
<a href="{ % url 'tarifs:edit' human.active_tariff.pk %}">{ { human.active_tariff.title }}</a>
{ % else %}
{ { human.active_tariff.title }}
{ % endif %}
{ % else %}&mdash;&mdash;&mdash;
{ % endif %}
</td>-->
<td>{{ human.ballance }}</td>
<td>
{% if perms.abonapp.delete_abon %}
@ -93,7 +104,7 @@
</tr>
{% empty %}
<tr>
<td colspan="9">
<td colspan="10">
{% trans 'Subscribers not found' %}.
{% if perms.abonapp.add_abon %}
<a href="{% url 'abonapp:add_abon' abon_group.pk %}">{% trans 'Add abon' %}</a>
@ -104,7 +115,7 @@
</tbody>
<tfoot>
<tr>
<td colspan="9" class="btn-group">
<td colspan="10" class="btn-group">
{% if perms.abonapp.add_abon %}
<a href="{% url 'abonapp:add_abon' abon_group.pk %}" class="btn btn-sm btn-default" title="Добавить">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add abon' %}

3
abonapp/tests.py

@ -2,8 +2,9 @@ from django.shortcuts import resolve_url
from django.test import TestCase
from django.test.client import Client
from agent import NasNetworkError
from .models import AbonTariff, Abon, AbonGroup, LogicError, AbonRawPassword
from .models import AbonTariff, Abon, AbonGroup, AbonRawPassword
from tariff_app.models import Tariff
from mydefs import LogicError
class AbonTestCase(TestCase):

2
abonapp/urls.py

@ -19,6 +19,8 @@ urlpatterns = [
url(r'^debtors$', views.debtors, name='debtors'),
url(r'^ping$', views.abon_ping, name='ping'),
url(r'^refresh_group_nas(?P<group_id>\d+)$', views.update_nas, name='update_nas'),
# Api's

4
abonapp/urls_abon.py

@ -20,6 +20,10 @@ urlpatterns = [
url(r'^(?P<uid>\d+)/complete_service(?P<srvid>\d+)$', views.complete_service, name='compl_srv'),
url(r'^(?P<uid>\d+)/activate_service(?P<srvid>\d+)$', views.activate_service, name='activate_service'),
url(r'^(?P<uid>\d+)/opt82$', views.opt82, name='opt82'),
url(r'^(?P<uid>\d+)/chart$', views.charts, name='charts'),
url(r'^(?P<uid>\d+)/extra_field$', views.make_extra_field, name='extra_field'),
url(r'^(?P<uid>\d+)/extra_field/(?P<fid>\d+)/delete$', views.extra_field_delete, name='extra_field_delete'),
url(r'^(?P<uid>\d+)/extra_field/edit$', views.extra_field_change, name='extra_field_edit'),
url(r'^(?P<uid>\d+)/unsubscribe_service(?P<srvid>\d+)$', views.unsubscribe_service,
name='unsubscribe_service'),

208
abonapp/views.py

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
from json import dumps
from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied, MultipleObjectsReturned
from django.db import IntegrityError
from django.core.exceptions import PermissionDenied
from django.db import IntegrityError, ProgrammingError
from django.db.models import Count
from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.contrib.auth.decorators import login_required, permission_required
@ -11,11 +11,11 @@ from django.http import HttpResponse
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from statistics.models import getModel
from tariff_app.models import Tariff
from agent import NasFailedResult, Transmitter, NasNetworkError
from . import forms
from . import models
from ip_pool.models import IpPoolItem
import mydefs
from devapp.models import Device
from datetime import datetime
@ -25,17 +25,30 @@ from datetime import datetime
@mydefs.only_admins
def peoples(request, gid):
street_id = mydefs.safe_int(request.GET.get('street'))
peoples_list = models.Abon.objects.select_related('group', 'street')
if street_id > 0:
peoples_list = models.Abon.objects.filter(group=gid, street=street_id)
peoples_list = peoples_list.filter(group=gid, street=street_id)
else:
peoples_list = models.Abon.objects.filter(group=gid)
peoples_list = peoples_list.filter(group=gid)
StatModel = getModel()
# фильтр
dr, field = mydefs.order_helper(request)
if field:
peoples_list = peoples_list.order_by(field)
peoples_list = mydefs.pag_mn(request, peoples_list)
try:
peoples_list = mydefs.pag_mn(request, peoples_list)
for abon in peoples_list:
if abon.ip_address is not None:
traf = StatModel.objects.traffic_by_ip(abon.ip_address)
if traf[1] is not None:
abon.traf = traf[1]
abon.is_online =traf[0]
except mydefs.LogicError as e:
messages.warning(request, e)
streets = models.AbonStreet.objects.filter(group=gid)
@ -123,7 +136,7 @@ def addabon(request, gid):
else:
messages.error(request, _('fix form errors'))
except (IntegrityError, NasFailedResult, NasNetworkError, models.LogicError) as e:
except (IntegrityError, NasFailedResult, NasNetworkError, mydefs.LogicError) as e:
messages.error(request, e)
except mydefs.MultipleException as errs:
for err in errs.err_list:
@ -260,14 +273,7 @@ def abonhome(request, gid, uid):
if abon.opt82 is None:
ip_str = request.POST.get('ip')
if ip_str:
try:
ip = IpPoolItem.objects.get(ip=ip_str)
abon.ip_address = ip
except MultipleObjectsReturned:
ips = models.IpPoolItem.objects.filter(ip=ip_str)
one_ip = ips[0]
models.IpPoolItem.objects.filter(pk__in=[ip.pk for ip in ips if ip != one_ip]).delete()
abon.ip_address = one_ip
abon.ip_address = ip_str
else:
abon.ip_address = None
frm.save()
@ -278,14 +284,13 @@ def abonhome(request, gid, uid):
passw = models.AbonRawPassword.objects.get(account=abon).passw_text
frm = forms.AbonForm(instance=abon, initial={'password': passw})
abon_device = models.AbonDevice.objects.get(abon=abon)
except IntegrityError as e:
messages.error(request, _('Ip address already exist. %s') % e)
except mydefs.LogicError as e:
messages.error(request, e)
passw = models.AbonRawPassword.objects.get(account=abon).passw_text
frm = forms.AbonForm(instance=abon, initial={'password': passw})
except (NasFailedResult, NasNetworkError) as e:
messages.error(request, e)
except IpPoolItem.DoesNotExist:
messages.error(request, _('Ip address not found'))
except models.AbonRawPassword.DoesNotExist:
messages.warning(request, _('User has not have password, and cannot login'))
except models.AbonDevice.DoesNotExist:
@ -300,6 +305,7 @@ def abonhome(request, gid, uid):
'abon': abon,
'abon_group': abon_group,
'ip': abon.ip_address,
'is_bad_ip': getattr(abon, 'is_bad_ip', False),
'tech_form': forms.Opt82Form(instance=abon.opt82),
'device': abon_device.device if abon_device is not None else None
})
@ -406,7 +412,7 @@ def pick_tariff(request, gid, uid):
abon.pick_tariff(trf, request.user, deadline=deadline)
messages.success(request, _('Tariff has been picked'))
return redirect('abonapp:abon_services', gid=gid, uid=abon.id)
except (models.LogicError, NasFailedResult) as e:
except (mydefs.LogicError, NasFailedResult) as e:
messages.error(request, e)
except NasNetworkError as e:
messages.error(request, e)
@ -484,11 +490,11 @@ def complete_service(request, gid, uid, srvid):
messages.success(request, _('Service has been finished successfully'))
return redirect('abonapp:abon_services', gid, uid)
else:
raise models.LogicError(_('Not confirmed'))
raise mydefs.LogicError(_('Not confirmed'))
time_use = mydefs.RuTimedelta(timezone.now() - abtar.time_start)
except (models.LogicError, NasFailedResult) as e:
except (mydefs.LogicError, NasFailedResult) as e:
messages.error(request, e)
except NasNetworkError as e:
messages.warning(request, e)
@ -522,7 +528,7 @@ def activate_service(request, gid, uid, srvid):
messages.success(request, _('Service has been activated successfully'))
return redirect('abonapp:abon_services', gid, uid)
except (NasFailedResult, models.LogicError) as e:
except (NasFailedResult, mydefs.LogicError) as e:
messages.error(request, e)
except NasNetworkError as e:
messages.warning(request, e)
@ -570,14 +576,15 @@ def log_page(request):
@mydefs.only_admins
def debtors(request):
# peoples_list = models.Abon.objects.filter(invoiceforpayment__status=True)
#peoples_list = mydefs.pag_mn(request, peoples_list)
# peoples_list = mydefs.pag_mn(request, peoples_list)
invs = models.InvoiceForPayment.objects.filter(status=True)
invs = mydefs.pag_mn(request, invs)
return render(request, 'abonapp/debtors.html', {
#'peoples': peoples_list
# 'peoples': peoples_list
'invoices': invs
})
@login_required
@mydefs.only_admins
def update_nas(request, group_id):
@ -704,6 +711,155 @@ def clear_dev(request, gid, uid):
return redirect('abonapp:abon_home', gid=gid, uid=uid)
@login_required
@mydefs.only_admins
def charts(request, gid, uid):
from statistics.models import getModel
from datetime import datetime, date, time, timedelta
high = 100
def byte_to_mbit(x):
return ((x/60)*8)/2**20
try:
StatElem = getModel()
abon = models.Abon.objects.get(pk=uid)
if abon.group is None:
abon.group = models.AbonGroup.objects.get(pk=gid)
abon.save(update_fields=['group'])
abongroup = abon.group
if abon.ip_address is None:
charts_data = None
else:
charts_data = StatElem.objects.filter(ip=abon.ip_address)
#oct_limit = StatElem.percentile([cd.octets for cd in charts_data], 0.05)
# ниже возвращаем пары значений трафика который переведён в mByte, и unix timestamp
midnight = datetime.combine(date.today(), time.min)
charts_data = [(cd.cur_time.timestamp()*1000, byte_to_mbit(cd.octets)) for cd in charts_data]
if len(charts_data) > 0:
charts_data.append( (charts_data[-1:][0][0], 0.0) )
charts_data = ["{x: new Date(%d), y: %.2f}" % (cd[0], cd[1]) for cd in charts_data]
charts_data.append("{x:new Date(%d),y:0}" % (int((midnight + timedelta(days=1)).timestamp()) * 1000))
abontariff = abon.active_tariff()
high = abontariff.speedIn + abontariff.speedOut
if high > 100:
high = 100
except models.Abon.DoesNotExist:
messages.error(request, _('Abon does not exist'))
return redirect('abonapp:people_list', gid)
except models.AbonGroup.DoesNotExist:
messages.error(request, _("Group what you want doesn't exist"))
return redirect('abonapp:group_list')
except ProgrammingError as e:
messages.error(request, e)
return redirect('abonapp:abon_home', gid=gid, uid=uid)
return render(request, 'abonapp/charts.html', {
'abon_group': abongroup,
'abon': abon,
'charts_data': ',\n'.join(charts_data) if charts_data is not None else None,
'high': high
})
@login_required
@permission_required('abonapp.add_extra_fields_model')
def make_extra_field(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid)
try:
if request.method == 'POST':
frm = forms.ExtraFieldForm(request.POST)
if frm.is_valid():
field_instance = frm.save()
abon.extra_fields.add(field_instance)
messages.success(request, _('Extra field successfully created'))
else:
messages.error(request, _('fix form errors'))
return redirect('abonapp:abon_home', gid=gid, uid=uid)
else:
frm = forms.ExtraFieldForm()
except (NasNetworkError, NasFailedResult) as e:
messages.error(request, e)
frm = forms.ExtraFieldForm()
except mydefs.MultipleException as errs:
for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err)
frm = forms.ExtraFieldForm()
return render_to_text('abonapp/modal_extra_field.html', {
'abon': abon,
'gid': gid,
'frm': frm
}, request=request)
@login_required
@permission_required('abonapp.change_extra_fields_model')
def extra_field_change(request, gid, uid):
extras = [(int(x), y) for x, y in zip(request.POST.getlist('ed'), request.POST.getlist('ex'))]
print(extras)
try:
for ex in extras:
extra_field = models.ExtraFieldsModel.objects.get(pk=ex[0])
extra_field.data = ex[1]
extra_field.save(update_fields=['data'])
messages.success(request, _("Extra fields has been saved"))
except models.ExtraFieldsModel.DoesNotExist:
messages.error(request, _('One or more extra fields has not been saved'))
return redirect('abonapp:abon_home', gid=gid, uid=uid)
@login_required
@permission_required('abonapp.delete_extra_fields_model')
def extra_field_delete(request, gid, uid, fid):
abon = get_object_or_404(models.Abon, pk=uid)
try:
extra_field = models.ExtraFieldsModel.objects.get(pk=fid)
abon.extra_fields.remove(extra_field)
extra_field.delete()
messages.success(request, _('Extra field successfully deleted'))
except models.ExtraFieldsModel.DoesNotExist:
messages.warning(request, _('Extra field does not exist'))
return redirect('abonapp:abon_home', gid=gid, uid=uid)
@login_required
def abon_ping(request):
ip = request.GET.get('cmd_param')
status = False
text = '<span class="glyphicon glyphicon-exclamation-sign"></span> %s' % _('no ping')
try:
tm = Transmitter()
ping_result = tm.ping(ip)
if ping_result is None:
if mydefs.ping(ip, 10):
status = True
text = '<span class="glyphicon glyphicon-ok"></span> %s' % _('ping ok')
else:
if type(ping_result) is tuple:
loses_percent = (ping_result[0] / ping_result[1] if ping_result[1] != 0 else 1)
if loses_percent > 0.5:
text = '<span class="glyphicon glyphicon-ok"></span> %s' % _('ok ping, %d/%d loses') % ping_result
status = True
else:
text = '<span class="glyphicon glyphicon-exclamation-sign"></span> %s' % _('no ping, %d/%d loses') % ping_result
else:
text = '<span class="glyphicon glyphicon-ok"></span> %s' % _('ping ok') + ' ' + str(ping_result)
status = True
except NasFailedResult as e:
messages.error(request, e)
except NasNetworkError as e:
messages.warning(request, e)
return HttpResponse(dumps({
'status': 0 if status else 1,
'dat': text
}))
# API's
def abons(request):
@ -731,5 +887,5 @@ def abons(request):
def search_abon(request):
word = request.GET.get('s')
results = models.Abon.objects.filter(fio__icontains=word)[:8]
results = [{'id':usr.pk, 'name':usr.username, 'fio':usr.fio} for usr in results]
results = [{'id': usr.pk, 'name': usr.username, 'fio': usr.fio} for usr in results]
return HttpResponse(dumps(results, ensure_ascii=False))

9
agent/core.py

@ -90,3 +90,12 @@ class BaseTransmitter(metaclass=ABCMeta):
@check_input_type(TariffStruct)
def remove_tariff(self, tid):
"""удаляем тариф"""
@abstractmethod
@check_input_type(TariffStruct)
def ping(self, host, count=10):
"""
:param host: ip адрес в текстовом виде, например '192.168.0.1'
:param count: количество пингов
:return: None если не пингуется, иначе кортеж, в котором (сколько вернулось, сколько было отправлено)
"""

15
agent/mod_mikrotik.py

@ -413,6 +413,21 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
if queue.abon != user:
QueueManager.update(self, user)
def ping(self, host, count=10):
r = self._exec_cmd([
'/ip/arp/print',
'?address=%s' % host
])
if r == [{}]:
return
interface = r[0]['=interface']
r = self._exec_cmd([
'/ping', '=address=%s' % host, '=arp-ping=yes', '=interval=100ms', '=count=%d' % count,
'=interface=%s' % interface
])
received, sent = int(r[-2:][0]['=received']), int(r[-2:][0]['=sent'])
return received, sent
# приостановливаем обслуживание абонента
def pause_user(self, user):
pass

6
agent/netflow/netflow_handler.sh

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
FNAME="$1"
@ -14,11 +14,9 @@ PATH=/usr/local/sbin:/usr/local/bin:/usr/bin
TMP_DUMP=/tmp/djing_flow/djing_flow_dump.tmp
cd $CUR_DIR
mkdir -p /tmp/djing_flow
mv $DUMP_FILE $TMP_DUMP
./djing_flow < $TMP_DUMP | /usr/bin/mysql -uUSER -h <IP Database> -p <DBUSER> --password=<DB_PASSWORD>
rm $TMP_DUMP

2
agent/netflow/start_netflow.sh

@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin

125
agent/netflow/to_mysql.c

@ -1,125 +0,0 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#define FLOW_COLS 8
#define uint unsigned int
uint32_t ip2int(const char* ip)
{
uint32_t res = 0;
inet_pton(AF_INET, ip, &res);
return htonl(res);
}
uint str_split(char* str, const char* delimiter, char** pInChunks)
{
char* dat = strtok(str, " ");
register uint n=0;
while(dat)
{
pInChunks[n++] = dat;
dat = strtok(NULL, " ");
}
return n;
}
void curtime(char* pInStrTime, const uint maxlen)
{
time_t rawtime;
time( &rawtime );
strftime(pInStrTime, maxlen, "flowstat_%d%m%Y", localtime( &rawtime ));
}
void convert(char* query, char* pInRes)
{
char* chunks[FLOW_COLS] = {NULL};
int chunk_count = str_split(query, " ", chunks);
if(chunk_count < 7)
{
printf("Too short input line\n");
exit(1);
}
uint32_t src_ip = ip2int(chunks[0]);
uint32_t dst_ip = ip2int(chunks[1]);
uint proto = atoi(chunks[2]);
uint16_t src_port = ip2int(chunks[3]);
uint16_t dst_port = ip2int(chunks[4]);
uint octets = atoi(chunks[5]);
uint packets = atoi(chunks[6]);
sprintf(pInRes, ",(%u,%u,%u,%u,%u,%u,%u)\0",
src_ip, dst_ip, proto, src_port, dst_port, octets, packets);
}
int main()
{
char buf_result_convert[0xff] = {0};
FILE* f = stdin;
char* input_line = malloc(0xff);
size_t input_line_len = 0;
ssize_t read_len = 0;
char table_name[19] = {0};
curtime(table_name, 19);
printf("CREATE TABLE IF NOT EXISTS %s (\n", table_name);
printf("`id` int(10) AUTO_INCREMENT NOT NULL,\n");
printf("`src_ip` INT(10) UNSIGNED NOT NULL,\n");
printf("`dst_ip` INT(10) UNSIGNED NOT NULL,\n");
printf("`proto` smallint(2) unsigned NOT NULL DEFAULT 0,\n");
printf("`src_port` smallint(5) unsigned NOT NULL DEFAULT 0,\n");
printf("`dst_port` smallint(5) unsigned NOT NULL DEFAULT 0,\n");
printf("`octets` INT unsigned NOT NULL DEFAULT 0,\n");
printf("`packets` INT unsigned NOT NULL DEFAULT 0,\n");
printf("PRIMARY KEY (`id`)\n");
printf(") ENGINE=MyISAM DEFAULT CHARSET=utf8;\n");
char ins_sql[0xff] = {0};
sprintf(ins_sql, "INSERT INTO %s(`src_ip`, `dst_ip`, `proto`, `src_port`, `dst_port`, `octets`, `packets`) VALUES", table_name);
// always none
read_len = getline(&input_line, &input_line_len, f);
while(true)
{
register uint n=0xfff;
read_len = getline(&input_line, &input_line_len, f);
if(read_len <= 0)
break;
convert(input_line, buf_result_convert);
printf("%s\n", ins_sql);
// without first comma
printf("%s\n", buf_result_convert+1);
while(n>0)
{
read_len = getline(&input_line, &input_line_len, f);
if(read_len <= 0)
break;
convert(input_line, buf_result_convert);
printf("%s\n", buf_result_convert);
n--;
}
putc(';', stdout);
}
free(input_line);
return 0;
}

66
agent/netflow/to_mysql.py

@ -1,66 +0,0 @@
#!/bin/env python3
import sys
import socket
import struct
from re import sub
from django.utils import timezone
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 = ",(%d,%d,%d,%d,%d,%d,%d)" % (
src_ip, dst_ip, proto, src_port, dst_port, octets, packets
)
return sql
if __name__ == '__main__':
f = sys.stdin
table_name = "flowstat_%s" % timezone.now().strftime("%d%m%Y")
print(("CREATE TABLE IF NOT EXISTS %s (" % table_name))
print("`id` int(10) AUTO_INCREMENT NOT NULL,")
print("`src_ip` INT(10) UNSIGNED NOT NULL,")
print("`dst_ip` INT(10) UNSIGNED NOT NULL,")
print("`proto` smallint(2) unsigned NOT NULL DEFAULT 0,")
print("`src_port` smallint(5) unsigned NOT NULL DEFAULT 0,")
print("`dst_port` smallint(5) unsigned NOT NULL DEFAULT 0,")
print("`octets` INT unsigned NOT NULL DEFAULT 0,")
print("`packets` INT unsigned NOT NULL DEFAULT 0,")
print("PRIMARY KEY (`id`)")
print(") ENGINE=MyISAM DEFAULT CHARSET=utf8;")
ins_sql = r"INSERT INTO %s(`src_ip`, `dst_ip`, `proto`, `src_port`, `dst_port`, `octets`, `packets`) VALUES" % table_name
# always none
f.readline()
while True:
n = 0xfff
rs = convert(f.readline())
if not rs: exit()
# without first comma
print(ins_sql)
print((rs[1:]))
while n > 0:
rs = convert(f.readline())
if not rs: exit()
print(rs)
n -= 1
print(';')
f.close()

1
bugs.txt

@ -7,3 +7,4 @@
- Доделать везде переводы
- Не надо коннектиться к микротику когда не собираемся ничего изменять. А то при сохранении залогинились и вышли без действий
- Не удаляет просроченные услуги если не пингуется NAS
- Надо отменить учёт временной зоны

4
clientsideapp/templates/clientsideapp/ext.html

@ -11,9 +11,7 @@
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/clientside/my_clientside.js"></script>
<link rel="shortcut icon" href="/static/img/favicon_m.ico">
<meta name="author" content="Дмитрий Новиков">
<meta name="feedback" content="https://vk.com/nerosketch">
</head>
/head>
<body>
<!-- Modal -->
<div class="modal fade" id="modFrm" tabindex="-1" role="dialog" aria-hidden="true">

4
clientsideapp/views.py

@ -5,9 +5,9 @@ from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from django.utils import timezone
from abonapp.models import AbonLog, AbonTariff, InvoiceForPayment, Abon, LogicError
from abonapp.models import AbonLog, AbonTariff, InvoiceForPayment, Abon
from tariff_app.models import Tariff
from mydefs import pag_mn, RuTimedelta
from mydefs import pag_mn, RuTimedelta, LogicError
from agent import NasFailedResult, NasNetworkError

3
cron.py

@ -4,8 +4,9 @@ import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
django.setup()
from abonapp.models import Abon, LogicError
from abonapp.models import Abon
from agent import Transmitter, NasNetworkError, NasFailedResult
from mydefs import LogicError
def main():

2
devapp/dev_types.py

@ -62,7 +62,7 @@ class DLinkDevice(DevBase, SNMPBaseWorker):
macs = self.get_list(oids['get_ports']['macs'])
speeds = self.get_list(oids['get_ports']['speeds'])
res = []
ln = len(speeds)
ln = len(macs)
for n in range(ln):
status = True if int(stats[n]) == 1 else False
res.append(DLinkPort(

2
devapp/forms.py

@ -25,7 +25,7 @@ class DeviceForm(forms.ModelForm):
}),
'man_passw': forms.PasswordInput(attrs={
'class': 'form-control'
}),
}, render_value=True),
'map_dot': forms.Select(attrs={
'class': 'form-control'
}),

19
dhcp_lever.py

@ -8,7 +8,6 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
django.setup()
from agent import NasFailedResult, NasNetworkError
from abonapp.models import Abon, Opt82
from ip_pool.models import IpPoolItem
def die(text):
@ -27,17 +26,6 @@ def get_82_opts(switch_mac, switch_port):
return opt82
def get_or_create_pool_item(ip):
try:
ip_item = IpPoolItem.objects.get(ip=ip)
except IpPoolItem.DoesNotExist:
ip_item = IpPoolItem.objects.create(ip=ip)
except MultipleObjectsReturned:
IpPoolItem.objects.filter(ip=ip)[1:].delete()
return get_or_create_pool_item(ip)
return ip_item
def dhcp_commit(client_ip, client_mac, switch_mac, switch_port):
opt82 = get_82_opts(switch_mac, switch_port)
if opt82 is None:
@ -45,7 +33,7 @@ def dhcp_commit(client_ip, client_mac, switch_mac, switch_port):
return
try:
abon = Abon.objects.get(opt82=opt82)
abon.ip_address = get_or_create_pool_item(client_ip)
abon.ip_address = client_ip
abon.is_dhcp = True
abon.save(update_fields=['ip_address'])
print(_('Ip address update for %s successfull') % abon.get_short_name())
@ -55,13 +43,10 @@ def dhcp_commit(client_ip, client_mac, switch_mac, switch_port):
def dhcp_expiry(client_ip):
try:
ip_item = IpPoolItem.objects.get(ip=client_ip)
abon = Abon.objects.get(ip_address=ip_item)
abon = Abon.objects.get(ip_address=client_ip)
abon.ip_address = None
abon.is_dhcp = True
abon.save(update_fields=['ip_address'])
except IpPoolItem.DoesNotExist:
pass
except Abon.DoesNotExist:
pass

1
djing/settings_example.py

@ -30,7 +30,6 @@ INSTALLED_APPS = [
'photo_app',
'abonapp',
'tariff_app',
'ip_pool',
'searchapp',
'devapp',
'mapapp',

1
djing/urls.py

@ -10,7 +10,6 @@ urlpatterns = [
url(r'^accounts/', include('accounts_app.urls', namespace='acc_app')),
url(r'^abons/', include('abonapp.urls', namespace='abonapp')),
url(r'^tarifs/', include('tariff_app.urls', namespace='tarifs')),
url(r'^ip_pool/', include('ip_pool.urls', namespace='ip_pool')),
url(r'^search/', include('searchapp.urls', namespace='searchapp')),
url(r'^dev/', include('devapp.urls', namespace='devapp')),
url(r'^map/', include('mapapp.urls', namespace='mapapp')),

1
ip_pool/__init__.py

@ -1 +0,0 @@
default_app_config = 'ip_pool.apps.IpPoolConfig'

6
ip_pool/admin.py

@ -1,6 +0,0 @@
from django.contrib import admin
from . import models
admin.site.register(models.IpPoolItem)

6
ip_pool/apps.py

@ -1,6 +0,0 @@
from django.apps import AppConfig
class IpPoolConfig(AppConfig):
name = 'ip_pool'
verbose_name = "Ip pool"

22
ip_pool/forms.py

@ -1,22 +0,0 @@
# -*- 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': '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': '127.0.0.1',
'id': 'end_ip',
'class': 'form-control',
'required': ''
}), required=True)

23
ip_pool/migrations/0001_initial.py

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-06-28 23:51
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='ipv4')),
],
),
]

0
ip_pool/migrations/__init__.py

59
ip_pool/models.py

@ -1,59 +0,0 @@
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('Not add over 5000 ip\'s')
sql_strs = [r"(%d)" % tip for tip in range(start_ip, end_ip + 1)]
sql = r'INSERT INTO ip_pool_ippoolitem (ip) VALUES %s' % r",".join(sql_strs)
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 __str__(self):
return self.ip

48
ip_pool/templates/ip_pool/add_pool.html

@ -1,48 +0,0 @@
{% extends 'base.html' %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'ip_pool:home' %}">IP Пул</a></li>
<li class="active">Добавить</li>
</ol>
{% include 'message_block.html' %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Добавить IP пул</h3>
</div>
<div class="panel-body">
<form role="form" action="{% url 'ip_pool:add' %}" method="post">{% csrf_token %}
<div class="form-group">
<label for="start_ip">Начальный ip</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-cutlery"></span></span>
{{ form.start_ip }}{{ form.start_ip.errors }}
</div>
</div>
<div class="form-group">
<label for="">Конечный ip</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-gbp"></span></span>
{{ form.end_ip }}{{ form.end_ip.errors }}
</div>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> Сохранить
</button>
<button type="reset" class="btn btn-sm btn-default">
<span class="glyphicon glyphicon-remove-circle"></span> Сбросить
</button>
</div>
</form>
</div>
</div>
{% endblock %}

73
ip_pool/templates/ip_pool/index.html

@ -1,73 +0,0 @@
{% extends 'base.html' %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li class="active">IP Пул</li>
</ol>
{% include 'message_block.html' %}
<h3>Пулы ip адресов</h3>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Начальный IP</th>
<th>Конечный IP</th>
<th>Количество</th>
<th width="50">Ред.</th>
<th width="50">Уд.</th>
</tr>
</thead>
<tbody>
{% for pl in pools %}
<tr>
<td>{{ pl.0 }}</td>
<td>{{ pl.1 }}</td>
<td>{{ pl.2 }}</td>
<td colspan="2" class="btn-group btn-group-sm btn-group-justified">
<a href="{% url 'ip_pool:ips' %}?ips={{ pl.0 }}&ipe={{ pl.1 }}"
class="btn btn-primary">
<span class="glyphicon glyphicon-edit"></span>
</a>
{% if perms.ip_pool.delete_ippoolitem %}
<a href="{% url 'ip_pool:ips_del' %}?ips={{ pl.0 }}&ipe={{ pl.1 }}" class="btn btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span>
</a>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="5">Нет ни одного пула зарезервированных ip адресов.
{% if perms.ip_pool.add_ippoolitem %}
<a href="{% url 'ip_pool:add' %}">Создать</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
{% if perms.ip_pool.add_ippoolitem %}
<tfoot>
<tr>
<td colspan="5">
<a href="{% url 'ip_pool:add' %}" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-plus"></span>
</a>
</td>
</tr>
</tfoot>
{% endif %}
</table>
</div>
{% include 'toolbar_page.html' with pag=pools %}
{% endblock %}

59
ip_pool/templates/ip_pool/ips.html

@ -1,59 +0,0 @@
{% extends 'base.html' %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'ip_pool:home' %}">IP Пул</a></li>
<li class="active">Редактировать/Просмотреть</li>
</ol>
<h3>История абонента</h3>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="30">Id</th>
<th>Ip</th>
<th>Клиент</th>
<th width="50">Уд.</th>
</tr>
</thead>
<tbody>
{% for pi in pool_ips %}
<tr>
<td>{{ pi.id }}</td>
<td>{{ pi.ip }}</td>
<td>{% if pi.abon %}
<a href="{% url 'abonapp:abon_home' pi.abon.group.id pi.abon.id %}">{{ pi.abon.username }}</a>
{% else %}---{% endif %}
</td>
<td>
{% if pi.abon %}
<button class="btn btn-sm btn-danger disabled">
<span class="glyphicon glyphicon-remove-circle"></span>
</button>
{% elif perms.ip_pool.delete_ippoolitem %}
<a href="{% url 'ip_pool:del_ip' %}?id={{ pi.id }}" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span>
</a>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="4">Нет ни одного пула зарезервированных ip адресов.
{% if perms.ip_pool.add_ippoolitem %}
<a href="{% url 'ip_pool:add' %}">Создать</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include 'toolbar_page.html' with pag=pool_ips %}
{% endblock %}

15
ip_pool/urls.py

@ -1,15 +0,0 @@
# -*- coding:utf-8 -*-
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.home, name='home'),
url(r'^range$', views.ips, name='ips'),
url(r'^del$', views.del_pool, name='ips_del'),
url(r'^add$', views.add_pool, name='add'),
url(r'^delip$', views.delip, name='del_ip')
]

81
ip_pool/views.py

@ -1,81 +0,0 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from .forms import PoolForm
from .models import IpPoolItem
import mydefs
@login_required
@mydefs.only_admins
def home(request):
pools = IpPoolItem.objects.get_pools()
if pools:
pools = [(mydefs.int2ip(ip[0]), mydefs.int2ip(ip[1]), ip[2]) for ip in pools]
pools = mydefs.pag_mn(request, pools)
return render(request, 'ip_pool/index.html', {
'pools': pools
})
@login_required
@mydefs.only_admins
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
@permission_required('ip_pool.delete_ippoolitem')
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, 'ip_pool:home')
@login_required
@permission_required('ip_pool.add_ippoolitem')
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('ip_pool:home')
else:
messages.error(request, 'Исправьте ошибки')
else:
frm = PoolForm()
return render(request, 'ip_pool/add_pool.html', {
'form': frm
})
@login_required
@permission_required('ip_pool.delete_ippoolitem')
def delip(request):
ipid = request.GET.get('id')
get_object_or_404(IpPoolItem, id=ipid).delete()
return mydefs.res_success(request, 'ip_pool:home')

17
mydefs.py

@ -75,12 +75,12 @@ class MyGenericIPAddressField(models.GenericIPAddressField):
description = "Int32 notation ip address"
def __init__(self, protocol='ipv4', *args, **kwargs):
super().__init__(protocol=protocol, *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().get_prep_value(value)
value = super(MyGenericIPAddressField, self).get_prep_value(value)
return ip2int(value)
def to_python(self, addr):
@ -91,7 +91,10 @@ class MyGenericIPAddressField(models.GenericIPAddressField):
@staticmethod
def from_db_value(value, expression, connection, context):
return int2ip(value)
return int2ip(value) if value != 0 else None
def int_ip(self):
return ip2int(self)
# Предназначен для Django CHOICES чтоб можно было передавать классы вместо просто описания поля,
@ -149,8 +152,8 @@ def only_admins(fn):
return wrapped
def ping(hostname):
response = os.system("`which ping` -c 1 %s > /dev/null" % hostname)
def ping(hostname, count=1):
response = os.system("`which ping` -4Anq -c%d -W1 %s > /dev/null" % (count, hostname))
return True if response == 0 else False
@ -205,3 +208,7 @@ class MultipleException(Exception):
if not isinstance(err_list, list):
raise TypeError
self.err_list = err_list
class LogicError(Exception):
pass

16
searchapp/views.py

@ -1,10 +1,9 @@
import re
from django.db.models import Q
from django.shortcuts import render
from django.utils.html import escape
from abonapp.models import Abon, IpPoolItem
from abonapp.models import Abon
from mydefs import ip_addr_regex
def replace_without_case(orig, old, new):
@ -15,11 +14,12 @@ def home(request):
s = request.GET.get('s')
if s:
ips = IpPoolItem.objects.filter(ip=s)
query = Q(fio__icontains=s) | Q(username__icontains=s) | Q(telephone__icontains=s)
if ips.count() > 0:
query |= Q(ip_address__in=ips)
abons = Abon.objects.filter(query)
if bool(re.match(ip_addr_regex, s)):
abons = Abon.objects.filter(ip_address=s)
else:
abons = Abon.objects.filter(
Q(fio__icontains=s) | Q(username__icontains=s) | Q(telephone__icontains=s)
)
else:
abons = []

4
static/css/all.min.css
File diff suppressed because it is too large
View File

8
static/css/custom.css

@ -231,3 +231,11 @@ button[data-toggle=offcanvas]{
background-color: #44596b;
border: 0;
}
/*
* Цвет заливки графика
*/
.ct-series-a .ct-area {
fill: black;
}

31
static/js/all.min.js
File diff suppressed because it is too large
View File

2
static/js/bootstrap-datetimepicker.min.js
File diff suppressed because it is too large
View File

562
static/js/datetime_with_moment.min.js
File diff suppressed because it is too large
View File

20
static/js/my.js

@ -129,4 +129,24 @@ $(document).ready(function () {
return false;
});
// кнопка посылающая комманду и возвращающая результат выполнения
$('.btn-cmd').on('click', function(){
var cmd_param = $(this).attr('data-param');
var self = $(this);
self.removeClass('btn-default');
self.removeClass('btn-danger');
self.removeClass('btn-success');
self.addClass('btn-info');
self.html('<span class="glyphicon glyphicon-refresh"></span> Подождите...');
$.getJSON(this.href, {cmd_param: cmd_param}, function(r){
self.removeClass('btn-info');
if(r.status == 0)
self.addClass('btn-success');
else
self.addClass('btn-danger');
self.html(r.dat);
});
return false;
})
});

6
statistics/admin.py

@ -1,6 +0,0 @@
from django.contrib import admin
from . import models
admin.site.register(models.StatElem)

53
statistics/fields.py

@ -0,0 +1,53 @@
#
# Get from https://github.com/Niklas9/django-unixdatetimefield
#
import datetime
import time
import django.db.models as models
class UnixDateTimeField(models.DateTimeField):
# TODO(niklas9):
# * should we take care of transforming between time zones in any way here ?
# * get default datetime format from settings ?
DEFAULT_DATETIME_FMT = '%Y-%m-%d %H:%M:%S'
TZ_CONST = '+'
# TODO(niklas9):
# * metaclass below just for Django < 1.9, fix a if stmt for it?
#__metaclass__ = models.SubfieldBase
description = "Unix timestamp integer to datetime object"
def get_internal_type(self):
return 'PositiveIntegerField'
def to_python(self, val):
if val is None or isinstance(val, datetime.datetime):
return val
if isinstance(val, datetime.date):
return datetime.datetime(val.year, val.month, val.day)
elif self._is_string(val):
# TODO(niklas9):
# * not addressing time zone support as todo above for now
if self.TZ_CONST in val:
val = val.split(self.TZ_CONST)[0]
return datetime.datetime.strptime(val, self.DEFAULT_DATETIME_FMT)
else:
return datetime.datetime.fromtimestamp(float(val))
def _is_string(value, val):
return isinstance(val, str)
def get_db_prep_value(self, val, *args, **kwargs):
if val is None:
if self.default == models.fields.NOT_PROVIDED: return None
return self.default
return int(time.mktime(val.timetuple()))
def value_to_string(self, obj):
val = self._get_val_from_obj(obj)
return self.to_python(val).strftime(self.DEFAULT_DATETIME_FMT)
def from_db_value(self, val, expression, connection, context):
return self.to_python(val)

18
statistics/migrations/0002_delete_statelem.py

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-04-25 13:27
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('statistics', '0001_initial'),
]
operations = [
migrations.DeleteModel(
name='StatElem',
),
]

64
statistics/models.py

@ -1,18 +1,66 @@
import math
from datetime import datetime, timedelta
from django.db import models, ProgrammingError
from django.utils import timezone
from mydefs import MyGenericIPAddressField
from .fields import UnixDateTimeField
from mydefs import LogicError
from django.db import models
class StatManager(models.Manager):
from mydefs import MyGenericIPAddressField
def traffic_by_ip(self, ip):
try:
traf = self.order_by('-cur_time').filter(ip=ip, octets__gt=524288)[0]
now = datetime.now()
if traf.cur_time < now - timedelta(minutes=55):
return False, traf
else:
return True, traf
except IndexError:
return False, None
except ProgrammingError as e:
raise LogicError(e)
class StatElem(models.Model):
src_ip = MyGenericIPAddressField()
dst_ip = MyGenericIPAddressField()
proto = models.PositiveSmallIntegerField(default=0)
src_port = models.PositiveIntegerField(default=0)
dst_port = models.PositiveIntegerField(default=0)
cur_time = UnixDateTimeField(primary_key=True)
ip = MyGenericIPAddressField()
octets = models.PositiveIntegerField(default=0)
packets = models.PositiveIntegerField(default=0)
objects = StatManager()
@staticmethod
def percentile(N, percent, key=lambda x:x):
"""
Find the percentile of a list of values.
@parameter N - is a list of values. Note N MUST BE already sorted.
@parameter percent - a float value from 0.0 to 1.0.
@parameter key - optional key function to compute value from each element of N.
@return - the percentile of the values
"""
if not N:
return None
k = (len(N)-1) * percent
f = math.floor(k)
c = math.ceil(k)
if f == c:
return key(N[int(k)])
d0 = key(N[int(f)]) * (c-k)
d1 = key(N[int(c)]) * (k-f)
return d0+d1
class Meta:
db_table = 'flowstat'
abstract = True
def getModel():
class DynamicStatElem(StatElem):
class Meta:
db_table = 'flowstat_%s' % timezone.now().strftime("%d%m%Y")
abstract = False
return DynamicStatElem

12
systemd_units/djing_rotate.service

@ -0,0 +1,12 @@
[Unit]
Description=A job for rotate djing netflow data
[Service]
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/bin"
Type=oneshot
ExecStart=/bin/bash -c "kill -HUP `cat /run/flow.pid.6343`"
User=root
Group=root
[Install]
WantedBy=multi-user.target

11
systemd_units/djing_rotate.timer

@ -0,0 +1,11 @@
[Unit]
Description=Run every one minute rotate flows for djing
[Timer]
OnCalendar=*-*-* *:*:59
Persistent=true
RandomizedDelaySec=5
Unit=djing_rotate.service
[Install]
WantedBy=timers.target

15
taskapp/templates/taskapp/add_edit_task.html

@ -4,8 +4,8 @@
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'taskapp:home' %}">{% trans 'Tasks' %}</a></li>
<li class="active">{% if task_id %}{% trans 'Edit' %}{% else %}{% trans 'Create' %}{% endif %}</li>
<li><a href="{% url 'taskapp:home' %}">{% trans 'Tasks' %}</a></li>
<li class="active">{% if task_id %}{% trans 'Edit' %}{% else %}{% trans 'Create' %}{% endif %}</li>
</ol>
{% include 'message_block.html' %}
@ -32,7 +32,7 @@
</div>
</div>
<div class="form-group">
<label for="id_mode">{% trans 'The nature of the damage' %}</label>
<label for="id_mode">{% trans 'The nature of the damage' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-pawn"></span></span>
@ -40,7 +40,7 @@
</div>
</div>
<div class="form-group">
<label for="id_priority">{% trans 'A priority' %}</label>
<label for="id_priority">{% trans 'A priority' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-sort-by-order"></span></span>
@ -48,7 +48,7 @@
</div>
</div>
<div class="form-group">
<label for="id_state">{% trans 'Condition' %}</label>
<label for="id_state">{% trans 'Condition' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-retweet"></span></span>
@ -56,7 +56,7 @@
</div>
</div>
<div class="form-group">
<label for="id_abon">{% trans 'Subscriber' %}</label>
<label for="id_abon">{% trans 'Subscriber' %}</label>
<div class="input-group selectajax">
<span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
@ -72,13 +72,12 @@
</div>
</div>
<div class="form-group">
<label for="id_out_date">{% trans 'Reality (the date by which you must complete the task)' %}</label>
<label for="id_out_date">{% trans 'Reality (the date by which you must complete the task)' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>
{{ form.out_date }}{{ form.out_date.errors }}
</div>
<script type="text/javascript" src="/static/js/datetime_with_moment.min.js"></script>
<script type="text/javascript">
$(function () {
$('#id_out_date').datetimepicker({

7
templates/404.html

@ -8,7 +8,6 @@
<link rel="shortcut icon" href="/static/img/favicon_m.ico">
<!-- Core CSS - Include with every page -->
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
<style>
.no-border-on-me>thead>tr>th,
@ -38,9 +37,9 @@
<div class="col-md-6 col-md-offset-3">
<div align="center"><h1>404 - Страница не найдена</h1></div>
<div align="center">
<p>Такого пути '{{ request.path }}' нет нет на сервере</p>
<a href="/accounts/">На главную</a>
</div>
<p>Такого пути '{{ request.path }}' нет нет на сервере</p>
<a href="/accounts/">На главную</a>
</div>
</div>
</div>
</div>

15
templates/base.html

@ -57,18 +57,11 @@
<span class="glyphicon glyphicon-map-marker"></span> Настройки карты
</a></li>
{% url 'django_messages:messages_inbox' as privmsg_home %}
<li{% if privmsg_home in request.path %} class="active"{% endif %}>
<a href="{{ privmsg_home }}">
<!--{ % url 'django_messages:messages_inbox' as privmsg_home % }
<li{ % if privmsg_home in request.path %} class="active"{ % endif % }>
<a href="{ { privmsg_home } }">
<span class="glyphicon glyphicon-envelope"></span> внутренняя переписка
</a></li>
{% url 'ip_pool:home' as ip_pool_home %}
<li{% if ip_pool_home in request.path %} class="active"{% endif %}>
<a href="{{ ip_pool_home }}">
<span class="glyphicon glyphicon-compressed"></span> ip пул
</a></li>
</a></li>-->
{% url 'devapp:group_list' as devapp_groups %}
<li{% if devapp_groups in request.path %} class="active"{% endif %}>

4
templates/message_block.html

@ -3,12 +3,16 @@
{% if message.tags == 'error' %}
<div class="alert alert-danger alert-dismissable">
<span class="glyphicon glyphicon-exclamation-sign"></span>
{% elif message.tags == 'warning' %}
<div class="alert alert-warning alert-dismissable">
<span class="glyphicon glyphicon-warning-sign"></span>
{% elif message.tags == 'success' %}
<div class="alert alert-success alert-dismissable">
<span class="glyphicon glyphicon-ok-circle"></span>
{% elif message.tags == 'info' %}
<div class="alert alert-info alert-dismissable">
<span class="glyphicon glyphicon-info-sign"></span>
{% endif %}
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>

Loading…
Cancel
Save