bashmak 8 years ago
parent
commit
12fd7499d4
  1. 17
      abonapp/locale/ru/LC_MESSAGES/django.po
  2. 1
      abonapp/models.py
  3. 2
      abonapp/templates/abonapp/dial_log.html
  4. 44
      abonapp/templates/abonapp/editAbon.html
  5. 4
      abonapp/templates/abonapp/ext.htm
  6. 10
      abonapp/templates/abonapp/group_list.html
  7. 32
      abonapp/templates/abonapp/modal_phonebook.html
  8. 41
      abonapp/templates/abonapp/peoples.html
  9. 2
      abonapp/urls_abon.py
  10. 54
      abonapp/views.py
  11. 4
      accounts_app/locale/ru/LC_MESSAGES/django.po
  12. 29
      accounts_app/templates/accounts/acc_list.html
  13. 10
      accounts_app/templates/accounts/ext.htm
  14. 4
      accounts_app/templates/accounts/index.html
  15. 13
      accounts_app/templates/accounts/login.html
  16. 14
      accounts_app/templates/accounts/settings/test.html
  17. 7
      agent/commands/dhcp.py
  18. 20
      agent/mod_mikrotik.py
  19. 8
      bugs.txt
  20. 1
      chatbot/admin.py
  21. 78
      chatbot/locale/ru/LC_MESSAGES/django.po
  22. 59
      chatbot/migrations/0002_auto_20171214_1517.py
  23. 52
      chatbot/models.py
  24. 17
      chatbot/telebot.py
  25. 5
      clientsideapp/templates/clientsideapp/ext.html
  26. 12
      clientsideapp/templates/clientsideapp/services.html
  27. 8
      clientsideapp/views.py
  28. 17
      cron.py
  29. 16
      devapp/base_intr.py
  30. 79
      devapp/dev_types.py
  31. 28
      devapp/forms.py
  32. 28
      devapp/locale/ru/LC_MESSAGES/django.po
  33. 72
      devapp/migrations/0004_auto_20171103_0006.py
  34. 20
      devapp/migrations/0005_device_snmp_item_num.py
  35. 112
      devapp/models.py
  36. 87
      devapp/templates/devapp/add_dev.html
  37. 22
      devapp/templates/devapp/custom_dev_page/olt.html
  38. 59
      devapp/templates/devapp/custom_dev_page/onu.html
  39. 2
      devapp/templates/devapp/custom_dev_page/ports.html
  40. 100
      devapp/templates/devapp/dev.html
  41. 36
      devapp/templates/devapp/devices.html
  42. 82
      devapp/templates/devapp/fix_dev_group.html
  43. 2
      devapp/templates/devapp/group_list.html
  44. 33
      devapp/templates/devapp/manage_ports/modal_add_edit_port.html
  45. 2
      devapp/urls.py
  46. 122
      devapp/views.py
  47. 44
      dhcp_lever.py
  48. 13
      dialing_app/locale/ru/LC_MESSAGES/django.po
  49. 4
      dialing_app/models.py
  50. 12
      dialing_app/templates/ext.html
  51. 23
      dialing_app/templates/index.html
  52. 10
      dialing_app/templates/vmail.html
  53. 4
      dialing_app/urls.py
  54. 30
      dialing_app/views.py
  55. 6
      djing/settings_example.py
  56. 8
      djing/urls.py
  57. 69
      docs/dev.md
  58. 12
      global_context_processors.py
  59. 7
      mapapp/forms.py
  60. 167
      mapapp/locale/ru/LC_MESSAGES/django.po
  61. 45
      mapapp/migrations/0002_auto_20171103_0006.py
  62. 15
      mapapp/models.py
  63. 70
      mapapp/templates/maps/add_device.html
  64. 118
      mapapp/templates/maps/dot.html
  65. 62
      mapapp/templates/maps/index.html
  66. 19
      mapapp/templates/maps/map_tooltip.html
  67. 20
      mapapp/templates/maps/modal_add_device.html
  68. 28
      mapapp/templates/maps/modal_add_dot.html
  69. 38
      mapapp/templates/maps/options.html
  70. 7
      mapapp/templates/maps/preload_devices_tmpl.html
  71. 121
      mapapp/templates/maps/ya_index.html
  72. 6
      mapapp/urls.py
  73. 149
      mapapp/views.py
  74. 5
      msg_app/admin.py
  75. 2
      msg_app/locale/ru/LC_MESSAGES/django.po
  76. 8
      msg_app/models.py
  77. 4
      msg_app/templates/msg_app/chat.html
  78. 3
      msg_app/urls.py
  79. 17
      msg_app/views.py
  80. 9
      mydefs.py
  81. 69
      queue_mngr.py
  82. 5
      requirements.txt
  83. 48
      static/clientside/ISPlaylist.m3u
  84. 19
      static/clientside/custom.css
  85. 27
      static/css/custom.css
  86. BIN
      static/img/loading.gif
  87. BIN
      static/img/noticon.png
  88. 111
      static/js/my.js
  89. 2
      systemd_units/djing_telebot.service
  90. 20
      systemd_units/do_backup.sh
  91. 20
      systemd_units/webdav_backup.py
  92. 6
      taskapp/handle.py
  93. 8
      taskapp/locale/ru/LC_MESSAGES/django.po
  94. 2
      taskapp/models.py
  95. 2
      taskapp/templates/taskapp/add_edit_task.html
  96. 19
      taskapp/templates/taskapp/footer_btns.html
  97. 10
      taskapp/templates/taskapp/tasklist.html
  98. 8
      taskapp/templates/taskapp/tasklist_all.html
  99. 10
      taskapp/templates/taskapp/tasklist_failed.html
  100. 11
      taskapp/templates/taskapp/tasklist_finish.html

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

@ -877,7 +877,7 @@ msgstr "Графики"
#: abonapp/templates/abonapp/ext.html:26
msgid "Sub information"
msgstr "Информация абонента"
msgstr "Инфо."
msgid "Streets"
msgstr "Улицы"
@ -1005,3 +1005,18 @@ msgstr "Может пинговать"
msgid "Can view additional telephones"
msgstr "Может видеть дополнительные телефоны"
#: abonapp/views.py:694
#, python-format
msgid "IP Conflict! %d/%d results"
msgstr "IP Конфликт! ping %d из %d"
msgid "Phonebook"
msgstr "Телефонная книга"
msgid "Reset ip"
msgstr "Сбросить ip"
msgid "Export to csv"
msgstr "Сохранить в csv"

1
abonapp/models.py

@ -172,6 +172,7 @@ class Abon(UserProfile):
('can_add_ballance', _('fill account')),
('can_ping', _('Can ping'))
)
unique_together = ('device', 'dev_port')
verbose_name = _('Abon')
verbose_name_plural = _('Abons')

2
abonapp/templates/abonapp/dial_log.html

@ -21,7 +21,7 @@
<tr>
<td>
<audio preload="metadata" controls>
<source src="{{ log.path_to_media }}/{{ log.calldate|date:"YmdHi" }}-{{ log.src }}-{{ log.dst }}.wav" type="audio/wav"/>
<source src="{{ log.url }}" type="audio/wav"/>
</audio>
</td>
<td>{{ log.calldate|date:'d E Y, H:i:s' }}</td>

44
abonapp/templates/abonapp/editAbon.html

@ -4,13 +4,13 @@
{% block content %}
<div class="row">
<div class="col-sm-6">
<div class="col-sm-12 col-xs-12 col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Change subscriber' %}</h3>
</div>
<div class="panel-body">
<form role="form" class="form-horizontal" action="{% url 'abonapp:abon_home' abon_group.pk abon.pk %}" method="post">{% csrf_token %}
<form role="form" autocomplete="off" class="form-horizontal" action="{% url 'abonapp:abon_home' abon_group.pk abon.pk %}" method="post">{% csrf_token %}
<div class="form-group-sm">
<label for="id_username" class="col-sm-4 control-label">{% trans 'login' %}</label>
@ -49,7 +49,14 @@
<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.is_dynamic_ip %} disabled{% endif %}/>
<div class="input-group input-group-sm">
<input type="text" value="{{ ip|default:'' }}" class="form-control" name="ip" placeholder="{% trans 'Not assigned' %}" id="ipfield" 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.is_dynamic_ip %} disabled{% endif %}/>
<span class="input-group-btn">
<a href="{% url 'abonapp:reset_ip' abon_group.pk abon.pk %}" class="btn btn-default btn-cmd" data-toggle="tooltip" title="{% trans 'Reset ip' %}" onclick="$('#ipfield').val('');">
<span class="glyphicon glyphicon-refresh"></span>
</a>
</span>
</div>
</div>
</div>
@ -111,7 +118,7 @@
</button>
{% if perms.taskapp.add_task %}
<a href="{% url 'taskapp:add' %}?uid={{ abon.username }}" class="btn btn-sm btn-success" title="{% trans 'Add new task' %}">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add new task' %}
<span class="glyphicon glyphicon-plus"></span> <span class="hidden-xs">{% trans 'Add new task' %}</span>
</a>
{% endif %}
</div>
@ -131,7 +138,7 @@
</div>
</div>
</div>
<div class="col-sm-6">
<div class="col-sm-12 col-xs-12 col-md-6">
{% if perms.abonapp.change_abon %}
<div class="panel panel-default">
<div class="panel-heading">
@ -142,14 +149,14 @@
<form role="form" class="form-horizontal" action="{% url 'abonapp:save_user_dev_port' abon_group.pk abon.pk %}" method="post">{% csrf_token %}
<div class="form-group-sm">
<label for="id_method" class="col-sm-2 control-label">{% trans 'Device' %}</label>
<div class="col-sm-10 btn-group btn-group-sm">
<label for="id_method" class="col-sm-4 control-label">{% trans 'Device' %}</label>
<div class="col-sm-8 btn-group btn-group-sm">
{% if device %}
<a href="{% url 'devapp:view' abon_group.pk device.pk %}" target="_blank" class="btn btn-sm btn-default" title="{% trans 'Mac Address' %} {{ device.mac_addr }}">
<span class="glyphicon glyphicon-hdd"></span> {{ device.comment|truncatechars:11 }} {{ device.ip_address }}
<a href="{% url 'devapp:view' abon_group.pk device.pk %}" target="_blank" class="btn btn-sm btn-default" title="{% trans 'Mac Address' %}: {{ device.mac_addr|default:_('Not assigned') }}">
<span class="glyphicon glyphicon-hdd"></span> <span class="hidden-md">{{ device.comment|truncatechars:11 }} {{ device.ip_address }}</span>
</a>
<a href="{% url 'abonapp:clear_dev' abon_group.pk abon.pk %}" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Remove clutch' %}
<span class="glyphicon glyphicon-remove-circle"></span> <span class="hidden-xs hidden-lg">{% trans 'Remove clutch' %}</span>
</a>
{% else %}
<a href="{% url 'abonapp:dev' abon_group.pk abon.pk %}" class="btn btn-success btn-sm btn-modal">
@ -161,8 +168,8 @@
{% if device %}
<div class="form-group-sm">
<label for="id_dev_port" class="col-sm-2 control-label">{% trans 'Device port' %}</label>
<div class="col-sm-10">
<label for="id_dev_port" class="col-sm-4 control-label">{% trans 'Device port' %}</label>
<div class="col-sm-8">
<select id="id_dev_port" class="form-control" name="user_port">
<option value="0">{% trans 'Not assigned' %}</option>
{% for port in dev_ports %}
@ -178,20 +185,17 @@
</div>
</div>
<div class="form-group-sm">
<div class="col-sm-offset-2 col-sm-10 checkbox">
<div class="col-sm-offset-4 col-sm-8 checkbox">
<label>
<input type="checkbox" name="is_dynamic_ip"{% if abon.is_dynamic_ip %} checked{% endif %}> {% trans 'Is dynamic network settings' %}
</label>
</div>
</div>
<div class="form-group-sm">
<div class="col-sm-offset-2 col-sm-10 btn-group btn-group-sm">
<div class="col-sm-8 col-sm-offset-4">
<button type="submit" class="btn btn-primary btn-sm">
<span class="glyphicon glyphicon-floppy-disk"></span> {% trans 'Save' %}
</button>
<button class="btn btn-default" type="reset" title="Reset">
<span class="glyphicon glyphicon-repeat"></span>
</button>
</div>
</div>
{% endif %}
@ -209,8 +213,8 @@
{% for ef in abon.extra_fields.all %}
<div class="form-group-sm">
<label class="col-sm-2 control-label">{{ ef.title }}</label>
<div class="col-sm-10">
<label class="col-sm-4 control-label">{{ ef.title }}</label>
<div class="col-sm-8">
<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">
@ -229,7 +233,7 @@
{% endfor %}
<div class="form-group-sm">
<div class="col-sm-offset-2 col-sm-10 btn-group btn-group-sm">
<div class="col-sm-offset-4 col-sm-8 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>

4
abonapp/templates/abonapp/ext.htm

@ -14,12 +14,12 @@
<div class="page-header">
<h2>{{ abon.fio|default:_("Long name") }}
<small>{% trans 'Ballance' %} <i class="glyphicon glyphicon-ruble"></i>
<b>{{ abon.ballance }}</b>
<b>{{ abon.ballance|floatformat:2 }}</b>
</small>
</h2>
</div>
<ul class="nav nav-tabs">
<ul class="nav nav-tabs nav-justified">
{% url 'abonapp:abon_home' abon_group.pk abon.pk as abon_home %}
<li{% if abon_home == request.path %} class="active"{% endif %}>

10
abonapp/templates/abonapp/group_list.html

@ -23,7 +23,7 @@
</a>
{% if order_by == 'title' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th width="100">
<th width="100" class="hidden-xs">
{% trans 'Number of subscribers' %}
</th>
<th width="100">#</th>
@ -34,7 +34,7 @@
<tr>
<td>{{ gr.pk }}</td>
<td><a href="{% url 'abonapp:people_list' gr.pk %}">{{ gr.title }}</a></td>
<td>{{ gr.usercount }}</td>
<td class="hidden-xs">{{ gr.usercount }}</td>
<td class="btn-group">
{% if perms.abonapp.delete_abongroup %}
{% if gr.usercount == 0 %}
@ -60,17 +60,17 @@
<td colspan="4" class="btn-group">
{% if perms.abonapp.add_abongroup %}
<a href="{% url 'abonapp:add_group' %}" class="btn btn-success btn-sm">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add group' %}
<span class="glyphicon glyphicon-plus"></span> <span class="hidden-xs">{% trans 'Add group' %}</span>
</a>
{% endif %}
{% if perms.abonapp.can_view_abonlog %}
<a href="{% url 'abonapp:log' %}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-record"></span> {% trans 'Subscribers actions' %}
<span class="glyphicon glyphicon-record"></span> <span class="hidden-xs">{% trans 'Subscribers actions' %}</span>
</a>
{% endif %}
{% if perms.abonapp.can_view_invoiceforpayment %}
<a href="{% url 'abonapp:debtors' %}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-exclamation-sign"></span> {% trans 'List of debtors' %}
<span class="glyphicon glyphicon-exclamation-sign"></span> <span class="hidden-xs">{% trans 'List of debtors' %}</span>
</a>
{% endif %}
</td>

32
abonapp/templates/abonapp/modal_phonebook.html

@ -0,0 +1,32 @@
{% load i18n %}
<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-earphone"></span>{% trans 'Phonebook' %}</h4>
</div>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Telephone' %}</th>
<th>{% trans 'Telephone owner' %}</th>
</tr>
</thead>
<tbody>
{% for t in tels %}
<tr>
<td><a href="sip:{{ t.0 }}" class="btn btn-link">{{ t.0 }}</a></td>
<td>{{ t.1 }}</td>
</tr>
{% empty %}
<tr>
<td colspan="2">{% trans 'Telephone numbers not found' %}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="modal-footer">
<a href="{% url 'abonapp:phonebook' gid %}?f=csv" class="btn btn-default" target="_blank">
<span class="glyphicon glyphicon-export"></span> {% trans 'Export to csv' %}
</a>
</div>

41
abonapp/templates/abonapp/peoples.html

@ -14,52 +14,52 @@
<h3>{% trans 'The people in the selected group' %}</h3>
<div class="row">
<div class="col-lg-10">
<div class="col-lg-10 col-md-8">
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th>
<th class="col-xs-1">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=username dir=dir|default:"down" %}">
{% trans 'Sub' %}
</a>
{% if order_by == 'username' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th>{% trans 'Last traffic' %}</th>
<th>
<th class="hidden-xs">{% trans 'Last traffic' %}</th>
<th class="col-xs-1 hidden-md">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=ip_address dir=dir|default:"down" %}">
{% trans 'Ip address' %}
</a>
{% if order_by == 'ip_address' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th>
<th class="col-xs-2">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=fio dir=dir|default:"down" %}">
{% trans 'fio' %}
</a>
{% if order_by == 'fio' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th>
<th class="col-xs-2">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=street dir=dir|default:"down" %}">
{% trans 'Street' %}
</a>
{% if order_by == 'street' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th width="100">
<th class="col-xs-1">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=house dir=dir|default:"down" %}">
{% trans 'Apartment' %}
</a>
{% 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="50">
<th class="col-xs-2">{% trans 'Telephone' %}</th>
<th class="col-xs-2">{% trans 'Service' %}</th>
<th class="hidden-xs col-sm-1">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=ballance dir=dir|default:"down" %}">
{% trans 'Ballance' %}
</a>
{% if order_by == 'ballance' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th width="10">#</th>
<th class="hidden-xs">#</th>
</tr>
</thead>
<tbody>
@ -78,7 +78,7 @@
<td>
<a href="{% url 'abonapp:abon_home' human.group.pk human.pk %}">{{ human.username }}</a>
</td>
<td>
<td class="hidden-xs">
{% if human.stat_cache %}
{% if human.stat_cache.is_today %}
{{ human.stat_cache.last_time|date:"H:i" }}
@ -87,7 +87,7 @@
{% endif %}
{% endif %}
</td>
<td>{{ human.ip_address|default:_('Not assigned') }}</td>
<td class="hidden-md">{{ human.ip_address|default:_('Not assigned') }}</td>
<td>{{ human.fio }}</td>
<td>{{ human.street|default:_('Not assigned') }}</td>
<td>{{ human.house|default:'-' }}</td>
@ -102,8 +102,8 @@
{% else %}&mdash;&mdash;&mdash;
{% endif %}
</td>
<td>{{ human.ballance }}</td>
<td>
<td class="hidden-xs">{{ human.ballance|floatformat:2 }}</td>
<td class="hidden-xs">
{% if can_del_trf %}
<a href="{% url 'abonapp:del_abon' %}?id={{ human.pk }}" class="btn btn-danger btn-sm">
<span class="glyphicon glyphicon-remove"></span>
@ -125,22 +125,25 @@
</tbody>
<tfoot>
<tr>
<td colspan="11" class="btn-group">
<td colspan="11" class="btn-group btn-group-sm">
{% if perms.abonapp.add_abon %}
<a href="{% url 'abonapp:add_abon' abon_group.pk %}" class="btn btn-sm btn-default" title="{% trans 'Add' %}" data-toggle="tooltip">
<a href="{% url 'abonapp:add_abon' abon_group.pk %}" class="btn btn-default" title="{% trans 'Add' %}" data-toggle="tooltip">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add abon' %}
</a>
{% endif %}
<a href="{% url 'abonapp:ch_group_tariff' abon_group.pk %}" class="btn btn-sm btn-default" title="{% trans 'User groups' %}" data-toggle="tooltip">
<a href="{% url 'abonapp:ch_group_tariff' abon_group.pk %}" class="btn btn-default" title="{% trans 'User groups' %}" data-toggle="tooltip">
<span class="glyphicon glyphicon-subscript"></span> {% trans 'Tariffs in groups' %}
</a>
<a href="{% url 'abonapp:phonebook' abon_group.pk %}" class="btn btn-default btn-modal">
<span class="glyphicon glyphicon-earphone"></span> {% trans 'Phonebook' %}
</a>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="col-lg-2 sidebar-offcanvas">
<div class="col-lg-2 col-md-4">
<div class="panel panel-default">
<div class="panel-heading">{% trans 'Streets' %}</div>
<div class="list-group">

2
abonapp/urls_abon.py

@ -6,6 +6,7 @@ urlpatterns = [
url(r'^$', views.peoples, name='people_list'),
url(r'^addabon$', views.addabon, name='add_abon'),
url(r'^services$', views.chgroup_tariff, name='ch_group_tariff'),
url(r'^phonebook$', views.phonebook, name='phonebook'),
url(r'^street/add$', views.street_add, name='street_add'),
url(r'^street/edit', views.street_edit, name='street_edit'),
url(r'^street/(?P<sid>\d+)/delete$', views.street_del, name='street_del'),
@ -21,6 +22,7 @@ urlpatterns = [
url(r'^(?P<uid>\d+)/passport_view$', views.passport_view, name='passport_view'),
url(r'^(?P<uid>\d+)/chart$', views.charts, name='charts'),
url(r'^(?P<uid>\d+)/dials$', views.dials, name='dials'),
url(r'^(?P<uid>\d+)/reset_ip$', views.reset_ip, name='reset_ip'),
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'),

54
abonapp/views.py

@ -3,7 +3,7 @@ from json import dumps
from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied
from django.db import IntegrityError, ProgrammingError
from django.db.models import Count, Q
from django.db.models import Count, Q, signals
from django.db.transaction import atomic
from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.contrib.auth.decorators import login_required
@ -18,7 +18,7 @@ from . import forms
from . import models
import mydefs
from devapp.models import Device, Port as DevPort
from datetime import datetime, date
from datetime import datetime, date, timedelta
from taskapp.models import Task
from dialing_app.models import AsteriskCDR
from statistics.models import getModel, get_dates
@ -286,7 +286,9 @@ def abonhome(request, gid, uid):
raise PermissionDenied
frm = forms.AbonForm(request.POST, instance=abon)
if frm.is_valid():
abon.ip_address = request.POST.get('ip')
newip = request.POST.get('ip')
if newip:
abon.ip_address = newip
frm.save()
messages.success(request, _('edit abon success msg'))
else:
@ -387,6 +389,7 @@ def pick_tariff(request, gid, uid):
abon.pick_tariff(trf, request.user)
else:
deadline = datetime.strptime(deadline, '%Y-%m-%d')
deadline += timedelta(hours=23, minutes=59, seconds=59)
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)
@ -687,12 +690,14 @@ def abon_ping(request):
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:
print(ping_result, loses_percent)
if loses_percent > 1.0:
text = '<span class="glyphicon glyphicon-exclamation-sign"></span> %s' % _('IP Conflict! %d/%d results') % ping_result
elif 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
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
@ -865,6 +870,43 @@ def tel_del(request, gid, uid):
return redirect('abonapp:abon_home', gid, uid)
@login_required
@permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid'))
def phonebook(request, gid):
res_format = request.GET.get('f')
t1 = models.Abon.objects.filter(group__id=int(gid)).only('telephone', 'fio').values_list('telephone', 'fio')
t2 = models.AdditionalTelephone.objects.filter(abon__group__id=gid).only('telephone', 'owner_name').values_list('telephone', 'owner_name')
tels = list(t1) + list(t2)
if res_format == 'csv':
import csv
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="phones.csv"'
writer = csv.writer(response, quoting=csv.QUOTE_NONNUMERIC)
for row in tels:
writer.writerow(row)
return response
return render_to_text('abonapp/modal_phonebook.html', {
'tels': tels,
'gid': gid
}, request=request)
@login_required
@permission_required('abonapp.change_abon')
@permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid'))
def reset_ip(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid)
signals.post_save.disconnect(models.abon_post_save, sender=models.Abon)
abon.ip_address = None
abon.save(update_fields=['ip_address'])
signals.post_save.connect(models.abon_post_save, sender=models.Abon)
return HttpResponse(dumps({
'status': 0,
'dat': "<span class='glyphicon glyphicon-refresh'></span>"
}))
# API's
def abons(request):

4
accounts_app/locale/ru/LC_MESSAGES/django.po

@ -26,7 +26,7 @@ msgstr "У пользователей должен быть номер теле
#: accounts_app/templates/accounts/group.html:7
#: accounts_app/templates/accounts/group_list.html:7
msgid "Administrators"
msgstr "Администраторы"
msgstr "Сотрудники"
#: accounts_app/templates/accounts/group.html:8
#: accounts_app/templates/accounts/group_list.html:8
@ -153,7 +153,7 @@ msgid "Change self onfo"
msgstr "Изменить инфу о себе"
msgid "Permission options"
msgstr "Настройка прав"
msgstr "Права"
msgid "Edit"
msgstr "Редактировать"

29
accounts_app/templates/accounts/acc_list.html

@ -14,11 +14,11 @@
<thead>
<tr>
<th class="col-sm-1">Фото</th>
<th>Ник</th>
<th>ФИО (или ник если нет)</th>
<th class="col-sm-2">Телефон</th>
<th class="col-sm-2">Email</th>
<th width="150">&mdash;</th>
<th class="col-sm-2">Ник</th>
<th class="col-sm-4">ФИО (или ник если нет)</th>
<th class="col-sm-2 hidden-xs">Телефон</th>
<th class="col-sm-2 hidden-xs">Email</th>
<th class="col-sm-1 hidden-xs">&mdash;</th>
</tr>
</thead>
<tbody>
@ -30,31 +30,24 @@
</a></td>
<td><a href="{% url 'acc_app:other_profile' usr.id %}">{{ usr.username }}</a></td>
<td>{{ usr.get_full_name }}</td>
<td>{% if usr.telephone %}<a href="tel:{{ usr.telephone }}">{{ usr.telephone }}</a>{% else %}
<td class="hidden-xs">{% if usr.telephone %}<a href="tel:{{ usr.telephone }}">{{ usr.telephone }}</a>{% else %}
Нету{% endif %}</td>
<td>{% if usr.email %}<a href="mailto:{{ usr.email }}">{{ usr.email }}{% else %}
<td class="hidden-xs">{% if usr.email %}<a href="mailto:{{ usr.email }}">{{ usr.email }}{% else %}
Нету{% endif %}</a></td>
<td class="btn-group">
<a href="#" class="btn btn-sm btn-default disabled"
title="Дать задание">
<span class="glyphicon glyphicon-tasks"></span>
</a>
<a href="{ % url 'django_messages:messages_compose' % }?a={{ usr.id }}" class="btn btn-sm btn-info"
title="Отправить сообщение">
<span class="glyphicon glyphicon-envelope"></span>
</a>
{% if usr.id == request.user.id or perms.acc_app.delete_userprofile %}
<td class="btn-group hidden-xs">
<a href="{% url 'acc_app:delete_profile' usr.id %}" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-remove"></span>
</a>
</td>
{% else %}
<td class="btn-group hidden-xs">
<a href="#" class="btn btn-sm btn-danger disabled" title="Не прав на удаление">
<span class="glyphicon glyphicon-remove"></span>
</a>
</td>
{% endif %}
</td>
</tr>
{% empty %}
<tr>

10
accounts_app/templates/accounts/ext.htm

@ -22,21 +22,21 @@
{% endif %}
<div class="caption btn-group btn-group-sm">
{% if userprofile == request.user %}
<a href="{% url 'acc_app:setup_info' %}" class="btn btn-primary" role="button">
<a href="{% url 'acc_app:setup_info' %}" class="btn btn-primary">
<span class="glyphicon glyphicon-edit"></span>
{% trans 'Edit' %}
<span class="hidden-sm hidden-md">{% trans 'Edit' %}</span>
</a>
{% endif %}
{% if request.user.is_superuser %}
{% if userprofile.is_superuser %}
<a href="{% url 'acc_app:setup_perms' userprofile.pk %}" class="btn btn-default" title="{% trans 'Profile is superuser, permissions to change it makes no sense' %}" data-toggle="tooltip">
<span class="glyphicon glyphicon-lock"></span>
{% trans 'Permission options' %}
<span class="hidden-sm hidden-md">{% trans 'Permission options' %}</span>
</a>
{% else %}
<a href="{% url 'acc_app:setup_perms' userprofile.pk %}" class="btn btn-default btn-sm">
<a href="{% url 'acc_app:setup_perms' userprofile.pk %}" class="btn btn-default">
<span class="glyphicon glyphicon-lock"></span>
{% trans 'Permission options' %}
<span class="hidden-sm hidden-md">{% trans 'Permission options' %}</span>
</a>
{% endif %}
{% endif %}

4
accounts_app/templates/accounts/index.html

@ -2,8 +2,8 @@
{% load i18n %}
{% block content %}
<div class="table-responsive">
<table class="table table-striped table-bordered">
<div class="table">
<table class="table-striped table-bordered">
<tbody>
<tr>
<td class="col-sm-4">{% trans 'Telephone' %}</td>

13
accounts_app/templates/accounts/login.html

@ -15,6 +15,10 @@
margin: 10% auto;
position: relative;
}
footer{
position: absolute;
bottom: 0;
}
</style>
</head>
<body>
@ -60,5 +64,14 @@
</div>
</div>
<footer class="footer">
<div class="container">
<p class="text-muted">
Напишите нам <i>is-ttk@ya.ru</i>.
</p>
</div>
</footer>
</div>
</body>
</html>

14
accounts_app/templates/accounts/settings/test.html

@ -1,14 +0,0 @@
<p>
<label for="id_permissions">Permissions:</label>
<select multiple="multiple" id="id_permissions" name="permissions">
<option value="add_abongroup">Can add abon group</option>
<option value="can_add_ballance">Пополнение счёта</option>
<option value="can_view_abongroup" selected="selected">Can view subscriber group</option>
<option value="change_abongroup">Can change abon group</option>
<option value="delete_abongroup">Can delete abon group</option>
</select>
</p>

7
agent/commands/dhcp.py

@ -10,7 +10,6 @@ def dhcp_commit(client_ip, client_mac, switch_mac, switch_port):
dev = Device.objects.get(mac_addr=switch_mac)
mngr_class = dev.get_manager_klass()
port = _('<never mind>')
if mngr_class.is_use_device_port():
port = Port.objects.get(device=dev, num=switch_port)
abon = Abon.objects.get(dev_port=port, device=dev)
@ -20,12 +19,12 @@ def dhcp_commit(client_ip, client_mac, switch_mac, switch_port):
print('D:', _('User settings is not dynamic'))
return
if not abon.is_access():
print('D:', _('User is not access to service'))
print('D:', 'User %s is not access to service' % abon.username)
return
abon.ip_address = client_ip
abon.is_dhcp = True
abon.save(update_fields=['ip_address'])
print('S:', _("Ip address:'%s' update for '%s' successfull, on port: %s") % (client_ip, abon.get_short_name(), port))
#print('S:', _("Ip address:'%s' update for '%s' successfull, on port: %s") % (client_ip, abon.get_short_name(), port))
except Abon.DoesNotExist:
print('N:', _("User with device '%s' does not exist") % dev)
except Device.DoesNotExist:
@ -33,7 +32,7 @@ def dhcp_commit(client_ip, client_mac, switch_mac, switch_port):
except Port.DoesNotExist:
print('N:', _('Port %d on device with mac %s does not exist') % (int(switch_port), switch_mac))
except MultipleObjectsReturned as e:
print('E:', 'MultipleObjectsReturned:', type(e), e)
print('E:', 'MultipleObjectsReturned:', type(e), e, port, dev)
def dhcp_expiry(client_ip):

20
agent/mod_mikrotik.py

@ -4,7 +4,7 @@ import binascii
from abc import ABCMeta
from hashlib import md5
from .core import BaseTransmitter, NasFailedResult, NasNetworkError
from mydefs import ping
from mydefs import ping, singleton
from .structs import TariffStruct, AbonStruct, IpStruct
from . import settings as local_settings
from django.conf import settings
@ -17,14 +17,23 @@ LIST_USERS_ALLOWED = 'DjingUsersAllowed'
LIST_USERS_BLOCKED = 'DjingUsersBlocked'
@singleton
class ApiRos:
"Routeros api"
sk = None
is_login = False
def __init__(self, sk):
def __init__(self, ip, port):
if self.sk is None:
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect((ip, port or getattr(local_settings, 'NAS_PORT', 8728)))
self.sk = sk
self.currenttag = 0
def login(self, username, pwd):
if self.is_login:
return
chal = None
for repl, attrs in self.talk_iter(["/login"]):
chal = binascii.unhexlify(attrs['=ret'])
@ -34,6 +43,7 @@ class ApiRos:
md.update(chal)
for r in self.talk_iter(["/login", "=name=" + username,
"=response=00" + binascii.hexlify(md.digest()).decode('utf-8')]): pass
self.is_login = True
def talk_iter(self, words):
if self.writeSentence(words) == 0: return
@ -143,6 +153,7 @@ class ApiRos:
class TransmitterManager(BaseTransmitter, metaclass=ABCMeta):
def __init__(self, login=None, password=None, ip=None, port=None):
ip = ip or getattr(local_settings, 'NAS_IP')
if ip is None:
@ -150,10 +161,7 @@ class TransmitterManager(BaseTransmitter, metaclass=ABCMeta):
if not ping(ip):
raise NasNetworkError('NAS %s не пингуется' % ip)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port or getattr(local_settings, 'NAS_PORT', 8728)))
self.s = s
self.ar = ApiRos(s)
self.ar = ApiRos(ip, port)
self.ar.login(login or getattr(local_settings, 'NAS_LOGIN'), password or getattr(local_settings, 'NAS_PASSW'))
except ConnectionRefusedError:
raise NasNetworkError('Подключение к %s отклонено (Connection Refused)' % ip)

8
bugs.txt

@ -1,8 +0,0 @@
- (GUI) Иконки возле кнопок не настроены, натыканы случайно
- В abonapp.complete_service нельзя досрочно завершить услугу пока нет связи с NAS'ом
- Надо указывать в /usr/lib/python3.5/site-packages/django/contrib/auth/decorators.py raise_exception=True
- Пароли абонентов надо шифровать ключом для паролей
- Доделать везде переводы
- Не надо коннектиться к микротику когда не собираемся ничего изменять. А то при сохранении залогинились и вышли без действий
- Не удаляет просроченные услуги если не пингуется NAS
! Проверить дату завершения услуги

1
chatbot/admin.py

@ -3,3 +3,4 @@ from . import models
admin.site.register(models.MessageHistory)
admin.site.register(models.TelegramBot)
admin.site.register(models.MessageQueue)

78
chatbot/locale/ru/LC_MESSAGES/django.po

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-24 23:49+0300\n"
"POT-Creation-Date: 2017-12-14 15:04+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Dmitry Novikov <nerosketch@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -20,15 +20,59 @@ msgstr ""
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
#: chatbot/telebot.py:61
#: models.py:13
msgid "Employee"
msgstr "Сотрудник"
#: models.py:14
msgid "Telegram chat id"
msgstr "Номер чата из telegram"
#: models.py:21
msgid "Telegram bot"
msgstr "Telegram бот"
#: models.py:22
msgid "Telegram bots"
msgstr "Telegram боты"
#: models.py:35
msgid "Message history"
msgstr "История собщений"
#: models.py:36
msgid "Message histories"
msgstr "Истории собщений"
#: models.py:53
msgid "Target employee"
msgstr "Сотрудник"
#: models.py:54
msgid "Message"
msgstr "Сообщение"
#: models.py:59
msgid "Status of message"
msgstr "Статус сообщения"
#: models.py:61
msgid "App tag"
msgstr "Тэг приложения"
#: models.py:70 models.py:71
msgid "Message queue"
msgstr "Очередь оповещений"
#: telebot.py:63
msgid "Let's get acquainted, what is your name? Write your login from billing."
msgstr "Давай знакомиться, как тебя зовут? Напиши свой логин из биллинга."
#: chatbot/telebot.py:81
#: telebot.py:84
msgid "I do not know the answer to this yet."
msgstr "Я пока не знаю ответа на это"
#: chatbot/telebot.py:100
#: telebot.py:104
msgid ""
"You are not found in the database, check that it correctly pointed out its "
"LOGIN. Try again"
@ -36,25 +80,29 @@ msgstr ""
"Ты не найден в базе, проверь что правильно указал именно свой ЛОГИН. "
"Попробуй ещё"
#: chatbot/telebot.py:123
#: telebot.py:120
msgid "Let's ping, write ip. It will be necessary to wait 10 seconds"
msgstr "Давай пинганём, напиши ip. Нужно будет подождать 10 сек"
#: telebot.py:127
msgid "It's not like ip address, try again"
msgstr "Это не похоже на ip адрес, попробуй ещё"
#: chatbot/telebot.py:126
#: telebot.py:130
#, python-format
msgid "You're '%s', right?"
msgstr "Ты ведь %s ?"
#: chatbot/telebot.py:136
#: telebot.py:138
msgid "Telegram bot token not found"
msgstr "Токен для бота Telegram не найден"
#: telebot.py:143
#, python-format
msgid "Recipient '%s' does not subscribed on notifications"
msgstr "%s не подписан на оповещения"
msgid "Let's ping, write ip. It will be necessary to wait 10 seconds"
msgstr "Давай пинганём, напиши ip. Нужно будет подождать 10 сек"
msgid "Yes, it's nice to meet% s, I will notify you about events in billing. Successful work;)"
msgstr "Да, приятно познакомиться %s, я буду оповещать тебя о событиях в биллинге. Удачной работы ;)"
msgid "Telegram bot token not found"
msgstr "Токен для бота Telegram не найден"
msgid ""
"Yes, it's nice to meet %(username)s, I will notify you about events in billing. Successful work ;)"
msgstr ""
"Да, приятно познакомиться %(username)s, я буду оповещать тебя о событиях в биллинге. Удачной работы ;)"

59
chatbot/migrations/0002_auto_20171214_1517.py

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-12-14 15:17
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):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('chatbot', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='MessageQueue',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('message', models.CharField(max_length=255, verbose_name='Message')),
('status', models.CharField(choices=[('n', 'New'), ('r', 'Read')], default='n', max_length=1, verbose_name='Status of message')),
('tag', models.CharField(default='none', max_length=6, verbose_name='App tag')),
('target_employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Target employee')),
],
options={
'verbose_name': 'Message queue',
'verbose_name_plural': 'Message queue',
'db_table': 'chat_message_queue',
},
),
migrations.AlterModelOptions(
name='messagehistory',
options={'verbose_name': 'Message history', 'verbose_name_plural': 'Message histories'},
),
migrations.AlterModelOptions(
name='telegrambot',
options={'verbose_name': 'Telegram bot', 'verbose_name_plural': 'Telegram bots'},
),
migrations.AlterField(
model_name='telegrambot',
name='chat_id',
field=models.PositiveIntegerField(default=0, verbose_name='Telegram chat id'),
),
migrations.AlterField(
model_name='telegrambot',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Employee'),
),
migrations.AlterModelTable(
name='messagehistory',
table='chat_message_history',
),
migrations.AlterModelTable(
name='telegrambot',
table='chat_telegram_bot',
),
]

52
chatbot/models.py

@ -1,3 +1,4 @@
from django.utils.translation import ugettext as _
from django.db import models
from django.conf import settings
@ -9,11 +10,16 @@ class ChatException(Exception):
class TelegramBot(models.Model):
user = models.ForeignKey(AUTH_USER_MODEL)
chat_id = models.PositiveIntegerField(default=0)
user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_('Employee'))
chat_id = models.PositiveIntegerField(_('Telegram chat id'), default=0)
def __str__(self):
return self.user.get_full_name() + ' - ' + str(self.chat_id)
return "%s - %d" % (self.user.get_full_name(), self.chat_id)
class Meta:
db_table = 'chat_telegram_bot'
verbose_name = _('Telegram bot')
verbose_name_plural = _('Telegram bots')
class MessageHistory(models.Model):
@ -23,3 +29,43 @@ class MessageHistory(models.Model):
def __str__(self):
return self.message
class Meta:
db_table = 'chat_message_history'
verbose_name = _('Message history')
verbose_name_plural = _('Message histories')
class MessageQueueManager(models.Manager):
def pop(self, user, tag='none'):
msgs = self.filter(target_employee=user, status='n', tag=tag)[:1].only('message').values('id', 'message')
if len(msgs) > 0:
self.filter(id=msgs[0]['id']).delete()
return msgs[0]['message']
def push(self, msg, user, tag='none'):
msg = self.create(target_employee=user, message=msg, tag=tag)
return msg
class MessageQueue(models.Model):
target_employee = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_('Target employee'))
message = models.CharField(_('Message'), max_length=255)
STATUSES = (
('n', 'New'),
('r', 'Read')
)
status = models.CharField(_('Status of message'), max_length=1, choices=STATUSES, default='n')
# tag каждое приложение ставит своим чтоб делить сообщения между этими приложениями
tag = models.CharField(_('App tag'), max_length=6, default='none')
objects = MessageQueueManager()
def __str__(self):
return self.message
class Meta:
db_table = 'chat_message_queue'
verbose_name = _('Message queue')
verbose_name_plural = _('Message queue')

17
chatbot/telebot.py

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
from telepot import helper, glance, Bot
from telepot.exception import TelegramError
import os
import socket
import collections
from django.utils.translation import ugettext as _
from urllib3.exceptions import ProtocolError
from .models import TelegramBot, ChatException
from .models import TelegramBot, ChatException, MessageQueue
from chatbot.models import MessageHistory
from accounts_app.models import UserProfile
from django.conf import settings
@ -19,7 +20,7 @@ class DjingTelebot(helper.ChatHandler):
_chat_id = 0
def __init__(self, seed_tuple, **kwargs):
super().__init__(seed_tuple, **kwargs)
super(DjingTelebot, self).__init__(seed_tuple, **kwargs)
self.cmds = {
'ping': self.ping,
'iam': self.say_me
@ -104,8 +105,8 @@ class DjingTelebot(helper.ChatHandler):
self._question(_("You are not found in the database, check that it correctly pointed out its LOGIN. Try again"),
self.question_name)
return
self._sent_reply("Yes, it's nice to meet %s, I will notify you about events in billing. Successful work;)"
% profile.get_full_name())
self._sent_reply(_("Yes, it's nice to meet %(username)s, I will notify you about events in billing. Successful work ;)")
% {'username': profile.get_full_name()})
# заканчивается время диалога
# ex - время ожидания (timeout=ex в pave_event_space)
@ -130,9 +131,10 @@ class DjingTelebot(helper.ChatHandler):
self._sent_reply(_("You're '%s', right?") % self._current_user.get_full_name())
# Просто отправляем текст оповещения указанному админу
def send_notify(msg_text, account):
# Просто отправляем текст оповещения указанной учётке
def send_notify(msg_text, account, tag='none'):
try:
MessageQueue.objects.push(msg=msg_text, user=account, tag=tag)
if token is None:
raise ChatException(_('Telegram bot token not found'))
tb = TelegramBot.objects.get(user=account)
@ -142,4 +144,5 @@ def send_notify(msg_text, account):
raise ChatException(_("Recipient '%s' does not subscribed on notifications") % account.get_full_name())
except ProtocolError as e:
raise ChatException(e)
except TelegramError as e:
raise ChatException("%s - %s" % (e, tb.user.get_full_name()))

5
clientsideapp/templates/clientsideapp/ext.html

@ -49,6 +49,7 @@
<a href="{{ client_side_services }}">Услуги</a>
</li>
<!--<li>
<a href="#">
Сообщения из администрации
@ -70,7 +71,7 @@
</ul>
</li>
</ul>
<span class="navbar-text">Ваш балланс <b>{{ subscriber.ballance }}</b> руб.</span>
<span class="navbar-text">Ваш балланс <b>{{ subscriber.ballance|floatformat:2 }}</b> руб.</span>
</div><!--/.nav-collapse -->
</div>
</div>
@ -94,7 +95,7 @@
<div id="footer">
<div class="container">
<p class="text-muted">
Напишите разработчику <i>djing-developer@yandex.ru</i>.
Напишите разработчику <i>is-ttk@ya.ru</i>.
</p>
</div>
</div>

12
clientsideapp/templates/clientsideapp/services.html

@ -6,7 +6,7 @@
<div class="container">
<div class="row">
<div class="col-lg-5">
<div class="col-lg-7">
{% if current_service %}
<div class="panel panel-default">
<div class="panel-heading">
@ -27,6 +27,8 @@
<p>{{ current_service.tariff.descr }}</p>
</div>
</div>
{% else %}
<div class="alert alert-warning" role="alert">
<span class="glyphicon glyphicon-exclamation-sign"></span>
@ -34,7 +36,7 @@
</div>
{% endif %}
</div>
<div class="col-lg-7">
<div class="col-lg-5">
<div class="panel panel-default">
<div class="panel-heading">
Доступные для заказа услуги
@ -66,12 +68,15 @@
</div>
</div>
</div>
</div>
</div>
{% include 'clientsideapp/services_ext.txt' %}
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="panel panel-info">
@ -89,5 +94,4 @@
</div>
</div>
</div>
{% endblock %}

8
clientsideapp/views.py

@ -28,12 +28,16 @@ def pays(request):
@login_required
def services(request):
try:
abon = Abon.objects.get(pk=request.user.pk)
all_tarifs = abon.group.tariffs.filter(is_admin=False)
current_service = abon.active_tariff()
except:
all_tarifs = None
current_service = None
return render(request, 'clientsideapp/services.html', {
'tarifs': all_tarifs,
'current_service': abon.active_tariff()
'current_service': current_service
})

17
cron.py

@ -4,25 +4,20 @@ import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
django.setup()
from abonapp.models import Abon
from django.utils import timezone
from django.db.models import signals
from abonapp.models import Abon, AbonTariff, abontariff_pre_delete
from agent import Transmitter, NasNetworkError, NasFailedResult
from mydefs import LogicError
def main():
users = Abon.objects.all()
for user in users:
try:
# бдим за услугами абонента
user.bill_service(user)
except (NasNetworkError, NasFailedResult) as er:
print("Error:", er)
except LogicError as er:
print("Notice:", er)
signals.pre_delete.disconnect(abontariff_pre_delete, sender=AbonTariff)
AbonTariff.objects.filter(deadline__lt=timezone.now()).delete()
tm = Transmitter()
users = Abon.objects.filter(is_dynamic_ip=False, is_active=True).exclude(current_tariff=None)
tm.sync_nas(users)
signals.pre_delete.connect(abontariff_pre_delete, sender=AbonTariff)
if __name__ == "__main__":

16
devapp/base_intr.py

@ -4,6 +4,10 @@ from easysnmp import Session
class DevBase(object, metaclass=ABCMeta):
def __init__(self, dev_instance=None):
self.db_instance = dev_instance
@staticmethod
def description():
"""Возвращает текстовое описание"""
@ -56,8 +60,7 @@ class BasePort(object, metaclass=ABCMeta):
pass
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]))
return ':'.join(['%x' % ord(i) for i in self._mac])
class SNMPBaseWorker(object, metaclass=ABCMeta):
@ -70,8 +73,13 @@ class SNMPBaseWorker(object, metaclass=ABCMeta):
return self.ses.set(oid, value)
def get_list(self, oid):
l = self.ses.walk(oid)
return [e.value for e in l]
for v in self.ses.walk(oid):
yield v.value
def get_list_keyval(self, oid):
for v in self.ses.walk(oid):
snmpnum = v.oid.split('.')[-1:]
yield v.value, snmpnum[0] if len(snmpnum) > 0 else None
def get_item(self, oid):
return self.ses.get(oid).value

79
devapp/dev_types.py

@ -2,6 +2,7 @@
from django.utils.translation import ugettext_lazy as _
from mydefs import RuTimedelta, safe_int
from datetime import timedelta
from easysnmp import EasySNMPTimeoutError
from .base_intr import DevBase, SNMPBaseWorker, BasePort
@ -28,9 +29,9 @@ class DLinkPort(BasePort):
class DLinkDevice(DevBase, SNMPBaseWorker):
def __init__(self, ip, snmp_community, ver=2):
DevBase.__init__(self)
SNMPBaseWorker.__init__(self, ip, snmp_community, ver)
def __init__(self, dev_instance):
DevBase.__init__(self, dev_instance)
SNMPBaseWorker.__init__(self, dev_instance.ip_address, dev_instance.man_passw, 2)
@staticmethod
def description():
@ -40,20 +41,19 @@ class DLinkDevice(DevBase, SNMPBaseWorker):
return self.get_item('.1.3.6.1.4.1.2021.8.1.101.1')
def get_ports(self):
nams = self.get_list('.1.3.6.1.4.1.171.10.134.2.1.1.100.2.1.3')
stats = self.get_list('.1.3.6.1.2.1.2.2.1.7')
macs = self.get_list('.1.3.6.1.2.1.2.2.1.6')
nams = list(self.get_list('.1.3.6.1.4.1.171.10.134.2.1.1.100.2.1.3'))
stats = list(self.get_list('.1.3.6.1.2.1.2.2.1.7'))
macs = list(self.get_list('.1.3.6.1.2.1.2.2.1.6'))
speeds = self.get_list('.1.3.6.1.2.1.31.1.1.1.15')
res = []
ln = len(speeds)
for n in range(ln):
for n, speed in enumerate(speeds):
status = True if int(stats[n]) == 1 else False
res.append(DLinkPort(
n+1,
nams[n] if len(nams) > 0 else _('does not fetch the name'),
status,
macs[n] if len(macs) > 0 else _('does not fetch the mac'),
int(speeds[n]) if len(speeds) > 0 else 0,
int(speed or 0),
self))
return res
@ -99,9 +99,9 @@ class ONUdev(BasePort):
class OLTDevice(DevBase, SNMPBaseWorker):
def __init__(self, ip, snmp_community, ver=2):
DevBase.__init__(self)
SNMPBaseWorker.__init__(self, ip, snmp_community, ver)
def __init__(self, dev_instance):
DevBase.__init__(self, dev_instance)
SNMPBaseWorker.__init__(self, dev_instance.ip_address, dev_instance.man_passw, 2)
@staticmethod
def description():
@ -115,17 +115,17 @@ class OLTDevice(DevBase, SNMPBaseWorker):
res = []
for nm in nms:
nm = int(nm)
status = int(self.get_item('.1.3.6.1.2.1.2.2.1.8.%d' % nm))
signal = self.get_item('.1.3.6.1.4.1.3320.101.10.5.1.5.%d' % nm)
n = int(nm)
status = self.get_item('.1.3.6.1.4.1.3320.101.10.1.1.26.%d' % n)
signal = self.get_item('.1.3.6.1.4.1.3320.101.10.5.1.5.%d' % n)
onu = ONUdev(
nm,
self.get_item('.1.3.6.1.2.1.2.2.1.2.%d' % nm),
True if status == 1 else False,
self.get_item('.1.3.6.1.4.1.3320.101.10.1.1.3.%d' % nm),
self.get_item('.1.3.6.1.4.1.3320.101.10.1.1.27.%d' % nm),
int(signal) / 10 if signal != 'NOSUCHINSTANCE' else 0,
self)
num=n,
name=self.get_item('.1.3.6.1.2.1.2.2.1.2.%d' % n),
status=True if status == '3' else False,
mac=self.get_item('.1.3.6.1.4.1.3320.101.10.1.1.3.%d' % n),
speed=0,
signal=int(signal) / 10 if signal != 'NOSUCHINSTANCE' else 0,
snmpWorker=self)
res.append(onu)
return res
@ -151,6 +151,10 @@ class OLTDevice(DevBase, SNMPBaseWorker):
class OnuDevice(DevBase, SNMPBaseWorker):
def __init__(self, dev_instance):
DevBase.__init__(self, dev_instance)
SNMPBaseWorker.__init__(self, dev_instance.ip_address, dev_instance.man_passw, 2)
@staticmethod
def description():
return _('PON ONU')
@ -178,6 +182,29 @@ class OnuDevice(DevBase, SNMPBaseWorker):
def is_use_device_port():
return False
def get_details(self):
if self.db_instance is None:
return
num = self.db_instance.snmp_item_num
if num == 0:
return
try:
status = self.get_item('.1.3.6.1.4.1.3320.101.10.1.1.26.%d' % num)
signal = self.get_item('.1.3.6.1.4.1.3320.101.10.5.1.5.%d' % num)
distance = self.get_item('.1.3.6.1.4.1.3320.101.10.1.1.27.%d' % num)
mac = ':'.join(['%x' % ord(i) for i in self.get_item('.1.3.6.1.4.1.3320.101.10.1.1.3.%d' % num)])
uptime = self.get_item('.1.3.6.1.2.1.2.2.1.9.%d' % num)
return {
'status': status,
'signal': int(signal) / 10 if signal != 'NOSUCHINSTANCE' else 0,
'name': self.get_item('.1.3.6.1.2.1.2.2.1.2.%d' % num),
'mac': mac,
'distance': int(distance) / 10 if distance != 'NOSUCHINSTANCE' else 0
}
except EasySNMPTimeoutError as e:
return {'err': "%s: %s" % (_('ONU not connected'), e)}
class EltexPort(BasePort):
@ -210,10 +237,10 @@ class EltexSwitch(DLinkDevice):
def get_ports(self):
#nams = self.get_list('.1.3.6.1.4.1.171.10.134.2.1.1.100.2.1.3')
stats = self.get_list('.1.3.6.1.2.1.2.2.1.7')
oper_stats = self.get_list('.1.3.6.1.2.1.2.2.1.8')
stats = list(self.get_list('.1.3.6.1.2.1.2.2.1.7'))
oper_stats = list(self.get_list('.1.3.6.1.2.1.2.2.1.8'))
#macs = self.get_list('.1.3.6.1.2.1.2.2.1.6')
speeds = self.get_list('.1.3.6.1.2.1.31.1.1.1.15')
speeds = list(self.get_list('.1.3.6.1.2.1.31.1.1.1.15'))
res = []
for n in range(28):
res.append(EltexPort(self,
@ -235,7 +262,7 @@ class EltexSwitch(DLinkDevice):
@staticmethod
def has_attachable_to_subscriber():
return False
return True
@staticmethod
def is_use_device_port():

28
devapp/forms.py

@ -11,8 +11,7 @@ from djing import MAC_ADDR_REGEX
class DeviceForm(forms.ModelForm):
mac_addr = forms.CharField(widget=forms.TextInput(attrs={
'pattern': MAC_ADDR_REGEX,
'required': True,
'class': 'form-control'
'required': True
}), error_messages={
'required': _('Mac address is required for fill'),
'unique': _('Device with that mac is already exist')
@ -20,31 +19,18 @@ class DeviceForm(forms.ModelForm):
class Meta:
model = models.Device
fields = '__all__'
exclude = ['map_dot']
widgets = {
'ip_address': forms.TextInput(attrs={
'pattern': ip_addr_regex,
'placeholder': '192.168.0.100',
'class': 'form-control'
'placeholder': '192.168.0.100'
}),
'comment': forms.TextInput(attrs={
'required': True,
'class': 'form-control'
}),
'devtype': forms.Select(attrs={
'class': 'form-control'
}),
'man_passw': forms.PasswordInput(attrs={
'class': 'form-control'
}, render_value=True),
'map_dot': forms.Select(attrs={
'class': 'form-control'
'required': True
}),
'man_passw': forms.PasswordInput(render_value=True),
'user_group': forms.Select(attrs={
'class': 'form-control'
}),
'parent_dev': forms.Select(attrs={
'class': 'form-control'
})
}
@ -55,11 +41,7 @@ class PortForm(forms.ModelForm):
exclude = ['device']
widgets = {
'num': forms.NumberInput(attrs={
'class': 'form-control',
'min': '0'
}),
'descr': forms.TextInput(attrs={
'class': 'form-control'
})
}

28
devapp/locale/ru/LC_MESSAGES/django.po

@ -77,6 +77,9 @@ msgstr "Тип устройства"
msgid "SNMP password"
msgstr "Пароль SNMP"
msgid "SNMP Num"
msgstr "SNMP Ном."
#: devapp/templates/devapp/add_dev.html:62 devapp/templates/devapp/dev.html:50
msgid "Map point"
msgstr "Точка топологии"
@ -309,3 +312,28 @@ msgstr "Устройство"
msgid "Can toggle ports"
msgstr "Может переключать порты"
msgid "Plugin output"
msgstr "Вывод мониторинга"
msgid "Create device"
msgstr "Создать устройство"
msgid "ONU error"
msgstr "ONU ошибка"
msgid "ONU not connected"
msgstr "ONU не в сети"
msgid "Parent device not found"
msgstr "Вышестоящее устройство не найдено"
msgid "Fixed"
msgstr "Исправлено, обновите страницу"
msgid "Fix it"
msgstr "Исправить"
msgid "Duplicate user and port: %s"
msgstr "Пользователь с таким портом и устройством уже есть: %s"

72
devapp/migrations/0004_auto_20171103_0006.py

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-11-03 00:06
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import djing.fields
import mydefs
class Migration(migrations.Migration):
dependencies = [
('devapp', '0003_auto_20170927_1838'),
]
operations = [
migrations.RemoveField(
model_name='device',
name='map_dot',
),
migrations.AlterField(
model_name='device',
name='comment',
field=models.CharField(max_length=256, verbose_name='Comment'),
),
migrations.AlterField(
model_name='device',
name='devtype',
field=models.CharField(choices=[('Dl', 'DLink switch'), ('Pn', 'PON OLT'), ('On', 'PON ONU'), ('Ex', 'Eltex switch')], default='Dl', max_length=2, verbose_name='Device type'),
),
migrations.AlterField(
model_name='device',
name='ip_address',
field=mydefs.MyGenericIPAddressField(max_length=8, protocol='ipv4', verbose_name='Ip address'),
),
migrations.AlterField(
model_name='device',
name='mac_addr',
field=djing.fields.MACAddressField(blank=True, integer=True, null=True, unique=True, verbose_name='Mac address'),
),
migrations.AlterField(
model_name='device',
name='man_passw',
field=models.CharField(blank=True, max_length=16, null=True, verbose_name='SNMP password'),
),
migrations.AlterField(
model_name='device',
name='parent_dev',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='devapp.Device', verbose_name='Parent device'),
),
migrations.AlterField(
model_name='device',
name='user_group',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='abonapp.AbonGroup', verbose_name='User group'),
),
migrations.AlterField(
model_name='port',
name='descr',
field=models.CharField(blank=True, max_length=60, null=True, verbose_name='Description'),
),
migrations.AlterField(
model_name='port',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='devapp.Device', verbose_name='Device'),
),
migrations.AlterField(
model_name='port',
name='num',
field=models.PositiveSmallIntegerField(default=0, verbose_name='Number'),
),
]

20
devapp/migrations/0005_device_snmp_item_num.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-11-07 16:19
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('devapp', '0004_auto_20171103_0006'),
]
operations = [
migrations.AddField(
model_name='device',
name='snmp_item_num',
field=models.PositiveSmallIntegerField(default=0),
),
]

112
devapp/models.py

@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
import requests
from django.db import models
from djing.fields import MACAddressField
from .base_intr import DevBase
from mydefs import MyGenericIPAddressField, MyChoicesAdapter
from mydefs import MyGenericIPAddressField, MyChoicesAdapter, ip2int
from . import dev_types
from mapapp.models import Dot
from subprocess import run
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from json.decoder import JSONDecodeError
DEVICE_TYPES = (
@ -23,15 +23,40 @@ class DeviceDBException(Exception):
pass
class DeviceMonitoringException(Exception):
pass
class DeviceManager(models.Manager):
@staticmethod
def wrap_monitoring_info(devices_queryset):
nag_url = getattr(settings, 'NAGIOS_URL', None)
if nag_url is not None:
addrs = ['h=%s' % hex(ip2int(dev.ip_address))[2:] for dev in devices_queryset]
url = '%s/host/status/arr?%s' % (nag_url, '&'.join(addrs))
try:
res = requests.get(url).json()
except (requests.exceptions.ConnectionError, JSONDecodeError) as e:
raise DeviceMonitoringException(e)
for dev in devices_queryset:
inf = [x for x in res if x.get('address') == dev.ip_address]
if len(inf) > 0:
setattr(dev, 'mon', inf[0].get('current_status'))
return devices_queryset
class Device(models.Model):
ip_address = MyGenericIPAddressField()
mac_addr = MACAddressField(null=True, blank=True, unique=True)
comment = models.CharField(max_length=256)
devtype = models.CharField(max_length=2, default=DEVICE_TYPES[0][0], choices=MyChoicesAdapter(DEVICE_TYPES))
man_passw = models.CharField(max_length=16, null=True, blank=True)
map_dot = models.ForeignKey(Dot, on_delete=models.SET_NULL, null=True, blank=True)
user_group = models.ForeignKey('abonapp.AbonGroup', on_delete=models.SET_NULL, null=True, blank=True)
parent_dev = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL)
ip_address = MyGenericIPAddressField(verbose_name=_('Ip address'))
mac_addr = MACAddressField(verbose_name=_('Mac address'), null=True, blank=True, unique=True)
comment = models.CharField(_('Comment'), max_length=256)
devtype = models.CharField(_('Device type'), max_length=2, default=DEVICE_TYPES[0][0], choices=MyChoicesAdapter(DEVICE_TYPES))
man_passw = models.CharField(_('SNMP password'), max_length=16, null=True, blank=True)
user_group = models.ForeignKey('abonapp.AbonGroup', verbose_name=_('User group'), on_delete=models.SET_NULL, null=True, blank=True)
parent_dev = models.ForeignKey('self', verbose_name=_('Parent device'), blank=True, null=True, on_delete=models.SET_NULL)
snmp_item_num = models.PositiveSmallIntegerField(_('SNMP Number'), default=0)
objects = DeviceManager()
class Meta:
db_table = 'dev'
@ -44,8 +69,10 @@ class Device(models.Model):
def get_abons(self):
pass
def get_stat(self):
pass
def get_status(self):
url = getattr(settings, 'NAGIOS_URL')
if url:
return requests.get('%s/host/status?addr=%s' % (url, self.ip_address))
def get_manager_klass(self):
klasses = [kl for kl in DEVICE_TYPES if kl[0] == self.devtype]
@ -55,6 +82,10 @@ class Device(models.Model):
return res
return
def get_manager_object(self):
man_klass = self.get_manager_klass()
return man_klass(self)
# Можно-ли подключать устройство к абоненту
def has_attachable_to_subscriber(self):
mngr_class = self.get_manager_klass()
@ -63,29 +94,10 @@ class Device(models.Model):
def __str__(self):
return "%s: (%s) %s %s" % (self.comment, self.get_devtype_display(), self.ip_address, self.mac_addr or '')
class Port(models.Model):
device = models.ForeignKey(Device)
num = models.PositiveSmallIntegerField(default=0)
descr = models.CharField(max_length=60, null=True, blank=True)
def __str__(self):
return "%d: %s" % (int(self.num), self.descr)
class Meta:
db_table = 'dev_port'
unique_together = (('device', 'num'))
permissions = (
('can_toggle_ports', _('Can toggle ports')),
)
verbose_name = _('Port')
verbose_name_plural = _('Ports')
def dev_post_save_signal(sender, instance, **kwargs):
if instance.devtype != 'On':
def update_dhcp(self):
if self.devtype not in ('On','Dl'):
return
grp = instance.user_group.pk
grp = self.user_group.id
code = ''
if grp == 87:
code = 'chk'
@ -105,12 +117,36 @@ def dev_post_save_signal(sender, instance, **kwargs):
code = 'psh'
elif grp == 92:
code = 'str'
elif grp == 80:
elif grp == 80 or grp == 94:
code = 'uy'
elif grp == 79 or grp == 91:
code = 'zrk'
newmac = str(instance.mac_addr)
elif grp == 95:
code = 'yst'
elif grp == 96:
code = 'lzk'
elif grp == 51:
code = 'sad'
newmac = str(self.mac_addr)
run(["%s/devapp/onu_register.sh" % settings.BASE_DIR, newmac, code])
models.signals.post_save.connect(dev_post_save_signal, sender=Device)
class Port(models.Model):
device = models.ForeignKey(Device, verbose_name=_('Device'))
num = models.PositiveSmallIntegerField(_('Number'), default=0)
descr = models.CharField(_('Description'), max_length=60, null=True, blank=True)
def __str__(self):
return "%d: %s" % (int(self.num), self.descr)
class Meta:
db_table = 'dev_port'
unique_together = (('device', 'num'))
permissions = (
('can_toggle_ports', _('Can toggle ports')),
)
verbose_name = _('Port')
verbose_name_plural = _('Ports')

87
devapp/templates/devapp/add_dev.html

@ -1,5 +1,6 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% load bootstrap3 %}
{% block main %}
<ol class="breadcrumb">
@ -23,76 +24,23 @@
<form role="form" action="{% url 'devapp:add' group.pk %}" method="post" autocomplete="off">{% csrf_token %}
<div class="form-group">
<label for="id_ip_address">{% trans 'Ip address' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-globe"></span></span>
{{ form.ip_address }}{{ form.ip_address.errors }}
</div>
</div>
<div class="form-group">
<label for="id_mac_addr">{% trans 'Mac address' %}</label>
<div class="input-group{% if form.mac_addr.errors %} has-error{% endif %}">
<span class="input-group-addon"><span class="glyphicon glyphicon-globe"></span></span>
{{ form.mac_addr }}
{% if already_dev %}
<span class="input-group-btn">
<a class="btn btn-danger" href="{% url 'devapp:view' group.pk already_dev.pk %}" title="{% trans 'View the device' %}" data-toggle="tooltip">
{{ already_dev.comment }}
</a>
</span>
{% endif %}
</div>
{{ form.mac_addr.errors }}
</div>
<div class="form-group">
<label for="id_comment">{% trans 'Comment' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-comment"></span></span>
{{ form.comment }}{{ form.comment.errors }}
</div>
</div>
<div class="form-group">
<label for="id_devtype">{% trans 'Device type' %}</label>
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.ip_address addon_before=ic %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-hdd"></span></span>
{{ form.devtype }}{{ form.devtype.errors }}
</div>
</div>
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.mac_addr addon_before=ic %}
<div class="form-group">
<label for="id_man_passw">{% trans 'SNMP password' %}</label>
{% bootstrap_icon 'comment' as ic %}
{% bootstrap_field form.comment addon_before=ic %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
{{ form.man_passw }}{{ form.man_passw.errors }}
</div>
</div>
{% bootstrap_icon 'hdd' as ic %}
{% bootstrap_field form.devtype addon_before=ic %}
<div class="form-group">
<label for="id_map_dot">{% trans 'Map point' %}</label>
{% bootstrap_icon 'lock' as ic %}
{% bootstrap_field form.man_passw addon_before=ic %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-map-marker"></span></span>
{{ form.map_dot }}{{ form.devtype.errors }}
</div>
</div>
<div class="form-group">
<label for="id_user_group">{% trans 'User group' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-subscript"></span></span>
{{ form.user_group }}{{ form.user_group.errors }}
</div>
</div>
{% bootstrap_icon 'subscript' as ic %}
{% bootstrap_field form.user_group addon_before=ic %}
<div class="form-group">
<label for="id_parent_dev">{% trans 'Parent device' %}</label>
@ -106,11 +54,14 @@
</div>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">
{% bootstrap_icon 'list-alt' as ic %}
{% bootstrap_field form.snmp_item_num addon_before=ic %}
<div class="btn-group btn-group-sm">
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
<button type="reset" class="btn btn-sm btn-default">
<button type="reset" class="btn btn-default">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %}
</button>
</div>

22
devapp/templates/devapp/custom_dev_page/olt.html

@ -5,18 +5,20 @@
<div class="row">
<div class="col-sm-12">
<div class="table-responsive">
{% with uptime=dev_manager.uptime %}
{% if uptime %}
{% trans 'Uptime' %} {{ uptime }}
{% endif %}
{% endwith %}
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="10">#</th>
<th>{% trans 'Mac' %}</th>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Distance(m)' %}</th>
<th width="250">{% trans 'Signal' %}</th>
<th width="10">#</th>
<th class="col-xs-1">#</th>
<th class="col-xs-1">{% trans 'SNMP Num' %}</th>
<th class="col-xs-4">{% trans 'Name' %}</th>
<th class="col-xs-3">{% trans 'Mac' %}</th>
<th class="col-xs-2">{% trans 'Signal' %}</th>
<th class="col-xs-1">#</th>
</tr>
</thead>
@ -28,19 +30,19 @@
{% else %}<span class="glyphicon glyphicon-warning-sign text-danger"></span>
{% endif %}
</td>
<td>{{ port.mac }}</td>
<td>{{ port.num }}</td>
<td>{{ port.nm }}</td>
<td>{{ port.sp }}</td>
<td>{{ port.mac }}</td>
<td>{{ port.signal }}</td>
<td>
<a href="{% url 'devapp:add' grp %}?mac={{ port.mac }}&t=On&c={{ port.nm }}&ip={{ dip }}" title="Создать устройство">
<a href="{% url 'devapp:add' grp %}?mac={{ port.mac }}&t=On&c={{ port.nm }}&ip={{ dip }}&n={{ port.num }}" title="{% trans 'Create device' %}">
<span class="glyphicon glyphicon-plus"></span>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6">{% trans 'Ports not found' %}</td>
<td colspan="7">{% trans 'Ports not found' %}</td>
</tr>
{% endfor %}
{% endwith %}

59
devapp/templates/devapp/custom_dev_page/onu.html

@ -2,8 +2,9 @@
{% load i18n %}
{% block content %}
{% with uptime=dev_manager.uptime onu_details=dev_manager.get_details %}
<div class="row">
<div class="col-sm-12">
<div class="col-xs-12 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<div class="panel-title">{{ dev.get_devtype_display|default:_('Title of the type of switch') }}.
@ -16,7 +17,7 @@
<ul class="list-group">
<li class="list-group-item">{% trans 'Ip address' %}: {{ dev.ip_address }}</li>
<li class="list-group-item">{% trans 'Mac' %}: {{ dev.mac_addr }}</li>
<li class="list-group-item">{% trans 'Description' %} {{ dev.comment }}</li>
<li class="list-group-item">{% trans 'Description' %}: {{ dev.comment }}</li>
{% for da in dev_accs %}
<li class="list-group-item">{% trans 'Attached user' %}:
{% if da.group %}
@ -40,6 +41,60 @@
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'ONU Status' %}</h3>
</div>
<div class="panel-body">
{% if onu_details %}
{% if onu_details.err %}
<div class="media">
<div class="media-left"><span class="media-object glyphicon glyphicon-remove-sign text-danger" style="font-size: 75px;"></span></div>
</div>
<div class="media-body">
<b>{% trans 'ONU error' %}</b>: {{ onu_details.err }}<br>
</div>
{% else %}
<div class="media">
<div class="media-left">
{% if onu_details.status == '3' %}
<span class="media-object glyphicon glyphicon-ok-sign text-success" style="font-size: 75px;"></span>
{% elif onu_details.status == '2' %}
<span class="media-object glyphicon glyphicon-remove-sign text-danger" style="font-size: 75px;"></span>
{% else %}
<span class="media-object glyphicon glyphicon-question-sign" style="font-size: 75px;"></span>
{% endif %}
</div>
<div class="media-body">
<b>{% trans 'Name on OLT' %}</b>: {{ onu_details.name }}<br>
<b>{% trans 'Distance(m)' %}</b>: {{ onu_details.distance }}<br>
<b>{% trans 'Signal' %}</b>: {{ onu_details.signal }}<br>
{% if dev.mac_addr != onu_details.mac %}
<span class="text-danger">
<b>{% trans 'Mac on OLT' %}</b>: {{ onu_details.mac }}
<h4 class="glyphicon glyphicon-exclamation-sign" title="{% trans 'Mac-addresses does not match' %}" data-toggle="tooltip"></h4>
<a href="{% url 'devapp:fix_onu' %}" data-param="{{ dev.mac_addr }}" class="btn btn-default btn-xs btn-cmd" title="{% trans 'Fix it' %}" data-toggle="tooltip">
<span class="glyphicon glyphicon-fire"></span>
<span class="hidden-xs">{% trans 'Fix it' %}</span>
</a>
</span>
{% else %}
<b>{% trans 'Mac on OLT' %}</b>: {{ onu_details.mac }}
{% endif %}<br>
</div>
</div>
{% endif %}
{% else %}
<h3>{% trans 'Info does not fetch' %}</h3>
{% endif %}
</div>
</div>
</div>
</div>
{% endwith %}
{% endblock %}

2
devapp/templates/devapp/custom_dev_page/ports.html

@ -5,6 +5,7 @@
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
{% with uptime=dev_manager.uptime %}
<div class="panel-heading">
<div class="panel-title">{{ dev.get_devtype_display|default:_('Title of the type of switch') }}.
{% if uptime %}
@ -12,6 +13,7 @@
{% endif %}
</div>
</div>
{% endwith %}
<div class="panel-body">
{% for port in ports %}

100
devapp/templates/devapp/dev.html

@ -1,8 +1,9 @@
{% extends request.is_ajax|yesno:'nullcont.htm,devapp/ext.htm' %}
{% load i18n %}
{% load bootstrap3 %}
{% block content %}
<div class="panel panel-default">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Device info' %}</h3>
</div>
@ -10,94 +11,49 @@
<form role="form" action="{% url 'devapp:edit' group.pk|default:0 dev.pk %}" method="post">{% csrf_token %}
<div class="form-group">
<label for="id_ip_address">{% trans 'Ip address' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-globe"></span></span>
{{ form.ip_address }}{{ form.ip_address.errors }}
</div>
</div>
<div class="form-group">
<label for="id_mac_addr">{% trans 'Mac address' %}</label>
<div class="input-group{% if form.mac_addr.errors %} has-error{% endif %}">
<span class="input-group-addon"><span class="glyphicon glyphicon-globe"></span></span>
{{ form.mac_addr }}
{% if already_dev %}
<span class="input-group-btn">
<a class="btn btn-danger" href="{% url 'devapp:view' group.pk already_dev.pk %}" title="{% trans 'View the device' %}" data-toggle="tooltip">
{{ already_dev.comment }}
</a>
</span>
{% endif %}
</div>
{{ form.mac_addr.errors }}
</div>
<div class="form-group">
<label for="id_comment">{% trans 'Comment' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-comment"></span></span>
{{ form.comment }}{{ form.comment.errors }}
</div>
</div>
<div class="form-group">
<label for="id_devtype">{% trans 'Device type' %}</label>
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.ip_address addon_before=ic %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-hdd"></span></span>
{{ form.devtype }}{{ form.devtype.errors }}
</div>
</div>
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.mac_addr addon_before=ic %}
<div class="form-group">
<label for="id_man_passw">{% trans 'SNMP password' %}</label>
{% bootstrap_icon 'comment' as ic %}
{% bootstrap_field form.comment addon_before=ic %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
{{ form.man_passw }}{{ form.man_passw.errors }}
</div>
</div>
{% bootstrap_icon 'hdd' as ic %}
{% bootstrap_field form.devtype addon_before=ic %}
<div class="form-group">
<label for="id_map_dot">{% trans 'Map point' %}</label>
{% bootstrap_icon 'lock' as ic %}
{% bootstrap_field form.man_passw addon_before=ic %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-map-marker"></span></span>
{{ form.map_dot }}{{ form.devtype.errors }}
</div>
</div>
<div class="form-group">
<label for="id_user_group">{% trans 'User group' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-subscript"></span></span>
{{ form.user_group }}{{ form.user_group.errors }}
</div>
</div>
{% bootstrap_icon 'subscript' as ic %}
{% bootstrap_field form.user_group addon_before=ic %}
<div class="form-group">
<label for="id_parent_dev">{% trans 'Parent device' %}</label>
<div class="input-group selectajax" data-dst="/dev/search_dev">
<span class="input-group-addon"><span class="glyphicon glyphicon-hdd"></span></span>
<input type="hidden" name="parent_dev" class="selectajax-hid"{% if selected_parent_dev %} value="{{ selected_parent_dev.pk }}"{% endif %}>
<input type="hidden" name="parent_dev" class="selectajax-hid" {% if selected_parent_dev %}
value="{{ selected_parent_dev.pk }}" {% endif %}>
{% if selected_parent_dev %}
<button class="selectajax-btn form-control btn btn-default">{{ selected_parent_dev.comment }}</button>
<input type="text" class="form-control dropdown-toggle selectajax-inp hidden" data-toggle="dropdown" id="id_parent_dev" placeholder="{% trans 'Find the device' %}">
<button class="selectajax-btn form-control btn btn-default">{{ selected_parent_dev.comment }}
</button>
<input type="text" class="form-control dropdown-toggle selectajax-inp hidden" data-toggle="dropdown"
id="id_parent_dev" placeholder="{% trans 'Find the device' %}">
{% else %}
<button class="selectajax-btn form-control btn btn-default hidden"></button>
<input type="text" class="form-control dropdown-toggle selectajax-inp" data-toggle="dropdown" id="id_parent_dev" placeholder="{% trans 'Find the device' %}">
<input type="text" class="form-control dropdown-toggle selectajax-inp" data-toggle="dropdown"
id="id_parent_dev" placeholder="{% trans 'Find the device' %}">
{% endif %}
<ul class="dropdown-menu selectajax-ul"></ul>{{ form.parent_dev.errors }}
<ul class="dropdown-menu selectajax-ul"></ul>
{{ form.parent_dev.errors }}
</div>
</div>
{% bootstrap_icon 'list-alt' as ic %}
{% bootstrap_field form.snmp_item_num addon_before=ic %}
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
@ -109,6 +65,6 @@
</form>
</div>
</div>
</div>
{% endblock %}

36
devapp/templates/devapp/devices.html

@ -12,29 +12,31 @@
<h3>{% trans 'Devices' %}</h3>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<table class="table table-bordered">
<thead>
<tr>
<th>
<th>#</th>
<th class="col-md-2">
<a href="{% url 'devapp:devs' group.pk %}?order_by=ip_address&dir={{ dir|default:"down" }}">
{% trans 'Ip address' %}
</a>
{% if order_by == 'ip_address' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th>
<th class="col-md-5">
<a href="{% url 'devapp:devs' group.pk %}?order_by=comment&dir={{ dir|default:"down" }}">
{% trans 'Comment' %}
</a>
{% if order_by == 'comment' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th>{% trans 'Mac address' %}</th>
<th width="250">
<th class="col-md-3">{% trans 'Mac address' %}</th>
<th class="col-md-3 hidden-xs hidden-sm">{% trans 'Plugin output' %}</th>
<th class="col-md-1">
<a href="{% url 'devapp:devs' group.pk %}?order_by=devtype&dir={{ dir|default:"down" }}">
{% trans 'Device type' %}
</a>
{% if order_by == 'devtype' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th width="100">Do</th>
<th class="col-md-1">Do</th>
</tr>
</thead>
@ -42,18 +44,28 @@
{% with can_del_dev=perms.devapp.delete_device can_ch_dev=perms.devapp.change_device %}
{% for dev in devices %}
<tr>
<td>
{% if dev.mon %}
{% if dev.mon.current_state == '0' %}
<span class="glyphicon glyphicon-ok-circle text-success"></span>
{% else %}
<span class="glyphicon glyphicon-exclamation-sign text-danger"></span>
{% endif %}
{% else %}&ndash;{% endif %}
</td>
<td><a href="{% url 'devapp:view' dev.user_group.pk dev.pk %}">{{ dev.ip_address }}</a></td>
<td>{{ dev.comment }}</td>
<td>{{ dev.mac_addr }}</td>
<td>{{ dev.mac_addr|default:_('Not assigned') }}</td>
<td class="hidden-xs hidden-sm">{{ dev.mon.plugin_output|default:'&ndash;' }}</td>
<td>{{ dev.get_devtype_display }}</td>
<td class="btn-group btn-group-sm">
<td class="btn-group btn-group-xs btn-group-justified">
{% if can_del_dev %}
<a href="{% url 'devapp:del' dev.user_group.pk dev.pk %}" class="btn btn-default btn-sm">
<a href="{% url 'devapp:del' dev.user_group.pk dev.pk %}" class="btn btn-danger">
<span class="glyphicon glyphicon-remove"></span>
</a>
{% endif %}
{% if can_ch_dev %}
<a href="{% url 'devapp:edit' dev.user_group.pk dev.id %}" class="btn btn-default btn-sm">
<a href="{% url 'devapp:edit' dev.user_group.pk dev.id %}" class="btn btn-default">
<span class="glyphicon glyphicon-edit"></span>
</a>
{% endif %}
@ -61,7 +73,7 @@
</tr>
{% empty %}
<tr>
<td colspan="5">{% trans 'Devices does not found' %}. <a href="{% url 'devapp:add' group.pk %}">{% trans 'Create' %}</a></td>
<td colspan="7">{% trans 'Devices does not found' %}. <a href="{% url 'devapp:add' group.pk %}">{% trans 'Create' %}</a></td>
</tr>
{% endfor %}
{% endwith %}
@ -69,7 +81,7 @@
<tfoot>
<tr>
<td colspan="5">
<td colspan="7">
<a href="{% url 'devapp:add' group.pk %}" class="btn btn-success btn-sm">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Create' %}
</a>

82
devapp/templates/devapp/fix_dev_group.html

@ -0,0 +1,82 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% load bootstrap3 %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'devapp:group_list' %}">{% trans 'Groups' %}</a></li>
<li class="active">{{ dev.comment }}</li>
</ol>
{% include 'message_block.html' %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Fix device group' %}</h3>
</div>
<div class="panel-body">
<form role="form" action="{% url 'devapp:fix_device_group' dev.pk %}" method="post">{% csrf_token %}
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.ip_address addon_before=ic %}
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.mac_addr addon_before=ic %}
{% bootstrap_icon 'comment' as ic %}
{% bootstrap_field form.comment addon_before=ic %}
{% bootstrap_icon 'hdd' as ic %}
{% bootstrap_field form.devtype addon_before=ic %}
{% bootstrap_icon 'lock' as ic %}
{% bootstrap_field form.man_passw addon_before=ic %}
<div class="form-group">
<label class="control-label" for="{{ form.user_group.id_for_label }}">{{ form.user_group.label }}</label>
<div class="input-group{% if not dev.user_group %} has-error{% endif %}">
<span class="input-group-addon">
{% bootstrap_icon 'subscript' %}
</span>
{{ form.user_group }}
</div>
</div>
<div class="form-group">
<label for="id_parent_dev">{% trans 'Parent device' %}</label>
<div class="input-group selectajax" data-dst="/dev/search_dev">
<span class="input-group-addon"><span class="glyphicon glyphicon-hdd"></span></span>
<input type="hidden" name="parent_dev" class="selectajax-hid" {% if selected_parent_dev %}
value="{{ selected_parent_dev.pk }}" {% endif %}>
{% if selected_parent_dev %}
<button class="selectajax-btn form-control btn btn-default">{{ selected_parent_dev.comment }}</button>
<input type="text" class="form-control dropdown-toggle selectajax-inp hidden" data-toggle="dropdown"
id="id_parent_dev" placeholder="{% trans 'Find the device' %}">
{% else %}
<button class="selectajax-btn form-control btn btn-default hidden"></button>
<input type="text" class="form-control dropdown-toggle selectajax-inp" data-toggle="dropdown"
id="id_parent_dev" placeholder="{% trans 'Find the device' %}">
{% endif %}
<ul class="dropdown-menu selectajax-ul"></ul>
{{ form.parent_dev.errors }}
</div>
</div>
<div class="btn-group btn-group-sm">
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
<button type="reset" class="btn btn-default">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %}
</button>
</div>
</form>
</div>
</div>
{% endblock %}

2
devapp/templates/devapp/group_list.html

@ -34,7 +34,7 @@
<td class="btn-group">
{% if perms.abonapp.add_abongroup %}
<a href="{% url 'abonapp:add_group' %}" class="btn btn-success btn-sm">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add group' %}
<span class="glyphicon glyphicon-plus"></span> <span class="hidden-xs">{% trans 'Add group' %}</span>
</a>
{% endif %}
<a href="{% url 'devapp:devices_null_group' %}" class="btn btn-primary btn-sm">

33
devapp/templates/devapp/manage_ports/modal_add_edit_port.html

@ -1,9 +1,14 @@
{% load i18n %}
{% load i18n %}{% load bootstrap3 %}
{% if port_id %}
<form role="form" action="{% url 'devapp:edit_port' gid did port_id %}" method="post">{% else %}
<form role="form" action="{% url 'devapp:add_port' gid did %}" method="post">{% endif %}{% csrf_token %}
{% url 'devapp:edit_port' gid did port_id as frm_url %}
{% else %}
{% url 'devapp:add_port' gid did as frm_url %}
{% endif %}
<form role="form" action="{{ frm_url }}" method="post">{% csrf_token %}
<input type="hidden" value="yes" name="confirm">
<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-exclamation-sign"></span>{% trans 'Are you sure?' %}</h4>
@ -11,27 +16,11 @@
<div class="modal-body">
<div class="form-group">
<label for="id_num">{% trans 'Number' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-bishop"></span></span>
{{ form.num }}{{ form.num.errors }}
</div>
</div>
<div class="form-group">
<label for="id_descr">{% trans 'Description' %}</label>
{% bootstrap_form form %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-comment"></span></span>
{{ form.descr }}{{ form.descr.errors }}
</div>
</div>
{% trans 'Save' as btntxt %}
{% bootstrap_button btntxt button_type="submit" button_class="btn-primary" icon="save" size='sm' %}
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
</div>
</form>

2
devapp/urls.py

@ -6,6 +6,7 @@ from . import views
urlpatterns = [
url(r'^$', views.group_list, name='group_list'),
url(r'^devices_without_groups$', views.devices_null_group, name='devices_null_group'),
url(r'^fix_onu/$', views.fix_onu, name='fix_onu'),
url(r'^(?P<grp>\d+)$', views.devices, name='devs'),
url(r'^(?P<grp>\d+)/add$', views.dev, name='add'),
url(r'^(\d+)/(?P<did>\d+)$', views.devview, name='view'),
@ -17,5 +18,6 @@ urlpatterns = [
url(r'^(\d+)/(?P<did>\d+)/(?P<portid>\d+)_(?P<status>[0-1]{1})$', views.toggle_port, name='port_toggle'),
url(r'^(?P<grp>\d+)/(?P<did>\d+)/(?P<portid>\d+)/del$', views.delete_single_port, name='del_port'),
url(r'^(?P<grp>\d+)/(?P<did>\d+)/(?P<pid>\d+)/edit$', views.edit_single_port, name='edit_port'),
url(r'^fix_device_group/(?P<did>\d+)$', views.fix_device_group, name='fix_device_group'),
url(r'^search_dev$', views.search_dev)
]

122
devapp/views.py

@ -2,6 +2,7 @@
from django.contrib.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied
from django.db import IntegrityError
from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404, resolve_url
@ -10,7 +11,7 @@ from django.utils.translation import ugettext_lazy as _
from easysnmp import EasySNMPTimeoutError, EasySNMPError
from json import dumps
from .models import Device, Port, DeviceDBException
from .models import Device, Port, DeviceDBException, DeviceMonitoringException
from mydefs import pag_mn, res_success, res_error, only_admins, ping, order_helper
from .forms import DeviceForm, PortForm
from abonapp.models import AbonGroup, Abon
@ -25,8 +26,8 @@ def devices(request, grp):
group = get_object_or_404(AbonGroup, pk=grp)
if not request.user.has_perm('abonapp.can_view_abongroup', group):
raise PermissionDenied
devs = Device.objects.filter(user_group=grp).only('comment', 'mac_addr', 'devtype', 'user_group', 'pk',
'ip_address')
try:
devs = Device.objects.filter(user_group=group).select_related('user_group').only('comment', 'mac_addr', 'devtype', 'user_group', 'pk', 'ip_address')
# фильтр
dr, field = order_helper(request)
@ -34,6 +35,10 @@ def devices(request, grp):
devs = devs.order_by(field)
devs = pag_mn(request, devs)
devs = Device.objects.wrap_monitoring_info(devs)
except (DeviceDBException, DeviceMonitoringException) as e:
messages.error(request, e)
return render(request, 'devapp/devices.html', {
'devices': devs,
@ -91,17 +96,30 @@ def dev(request, grp, devid=0):
else:
if not request.user.has_perm('devapp.change_device'):
raise PermissionDenied
try:
frm = DeviceForm(request.POST, instance=devinst)
if frm.is_valid():
ndev = frm.save()
ndev.update_dhcp()
messages.success(request, _('Device info has been saved'))
return redirect('devapp:edit', grp, ndev.pk)
return redirect('devapp:edit', ndev.user_group.pk, ndev.pk)
else:
try:
already_dev = Device.objects.get(mac_addr=request.POST.get('mac_addr'))
if already_dev.user_group:
messages.warning(request, _('You have redirected to existing device'))
return redirect('devapp:view', already_dev.user_group.pk, already_dev.pk)
else:
messages.warning(request, _('Please attach user group for device'))
return redirect('devapp:fix_device_group', already_dev.pk)
except Device.DoesNotExist:
pass
messages.error(request, _('Form is invalid, check fields and try again'))
except IntegrityError as e:
if 'unique constraint' in e.message:
messages.error(request, _('Duplicate user and port: %s') % e)
else:
messages.error(request, e)
else:
if devinst is None:
frm = DeviceForm(initial={
@ -110,7 +128,8 @@ def dev(request, grp, devid=0):
'mac_addr': request.GET.get('mac'),
'comment': request.GET.get('c'),
'ip_address': request.GET.get('ip'),
'man_passw': getattr(settings, 'DEFAULT_SNMP_PASSWORD', '')
'man_passw': getattr(settings, 'DEFAULT_SNMP_PASSWORD', ''),
'snmp_item_num': request.GET.get('n')
})
else:
frm = DeviceForm(instance=devinst)
@ -125,7 +144,7 @@ def dev(request, grp, devid=0):
return render(request, 'devapp/dev.html', {
'form': frm,
'dev': devinst,
'selected_parent_dev': devinst.parent_dev or None,
'selected_parent_dev': devinst.parent_dev,
'group': user_group,
'already_dev': already_dev
})
@ -138,7 +157,7 @@ def manage_ports(request, devid):
dev = Device.objects.get(pk=devid)
if dev.user_group is None:
messages.error(request, _('Device is not have a group, please fix that'))
return redirect('devapp:group_list')
return redirect('devapp:fix_device_group', dev.pk)
ports = Port.objects.filter(device=dev)
except Device.DoesNotExist:
@ -177,7 +196,7 @@ def add_ports(request, devid):
dev = Device.objects.get(pk=devid)
if dev.user_group is None:
messages.error(request, _('Device is not have a group, please fix that'))
return redirect('devapp:group_list')
return redirect('devapp:fix_device_group', dev.pk)
if request.method == 'POST':
ports = zip(
request.POST.getlist('p_text'),
@ -200,7 +219,7 @@ def add_ports(request, devid):
db_ports = Port.objects.filter(device=dev)
db_ports = [TempPort(p.num, p.descr, None, True, p.pk) for p in db_ports]
manager = dev.get_manager_klass()(dev.ip_address, dev.man_passw)
manager = dev.get_manager_object()
ports = manager.get_ports()
if ports is not None:
ports = [TempPort(p.num, p.nm, p.st, False) for p in ports]
@ -303,33 +322,36 @@ def add_single_port(request, grp, did):
@login_required
@permission_required('devapp.can_view_device')
def devview(request, did):
ports = None
uptime = 0
ports, manager = None, None
dev = get_object_or_404(Device, id=did)
if not dev.user_group:
messages.warning(request, _('Please attach user group for device'))
return redirect('devapp:fix_device_group', dev.pk)
template_name = 'ports.html'
try:
if ping(dev.ip_address):
if dev.man_passw:
manager = dev.get_manager_klass()(dev.ip_address, dev.man_passw)
uptime = manager.uptime()
manager = dev.get_manager_object()
ports = manager.get_ports()
template_name = manager.get_template_name()
else:
messages.warning(request, _('Not Set snmp device password'))
else:
messages.error(request, _('Dot was not pinged'))
except EasySNMPTimeoutError:
messages.error(request, _('wait for a reply from the SNMP Timeout'))
return render(request, 'devapp/custom_dev_page/' + template_name, {
'dev': dev,
'ports': ports,
'dev_accs': Abon.objects.filter(device=dev),
'dev_manager': manager
})
except EasySNMPError:
messages.error(request, _('SNMP error on device'))
except DeviceDBException as e:
messages.error(request, e)
return render(request, 'devapp/custom_dev_page/' + template_name, {
'dev': dev,
'ports': ports,
'uptime': uptime,
'dev_accs': Abon.objects.filter(device=dev)
'dev': dev
})
@ -342,7 +364,7 @@ def toggle_port(request, did, portid, status=0):
try:
if ping(dev.ip_address):
if dev.man_passw:
manager = dev.get_manager_klass()(dev.ip_address, dev.man_passw)
manager = dev.get_manager_object()
ports = manager.get_ports()
if status:
ports[portid - 1].enable()
@ -354,6 +376,8 @@ def toggle_port(request, did, portid, status=0):
messages.error(request, _('Dot was not pinged'))
except EasySNMPTimeoutError:
messages.error(request, _('wait for a reply from the SNMP Timeout'))
except EasySNMPError as e:
messages.error(request, e)
return redirect('devapp:view', dev.user_group.pk if dev.user_group is not None else 0, did)
@ -378,3 +402,59 @@ def search_dev(request):
).only('pk', 'ip_address', 'comment')[:16]
results = [{'id': dev.pk, 'text': "%s: %s" % (dev.ip_address, dev.comment)} for dev in results]
return HttpResponse(dumps(results, ensure_ascii=False))
@login_required
def fix_device_group(request, did):
dev = get_object_or_404(Device, pk=did)
try:
if request.method == 'POST':
frm = DeviceForm(request.POST, instance=dev)
if frm.is_valid():
ch_dev = frm.save()
if ch_dev.user_group:
messages.success(request, _('Device fixed'))
return redirect('devapp:devs', ch_dev.user_group.pk)
else:
messages.error(request, _('Please attach user group for device'))
else:
messages.error(request, _('Form is invalid, check fields and try again'))
else:
frm = DeviceForm(instance=dev)
except ValueError:
return HttpResponse('ValueError')
return render(request, 'devapp/fix_dev_group.html', {
'form': frm,
'dev': dev,
'selected_parent_dev': dev.parent_dev
})
@login_required
def fix_onu(request):
mac = request.GET.get('cmd_param')
status = 1
text = '<span class="glyphicon glyphicon-exclamation-sign"></span>'
try:
onu = Device.objects.get(mac_addr=mac, devtype='On')
parent = onu.parent_dev
if parent is not None:
manobj = parent.get_manager_object()
ports = manobj.get_list_keyval('.1.3.6.1.4.1.3320.101.10.1.1.3')
for srcmac, snmpnum in ports:
real_mac = ':'.join(['%x' % ord(i) for i in srcmac])
if mac == real_mac:
onu.snmp_item_num = snmpnum
onu.save(update_fields=['snmp_item_num'])
status = 0
text = '<span class="glyphicon glyphicon-ok"></span> <span class="hidden-xs">%s</span>' % _('Fixed')
break
else:
text = text + ' %s' % _('Parent device not found')
except Device.DoesNotExist:
pass
return HttpResponse(dumps({
'status': status,
'dat': text
}))

44
dhcp_lever.py

@ -1,25 +1,53 @@
#!/usr/bin/env python3
import sys
from redis import Redis
from rq import Queue
import socket
def die(text):
print(text)
exit(1)
'''
obj = {
'client_ip': ip2int('127.0.0.1'),
'client_mac': 'aa:bb:cc:dd:ee:ff',
'switch_mac': 'aa:bb:cc:dd:ee:ff',
'switch_port': 3,
'cmd': 'commit'
}
'''
def send_to(data, addr='127.0.0.1', port=5436):
from pickle import dumps
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((addr, port))
data = dumps(data)
s.send(data)
except ConnectionRefusedError:
print('ERROR: connection refused')
if __name__ == "__main__":
argv = sys.argv
if len(argv) < 3:
die('Too few arguments, exiting...')
action = argv[1]
q = Queue(connection=Redis())
if action == 'commit':
if len(argv) < 6:
die('Too few arguments, exiting...')
q.enqueue('agent.commands.dhcp.dhcp_commit', argv[2], argv[3], argv[4], int(argv[5]))
elif action == 'expiry':
q.enqueue('agent.commands.dhcp.dhcp_expiry', argv[2])
elif action == 'release':
q.enqueue('agent.commands.dhcp.dhcp_release', argv[2])
dat = {
'client_ip': argv[2],
'client_mac': argv[3],
'switch_mac': argv[4],
'switch_port': int(argv[5]),
'cmd': 'commit'
}
send_to(dat)
elif action == 'expiry' or action == 'release':
dat = {
'client_ip': argv[2],
'cmd': action
}
send_to(dat)

13
dialing_app/locale/ru/LC_MESSAGES/django.po

@ -95,14 +95,9 @@ msgstr "Несколько абонентов с указанным номеро
msgid "User with the telephone number not found"
msgstr "Абонент с таким номером телефона не найден"
msgid "Voice mail"
msgstr "Оставленные сообщения"
msgid "Voice mail request"
msgstr "Заявки на подключение"
msgid "Type"
msgstr "Тип"
msgid "Voice mail report"
msgstr "Заявки на поломки"
msgid "Request"
msgstr "Заявка"
msgid "Report"
msgstr "Поломка"

4
dialing_app/models.py

@ -59,5 +59,9 @@ class AsteriskCDR(models.Model):
return "%s/recording/bug" % path
return "%s/monitor" % path
def url(self):
return "%s/%s-%s-%s.wav" % ( self.path_to_media(), self.calldate.strftime('%Y/%m/%d/%H_%M'), self.src, self.dst )
class Meta:
db_table = 'cdr'
managed = False

12
dialing_app/templates/ext.html

@ -22,13 +22,21 @@
</a>
</li>
{% url 'dialapp:vmail' as dialmail %}
{% url 'dialapp:vmail_request' as dialmail %}
<li{% if dialmail == request.path %} class="active"{% endif %}>
<a href="{{ dialmail }}">
{% trans 'Voice mail' %}
{% trans 'Voice mail request' %}
</a>
</li>
{% url 'dialapp:vmail_report' as dialmail %}
<li{% if dialmail == request.path %} class="active"{% endif %}>
<a href="{{ dialmail }}">
{% trans 'Voice mail report' %}
</a>
</li>
</ul>
<div class="tab-content">

23
dialing_app/templates/index.html

@ -17,16 +17,34 @@
<th>{% trans 'end' %}</th>
<th>{% trans 'disposition' %}</th>
</tr>
<tr>
<th colspan="9">
<form class="form-inline" action="{% url 'dialapp:vfilter' %}" method="get">
<div class="form-group">
<label class="sr-only" for="dialsearch">{% trans 'Find dials' %}</label>
<div class="input-group input-group-sm">
<input type="text" class="form-control" id="dialsearch" placeholder="{% trans 'Telephone' %}" name="s"{% if s %} value="{{ s }}"{% endif %}>
<div class="input-group-btn">
<button class="btn btn-default" type="submit">
<span class="glyphicon glyphicon-search"></span>
</button>
</div>
</div>
</div>
</form>
</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
{% with lurl=log.url %}
<td class="btn-group btn-group-xs btn-group-justify">
<button class="btn btn-default player-btn disabled">
<span class="glyphicon glyphicon-play"></span>
<audio preload="metadata" src="{{ log.path_to_media }}/{{ log.calldate|date:"Y/m/d/H_i" }}-{{ log.src }}-{{ log.dst }}.wav"></audio>
<audio preload="metadata" src="{{ lurl }}"></audio>
</button>
<a href="{{ log.path_to_media }}/{{ log.calldate|date:"Y/m/d/H_i" }}-{{ log.src }}-{{ log.dst }}.wav" class="btn btn-default disabled" target="_blank" title="{% trans 'Download' %}">
<a href="{{ lurl }}" class="btn btn-default disabled" target="_blank" title="{% trans 'Download' %}">
<span class="glyphicon glyphicon-download-alt"></span>
</a>
</td>
@ -38,6 +56,7 @@
<td>{{ log.answer|date:'d M, H:i:s' }}</td>
<td>{{ log.end|date:'d M, H:i:s' }}</td>
<td>{{ log.locate_disposition }}</td>
{% endwith %}
</tr>
{% empty %}
<tr>

10
dialing_app/templates/vmail.html

@ -10,7 +10,6 @@
<th>{% trans 'Play' %}</th>
<th>{% trans 'calldate' %}</th>
<th>{% trans 'src' %}</th>
<th>{% trans 'Type' %}</th>
<th>{% trans 'duration' %}</th>
<th>{% trans 'start' %}</th>
<th>{% trans 'answer' %}</th>
@ -21,7 +20,7 @@
<tbody>
{% for vmail in vmessages %}
<tr>
<td class="btn-group btn-group-sm">
<td class="btn-group btn-group-xs">
<button class="btn btn-default player-btn disabled">
<span class="glyphicon glyphicon-play"></span>
<audio preload="metadata" src="{{ vmail.path_to_media }}/{{ vmail.calldate|date:"YmdHi" }}-{{ vmail.src }}-{{ vmail.dst }}.wav"></audio>
@ -32,11 +31,6 @@
</td>
<td>{{ vmail.calldate|date:'d E Y, H:i:s' }}</td>
<td>{{ vmail.src|abon_if_telephone|safe }}</td>
<td>
{% if vmail.userfield == 'request' %}{% trans 'Request' %}
{% elif vmail.userfield == 'report' %}{% trans 'Report' %}
{% else %}{{ vmail.userfield }}{% endif %}
</td>
<td>{{ vmail.duration }}</td>
<td>{{ vmail.start|date:'d E Y, H:i:s' }}</td>
<td>{{ vmail.answer|date:'d E Y, H:i:s' }}</td>
@ -45,7 +39,7 @@
</tr>
{% empty %}
<tr>
<td colspan="9">{% trans 'Calls was not found' %}</td>
<td colspan="8">{% trans 'Calls was not found' %}</td>
</tr>
{% endfor %}
</tbody>

4
dialing_app/urls.py

@ -4,6 +4,8 @@ from . import views
urlpatterns = [
url(r'^$', views.home, name='home'),
url(r'^filter$', views.vfilter, name='vfilter'),
url(r'^to_abon(?P<tel>\+?\d+)$', views.to_abon, name='to_abon'),
url(r'^voicemail$', views.vmail, name='vmail')
url(r'^requests$', views.vmail_request, name='vmail_request'),
url(r'^reports$', views.vmail_report, name='vmail_report')
]

30
dialing_app/views.py

@ -3,6 +3,7 @@ from django.contrib import messages
from django.shortcuts import render, redirect
from django.utils.translation import ugettext_lazy as _
from guardian.decorators import permission_required_or_403 as permission_required
from django.db.models import Q
from abonapp.models import Abon
from mydefs import only_admins, pag_mn
@ -41,11 +42,36 @@ def to_abon(request, tel):
@login_required
@only_admins
def vmail(request):
title = _('Voice mail')
def vmail_request(request):
title = _('Voice mail request')
cdr = AsteriskCDR.objects.filter(userfield='request').order_by('-calldate')
cdr = pag_mn(request, cdr)
return render(request, 'vmail.html', {
'title': title,
'vmessages': cdr
})
@login_required
@only_admins
def vmail_report(request):
title = _('Voice mail report')
cdr = AsteriskCDR.objects.filter(userfield='report').order_by('-calldate')
cdr = pag_mn(request, cdr)
return render(request, 'vmail.html', {
'title': title,
'vmessages': cdr
})
@login_required
@only_admins
def vfilter(request):
s = request.GET.get('s')
cdr_q = Q(src__icontains=s) | Q(dst__icontains=s)
cdr = AsteriskCDR.objects.filter(cdr_q)
return render(request, 'index.html', {
'logs': cdr,
'title': _('Find dials'),
's': s
})

6
djing/settings_example.py

@ -44,7 +44,11 @@ INSTALLED_APPS = [
'chatbot',
'msg_app',
'dialing_app',
'guardian'
'guardian',
'pinax_theme_bootstrap',
'bootstrapform',
'bootstrap3'
]
MIDDLEWARE_CLASSES = [

8
djing/urls.py

@ -1,7 +1,6 @@
from django.conf.urls import url, include
from django.contrib import admin
from django.conf import settings
from . import settings
from .views import home
@ -17,13 +16,14 @@ urlpatterns = [
url(r'^tasks/', include('taskapp.urls', namespace='taskapp')),
url(r'^client/', include('clientsideapp.urls', namespace='client_side')),
url(r'^msg/', include('msg_app.urls', namespace='msg_app')),
url(r'^dialing/', include('dialing_app.urls', namespace='dialapp')),
url(r'^admin/', admin.site.urls)
url(r'^dialing/', include('dialing_app.urls', namespace='dialapp'))
]
if settings.DEBUG:
from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.contrib import admin
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += staticfiles_urlpatterns()
urlpatterns += [url(r'^admin/', admin.site.urls)]

69
docs/dev.md

@ -289,3 +289,72 @@ def add_user_range(self, user_list):
def add_tariff_range(self, tariff_list):
pass
```
## Отправляем оповещения
Для того чтоб оправить важное сообщение работнику через все возможные настроенные системы(смс, телеграм, браузер) мы можем
воспользоваться одной процедурой из модуля **chatbot**.
```python
from chatbot.telebot import send_notify
send_notify(msg_text='Text message',account=employee_profile, tag='apptag')
```
Процедура **send_notify** принимает 3 параметра. 2 первых обязательны а последний не обязаелен.
*msg_text* - Текст сообщения
*account* - Учётка работника которому отправляем сообщение
*tag* - Тэг сообщения, это поле предназначено для фильтрации ваших сообщений в вашем приложении. Каждое приложение в пределах
своих вызовов использует один и тот жеж уникальный тэг. Для примера приложение личных сообщений видит сообщения только для себя
с помощью тега *msgapp*, и вы не спутаете ваши сообщения с сообщениями из модуля, например, задач который использует тэг *taskap*.
#### Получение оповещения
```python
from chatbot.models import MessageQueue
msg = MessageQueue.objects.pop(user=employee_profile, tag='apptag')
```
Метод **pop** плучает первое сообщение и удаляет его.
#### Отображаем оповещение в браузере
Чтоб отобразить **WebNotification** добавте в файле скриптов *static/js/my.js* строку вида:
```javascript
$(document).notifys({news_url: '/url/to/your_view', check_interval: 60});
```
Тут *news_url* это путь к вашему представления которое возвращает новые оповещения, а *check_interval* - это интервал обращения к вашему представлению.
Представление для Web оповещений выглядит примерно так:
```python
@login_required
@only_admins
def check_news(request):
msg = MessageQueue.objects.pop(user=request.user, tag='apptag')
if msg is not None:
r = {
'exist': True,
'content': msg,
'title': 'Message title'
}
else:
r = {'exist': False}
return HttpResponse(dumps(r))
```
Убедитесь что вашему представлению не будет доступа от абонентов, об этом позаботится декоратор *only_admins* из *mydefs*. *mydefs* лежит в корне проекта.
После получения сообщения надо вернуть словарь с параметрами:
*exist* - Логическое значение, обозначает есть или нет информации в ответе. Если *exist* == True тогда возвращае ещё *content* и *title*.
*content* - Соответственно содержимое оповещения.
*title* - Заголовок оповещения.

12
global_context_processors.py

@ -1,18 +1,12 @@
# -*- coding: utf-8 -*-
from django.shortcuts import get_object_or_404
from abonapp.models import Abon
#def context_processor_client_ipaddress(request):
# ip = request.META.get('REMOTE_ADDR', '') or request.META.get('HTTP_X_FORWARDED_FOR', '')
# return {
# 'client_ipaddress': ip
# }
from django.conf import settings
# От сюда можно получать на клиентской стороне профиль абонента
def context_processor_additional_profile(request):
if request.user.is_staff or request.user.is_anonymous():
return {'subscriber': request.user}
return {'subscriber': request.user, 'FILE_UPLOAD_MAX_MEMORY_SIZE': settings.FILE_UPLOAD_MAX_MEMORY_SIZE}
else:
return {'subscriber': get_object_or_404(Abon, id=request.user.pk)}
return {'subscriber': get_object_or_404(Abon, id=request.user.pk), 'FILE_UPLOAD_MAX_MEMORY_SIZE': settings.FILE_UPLOAD_MAX_MEMORY_SIZE}

7
mapapp/forms.py

@ -6,9 +6,8 @@ from .models import Dot
class DotForm(forms.ModelForm):
class Meta:
model = Dot
fields = '__all__'
exclude = ['devices']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control', 'required': '', 'autofocus':''}),
'latitude': forms.NumberInput(attrs={'class': 'form-control', 'required': ''}),
'longitude': forms.NumberInput(attrs={'class': 'form-control', 'required': ''})
'title': forms.TextInput(attrs={'required': '', 'autofocus': ''}),
}

167
mapapp/locale/ru/LC_MESSAGES/django.po

@ -0,0 +1,167 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Dmitry Novikov nerosketch@gmail.com, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-11-03 00:12+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Dmitry Novikov nerosketch@gmail.com\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
#: models.py:7
msgid "Map point title"
msgstr "Название точки"
#: models.py:8
msgid "Latitude"
msgstr "Широта"
#: models.py:9 templates/maps/options.html.py:18
msgid "Longitude"
msgstr "Долгота"
#: models.py:10 templates/maps/dot.html.py:63
msgid "Devices"
msgstr "Устройства"
#: models.py:11
msgid "Attachment"
msgstr "Приложение"
#: models.py:15
msgid "Map point"
msgstr "Геоточка"
#: models.py:16
msgid "Map points"
msgstr "Геоточки"
#: models.py:18
msgid "Can view"
msgstr "Может просматривать"
#: templates/maps/add_device.html:8 templates/maps/dot.html.py:8
#: templates/maps/options.html:7
msgid "Map settings"
msgstr "Настройки карты"
#: templates/maps/add_device.html:10
msgid "Add devices"
msgstr "Добавить устройства"
#: templates/maps/add_device.html:24
msgid "Pick the group"
msgstr "Выбрать группу"
#: templates/maps/add_device.html:55
msgid "Select the devices"
msgstr "Выбрать устройства"
#: templates/maps/add_device.html:62 templates/maps/dot.html.py:49
#: templates/maps/modal_add_device.html:15 templates/maps/modal_add_dot.html:25
msgid "Save"
msgstr "Сохранить"
#: templates/maps/dot.html:13
msgid "Add new point"
msgstr "Добавить новую точку"
#: templates/maps/dot.html:52
msgid "Reset"
msgstr "Сбросить"
#: templates/maps/dot.html:83
msgid "User group has no attached"
msgstr "Группа абонентов не привязана"
#: templates/maps/dot.html:89
msgid "Devices not found"
msgstr "Устройства не найдены"
#: templates/maps/dot.html:94
msgid "Add"
msgstr "Добавить"
#: templates/maps/map_tooltip.html:15
msgid "Pinned devices not found"
msgstr "Привязанные устройства не найдены"
#: templates/maps/modal_add_device.html:5
msgid "Add device"
msgstr "Добавить устройство"
#: templates/maps/modal_add_device.html:17
msgid "Close"
msgstr "Закрыть"
#: templates/maps/modal_add_dot.html:6
msgid "Add point"
msgstr "Добавить точку"
#: templates/maps/modal_add_dot.html:9
#, python-format
msgid "Coords: %(coords)s"
msgstr "Координаты %(coords)s"
#: templates/maps/options.html:17
msgid "Title"
msgstr "Название"
#: templates/maps/options.html:32
msgid "Edit"
msgstr "Изменить"
#: templates/maps/options.html:39
msgid "Delete"
msgstr "Удалить"
#: templates/maps/options.html:49
msgid "You have not created map points yet"
msgstr "Вы ещё не создали ни одной геоточки"
#: templates/maps/options.html:49
msgid "Create"
msgstr "Создать"
#: templates/maps/preload_devices_tmpl.html:7
msgid "no devices found"
msgstr "нет усройств"
#: templates/maps/ya_index.html:11
msgid "Loading.."
msgstr "Загрузка.."
#: templates/maps/ya_index.html:144
msgid "Layers"
msgstr "Слои"
#: templates/maps/ya_index.html:153
msgid "Show all"
msgstr "Показать всё"
#: views.py:53
msgid "Map point has been saved"
msgstr "Геоточка сохранена"
#: views.py:56 views.py:111
msgid "fix form errors"
msgstr "исправте ошибки формы"
#: views.py:66 views.py:79
msgid "Map point does not exist"
msgstr "Геоточка не найдена"
#: views.py:77
#, python-format
msgid "Map point '%(title)s' has been deleted"
msgstr "Геоточка '%(title)s' была удалена"

45
mapapp/migrations/0002_auto_20171103_0006.py

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-11-03 00:06
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('devapp', '0004_auto_20171103_0006'),
('mapapp', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='dot',
options={'permissions': (('can_view', 'Can view'),), 'verbose_name': 'Map dot', 'verbose_name_plural': 'Map dots'},
),
migrations.AddField(
model_name='dot',
name='attachment',
field=models.FileField(blank=True, null=True, upload_to='map_attachments/%Y_%m_%d', verbose_name='Attachment'),
),
migrations.AddField(
model_name='dot',
name='devices',
field=models.ManyToManyField(db_table='dot_device', to='devapp.Device', verbose_name='Devices'),
),
migrations.AlterField(
model_name='dot',
name='latitude',
field=models.FloatField(verbose_name='Latitude'),
),
migrations.AlterField(
model_name='dot',
name='longitude',
field=models.FloatField(verbose_name='Longitude'),
),
migrations.AlterField(
model_name='dot',
name='title',
field=models.CharField(max_length=127, verbose_name='Map point title'),
),
]

15
mapapp/models.py

@ -1,13 +1,22 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from devapp.models import Device
class Dot(models.Model):
title = models.CharField(max_length=127)
latitude = models.FloatField()
longitude = models.FloatField()
title = models.CharField(_('Map point title'), max_length=127)
latitude = models.FloatField(_('Latitude'))
longitude = models.FloatField(_('Longitude'))
devices = models.ManyToManyField(Device, verbose_name=_('Devices'), db_table='dot_device')
attachment = models.FileField(_('Attachment'), upload_to='map_attachments/%Y_%m_%d', null=True, blank=True)
class Meta:
db_table = 'dots'
verbose_name = _('Map point')
verbose_name_plural = _('Map points')
permissions = (
('can_view', _('Can view')),
)
def __str__(self):
return self.title

70
mapapp/templates/maps/add_device.html

@ -0,0 +1,70 @@
{% extends 'base.html' %}
{% load i18n %}
{% load bootstrap3 %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'mapapp:options' %}">{% trans 'Map settings' %}</a></li>
<li><a href="{% url 'mapapp:edit_dot' dot.pk %}">{{ dot.title }}</a></li>
<li class="active">{% trans 'Add devices' %}</li>
</ol>
{% include 'message_block.html' %}
<script type="text/javascript">
</script>
<div class="row">
<div class="col-sm-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Pick the group' %}</h3>
</div>
<div class="panel-body">
{% url 'mapapp:preload_devices' as prel_url %}
{% for group in groups %}
{% if group.pk == grp %}
<u><a href="#" data-href="{{ prel_url }}?dot={{ dot.pk }}&grp={{ group.pk }}" class="btn_ajloader">{{ group.title }}</a></u>
{% else %}
<a href="#" data-href="{{ prel_url }}?dot={{ dot.pk }}&grp={{ group.pk }}" class="btn_ajloader">{{ group.title }}</a>
{% endif %}<br>
{% endfor %}
</div>
</div>
<script type="text/javascript">
$(window).load(function() {
$('.btn_ajloader').on('click', function(e){
var grp_id = $(this).attr('data-href');
grp_id = grp_id.split('=');
grp_id = grp_id[grp_id.length-1];
$('#selected_user_group').val(grp_id);
e.preventDefault();
});
});
</script>
</div>
<div class="col-sm-8">
<form role="form" action="{% url 'mapapp:add_dev' dot.pk %}?grp={{ grp }}" method="post">{% csrf_token %}
<input type="hidden" id="selected_user_group" name="selected_user_group">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Select the devices' %}</h3>
</div>
<div class="panel-body" id="id_block_devices">
{% include 'maps/preload_devices_tmpl.html' with all_devices=existing_devs dot_devices_ids=dot_devices_ids %}
</div>
<div class="panel-footer">
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

118
mapapp/templates/maps/dot.html

@ -1,63 +1,103 @@
{% extends 'base.html' %}
{% load i18n %}
{% load bootstrap3 %}
{% block main %}
<ol class="breadcrumb">
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'mapapp:options' %}">Настройки карты</a></li>
<li><a href="{% url 'mapapp:options' %}">{% trans 'Map settings' %}</a></li>
{% if dot.id %}
<li class="active">{{ dot.title }}</li>
</ol>
{% url 'mapapp:edit_dot' dot.id as form_url %}
{% else %}
<li class="active">{% trans 'Add new point' %}</li>
{% url 'mapapp:add_dot' as form_url %}
{% endif %}
</ol>
{% include 'message_block.html' %}
{% include 'message_block.html' %}
<div class="row">
<div class="{% if dot.id %}col-sm-6{% else %}col-sm-12{% endif %}">
<form role="form" action="{{ form_url }}" method="post" enctype="multipart/form-data">{% csrf_token %}
<input type="hidden" name="MAX_FILE_SIZE" value="{{ FILE_UPLOAD_MAX_MEMORY_SIZE }}"/>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Точка топологии</h3>
<h3 class="panel-title">{% trans 'Map point' %}</h3>
</div>
<div class="panel-body">
{% if dot.id %}
<form role="form" action="{% url 'mapapp:edit_dot' dot.id %}" method="post">
{% else %}
<form role="form" action="{% url 'mapapp:add_dot' %}" method="post">
{% endif %}
{% csrf_token %}
{# title input #}
{% bootstrap_icon 'edit' as ic %}
{% bootstrap_field form.title addon_before=ic %}
<div class="form-group">
<label for="id_title">Название точки топологии</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-edit"></span></span>
{{ form.title }}{{ form.title.errors }}
</div>
</div>
{# longitude input #}
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.longitude addon_before=ic %}
<div class="form-group">
<label for="id_longitude">Longitude</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-globe"></span></span>
{{ form.longitude }}{{ form.longitude.errors }}
</div>
</div>
{# latitude input #}
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.latitude addon_before=ic %}
<div class="form-group">
<label for="id_latitude">Latitude</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-globe"></span></span>
{{ form.latitude }}{{ form.latitude.errors }}
</div>
</div>
{# attachment input #}
{% bootstrap_field form.attachment %}
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> Сохранить
</div>
<div class="panel-footer">
<div class="btn-group btn-group-sm">
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
<button type="reset" class="btn btn-sm btn-default">
<span class="glyphicon glyphicon-remove-circle"></span> Сбросить
<button type="reset" class="btn btn-default">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %}
</button>
</div>
</div>
</div>
</form>
</div>
{% if dot.id %}
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Devices' %}</h3>
</div>
<table class="table">
{% for dev in dot.devices.all %}
<tr>
<td>
{% if dev.user_group %}
<a href="{% url 'devapp:view' dev.user_group.pk dev.pk %}">{{ dev.comment }}</a>
{% else %}
<a href="{% url 'devapp:view' 0 dev.pk %}">{{ dev.comment }}</a>
{% endif %}
</td>
<td>{{ dev.ip_address }}</td>
<td>{{ dev.get_comment_display }}</td>
<td>
{% if dev.user_group %}
<a href="{% url 'abonapp:people_list' dev.user_group.pk %}">
{{ dev.user_group }}
</a>
{% else %}
{% trans 'User group has no attached' %}
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="3">{% trans 'Devices not found' %}</td>
</tr>
{% endfor %}
</table>
<div class="panel-footer">
{% trans 'Add' as trns %}
{% url 'mapapp:add_dev' dot.pk as url %}
{% bootstrap_button trns button_type="link" icon='plus' size='sm' button_class='btn-success' href=url %}
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}

62
mapapp/templates/maps/index.html

@ -1,62 +0,0 @@
{% extends 'base_no_lmenu.html' %}
{% block main %}
<script src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
<script>
var markers = {
dev_ok: 'dev_ok.png',
dev_bug: 'dev_bug.png',
dev: 'dev.png',
disabled: 'flag_black.png'
};
google.maps.Map.prototype.onMapDblClick = function (e) {
};
google.maps.Map.prototype.SetStaticMarker = function (wlink, type, x, y) {
var dot = new google.maps.LatLng(x, y);
var m = new google.maps.Marker({icon: '/static/img/gmarkers/' + type, position: dot});
var iw = new google.maps.InfoWindow();
google.maps.event.addListener(m, 'click', function () {
var mp = this.map;
$.get(wlink, function (r) {
iw.setContent(r);
iw.open(mp, m);
});
});
m.setMap(this);
};
function MyISGmap(elId) {
var contain_block = document.getElementById(elId);
this.map = new google.maps.Map(contain_block, {
center: new google.maps.LatLng(45.4489018, 34.7880390),
zoom: 11,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
this.map.set("disableDoubleClickZoom", true);
google.maps.event.addListener(this.map, 'dblclick', this.map.onMapDblClick);
}
function initialize() {
var mm = new MyISGmap("main");
$.getJSON("#{ % url 'maps_get_dots' %}", function (r) {
for (var n in r.dots) {
var el = r.dots[n];
mm.map.SetStaticMarker("/", markers.disabled, el[1], el[2]);
}
});
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
{% endblock %}

19
mapapp/templates/maps/map_tooltip.html

@ -1,10 +1,23 @@
{% load i18n %}
<h4>{{ dot.title }}</h4>
<div class="list-group">
{% for dev in devs %}
<a class="list-group-item" href="{% url 'devapp:view' dev.user_group.pk dev.pk %}" target="_blank">
{{ dev.comment }}
<a class="list-group-item" href="{% url 'devapp:view' dev.user_group.pk dev.pk %}" target="_blank" title="{{ dev.mon.plugin_output }}">
{% if dev.mon.current_state == '0' %}
<span class="glyphicon glyphicon-ok-circle text-success"></span>
{% else %}
<span class="glyphicon glyphicon-exclamation-sign text-danger"></span>
{% endif %}
<b>{{ dev.comment }}</b> {{ dev.mon.plugin_output }}
</a>
{% empty %}
<li class="list-group-item">нет привязанных устройств</li>
<li class="list-group-item">{% trans 'Pinned devices not found' %}</li>
{% endfor %}
</div>
{% if dot.attachment %}
<span class="glyphicon glyphicon-file"></span>
<a href="{{ dot.attachment.url }}" class="btn btn-xs btn-dfault" target="_blank">{{ dot.attachment.name }}</a>
{% endif %}

20
mapapp/templates/maps/modal_add_device.html

@ -0,0 +1,20 @@
{% load i18n %}{% load bootstrap3 %}
<form action="{% url 'mapapp:add_dev' dot_id %}" 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-warning-sign"></span>{% trans 'Add device' %}</h4>
</div>
<div class="modal-body">
{% bootstrap_form form %}
</div>
<div class="modal-footer">
<div class="btn-group btn-grup-sm">
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button>
</div>
</div>
</form>

28
mapapp/templates/maps/modal_add_dot.html

@ -1,20 +1,28 @@
<form action="{% url 'mapapp:modal_add_dot' %}" method="post">{% csrf_token %}
{% load i18n %}{% load bootstrap3 %}
<form action="{% url 'mapapp:modal_add_dot' %}" class="form-ajax" method="post" enctype="multipart/form-data">{% csrf_token %}
<input type="hidden" name="MAX_FILE_SIZE" value="{{ FILE_UPLOAD_MAX_MEMORY_SIZE }}"/>
<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-warning-sign"></span>Добавить точку</h4>
<h4 class="modal-title"><span class="glyphicon glyphicon-warning-sign"></span>{% trans 'Add point' %}</h4>
</div>
<div class="modal-body">
<h4>Координаты: {{ coords }}</h4>
<div class="form-group">
<label for="id_title">Название точки</label>
<input type="text" class="form-control" autofocus name="title" id="id_title">
</div>
<input type="hidden" name="coords" value="{{ coords }}">
<h4>{% blocktrans %}Coords: {{ coords }}{% endblocktrans %}</h4>
{% bootstrap_icon 'edit' as ic %}
{% bootstrap_field form.title addon_before=ic %}
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.longitude addon_before=ic %}
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.latitude addon_before=ic %}
{% bootstrap_field form.attachment %}
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> Сохранить
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button>
</div>
</form>

38
mapapp/templates/maps/options.html

@ -1,24 +1,24 @@
{% extends 'base.html' %}
{% load i18n %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li class="active">Настройки карты</li>
<li class="active">{% trans 'Map settings' %}</li>
</ol>
{% include 'message_block.html' %}
<h3>Точки топологии</h3>
{% with can_change_dot=perms.mapapp.change_dot can_delete_dot=perms.mapapp.delete_dot %}
<h3>{% trans 'Map point' %}</h3>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Название</th>
<th>Longitude</th>
<th>latitude</th>
<th width="50">Ред.</th>
<th width="50">Уд.</th>
<th>{% trans 'Title' %}</th>
<th>{% trans 'Longitude' %}</th>
<th>{% trans 'Latitude' %}</th>
{% if can_change_dot or can_delete_dot %}<th width="90">#</th>{% endif %}
</tr>
</thead>
<tbody>
@ -27,33 +27,38 @@
<td>{{ dot.title }}</td>
<td>{{ dot.longitude }}</td>
<td>{{ dot.latitude }}</td>
<td colspan="2" class="btn-group-xs">
{% if can_change_dot or can_delete_dot %}
<td colspan="2" class="btn-group btn-group-xs">
{% if perms.mapapp.change_dot %}
<a href="{% url 'mapapp:edit_dot' dot.id %}"
{% if can_change_dot %}
<a href="{% url 'mapapp:edit_dot' dot.id %}" title="{% trans 'Edit' %}" data-toggle="tooltip"
class="btn btn-primary">
<span class="glyphicon glyphicon-edit"></span>
</a>
{% endif %}
{% if perms.mapapp.delete_dot %}
<a href="{% url 'mapapp:remove_dot' dot.id %}"
{% if can_delete_dot %}
<a href="{% url 'mapapp:remove_dot' dot.id %}" title="{% trans 'Delete' %}" data-toggle="tooltip"
class="btn btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span>
<span class="glyphicon glyphicon-remove"></span>
</a>
{% endif %}
</td>
{% endif %}
</tr>
{% empty %}
<tr>
<td colspan="5">Вы ещё не создавали точки топологии. <a href="{% url 'mapapp:add_dot' %}">Создать</a></td>
<td colspan="{% if can_change_dot or can_delete_dot %}4{% else %}3{% endif %}">
{% trans 'You have not created map points yet' %}
<a href="{% url 'mapapp:add_dot' %}">{% trans 'Create' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="5">
<td colspan="{% if can_change_dot or can_delete_dot %}4{% else %}3{% endif %}">
<a href="{% url 'mapapp:add_dot' %}" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-plus"></span>
</a>
@ -62,6 +67,7 @@
</tfoot>
</table>
</div>
{% endwith %}
{% include 'toolbar_page.html' with pag=dots %}
{% endblock %}

7
mapapp/templates/maps/preload_devices_tmpl.html

@ -0,0 +1,7 @@
{% load i18n %}
{% for dev in all_devices %}<div class="checkbox">
<label>
<input name="dv" type="checkbox"{% if dev.pk in dot_devices_ids %} checked{% endif %} value="{{ dev.pk }}"/> {{ dev.comment }}
</label>
</div>
{% empty %}{% trans 'no devices found' %}{% endfor %}

121
mapapp/templates/maps/ya_index.html

@ -1,4 +1,4 @@
{% extends 'base_no_lmenu.html' %}
{% extends 'base_no_lmenu.html' %}{% load i18n %}
{% block main %}
<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script>
@ -8,7 +8,7 @@
function placemark_click(e){
var plcmrk = e.get('target');
plcmrk.properties.set('balloonContent', "Получаю инфу..");
plcmrk.properties.set('balloonContent', "{% trans 'Loading..' %}");
var html = $.ajax({
url: "{% url 'mapapp:dot_tooltip' %}",
data: {'d': plcmrk.properties._data.dot_id},
@ -18,23 +18,48 @@
}
function load_dots(r){
for(var n=0; n< r.length; n++){
var dot = new ymaps.Placemark(
[r[n].fields.latitude, r[n].fields.longitude],
{
hintContent: r[n].fields.title,
dot_id: r[n].pk
}
);
for(let e of r){
var iconname='flag_black.png';
if(e.devcount > 0)
iconname='dev_ok.png';
dot_place([e.latitude, e.longitude], {
hintContent: e.title,
dot_id: e.pk
}, iconname);
}
}
function dot_place(pos, opts, icon_name='dev_ok.png'){
var dot = new ymaps.Placemark(pos, opts, {
iconLayout: 'default#image',
iconImageHref: '/static/img/gmarkers/'+icon_name,
iconImageSize: [32, 48],
iconImageOffset: [-16, -48]
});
dot.events.add('click', placemark_click);
myMap.geoObjects.add(dot);
}
function on_success_add_dot(r){
hide_ModalMyContent();
var d = $.parseJSON(r);
if(d.error){
alert(d.error);
return false;
}
dot_place([d.latitude, d.longitude], {
hintContent: d.title,
dot_id: d.pk
});
}
function add_dot(e){
var coords = e.get('coords');
$.get('{% url 'mapapp:modal_add_dot' %}', {'coords': coords.join(',')}, function(r){
show_ModalMyContent(r);
$('.form-ajax').ajform({'on_response': on_success_add_dot});
});
e.preventDefault();
}
@ -42,29 +67,93 @@
function init(){
var win = $(window);
$('#yamap')
.css('width', win.width()-5+'px')
.css('height', win.height()+'px');
myMap = new ymaps.Map("yamap", {
center: [45.449160, 34.735454],
zoom: 12
zoom: 12,
controls: ['zoomControl', 'geolocationControl']
});
myMap.controls.add('typeSelector', {position: {left: 45, top: 10}});
$.getJSON("{% url 'mapapp:get_dots' %}", load_dots);
myMap.events.add('dblclick', add_dot);
$('.mapbtns').on('click', function(){
$.getJSON($(this).attr('data-href'), function(dot_ids){
var it = myMap.geoObjects.getIterator(), obj;
var points = [];
while(obj = it.getNext()){
if(obj.geometry == undefined) break;
if(obj.geometry.getType() == "Point"){
var dot_id = obj.properties.get('dot_id');
var is_dot_contains = dot_ids.includes(dot_id);
obj.options.set('visible', is_dot_contains);
if(is_dot_contains)
points.push(obj);
}
}
var bnds = ymaps.geoQuery(points).getBounds();
if(bnds){
if(points.length > 1)
myMap.setBounds(bnds, {duration: 400, checkZoomRange: true, useMapMargin: true, zoomMargin: 30});
else
myMap.panTo([points[0].geometry.getCoordinates()]);
}
});
$('.mapbtns').removeClass('active');
$(this).addClass('active');
});
}
function showAll(){
var it = myMap.geoObjects.getIterator(), obj;
while(obj = it.getNext()){
if(obj.geometry == undefined) break;
if(obj.geometry.getType() == "Point"){
obj.options.set('visible', true);
}
}
myMap.setBounds(myMap.geoObjects.getBounds(), {duration: 400});
$('.mapbtns').removeClass('active');
}
</script>
<div id="yamap"></div>
<div id="yamap" class="col-sm-12"></div>
<style>
#yamap{
margin-left: -15px;
margin-top: -9px;
padding: 0;
position: fixed;
height: 95vh;
width: 100%;
}
#yapanel{
position: absolute;
right: 12px;
top: 5px;
}
.list-group-item{
padding: 3px 15px;
}
</style>
<div class="panel panel-default hidden-sm hidden-xs" id="yapanel">
<div class="panel-heading">
<h4 class="modal-title">{% trans 'Layers' %}</h4>
</div>
<div class="list-group">
{% for grp in abon_groups %}
<button type="button" data-href="{% url 'mapapp:resolve_dots_by_group' grp.pk %}" class="list-group-item mapbtns">{{ grp.title }}</button>
{% endfor %}
</div>
<div class="panel-footer">
<a href="#" onclick="showAll();" class="btn btn-default">
<span class="glyphicon glyphicon-eye-open"></span> {% trans 'Show all' %}
</a>
</div>
</div>
{% endblock %}

6
mapapp/urls.py

@ -10,8 +10,12 @@ urlpatterns = [
url(r'^options/add$', views.dot, name='add_dot'),
url(r'^options/(?P<did>\d+)/edit$', views.dot, name='edit_dot'),
url(r'^options/(?P<did>\d+)/remove$', views.remove, name='remove_dot'),
url(r'^options/(?P<did>\d+)/add_dev$', views.add_dev, name='add_dev'),
url(r'^preload_devices$', views.preload_devices, name='preload_devices'),
url(r'^get_dots$', views.get_dots, name='get_dots'),
url(r'^modal_add_dot$', views.modal_add_dot, name='modal_add_dot'),
url(r'^j_dot_tooltip$', views.dot_tooltip, name='dot_tooltip')
url(r'^j_dot_tooltip$', views.dot_tooltip, name='dot_tooltip'),
url(r'^resolve_dots_by_group(?P<grp_id>\d+)$', views.resolve_dots_by_group, name='resolve_dots_by_group')
]

149
mapapp/views.py

@ -2,26 +2,35 @@ from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.core.serializers import serialize
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.utils.translation import ugettext_lazy as _
from django.db.models import Count
from .models import Dot
from .forms import DotForm
from mydefs import pag_mn
from mydefs import pag_mn, safe_int
from devapp.models import Device
from guardian.decorators import permission_required_or_403 as permission_required
from guardian.decorators import permission_required
from abonapp.models import AbonGroup
from json import dumps
@login_required
def home(request):
if not request.user.is_superuser:
return redirect('/')
dots = Dot.objects.all()
groups = AbonGroup.objects.all()
return render(request, 'maps/ya_index.html', {
'dots': dots
'dots': dots,
'abon_groups': groups
})
@login_required
def options(request):
if not request.user.is_superuser:
return redirect('/')
dots = Dot.objects.all()
dots = pag_mn(request, dots)
return render(request, 'maps/options.html', {
@ -31,6 +40,8 @@ def options(request):
@login_required
def dot(request, did=0):
if not request.user.is_superuser:
return redirect('/')
try:
if did == 0:
dot = Dot()
@ -42,12 +53,13 @@ def dot(request, did=0):
dot = Dot.objects.get(id=did)
if request.method == 'POST':
frm = DotForm(request.POST, instance=dot)
frm = DotForm(request.POST, request.FILES, instance=dot)
if frm.is_valid():
frm.save()
messages.success(request, 'Точка топологии сохранена')
new_dot = frm.save()
messages.success(request, _('Map point has been saved'))
return redirect('mapapp:edit_dot', new_dot.pk)
else:
messages.error(request, 'ошибки в форме')
messages.error(request, _('fix form errors'))
else:
frm = DotForm(instance=dot)
@ -57,7 +69,7 @@ def dot(request, did=0):
})
except Dot.DoesNotExist:
messages.error(request, 'Эта точка топологии не существует')
messages.error(request, _('Map point does not exist'))
return redirect('mapapp:options')
@ -68,49 +80,128 @@ def remove(request, did):
dot = Dot.objects.get(id=did)
title = dot.title
dot.delete()
messages.success(request, "Точка топологии '%s' успешно удалена" % title)
messages.success(request, _("Map point '%(title)s' has been deleted") % {'title': title})
except Dot.DoesNotExist:
messages.error(request, 'Эта точка топологии не существует')
messages.error(request, _('Map point does not exist'))
return redirect('mapapp:options')
@login_required
def get_dots(request):
dots = Dot.objects.all()
return HttpResponse(serialize('json', dots, ensure_ascii=False), content_type='application/json')
if not request.user.is_superuser:
return HttpResponseForbidden('you have not super user')
dots = Dot.objects.all().annotate(devcount=Count('devices'))
res = [{
'devcount': e.devcount,
'latitude': e.latitude,
'longitude': e.longitude,
'title': e.title,
'pk': e.pk
} for e in dots]
return HttpResponse(dumps(res), content_type='application/json')
@login_required
@permission_required('mapapp.add_dot')
def modal_add_dot(request):
#FIXME: тут надо удостовериться что при отсутствии прав, сообщение об этом корректно отобразится
if not request.user.has_perm('mapapp.add_dot'):
return render_to_text('403_for_modal.html')
if request.method == 'POST':
coords = request.POST.get('coords')
title = request.POST.get('title')
lat, lon = coords.split(',')
print(lat, lon)
Dot.objects.create(
title=title,
latitude=float(lat),
longitude=float(lon)
)
return redirect('mapapp:home')
frm = DotForm(request.POST, request.FILES)
if frm.is_valid():
new_dot = frm.save()
res = {
'latitude': new_dot.latitude,
'longitude': new_dot.longitude,
'title': new_dot.title,
'pk': new_dot.pk
}
else:
res = {
'error': _('fix form errors')
}
return HttpResponse(dumps(res))
else:
coords = request.GET.get('coords')
lat, lon = coords.split(',')
frm = DotForm(initial={'latitude': lat, 'longitude': lon})
return render_to_text('maps/modal_add_dot.html', {
'coords': coords
'coords': coords,
'form': frm
}, request=request)
@login_required
def preload_devices(request):
if not request.user.is_superuser:
return HttpResponseForbidden('you have not super user')
grp = request.GET.get('grp')
dot = request.GET.get('dot')
#user_group = AbonGroup.objects.get(pk=grp)
all_devices = Device.objects.filter(user_group__id=grp)
dot_devices = Device.objects.filter(dot__id=dot)
dot_devices_ids = [dev.pk for dev in dot_devices]
ret = render_to_text('maps/preload_devices_tmpl.html', {
'all_devices': all_devices,
'dot_devices_ids': dot_devices_ids
})
return HttpResponse(ret, content_type='text/html')
@login_required
def dot_tooltip(request):
if not request.user.is_superuser:
return render_to_text('403_for_modal.html')
d = request.GET.get('d')
devs, dot = None, None
try:
dot = Dot.objects.get(id=d)
devs = Device.objects.filter(map_dot=dot)
devs = dot.devices.all()
devs = Device.objects.wrap_monitoring_info(devs)
except Dot.DoesNotExist:
pass
return render_to_text('maps/map_tooltip.html', {
'devs': devs,
'dot': dot
})
@login_required
def add_dev(request, did):
if not request.user.is_superuser:
return redirect('/')
groups = AbonGroup.objects.all()
dot = get_object_or_404(Dot, pk=did)
param_user_group = safe_int(request.GET.get('grp'))
if request.method == 'POST':
selected_devs = request.POST.getlist('dv')
selected_user_group = safe_int(request.POST.get('selected_user_group'))
existing_devs = Device.objects.filter(user_group__id=selected_user_group or param_user_group)
if existing_devs.count() > 0:
dot.devices.remove(*[dev.pk for dev in existing_devs])
dot.devices.add(*selected_devs)
url = resolve_url('mapapp:add_dev', did=dot.pk)
return HttpResponseRedirect("%s?grp=%d" % (url, selected_user_group or param_user_group))
else:
existing_devs = Device.objects.filter(user_group=param_user_group)
return render(request, 'maps/add_device.html', {
'groups': groups,
'dot': dot,
'existing_devs': existing_devs,
'grp': param_user_group,
'dot_devices_ids': [dev.pk for dev in Device.objects.filter(dot=dot)]
})
@login_required
def resolve_dots_by_group(request, grp_id):
if not request.user.is_superuser:
return HttpResponseForbidden('you have not super user')
devs = Device.objects.filter(user_group__id=grp_id)
dots = Dot.objects.filter(devices__in=devs).annotate(devcount=Count('devices')).only('pk')
res = [dot.pk for dot in dots]
return HttpResponse(dumps(res), content_type='application/json')

5
msg_app/admin.py

@ -1,3 +1,6 @@
from django.contrib import admin
from . import models
admin.site.register(models.Message)
admin.site.register(models.Conversation)
# Register your models here.

2
msg_app/locale/ru/LC_MESSAGES/django.po

@ -146,7 +146,7 @@ msgstr "Создать беседу"
#: templates/msg_app/modal_new_conversation.html:19
msgid "for select multiple press ctrl and click on field"
msgstr "Для выбора нескольких ывриантов зажмите ctrl и кликните вариант"
msgstr "Для выбора нескольких вариантов зажмите ctrl и кликните нужный вариант"
#: templates/msg_app/modal_new_conversation.html:28
msgid "Add"

8
msg_app/models.py

@ -1,6 +1,8 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from accounts_app.models import UserProfile
from chatbot.telebot import send_notify
from chatbot.models import ChatException
class MessageError(Exception):
@ -146,7 +148,7 @@ class Conversation(models.Model):
objects = ConversationManager()
def get_messages(self):
return Message.objects.filter(conversation=self).order_by('sent_at')[:10]
return Message.objects.filter(conversation=self).order_by('sent_at')
def get_messages_new_count(self, account):
msgs = Message.objects.filter(conversation=self)
@ -158,6 +160,7 @@ class Conversation(models.Model):
return messages[0]
def new_message(self, text, attachment, author, with_status=True):
try:
msg = Message.objects.create(
text=text, conversation=self, attachment=attachment, author=author
)
@ -166,7 +169,10 @@ class Conversation(models.Model):
if participant == author:
continue
MessageStatus.objects.create(msg=msg, user=participant)
send_notify(msg_text=text, account=participant, tag='msgapp')
return msg
except ChatException as e:
raise MessageError(e)
def remove_message(self, msg):
if isinstance(msg, Message):

4
msg_app/templates/msg_app/chat.html

@ -16,12 +16,12 @@
<small>{{ conv.participants.count }} {% trans 'peoples' %}</small>
</h3>
</div>
<div class="list-group">
<div class="list-group scroll-area">
{% with can_view_profile=perms.accounts_app.can_view_userprofile %}
{% for msg in msg_list %}
{% with author=msg.author %}
<div class="list-group-item clearfix">
<div class="list-group-item">
{% if msg.author == request.user %}
<a href="{% url 'msg_app:remove_msg' conv.pk msg.pk %}" class="btn btn-link pull-right" title="{% trans 'Delete' %}" data-toggle="tooltip">
<span class="glyphicon glyphicon-remove"></span>

3
msg_app/urls.py

@ -6,5 +6,6 @@ urlpatterns = [
url(r'^$', views.home, name='home'),
url(r'^new$', views.new_conversation, name='new_conversation'),
url(r'^(?P<conv_id>\d+)/$', views.to_conversation, name='to_conversation'),
url(r'^(?P<conv_id>\d+)/(?P<msg_id>\d+)/del$', views.remove_msg, name='remove_msg')
url(r'^(?P<conv_id>\d+)/(?P<msg_id>\d+)/del$', views.remove_msg, name='remove_msg'),
url(r'^check_news$', views.check_news, name='check_news')
]

17
msg_app/views.py

@ -1,9 +1,12 @@
from json import dumps
from django.contrib.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
from django.shortcuts import render, redirect, get_object_or_404
from chatbot.models import MessageQueue
from mydefs import pag_mn
from .models import Conversation, MessageError, Message
@ -70,3 +73,17 @@ def remove_msg(request, conv_id, msg_id):
conversation_id = msg.conversation.pk
msg.delete()
return redirect('msg_app:to_conversation', conversation_id)
@login_required
def check_news(request):
msg = MessageQueue.objects.pop(user=request.user, tag='msgapp')
if msg is not None:
r = {
'exist': True,
'content': msg,
'title': "%s" % _('Message')
}
else:
r = {'exist': False}
return HttpResponse(dumps(r))

9
mydefs.py

@ -215,3 +215,12 @@ class MultipleException(Exception):
class LogicError(Exception):
pass
def singleton(class_):
instances = {}
def getinstance(*args, **kwargs):
if class_ not in instances:
instances[class_] = class_(*args, **kwargs)
return instances[class_]
return getinstance

69
queue_mngr.py

@ -1,30 +1,55 @@
#!/usr/bin/env python3
import os
import sys
from rq import Connection, Worker
from pickle import loads
from pid.decorator import pidfile
import socket
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
from agent import NasFailedResult, NasNetworkError
from django.core.exceptions import ValidationError
"""
Заустить этот скрипт как демон, он соединяет redis и django
"""
@pidfile()
def main():
'''
obj = {
'client_ip': ip2int('127.0.0.1'),
'client_mac': 'aa:bb:cc:dd:ee:ff',
'switch_mac': 'aa:bb:cc:dd:ee:ff',
'switch_port': 3,
'cmd': 'commit'
}
'''
def on_new_data(client_sock, ip):
data = client_sock.recv(16384)
data = loads(data)
action = data['cmd']
if action == 'commit':
dhcp_commit(
data['client_ip'], data['client_mac'],
data['switch_mac'], data['switch_port']
)
elif action == 'expiry':
dhcp_expiry(data['client_ip'])
elif action == 'release':
dhcp_release(data['client_ip'])
client_sock.close()
@pidfile(pidname='queue_mngr.py.pid', piddir='/run')
def serve(addr='127.0.0.1', port=5436):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((addr, port))
s.listen(3)
print('ready')
while True:
conn, client_addr = s.accept()
on_new_data(conn, client_addr)
except ConnectionRefusedError:
print('ERROR: connection refused')
if __name__ == '__main__':
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
django.setup()
with Connection():
qs = sys.argv[1:] or ['default']
w = Worker(qs)
w.work()
except (NasNetworkError, NasFailedResult) as e:
print('NAS:', e)
except (ValidationError, ValueError) as e:
print('ERROR:', e)
if __name__ == "__main__":
main()
from agent.commands.dhcp import dhcp_commit, dhcp_expiry, dhcp_release
serve()

5
requirements.txt

@ -12,6 +12,9 @@ xmltodict
mysqlclient
easysnmp
rq
pid
django-guardian
pinax-theme-bootstrap
django-bootstrap3
requests
webdavclient

48
static/clientside/ISPlaylist.m3u

@ -1,48 +0,0 @@
#EXTM3U
#EXTINF:-1,ПЕРВЫЙ КАНАЛ
udp://@239.255.1.15:1234
#EXTINF:-1,Россия-1
udp://@239.255.1.16:1234
#EXTINF:-1,МАТЧ ТВ
udp://@239.255.1.17:1234
#EXTINF:-1,НТВ
udp://@239.255.1.18:1234
#EXTINF:-1,ПЯТЫЙ КАНАЛ
udp://@239.255.1.19:1234
#EXTINF:-1,РОССИЯ-КУЛЬТУРА
udp://@239.255.1.20:1234
#EXTINF:-1,Россия-24
udp://@239.255.1.21:1234
#EXTINF:-1,КАРУСЕЛЬ
udp://@239.255.1.22:1234
#EXTINF:-1,ОТР
udp://@239.255.1.23:1234
#EXTINF:-1,ТВ Центр
udp://@239.255.1.24:1234
#EXTINF:-1,РЕН ТВ
udp://@239.255.1.28:1234
#EXTINF:-1,Спас
udp://@239.255.1.29:1234
#EXTINF:-1,СТС
udp://@239.255.1.30:1234
#EXTINF:-1,Домашний
udp://@239.255.1.31:1234
#EXTINF:-1,ТВ3
udp://@239.255.1.32:1234
#EXTINF:-1,Звезда
udp://@239.255.1.34:1234
#EXTINF:-1,Мир-24
udp://@239.255.1.9:1234
#EXTINF:-1,ТНТ
udp://@239.255.1.35:1234
#EXTINF:-1,МУЗ ТВ
udp://@239.255.1.36:1234
#EXTINF:-1,Крым 24
udp://@239.255.1.14:1234
#EXTINF:-1,Перец
udp://@239.255.1.12:1234

19
static/clientside/custom.css

@ -12,8 +12,27 @@ body > .container {
.table thead {
background-color: #ddd;
}
/* White background in tables */
.table tr:nth-of-type(2n) {
background-color: white;
}
.modal-header{border-radius: 4px 4px 0 0;}
.modal-header.warning {background-color: #d2322d;}
.modal-header.success {background-color: #6ad245;}
.modal-header.primary {background-color: #789cbb;}
html{height: 100%;}
body{
padding: 88px 15px 0;
background-image: url(../img/Transparent_snowman_PNG.png);
background-repeat: no-repeat;
background-position: bottom left;
background-attachment: fixed;
}

27
static/css/custom.css

@ -75,6 +75,7 @@ body {
.main {
margin-top: 10px;
/*display: table;*/
}
.table-responsive thead {
@ -246,7 +247,29 @@ button[data-toggle=offcanvas]{
fill-opacity: 0.3;
}
a.navbar-brand {
padding-left: 88px;
/*
* Ajax loader
*/
div#loading {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
display: none;
}
div#loading>div.gif {
position: absolute;
left: 49%;
top: 39%;
background-color: white;
border-radius: 13px;
border-style: ridge;
}
.scroll-area {
overflow-y: auto;
max-height: 65vh;
}

BIN
static/img/loading.gif

After

Width: 100  |  Height: 100  |  Size: 6.6 KiB

BIN
static/img/noticon.png

After

Width: 192  |  Height: 192  |  Size: 12 KiB

111
static/js/my.js

@ -2,6 +2,7 @@ function show_ModalMyContent(content){
$('#modContent').html(content);
$('#modFrm').modal();
}
function hide_ModalMyContent(){$('#modFrm').modal('hide');}
function show_Modal(title, content, type_class){
var cnt='<div class="modal-header '+type_class+'">' +
'<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
@ -10,6 +11,7 @@ var cnt='<div class="modal-header '+type_class+'">' +
'<div class="modal-body">'+content+'</div>' +
'<div class="modal-footer"><button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button></div>';
show_ModalMyContent(cnt);
$('#loading').hide();
}
function showErr(errContent) {show_Modal('Ошибка', errContent, 'warning');}
@ -17,7 +19,6 @@ function showSuccess(errContent) {show_Modal('Успешно', errContent, 'succ
function showPrimary(errContent) {show_Modal('Внимание!', errContent, 'primary');}
$(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
//loaderShow(false);
showErr(jqXHR.status + ': ' + jqXHR.statusText);
});
@ -116,6 +117,109 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
})(jQuery);
// Ajax Loader
(function ($){
$.fn.ajloader = function(opt){
var settings = $.extend({
dst_block :'id_block_obj'
}, opt);
var fill_block_fn = function(){
$('#loading').show();
var url = $(this).attr('data-href');
$.get(url, function(r){
$(settings.dst_block).html(r);
$('#loading').hide();
});
};
this.each(function(){
$(this).on('click', fill_block_fn);
});
};
})(jQuery);
// Ajax form
(function ($){
$.fn.ajform = function(opt){
var settings = $.extend({
on_response : on_response_default
}, opt);
var on_response_default = function(r){
alert('You must assign callback function for response');
};
var on_submit = function(e){
e.preventDefault();
var formData = new FormData(this);
$.ajax({
url: $(this).attr('action'),
type: 'POST',
data: formData,
async: true,
success: settings.on_response,
cache: false,
contentType: false,
processData: false
});
};
this.each(function(){
$(this).on('submit', on_submit);
});
};
})(jQuery);
(function($){
$.fn.notifys = function(opt){
var settings = $.extend({
news_url: null,
check_interval: 60
}, opt);
var notifShow = function(title, content){
if(!settings.news_url) return;
var perm = Notification.permission.toLowerCase();
if(perm == "granted"){
curnotify = new Notification(title, {
tag: 'djing-notify',
body: content,
icon: '/static/img/noticon.png'}
);
}else if(perm == "default"){
Notification.requestPermission(on_ask_perm);
}
}
var on_ask_perm = function(r){
console.log("Thanks for letting notify you");
}
var check_news = function(){
var perm = Notification.permission.toLowerCase();
if(perm == "granted" && settings.news_url){
$.getJSON(settings.news_url, function(r){
if(r.exist){
notifShow(r.title, r.content);
/*console.log('News from '+settings.news_url+'. '+r.content);*/
}/*else console.log('No news from '+settings.news_url);*/
});
}
}
if(settings.news_url){
// прверяем новости раз в минуту
var tiid = setInterval(check_news, settings.check_interval*1000);
//Notification.requestPermission(on_ask_perm);
}
}
})(jQuery);
$(document).ready(function () {
@ -189,4 +293,9 @@ $(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip({container:'body'});
$('.btn_ajloader').ajloader({'dst_block': '#id_block_devices'});
$(document).notifys({news_url: '/tasks/check_news', check_interval: 10});
$(document).notifys({news_url: '/msg/check_news', check_interval: 55});
});

2
systemd_units/djing_telebot.service

@ -4,7 +4,7 @@ Description=Djing telegram bot
[Service]
Type=simple
ExecStart=/usr/bin/python3 ./telebot.py > /dev/null
PIDFile=/run/telebot.py.pid
PIDFile=/run/djing_telebot.pid
WorkingDirectory=/var/www/djing
TimeoutSec=9
Restart=always

20
systemd_units/do_backup.sh

@ -0,0 +1,20 @@
#!/bin/bash
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
cd /var/backups
file="djing`date "+%Y-%m-%d_%H.%M.%S"`.sql.gz"
mysql_passw=MYSQL ROOT PASSWORD
echo show tables | mysql -uroot -p$mysql_passw djingdb | \
grep -v '^flowstat' | grep -v 'traflost' | grep -v '^Tables' | \
xargs mysqldump -R -Q --add-locks -uroot --password=$mysql_passw djingdb $1 | gzip -9 > $file
chmod 400 $file
./webdav_backup.py $file
# удаляем старые
find . -name "djing20??-??-??_??.??.??.sql.gz" -mtime +30 -type f -delete

20
systemd_units/webdav_backup.py

@ -0,0 +1,20 @@
#!/usr/bin/env python3
import webdav.client as wc
from webdav.client import WebDavException
from sys import argv
options = {
'webdav_hostname': "https://webdav.yandex.ru/",
'webdav_login': "YANDEX USERNAME",
'webdav_password': "YANDEX PASSWORD"
}
if __name__ == '__main__':
reqfile = argv[1]
try:
client = wc.Client(options)
client.upload_sync(remote_path="ISBackups/%s" % reqfile, local_path="/var/backups/%s" % reqfile)
except WebDavException as we:
print(we, type(we))

6
taskapp/handle.py

@ -35,16 +35,14 @@ def handle(task, author, recipients, abon_group):
fulltext += _('Task type - %s.') % task.get_mode_display() + '\n'
fulltext += task.descr if task.descr else ''
print('task.state:', task.state)
if task.state == 'F' or task.state == 'C':
# Если задача завершена или провалена то отправляем одно оповещение автору
try:
send_notify(fulltext, author)
send_notify(fulltext, author, tag='taskap')
except ChatException as e:
raise TaskException(e)
else:
send_notify(fulltext, dst_account)
send_notify(fulltext, dst_account, tag='taskap')
except ChatException as e:
errors.append(e)
if len(errors) > 0:

8
taskapp/locale/ru/LC_MESSAGES/django.po

@ -163,7 +163,7 @@ msgstr "Создать"
#: taskapp/templates/taskapp/tasklist_all.html:93
#: taskapp/templates/taskapp/tasklist_all.html:94
msgid "Add new task"
msgstr "Добавьте новую задачу"
msgstr "Добавьте задачу"
#: taskapp/templates/taskapp/add_edit_task.html:27
#: taskapp/templates/taskapp/tasklist.html:13
@ -378,3 +378,9 @@ msgstr "Задачи, которые необходимо выполнить"
msgid "locality %s. Task type - %s. "
msgstr "с. %s. Тип задачи - %s. "
msgid "ip conflict"
msgstr "Ip конфликт"
msgid "Internet crash"
msgstr "Нет интернета"

2
taskapp/models.py

@ -23,6 +23,7 @@ TASK_STATES = (
TASK_TYPES = (
('na', _('not chosen')),
('ic', _('ip conflict')),
('yt', _('yellow triangle')),
('rc', _('red cross')),
('ls', _('weak speed')),
@ -32,6 +33,7 @@ TASK_TYPES = (
('cr', _('router setup')),
('co', _('configure onu')),
('fc', _('crimp cable')),
('ni', _('Internet crash')),
('ot', _('other'))
)

2
taskapp/templates/taskapp/add_edit_task.html

@ -21,7 +21,7 @@
{% else %}
<form role="form" action="{% url 'taskapp:add' %}" method="post" enctype="multipart/form-data">
{% endif %}
{% csrf_token %}<input type="hidden" name="MAX_FILE_SIZE" value="409600"/>
{% csrf_token %}<input type="hidden" name="MAX_FILE_SIZE" value="{{ FILE_UPLOAD_MAX_MEMORY_SIZE }}"/>
<div class="form-group">
<label for="id_descr">{% trans 'Description' %}</label>

19
taskapp/templates/taskapp/footer_btns.html

@ -1,11 +1,14 @@
{% load i18n %}
{% if perms.taskapp.add_task %}
<a href="{% url 'taskapp:add' %}" class="btn btn-sm btn-success">
<div class="btn-group btn-group-sm">
{% if perms.taskapp.add_task %}
<a href="{% url 'taskapp:add' %}" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add new task' %}
</a>
{% endif %}
{% if perms.taskapp.can_viewall %}
<a href="{% url 'taskapp:all_tasks' %}" class="btn btn-sm btn-info">
</a>
{% endif %}
{% if perms.taskapp.can_viewall %}
<a href="{% url 'taskapp:all_tasks' %}" class="btn btn-sm btn-info">
<span class="glyphicon glyphicon-baby-formula"></span> {% trans 'View all tasks' %}
</a>
{% endif %}
</a>
{% endif %}
</div>

10
taskapp/templates/taskapp/tasklist.html

@ -6,13 +6,13 @@
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th class="hidden-xs">#</th>
<th class="col-sm-3">{% trans 'Name' %}</th>
<th class="col-sm-1">{% trans 'Address' %}</th>
<th class="col-sm-1">{% trans 'The nature of the damage' %}</th>
<th class="col-sm-4">{% trans 'Description' %}</th>
<th class="col-sm-1">{% trans 'Task author' %}</th>
<th class="col-sm-1">{% trans 'Date of create' %}</th>
<th class="col-sm-1 hidden-xs">{% trans 'Date of create' %}</th>
<th class="col-sm-1">{% trans 'Actions' %}</th>
</tr>
</thead>
@ -33,11 +33,11 @@
{% endif %}
{% endif %}
<td><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
{% if task.abon and task.abon.group %}
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" target="_blank" title="{{ task.abon.description|default:'' }}" data-toggle="tooltip">{{ task.abon.get_full_name }}</a></td>
<td>{{ task.abon.group.title }}, {{ task.abon.street|default:_('Not assigned') }} {{ task.abon.house|default:_('Not assigned') }}</td>
<td>{{ task.abon.group.title }}, {{ task.abon.street|default:'' }} {{ task.abon.house|default:'' }}</td>
{% else %}
<td>{% trans 'User does not exist' %}</td>
<td>---</td>
@ -52,7 +52,7 @@
{% trans 'Author does not exist' %}
{% endif %}
</td>
<td>{{ task.time_of_create|date:'d E H:i' }}</td>
<td class="hidden-xs">{{ task.time_of_create|date:'d E H:i' }}</td>
<td class="btn-group btn-group-xs btn-group-justified">
<a href="{% url 'taskapp:finish' task.pk %}" class="btn btn-success" data-toggle="tooltip" title="{% trans 'Complete' %}">

8
taskapp/templates/taskapp/tasklist_all.html

@ -18,14 +18,14 @@
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th class="hidden-xs">#</th>
<th class="col-sm-3">{% trans 'Name' %}</th>
<th class="col-sm-1">{% trans 'Address' %}</th>
<th class="col-sm-1">{% trans 'The nature of the damage' %}</th>
<th class="col-sm-3">{% trans 'Description' %}</th>
<th class="col-sm-1">{% trans 'Task author' %}</th>
<th class="col-sm-1">{% trans 'Condition' %}</th>
<th class="col-sm-1">{% trans 'Date of create' %}</th>
<th class="col-sm-1 hidden-xs">{% trans 'Date of create' %}</th>
<th class="col-sm-1">{% trans 'Actions' %}</th>
</tr>
</thead>
@ -46,7 +46,7 @@
{% endif %}
{% endif %}
<td><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
{% if task.abon and task.abon.group %}
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" target="_blank">{{ task.abon.get_full_name }}</a></td>
@ -67,7 +67,7 @@
{% endif %}
</td>
<td>{{ task.get_state_display }}</td>
<td>{{ task.time_of_create|date:'d E H:i' }}</td>
<td class="hidden-xs">{{ task.time_of_create|date:'d E H:i' }}</td>
<td class="btn-group btn-group-sm btn-group-justified">
{% if perms.taskapp.change_task %}

10
taskapp/templates/taskapp/tasklist_failed.html

@ -6,13 +6,13 @@
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th class="hidden-xs">#</th>
<th class="col-sm-3">{% trans 'Name' %}</th>
<th class="col-sm-1">{% trans 'Address' %}</th>
<th class="col-sm-1">{% trans 'The nature of the damage' %}</th>
<th class="col-sm-4">{% trans 'Description' %}</th>
<th class="col-sm-1">{% trans 'Task author' %}</th>
<th class="col-sm-1">{% trans 'Date of create' %}</th>
<th class="col-sm-1 hidden-xs">{% trans 'Date of create' %}</th>
<th class="col-sm-1">{% trans 'Actions' %}</th>
</tr>
</thead>
@ -33,7 +33,7 @@
{% endif %}
{% endif %}
<td><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
{% if task.abon and task.abon.group %}
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" target="_blank">{{ task.abon.get_full_name }}</a></td>
@ -47,9 +47,9 @@
<td>{{ task.descr }}</td>
<td><a href="{% url 'acc_app:other_profile' task.author.pk %}" data-toggle="tooltip"
title="{{ task.author.get_full_name }}">{{ task.author.username }}</a></td>
<td>{{ task.time_of_create|date:'d E H:i' }}</td>
<td class="hidden-xs">{{ task.time_of_create|date:'d E H:i' }}</td>
<td class="btn-group btn-group-sm">
<td class="btn-group btn-group-sm btn-group-justified">
<a href="{% url 'taskapp:finish' task.pk %}" class="btn btn-success" title="{% trans 'Complete' %}" data-toggle="tooltip">
<span class="glyphicon glyphicon-ok"></span>
</a>

11
taskapp/templates/taskapp/tasklist_finish.html

@ -6,13 +6,13 @@
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th class="hidden-xs">#</th>
<th class="col-sm-3">{% trans 'Name' %}</th>
<th class="col-sm-2">{% trans 'Address' %}</th>
<th class="col-sm-1">{% trans 'The nature of the damage' %}</th>
<th class="col-sm-4">{% trans 'Description' %}</th>
<th class="col-sm-1">{% trans 'Task author' %}</th>
<th class="col-sm-1">{% trans 'Date of create' %}</th>
<th class="col-sm-1 hidden-xs">{% trans 'Date of create' %}</th>
<th>#</th>
</tr>
</thead>
@ -33,7 +33,7 @@
{% endif %}
{% endif %}
<td><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
{% if task.abon and task.abon.group %}
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" target="_blank">{{ task.abon.get_full_name }}</a></td>
@ -45,9 +45,8 @@
<td>{{ task.get_mode_display }}</td>
<td>{{ task.descr }}</td>
<td><a href="{% url 'acc_app:other_profile' task.author.pk %}" data-toggle="tooltip"
title="{{ task.author.get_full_name }}">{{ task.author.username }}</a></td>
<td>{{ task.time_of_create|date:'d E H:i' }}</td>
<td><a href="{% url 'acc_app:other_profile' task.author.pk %}" data-toggle="tooltip" title="{{ task.author.get_full_name }}">{{ task.author.username }}</a></td>
<td class="hidden-xs">{{ task.time_of_create|date:'d E H:i' }}</td>
<td>
{% if perms.taskapp.change_task %}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save