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. 48
      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. 37
      accounts_app/templates/accounts/acc_list.html
  13. 10
      accounts_app/templates/accounts/ext.htm
  14. 4
      accounts_app/templates/accounts/index.html
  15. 15
      accounts_app/templates/accounts/login.html
  16. 14
      accounts_app/templates/accounts/settings/test.html
  17. 7
      agent/commands/dhcp.py
  18. 22
      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. 12
      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. 132
      devapp/models.py
  36. 87
      devapp/templates/devapp/add_dev.html
  37. 24
      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. 144
      devapp/templates/devapp/dev.html
  41. 38
      devapp/templates/devapp/devices.html
  42. 82
      devapp/templates/devapp/fix_dev_group.html
  43. 4
      devapp/templates/devapp/group_list.html
  44. 33
      devapp/templates/devapp/manage_ports/modal_add_edit_port.html
  45. 2
      devapp/urls.py
  46. 152
      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. 27
      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. 140
      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. 127
      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. 26
      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. 71
      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. 23
      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 #: abonapp/templates/abonapp/ext.html:26
msgid "Sub information" msgid "Sub information"
msgstr "Информация абонента"
msgstr "Инфо."
msgid "Streets" msgid "Streets"
msgstr "Улицы" msgstr "Улицы"
@ -1005,3 +1005,18 @@ msgstr "Может пинговать"
msgid "Can view additional telephones" msgid "Can view additional telephones"
msgstr "Может видеть дополнительные телефоны" 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_add_ballance', _('fill account')),
('can_ping', _('Can ping')) ('can_ping', _('Can ping'))
) )
unique_together = ('device', 'dev_port')
verbose_name = _('Abon') verbose_name = _('Abon')
verbose_name_plural = _('Abons') verbose_name_plural = _('Abons')

2
abonapp/templates/abonapp/dial_log.html

@ -21,7 +21,7 @@
<tr> <tr>
<td> <td>
<audio preload="metadata" controls> <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> </audio>
</td> </td>
<td>{{ log.calldate|date:'d E Y, H:i:s' }}</td> <td>{{ log.calldate|date:'d E Y, H:i:s' }}</td>

48
abonapp/templates/abonapp/editAbon.html

@ -4,13 +4,13 @@
{% block content %} {% block content %}
<div class="row"> <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 panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{% trans 'Change subscriber' %}</h3> <h3 class="panel-title">{% trans 'Change subscriber' %}</h3>
</div> </div>
<div class="panel-body"> <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"> <div class="form-group-sm">
<label for="id_username" class="col-sm-4 control-label">{% trans 'login' %}</label> <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 %}"> <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> <label for="id_ip" class="col-sm-4 control-label">{% trans 'Ip Address' %}</label>
<div class="col-sm-8"> <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>
</div> </div>
@ -111,7 +118,7 @@
</button> </button>
{% if perms.taskapp.add_task %} {% if perms.taskapp.add_task %}
<a href="{% url 'taskapp:add' %}?uid={{ abon.username }}" class="btn btn-sm btn-success" title="{% trans 'Add new 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> </a>
{% endif %} {% endif %}
</div> </div>
@ -131,7 +138,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm-6">
<div class="col-sm-12 col-xs-12 col-md-6">
{% if perms.abonapp.change_abon %} {% if perms.abonapp.change_abon %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <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 %} <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"> <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 %} {% 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>
<a href="{% url 'abonapp:clear_dev' abon_group.pk abon.pk %}" class="btn btn-sm btn-danger"> <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> </a>
{% else %} {% else %}
<a href="{% url 'abonapp:dev' abon_group.pk abon.pk %}" class="btn btn-success btn-sm btn-modal"> <a href="{% url 'abonapp:dev' abon_group.pk abon.pk %}" class="btn btn-success btn-sm btn-modal">
@ -161,8 +168,8 @@
{% if device %} {% if device %}
<div class="form-group-sm"> <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"> <select id="id_dev_port" class="form-control" name="user_port">
<option value="0">{% trans 'Not assigned' %}</option> <option value="0">{% trans 'Not assigned' %}</option>
{% for port in dev_ports %} {% for port in dev_ports %}
@ -178,21 +185,18 @@
</div> </div>
</div> </div>
<div class="form-group-sm"> <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> <label>
<input type="checkbox" name="is_dynamic_ip"{% if abon.is_dynamic_ip %} checked{% endif %}> {% trans 'Is dynamic network settings' %} <input type="checkbox" name="is_dynamic_ip"{% if abon.is_dynamic_ip %} checked{% endif %}> {% trans 'Is dynamic network settings' %}
</label> </label>
</div> </div>
</div> </div>
<div class="form-group-sm"> <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"> <button type="submit" class="btn btn-primary btn-sm">
<span class="glyphicon glyphicon-floppy-disk"></span> {% trans 'Save' %} <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>
</button>
</div>
</div> </div>
{% endif %} {% endif %}
</form> </form>
@ -209,8 +213,8 @@
{% for ef in abon.extra_fields.all %} {% for ef in abon.extra_fields.all %}
<div class="form-group-sm"> <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"> <div class="input-group input-group-sm">
<input type="text" value="{{ ef.data|default:_('Not assigned') }}" class="form-control" pattern="{{ ef.get_regexp }}" name="ex"> <input type="text" value="{{ ef.data|default:_('Not assigned') }}" class="form-control" pattern="{{ ef.get_regexp }}" name="ex">
@ -229,7 +233,7 @@
{% endfor %} {% endfor %}
<div class="form-group-sm"> <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' %}"> <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' %} <span class="glyphicon glyphicon-plus"></span> {% trans 'Add' %}
</a> </a>

4
abonapp/templates/abonapp/ext.htm

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

10
abonapp/templates/abonapp/group_list.html

@ -23,7 +23,7 @@
</a> </a>
{% if order_by == 'title' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'title' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
<th width="100">
<th width="100" class="hidden-xs">
{% trans 'Number of subscribers' %} {% trans 'Number of subscribers' %}
</th> </th>
<th width="100">#</th> <th width="100">#</th>
@ -34,7 +34,7 @@
<tr> <tr>
<td>{{ gr.pk }}</td> <td>{{ gr.pk }}</td>
<td><a href="{% url 'abonapp:people_list' gr.pk %}">{{ gr.title }}</a></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"> <td class="btn-group">
{% if perms.abonapp.delete_abongroup %} {% if perms.abonapp.delete_abongroup %}
{% if gr.usercount == 0 %} {% if gr.usercount == 0 %}
@ -60,17 +60,17 @@
<td colspan="4" class="btn-group"> <td colspan="4" class="btn-group">
{% if perms.abonapp.add_abongroup %} {% if perms.abonapp.add_abongroup %}
<a href="{% url 'abonapp:add_group' %}" class="btn btn-success btn-sm"> <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> </a>
{% endif %} {% endif %}
{% if perms.abonapp.can_view_abonlog %} {% if perms.abonapp.can_view_abonlog %}
<a href="{% url 'abonapp:log' %}" class="btn btn-default btn-sm"> <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> </a>
{% endif %} {% endif %}
{% if perms.abonapp.can_view_invoiceforpayment %} {% if perms.abonapp.can_view_invoiceforpayment %}
<a href="{% url 'abonapp:debtors' %}" class="btn btn-default btn-sm"> <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> </a>
{% endif %} {% endif %}
</td> </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> <h3>{% trans 'The people in the selected group' %}</h3>
<div class="row"> <div class="row">
<div class="col-lg-10">
<div class="col-lg-10 col-md-8">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th>#</th> <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" %}"> <a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=username dir=dir|default:"down" %}">
{% trans 'Sub' %} {% trans 'Sub' %}
</a> </a>
{% if order_by == 'username' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'username' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </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" %}"> <a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=ip_address dir=dir|default:"down" %}">
{% trans 'Ip address' %} {% trans 'Ip address' %}
</a> </a>
{% if order_by == 'ip_address' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'ip_address' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </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" %}"> <a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=fio dir=dir|default:"down" %}">
{% trans 'fio' %} {% trans 'fio' %}
</a> </a>
{% if order_by == 'fio' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'fio' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </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" %}"> <a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=street dir=dir|default:"down" %}">
{% trans 'Street' %} {% trans 'Street' %}
</a> </a>
{% if order_by == 'street' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'street' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </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" %}"> <a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=house dir=dir|default:"down" %}">
{% trans 'Apartment' %} {% trans 'Apartment' %}
</a> </a>
{% if order_by == 'house' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'house' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </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" %}"> <a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=ballance dir=dir|default:"down" %}">
{% trans 'Ballance' %} {% trans 'Ballance' %}
</a> </a>
{% if order_by == 'ballance' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'ballance' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
<th width="10">#</th>
<th class="hidden-xs">#</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -78,7 +78,7 @@
<td> <td>
<a href="{% url 'abonapp:abon_home' human.group.pk human.pk %}">{{ human.username }}</a> <a href="{% url 'abonapp:abon_home' human.group.pk human.pk %}">{{ human.username }}</a>
</td> </td>
<td>
<td class="hidden-xs">
{% if human.stat_cache %} {% if human.stat_cache %}
{% if human.stat_cache.is_today %} {% if human.stat_cache.is_today %}
{{ human.stat_cache.last_time|date:"H:i" }} {{ human.stat_cache.last_time|date:"H:i" }}
@ -87,7 +87,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </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.fio }}</td>
<td>{{ human.street|default:_('Not assigned') }}</td> <td>{{ human.street|default:_('Not assigned') }}</td>
<td>{{ human.house|default:'-' }}</td> <td>{{ human.house|default:'-' }}</td>
@ -102,8 +102,8 @@
{% else %}&mdash;&mdash;&mdash; {% else %}&mdash;&mdash;&mdash;
{% endif %} {% endif %}
</td> </td>
<td>{{ human.ballance }}</td>
<td>
<td class="hidden-xs">{{ human.ballance|floatformat:2 }}</td>
<td class="hidden-xs">
{% if can_del_trf %} {% if can_del_trf %}
<a href="{% url 'abonapp:del_abon' %}?id={{ human.pk }}" class="btn btn-danger btn-sm"> <a href="{% url 'abonapp:del_abon' %}?id={{ human.pk }}" class="btn btn-danger btn-sm">
<span class="glyphicon glyphicon-remove"></span> <span class="glyphicon glyphicon-remove"></span>
@ -125,22 +125,25 @@
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td colspan="11" class="btn-group">
<td colspan="11" class="btn-group btn-group-sm">
{% if perms.abonapp.add_abon %} {% 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' %} <span class="glyphicon glyphicon-plus"></span> {% trans 'Add abon' %}
</a> </a>
{% endif %} {% 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' %} <span class="glyphicon glyphicon-subscript"></span> {% trans 'Tariffs in groups' %}
</a> </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> </td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
</div> </div>
</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 panel-default">
<div class="panel-heading">{% trans 'Streets' %}</div> <div class="panel-heading">{% trans 'Streets' %}</div>
<div class="list-group"> <div class="list-group">

2
abonapp/urls_abon.py

@ -6,6 +6,7 @@ urlpatterns = [
url(r'^$', views.peoples, name='people_list'), url(r'^$', views.peoples, name='people_list'),
url(r'^addabon$', views.addabon, name='add_abon'), url(r'^addabon$', views.addabon, name='add_abon'),
url(r'^services$', views.chgroup_tariff, name='ch_group_tariff'), 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/add$', views.street_add, name='street_add'),
url(r'^street/edit', views.street_edit, name='street_edit'), url(r'^street/edit', views.street_edit, name='street_edit'),
url(r'^street/(?P<sid>\d+)/delete$', views.street_del, name='street_del'), 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+)/passport_view$', views.passport_view, name='passport_view'),
url(r'^(?P<uid>\d+)/chart$', views.charts, name='charts'), 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+)/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$', 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/(?P<fid>\d+)/delete$', views.extra_field_delete, name='extra_field_delete'),
url(r'^(?P<uid>\d+)/extra_field/edit$', views.extra_field_change, name='extra_field_edit'), url(r'^(?P<uid>\d+)/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.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import IntegrityError, ProgrammingError 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.db.transaction import atomic
from django.shortcuts import render, redirect, get_object_or_404, resolve_url from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -18,7 +18,7 @@ from . import forms
from . import models from . import models
import mydefs import mydefs
from devapp.models import Device, Port as DevPort 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 taskapp.models import Task
from dialing_app.models import AsteriskCDR from dialing_app.models import AsteriskCDR
from statistics.models import getModel, get_dates from statistics.models import getModel, get_dates
@ -286,7 +286,9 @@ def abonhome(request, gid, uid):
raise PermissionDenied raise PermissionDenied
frm = forms.AbonForm(request.POST, instance=abon) frm = forms.AbonForm(request.POST, instance=abon)
if frm.is_valid(): if frm.is_valid():
abon.ip_address = request.POST.get('ip')
newip = request.POST.get('ip')
if newip:
abon.ip_address = newip
frm.save() frm.save()
messages.success(request, _('edit abon success msg')) messages.success(request, _('edit abon success msg'))
else: else:
@ -387,6 +389,7 @@ def pick_tariff(request, gid, uid):
abon.pick_tariff(trf, request.user) abon.pick_tariff(trf, request.user)
else: else:
deadline = datetime.strptime(deadline, '%Y-%m-%d') deadline = datetime.strptime(deadline, '%Y-%m-%d')
deadline += timedelta(hours=23, minutes=59, seconds=59)
abon.pick_tariff(trf, request.user, deadline=deadline) abon.pick_tariff(trf, request.user, deadline=deadline)
messages.success(request, _('Tariff has been picked')) messages.success(request, _('Tariff has been picked'))
return redirect('abonapp:abon_services', gid=gid, uid=abon.id) return redirect('abonapp:abon_services', gid=gid, uid=abon.id)
@ -687,12 +690,14 @@ def abon_ping(request):
else: else:
if type(ping_result) is tuple: if type(ping_result) is tuple:
loses_percent = (ping_result[0] / ping_result[1] if ping_result[1] != 0 else 1) 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 text = '<span class="glyphicon glyphicon-ok"></span> %s' % _('ok ping, %d/%d loses') % ping_result
status = True status = True
else: 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: else:
text = '<span class="glyphicon glyphicon-ok"></span> %s' % _('ping ok') + ' ' + str(ping_result) text = '<span class="glyphicon glyphicon-ok"></span> %s' % _('ping ok') + ' ' + str(ping_result)
status = True status = True
@ -865,6 +870,43 @@ def tel_del(request, gid, uid):
return redirect('abonapp:abon_home', 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 # API's
def abons(request): 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.html:7
#: accounts_app/templates/accounts/group_list.html:7 #: accounts_app/templates/accounts/group_list.html:7
msgid "Administrators" msgid "Administrators"
msgstr "Администраторы"
msgstr "Сотрудники"
#: accounts_app/templates/accounts/group.html:8 #: accounts_app/templates/accounts/group.html:8
#: accounts_app/templates/accounts/group_list.html:8 #: accounts_app/templates/accounts/group_list.html:8
@ -153,7 +153,7 @@ msgid "Change self onfo"
msgstr "Изменить инфу о себе" msgstr "Изменить инфу о себе"
msgid "Permission options" msgid "Permission options"
msgstr "Настройка прав"
msgstr "Права"
msgid "Edit" msgid "Edit"
msgstr "Редактировать" msgstr "Редактировать"

37
accounts_app/templates/accounts/acc_list.html

@ -14,11 +14,11 @@
<thead> <thead>
<tr> <tr>
<th class="col-sm-1">Фото</th> <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> </tr>
</thead> </thead>
<tbody> <tbody>
@ -30,31 +30,24 @@
</a></td> </a></td>
<td><a href="{% url 'acc_app:other_profile' usr.id %}">{{ usr.username }}</a></td> <td><a href="{% url 'acc_app:other_profile' usr.id %}">{{ usr.username }}</a></td>
<td>{{ usr.get_full_name }}</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> Нету{% 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> Нету{% 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 %}
{% 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"> <a href="{% url 'acc_app:delete_profile' usr.id %}" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-remove"></span> <span class="glyphicon glyphicon-remove"></span>
</a> </a>
{% else %}
</td>
{% else %}
<td class="btn-group hidden-xs">
<a href="#" class="btn btn-sm btn-danger disabled" title="Не прав на удаление"> <a href="#" class="btn btn-sm btn-danger disabled" title="Не прав на удаление">
<span class="glyphicon glyphicon-remove"></span> <span class="glyphicon glyphicon-remove"></span>
</a> </a>
{% endif %}
</td>
{% endif %}
</td>
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
@ -78,4 +71,4 @@
{% include 'toolbar_page.html' with pag=users %} {% include 'toolbar_page.html' with pag=users %}
{% endblock %}
{% endblock %}

10
accounts_app/templates/accounts/ext.htm

@ -22,21 +22,21 @@
{% endif %} {% endif %}
<div class="caption btn-group btn-group-sm"> <div class="caption btn-group btn-group-sm">
{% if userprofile == request.user %} {% 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> <span class="glyphicon glyphicon-edit"></span>
{% trans 'Edit' %}
<span class="hidden-sm hidden-md">{% trans 'Edit' %}</span>
</a> </a>
{% endif %} {% endif %}
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
{% if userprofile.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"> <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> <span class="glyphicon glyphicon-lock"></span>
{% trans 'Permission options' %}
<span class="hidden-sm hidden-md">{% trans 'Permission options' %}</span>
</a> </a>
{% else %} {% 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> <span class="glyphicon glyphicon-lock"></span>
{% trans 'Permission options' %}
<span class="hidden-sm hidden-md">{% trans 'Permission options' %}</span>
</a> </a>
{% endif %} {% endif %}
{% endif %} {% endif %}

4
accounts_app/templates/accounts/index.html

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

15
accounts_app/templates/accounts/login.html

@ -15,6 +15,10 @@
margin: 10% auto; margin: 10% auto;
position: relative; position: relative;
} }
footer{
position: absolute;
bottom: 0;
}
</style> </style>
</head> </head>
<body> <body>
@ -60,5 +64,14 @@
</div> </div>
</div> </div>
<footer class="footer">
<div class="container">
<p class="text-muted">
Напишите нам <i>is-ttk@ya.ru</i>.
</p>
</div>
</footer>
</div>
</body> </body>
</html>
</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) dev = Device.objects.get(mac_addr=switch_mac)
mngr_class = dev.get_manager_klass() mngr_class = dev.get_manager_klass()
port = _('<never mind>')
if mngr_class.is_use_device_port(): if mngr_class.is_use_device_port():
port = Port.objects.get(device=dev, num=switch_port) port = Port.objects.get(device=dev, num=switch_port)
abon = Abon.objects.get(dev_port=port, device=dev) 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')) print('D:', _('User settings is not dynamic'))
return return
if not abon.is_access(): 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 return
abon.ip_address = client_ip abon.ip_address = client_ip
abon.is_dhcp = True abon.is_dhcp = True
abon.save(update_fields=['ip_address']) 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: except Abon.DoesNotExist:
print('N:', _("User with device '%s' does not exist") % dev) print('N:', _("User with device '%s' does not exist") % dev)
except Device.DoesNotExist: except Device.DoesNotExist:
@ -33,7 +32,7 @@ def dhcp_commit(client_ip, client_mac, switch_mac, switch_port):
except Port.DoesNotExist: except Port.DoesNotExist:
print('N:', _('Port %d on device with mac %s does not exist') % (int(switch_port), switch_mac)) print('N:', _('Port %d on device with mac %s does not exist') % (int(switch_port), switch_mac))
except MultipleObjectsReturned as e: except MultipleObjectsReturned as e:
print('E:', 'MultipleObjectsReturned:', type(e), e)
print('E:', 'MultipleObjectsReturned:', type(e), e, port, dev)
def dhcp_expiry(client_ip): def dhcp_expiry(client_ip):

22
agent/mod_mikrotik.py

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

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

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Dmitry Novikov <nerosketch@gmail.com>\n" "Last-Translator: Dmitry Novikov <nerosketch@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\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" "%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" "%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." msgid "Let's get acquainted, what is your name? Write your login from billing."
msgstr "Давай знакомиться, как тебя зовут? Напиши свой логин из биллинга." msgstr "Давай знакомиться, как тебя зовут? Напиши свой логин из биллинга."
#: chatbot/telebot.py:81
#: telebot.py:84
msgid "I do not know the answer to this yet." msgid "I do not know the answer to this yet."
msgstr "Я пока не знаю ответа на это" msgstr "Я пока не знаю ответа на это"
#: chatbot/telebot.py:100
#: telebot.py:104
msgid "" msgid ""
"You are not found in the database, check that it correctly pointed out its " "You are not found in the database, check that it correctly pointed out its "
"LOGIN. Try again" "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" msgid "It's not like ip address, try again"
msgstr "Это не похоже на ip адрес, попробуй ещё" msgstr "Это не похоже на ip адрес, попробуй ещё"
#: chatbot/telebot.py:126
#: telebot.py:130
#, python-format #, python-format
msgid "You're '%s', right?" msgid "You're '%s', right?"
msgstr "Ты ведь %s ?" msgstr "Ты ведь %s ?"
#: chatbot/telebot.py:136
#: telebot.py:138
msgid "Telegram bot token not found"
msgstr "Токен для бота Telegram не найден"
#: telebot.py:143
#, python-format #, python-format
msgid "Recipient '%s' does not subscribed on notifications" msgid "Recipient '%s' does not subscribed on notifications"
msgstr "%s не подписан на оповещения" 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.db import models
from django.conf import settings from django.conf import settings
@ -9,11 +10,16 @@ class ChatException(Exception):
class TelegramBot(models.Model): 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): 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): class MessageHistory(models.Model):
@ -23,3 +29,43 @@ class MessageHistory(models.Model):
def __str__(self): def __str__(self):
return self.message 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 -*- # -*- coding: utf-8 -*-
from telepot import helper, glance, Bot from telepot import helper, glance, Bot
from telepot.exception import TelegramError
import os import os
import socket import socket
import collections import collections
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from urllib3.exceptions import ProtocolError from urllib3.exceptions import ProtocolError
from .models import TelegramBot, ChatException
from .models import TelegramBot, ChatException, MessageQueue
from chatbot.models import MessageHistory from chatbot.models import MessageHistory
from accounts_app.models import UserProfile from accounts_app.models import UserProfile
from django.conf import settings from django.conf import settings
@ -19,7 +20,7 @@ class DjingTelebot(helper.ChatHandler):
_chat_id = 0 _chat_id = 0
def __init__(self, seed_tuple, **kwargs): def __init__(self, seed_tuple, **kwargs):
super().__init__(seed_tuple, **kwargs)
super(DjingTelebot, self).__init__(seed_tuple, **kwargs)
self.cmds = { self.cmds = {
'ping': self.ping, 'ping': self.ping,
'iam': self.say_me '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(_("You are not found in the database, check that it correctly pointed out its LOGIN. Try again"),
self.question_name) self.question_name)
return 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) # 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()) 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: try:
MessageQueue.objects.push(msg=msg_text, user=account, tag=tag)
if token is None: if token is None:
raise ChatException(_('Telegram bot token not found')) raise ChatException(_('Telegram bot token not found'))
tb = TelegramBot.objects.get(user=account) 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()) raise ChatException(_("Recipient '%s' does not subscribed on notifications") % account.get_full_name())
except ProtocolError as e: except ProtocolError as e:
raise ChatException(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> <a href="{{ client_side_services }}">Услуги</a>
</li> </li>
<!--<li> <!--<li>
<a href="#"> <a href="#">
Сообщения из администрации Сообщения из администрации
@ -70,7 +71,7 @@
</ul> </ul>
</li> </li>
</ul> </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><!--/.nav-collapse -->
</div> </div>
</div> </div>
@ -94,7 +95,7 @@
<div id="footer"> <div id="footer">
<div class="container"> <div class="container">
<p class="text-muted"> <p class="text-muted">
Напишите разработчику <i>djing-developer@yandex.ru</i>.
Напишите разработчику <i>is-ttk@ya.ru</i>.
</p> </p>
</div> </div>
</div> </div>

12
clientsideapp/templates/clientsideapp/services.html

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

12
clientsideapp/views.py

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

17
cron.py

@ -4,25 +4,20 @@ import os
import django import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
django.setup() 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 agent import Transmitter, NasNetworkError, NasFailedResult
from mydefs import LogicError from mydefs import LogicError
def main(): 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() tm = Transmitter()
users = Abon.objects.filter(is_dynamic_ip=False, is_active=True).exclude(current_tariff=None) users = Abon.objects.filter(is_dynamic_ip=False, is_active=True).exclude(current_tariff=None)
tm.sync_nas(users) tm.sync_nas(users)
signals.pre_delete.connect(abontariff_pre_delete, sender=AbonTariff)
if __name__ == "__main__": if __name__ == "__main__":

16
devapp/base_intr.py

@ -4,6 +4,10 @@ from easysnmp import Session
class DevBase(object, metaclass=ABCMeta): class DevBase(object, metaclass=ABCMeta):
def __init__(self, dev_instance=None):
self.db_instance = dev_instance
@staticmethod @staticmethod
def description(): def description():
"""Возвращает текстовое описание""" """Возвращает текстовое описание"""
@ -56,8 +60,7 @@ class BasePort(object, metaclass=ABCMeta):
pass pass
def mac(self): 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): class SNMPBaseWorker(object, metaclass=ABCMeta):
@ -70,8 +73,13 @@ class SNMPBaseWorker(object, metaclass=ABCMeta):
return self.ses.set(oid, value) return self.ses.set(oid, value)
def get_list(self, oid): 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): def get_item(self, oid):
return self.ses.get(oid).value return self.ses.get(oid).value

79
devapp/dev_types.py

@ -2,6 +2,7 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from mydefs import RuTimedelta, safe_int from mydefs import RuTimedelta, safe_int
from datetime import timedelta from datetime import timedelta
from easysnmp import EasySNMPTimeoutError
from .base_intr import DevBase, SNMPBaseWorker, BasePort from .base_intr import DevBase, SNMPBaseWorker, BasePort
@ -28,9 +29,9 @@ class DLinkPort(BasePort):
class DLinkDevice(DevBase, SNMPBaseWorker): 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 @staticmethod
def description(): 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') return self.get_item('.1.3.6.1.4.1.2021.8.1.101.1')
def get_ports(self): 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') speeds = self.get_list('.1.3.6.1.2.1.31.1.1.1.15')
res = [] res = []
ln = len(speeds)
for n in range(ln):
for n, speed in enumerate(speeds):
status = True if int(stats[n]) == 1 else False status = True if int(stats[n]) == 1 else False
res.append(DLinkPort( res.append(DLinkPort(
n+1, n+1,
nams[n] if len(nams) > 0 else _('does not fetch the name'), nams[n] if len(nams) > 0 else _('does not fetch the name'),
status, status,
macs[n] if len(macs) > 0 else _('does not fetch the mac'), 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)) self))
return res return res
@ -99,9 +99,9 @@ class ONUdev(BasePort):
class OLTDevice(DevBase, SNMPBaseWorker): 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 @staticmethod
def description(): def description():
@ -115,17 +115,17 @@ class OLTDevice(DevBase, SNMPBaseWorker):
res = [] res = []
for nm in nms: 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( 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) res.append(onu)
return res return res
@ -151,6 +151,10 @@ class OLTDevice(DevBase, SNMPBaseWorker):
class OnuDevice(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 @staticmethod
def description(): def description():
return _('PON ONU') return _('PON ONU')
@ -178,6 +182,29 @@ class OnuDevice(DevBase, SNMPBaseWorker):
def is_use_device_port(): def is_use_device_port():
return False 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): class EltexPort(BasePort):
@ -210,10 +237,10 @@ class EltexSwitch(DLinkDevice):
def get_ports(self): def get_ports(self):
#nams = self.get_list('.1.3.6.1.4.1.171.10.134.2.1.1.100.2.1.3') #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') #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 = [] res = []
for n in range(28): for n in range(28):
res.append(EltexPort(self, res.append(EltexPort(self,
@ -235,7 +262,7 @@ class EltexSwitch(DLinkDevice):
@staticmethod @staticmethod
def has_attachable_to_subscriber(): def has_attachable_to_subscriber():
return False
return True
@staticmethod @staticmethod
def is_use_device_port(): def is_use_device_port():

28
devapp/forms.py

@ -11,8 +11,7 @@ from djing import MAC_ADDR_REGEX
class DeviceForm(forms.ModelForm): class DeviceForm(forms.ModelForm):
mac_addr = forms.CharField(widget=forms.TextInput(attrs={ mac_addr = forms.CharField(widget=forms.TextInput(attrs={
'pattern': MAC_ADDR_REGEX, 'pattern': MAC_ADDR_REGEX,
'required': True,
'class': 'form-control'
'required': True
}), error_messages={ }), error_messages={
'required': _('Mac address is required for fill'), 'required': _('Mac address is required for fill'),
'unique': _('Device with that mac is already exist') 'unique': _('Device with that mac is already exist')
@ -20,31 +19,18 @@ class DeviceForm(forms.ModelForm):
class Meta: class Meta:
model = models.Device model = models.Device
fields = '__all__'
exclude = ['map_dot']
widgets = { widgets = {
'ip_address': forms.TextInput(attrs={ 'ip_address': forms.TextInput(attrs={
'pattern': ip_addr_regex, 'pattern': ip_addr_regex,
'placeholder': '192.168.0.100',
'class': 'form-control'
'placeholder': '192.168.0.100'
}), }),
'comment': forms.TextInput(attrs={ '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={ 'user_group': forms.Select(attrs={
'class': 'form-control' 'class': 'form-control'
}),
'parent_dev': forms.Select(attrs={
'class': 'form-control'
}) })
} }
@ -55,11 +41,7 @@ class PortForm(forms.ModelForm):
exclude = ['device'] exclude = ['device']
widgets = { widgets = {
'num': forms.NumberInput(attrs={ 'num': forms.NumberInput(attrs={
'class': 'form-control',
'min': '0' '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" msgid "SNMP password"
msgstr "Пароль SNMP" msgstr "Пароль SNMP"
msgid "SNMP Num"
msgstr "SNMP Ном."
#: devapp/templates/devapp/add_dev.html:62 devapp/templates/devapp/dev.html:50 #: devapp/templates/devapp/add_dev.html:62 devapp/templates/devapp/dev.html:50
msgid "Map point" msgid "Map point"
msgstr "Точка топологии" msgstr "Точка топологии"
@ -309,3 +312,28 @@ msgstr "Устройство"
msgid "Can toggle ports" msgid "Can toggle ports"
msgstr "Может переключать порты" 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),
),
]

132
devapp/models.py

@ -1,14 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import requests
from django.db import models from django.db import models
from djing.fields import MACAddressField from djing.fields import MACAddressField
from .base_intr import DevBase from .base_intr import DevBase
from mydefs import MyGenericIPAddressField, MyChoicesAdapter
from mydefs import MyGenericIPAddressField, MyChoicesAdapter, ip2int
from . import dev_types from . import dev_types
from mapapp.models import Dot
from subprocess import run from subprocess import run
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from json.decoder import JSONDecodeError
DEVICE_TYPES = ( DEVICE_TYPES = (
@ -23,15 +23,40 @@ class DeviceDBException(Exception):
pass 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): 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: class Meta:
db_table = 'dev' db_table = 'dev'
@ -44,8 +69,10 @@ class Device(models.Model):
def get_abons(self): def get_abons(self):
pass 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): def get_manager_klass(self):
klasses = [kl for kl in DEVICE_TYPES if kl[0] == self.devtype] klasses = [kl for kl in DEVICE_TYPES if kl[0] == self.devtype]
@ -55,6 +82,10 @@ class Device(models.Model):
return res return res
return return
def get_manager_object(self):
man_klass = self.get_manager_klass()
return man_klass(self)
# Можно-ли подключать устройство к абоненту # Можно-ли подключать устройство к абоненту
def has_attachable_to_subscriber(self): def has_attachable_to_subscriber(self):
mngr_class = self.get_manager_klass() mngr_class = self.get_manager_klass()
@ -63,11 +94,47 @@ class Device(models.Model):
def __str__(self): def __str__(self):
return "%s: (%s) %s %s" % (self.comment, self.get_devtype_display(), self.ip_address, self.mac_addr or '') return "%s: (%s) %s %s" % (self.comment, self.get_devtype_display(), self.ip_address, self.mac_addr or '')
def update_dhcp(self):
if self.devtype not in ('On','Dl'):
return
grp = self.user_group.id
code = ''
if grp == 87:
code = 'chk'
elif grp == 85:
code = 'drf'
elif grp == 86:
code = 'eme'
elif grp == 84:
code = 'kunc'
elif grp == 47:
code = 'mtr'
elif grp == 60:
code = 'nvg'
elif grp == 65:
code = 'ohot'
elif grp == 89:
code = 'psh'
elif grp == 92:
code = 'str'
elif grp == 80 or grp == 94:
code = 'uy'
elif grp == 79 or grp == 91:
code = 'zrk'
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])
class Port(models.Model): class Port(models.Model):
device = models.ForeignKey(Device)
num = models.PositiveSmallIntegerField(default=0)
descr = models.CharField(max_length=60, null=True, blank=True)
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): def __str__(self):
return "%d: %s" % (int(self.num), self.descr) return "%d: %s" % (int(self.num), self.descr)
@ -82,35 +149,4 @@ class Port(models.Model):
verbose_name_plural = _('Ports') verbose_name_plural = _('Ports')
def dev_post_save_signal(sender, instance, **kwargs):
if instance.devtype != 'On':
return
grp = instance.user_group.pk
code = ''
if grp == 87:
code = 'chk'
elif grp == 85:
code = 'drf'
elif grp == 86:
code = 'eme'
elif grp == 84:
code = 'kunc'
elif grp == 47:
code = 'mtr'
elif grp == 60:
code = 'nvg'
elif grp == 65:
code = 'ohot'
elif grp == 89:
code = 'psh'
elif grp == 92:
code = 'str'
elif grp == 80:
code = 'uy'
elif grp == 79 or grp == 91:
code = 'zrk'
newmac = str(instance.mac_addr)
run(["%s/devapp/onu_register.sh" % settings.BASE_DIR, newmac, code])
models.signals.post_save.connect(dev_post_save_signal, sender=Device)

87
devapp/templates/devapp/add_dev.html

@ -1,5 +1,6 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %} {% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %} {% load i18n %}
{% load bootstrap3 %}
{% block main %} {% block main %}
<ol class="breadcrumb"> <ol class="breadcrumb">
@ -23,76 +24,23 @@
<form role="form" action="{% url 'devapp:add' group.pk %}" method="post" autocomplete="off">{% csrf_token %} <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"> <div class="form-group">
<label for="id_parent_dev">{% trans 'Parent device' %}</label> <label for="id_parent_dev">{% trans 'Parent device' %}</label>
@ -106,11 +54,14 @@
</div> </div>
</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' %} <span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button> </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' %} <span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %}
</button> </button>
</div> </div>

24
devapp/templates/devapp/custom_dev_page/olt.html

@ -5,18 +5,20 @@
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<div class="table-responsive"> <div class="table-responsive">
{% if uptime %}
{% with uptime=dev_manager.uptime %}
{% if uptime %}
{% trans 'Uptime' %} {{ uptime }} {% trans 'Uptime' %} {{ uptime }}
{% endif %} {% endif %}
{% endwith %}
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <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> </tr>
</thead> </thead>
@ -28,19 +30,19 @@
{% else %}<span class="glyphicon glyphicon-warning-sign text-danger"></span> {% else %}<span class="glyphicon glyphicon-warning-sign text-danger"></span>
{% endif %} {% endif %}
</td> </td>
<td>{{ port.mac }}</td>
<td>{{ port.num }}</td>
<td>{{ port.nm }}</td> <td>{{ port.nm }}</td>
<td>{{ port.sp }}</td>
<td>{{ port.mac }}</td>
<td>{{ port.signal }}</td> <td>{{ port.signal }}</td>
<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> <span class="glyphicon glyphicon-plus"></span>
</a> </a>
</td> </td>
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="6">{% trans 'Ports not found' %}</td>
<td colspan="7">{% trans 'Ports not found' %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
{% endwith %} {% endwith %}

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

@ -2,8 +2,9 @@
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
{% with uptime=dev_manager.uptime onu_details=dev_manager.get_details %}
<div class="row"> <div class="row">
<div class="col-sm-12">
<div class="col-xs-12 col-sm-6">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="panel-title">{{ dev.get_devtype_display|default:_('Title of the type of switch') }}. <div class="panel-title">{{ dev.get_devtype_display|default:_('Title of the type of switch') }}.
@ -16,7 +17,7 @@
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item">{% trans 'Ip address' %}: {{ dev.ip_address }}</li> <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 '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 %} {% for da in dev_accs %}
<li class="list-group-item">{% trans 'Attached user' %}: <li class="list-group-item">{% trans 'Attached user' %}:
{% if da.group %} {% if da.group %}
@ -40,6 +41,60 @@
</div> </div>
</div> </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> </div>
{% endwith %}
{% endblock %} {% endblock %}

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

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

144
devapp/templates/devapp/dev.html

@ -1,114 +1,70 @@
{% extends request.is_ajax|yesno:'nullcont.htm,devapp/ext.htm' %} {% extends request.is_ajax|yesno:'nullcont.htm,devapp/ext.htm' %}
{% load i18n %} {% load i18n %}
{% load bootstrap3 %}
{% block content %} {% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Device info' %}</h3>
</div>
<div class="panel-body">
<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="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Device info' %}</h3>
</div>
<div class="panel-body">
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-comment"></span></span>
{{ form.comment }}{{ form.comment.errors }}
</div>
</div>
<form role="form" action="{% url 'devapp:edit' group.pk|default:0 dev.pk %}" method="post">{% csrf_token %}
<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>
{% bootstrap_icon 'subscript' as ic %}
{% bootstrap_field form.user_group addon_before=ic %}
<div class="form-group">
<label for="id_user_group">{% trans 'User group' %}</label>
<div class="form-group">
<label for="id_parent_dev">{% trans 'Parent device' %}</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 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>
<div class="form-group">
<label for="id_parent_dev">{% trans 'Parent device' %}</label>
{% bootstrap_icon 'list-alt' as ic %}
{% bootstrap_field form.snmp_item_num addon_before=ic %}
<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">
<button type="submit" class="btn btn-sm 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> {% trans 'Reset' %}
</button>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-sm 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> {% trans 'Reset' %}
</button>
</div>
</form>
</div>
</form>
</div> </div>
</div>
{% endblock %} {% endblock %}

38
devapp/templates/devapp/devices.html

@ -12,29 +12,31 @@
<h3>{% trans 'Devices' %}</h3> <h3>{% trans 'Devices' %}</h3>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-bordered">
<table class="table table-bordered">
<thead> <thead>
<tr> <tr>
<th>
<th>#</th>
<th class="col-md-2">
<a href="{% url 'devapp:devs' group.pk %}?order_by=ip_address&dir={{ dir|default:"down" }}"> <a href="{% url 'devapp:devs' group.pk %}?order_by=ip_address&dir={{ dir|default:"down" }}">
{% trans 'Ip address' %} {% trans 'Ip address' %}
</a> </a>
{% if order_by == 'ip_address' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'ip_address' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
<th>
<th class="col-md-5">
<a href="{% url 'devapp:devs' group.pk %}?order_by=comment&dir={{ dir|default:"down" }}"> <a href="{% url 'devapp:devs' group.pk %}?order_by=comment&dir={{ dir|default:"down" }}">
{% trans 'Comment' %} {% trans 'Comment' %}
</a> </a>
{% if order_by == 'comment' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'comment' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </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" }}"> <a href="{% url 'devapp:devs' group.pk %}?order_by=devtype&dir={{ dir|default:"down" }}">
{% trans 'Device type' %} {% trans 'Device type' %}
</a> </a>
{% if order_by == 'devtype' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'devtype' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
<th width="100">Do</th>
<th class="col-md-1">Do</th>
</tr> </tr>
</thead> </thead>
@ -42,18 +44,28 @@
{% with can_del_dev=perms.devapp.delete_device can_ch_dev=perms.devapp.change_device %} {% with can_del_dev=perms.devapp.delete_device can_ch_dev=perms.devapp.change_device %}
{% for dev in devices %} {% for dev in devices %}
<tr> <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><a href="{% url 'devapp:view' dev.user_group.pk dev.pk %}">{{ dev.ip_address }}</a></td>
<td>{{ dev.comment }}</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>{{ 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 %} {% 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> <span class="glyphicon glyphicon-remove"></span>
</a> </a>
{% endif %} {% endif %}
{% if can_ch_dev %} {% 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> <span class="glyphicon glyphicon-edit"></span>
</a> </a>
{% endif %} {% endif %}
@ -61,7 +73,7 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <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> </tr>
{% endfor %} {% endfor %}
{% endwith %} {% endwith %}
@ -69,7 +81,7 @@
<tfoot> <tfoot>
<tr> <tr>
<td colspan="5">
<td colspan="7">
<a href="{% url 'devapp:add' group.pk %}" class="btn btn-success btn-sm"> <a href="{% url 'devapp:add' group.pk %}" class="btn btn-success btn-sm">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Create' %} <span class="glyphicon glyphicon-plus"></span> {% trans 'Create' %}
</a> </a>
@ -82,4 +94,4 @@
{% include 'toolbar_page.html' with pag=devices %} {% include 'toolbar_page.html' with pag=devices %}
{% endblock %}
{% endblock %}

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 %}

4
devapp/templates/devapp/group_list.html

@ -34,7 +34,7 @@
<td class="btn-group"> <td class="btn-group">
{% if perms.abonapp.add_abongroup %} {% if perms.abonapp.add_abongroup %}
<a href="{% url 'abonapp:add_group' %}" class="btn btn-success btn-sm"> <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> </a>
{% endif %} {% endif %}
<a href="{% url 'devapp:devices_null_group' %}" class="btn btn-primary btn-sm"> <a href="{% url 'devapp:devices_null_group' %}" class="btn btn-primary btn-sm">
@ -48,4 +48,4 @@
{% include 'toolbar_page.html' with pag=groups %} {% include 'toolbar_page.html' with pag=groups %}
{% endblock %}
{% endblock %}

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

@ -1,9 +1,14 @@
{% load i18n %}
{% load i18n %}{% load bootstrap3 %}
{% if port_id %} {% 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"> <input type="hidden" value="yes" name="confirm">
<div class="modal-header primary"> <div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <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> <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="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> </div>
</form> </form>

2
devapp/urls.py

@ -6,6 +6,7 @@ from . import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.group_list, name='group_list'), url(r'^$', views.group_list, name='group_list'),
url(r'^devices_without_groups$', views.devices_null_group, name='devices_null_group'), 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+)$', views.devices, name='devs'),
url(r'^(?P<grp>\d+)/add$', views.dev, name='add'), url(r'^(?P<grp>\d+)/add$', views.dev, name='add'),
url(r'^(\d+)/(?P<did>\d+)$', views.devview, name='view'), 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'^(\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<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'^(?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) url(r'^search_dev$', views.search_dev)
] ]

152
devapp/views.py

@ -2,6 +2,7 @@
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import IntegrityError
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404, resolve_url 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 easysnmp import EasySNMPTimeoutError, EasySNMPError
from json import dumps 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 mydefs import pag_mn, res_success, res_error, only_admins, ping, order_helper
from .forms import DeviceForm, PortForm from .forms import DeviceForm, PortForm
from abonapp.models import AbonGroup, Abon from abonapp.models import AbonGroup, Abon
@ -25,15 +26,19 @@ def devices(request, grp):
group = get_object_or_404(AbonGroup, pk=grp) group = get_object_or_404(AbonGroup, pk=grp)
if not request.user.has_perm('abonapp.can_view_abongroup', group): if not request.user.has_perm('abonapp.can_view_abongroup', group):
raise PermissionDenied 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)
if field:
devs = devs.order_by(field)
# фильтр
dr, field = order_helper(request)
if field:
devs = devs.order_by(field)
devs = pag_mn(request, devs)
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', { return render(request, 'devapp/devices.html', {
'devices': devs, 'devices': devs,
@ -91,17 +96,30 @@ def dev(request, grp, devid=0):
else: else:
if not request.user.has_perm('devapp.change_device'): if not request.user.has_perm('devapp.change_device'):
raise PermissionDenied raise PermissionDenied
frm = DeviceForm(request.POST, instance=devinst)
if frm.is_valid():
ndev = frm.save()
messages.success(request, _('Device info has been saved'))
return redirect('devapp:edit', grp, ndev.pk)
else:
try:
already_dev = Device.objects.get(mac_addr=request.POST.get('mac_addr'))
except Device.DoesNotExist:
pass
messages.error(request, _('Form is invalid, check fields and try again'))
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', 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: else:
if devinst is None: if devinst is None:
frm = DeviceForm(initial={ frm = DeviceForm(initial={
@ -110,7 +128,8 @@ def dev(request, grp, devid=0):
'mac_addr': request.GET.get('mac'), 'mac_addr': request.GET.get('mac'),
'comment': request.GET.get('c'), 'comment': request.GET.get('c'),
'ip_address': request.GET.get('ip'), '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: else:
frm = DeviceForm(instance=devinst) frm = DeviceForm(instance=devinst)
@ -125,7 +144,7 @@ def dev(request, grp, devid=0):
return render(request, 'devapp/dev.html', { return render(request, 'devapp/dev.html', {
'form': frm, 'form': frm,
'dev': devinst, 'dev': devinst,
'selected_parent_dev': devinst.parent_dev or None,
'selected_parent_dev': devinst.parent_dev,
'group': user_group, 'group': user_group,
'already_dev': already_dev 'already_dev': already_dev
}) })
@ -138,7 +157,7 @@ def manage_ports(request, devid):
dev = Device.objects.get(pk=devid) dev = Device.objects.get(pk=devid)
if dev.user_group is None: if dev.user_group is None:
messages.error(request, _('Device is not have a group, please fix that')) 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) ports = Port.objects.filter(device=dev)
except Device.DoesNotExist: except Device.DoesNotExist:
@ -177,7 +196,7 @@ def add_ports(request, devid):
dev = Device.objects.get(pk=devid) dev = Device.objects.get(pk=devid)
if dev.user_group is None: if dev.user_group is None:
messages.error(request, _('Device is not have a group, please fix that')) 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': if request.method == 'POST':
ports = zip( ports = zip(
request.POST.getlist('p_text'), request.POST.getlist('p_text'),
@ -200,7 +219,7 @@ def add_ports(request, devid):
db_ports = Port.objects.filter(device=dev) db_ports = Port.objects.filter(device=dev)
db_ports = [TempPort(p.num, p.descr, None, True, p.pk) for p in db_ports] 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() ports = manager.get_ports()
if ports is not None: if ports is not None:
ports = [TempPort(p.num, p.nm, p.st, False) for p in ports] 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 @login_required
@permission_required('devapp.can_view_device') @permission_required('devapp.can_view_device')
def devview(request, did): def devview(request, did):
ports = None
uptime = 0
ports, manager = None, None
dev = get_object_or_404(Device, id=did) 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' template_name = 'ports.html'
try: try:
if ping(dev.ip_address): if ping(dev.ip_address):
if dev.man_passw: 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() ports = manager.get_ports()
template_name = manager.get_template_name() template_name = manager.get_template_name()
else: else:
messages.warning(request, _('Not Set snmp device password')) messages.warning(request, _('Not Set snmp device password'))
else: else:
messages.error(request, _('Dot was not pinged')) 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: except EasySNMPError:
messages.error(request, _('SNMP error on device')) messages.error(request, _('SNMP error on device'))
except DeviceDBException as e: except DeviceDBException as e:
messages.error(request, e) messages.error(request, e)
return render(request, 'devapp/custom_dev_page/' + template_name, { 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: try:
if ping(dev.ip_address): if ping(dev.ip_address):
if dev.man_passw: if dev.man_passw:
manager = dev.get_manager_klass()(dev.ip_address, dev.man_passw)
manager = dev.get_manager_object()
ports = manager.get_ports() ports = manager.get_ports()
if status: if status:
ports[portid - 1].enable() ports[portid - 1].enable()
@ -354,6 +376,8 @@ def toggle_port(request, did, portid, status=0):
messages.error(request, _('Dot was not pinged')) messages.error(request, _('Dot was not pinged'))
except EasySNMPTimeoutError: except EasySNMPTimeoutError:
messages.error(request, _('wait for a reply from the SNMP Timeout')) 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) 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] ).only('pk', 'ip_address', 'comment')[:16]
results = [{'id': dev.pk, 'text': "%s: %s" % (dev.ip_address, dev.comment)} for dev in results] results = [{'id': dev.pk, 'text': "%s: %s" % (dev.ip_address, dev.comment)} for dev in results]
return HttpResponse(dumps(results, ensure_ascii=False)) 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 #!/usr/bin/env python3
import sys import sys
from redis import Redis
from rq import Queue
import socket
def die(text): def die(text):
print(text) print(text)
exit(1) 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__": if __name__ == "__main__":
argv = sys.argv argv = sys.argv
if len(argv) < 3: if len(argv) < 3:
die('Too few arguments, exiting...') die('Too few arguments, exiting...')
action = argv[1] action = argv[1]
q = Queue(connection=Redis())
if action == 'commit': if action == 'commit':
if len(argv) < 6: if len(argv) < 6:
die('Too few arguments, exiting...') 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" msgid "User with the telephone number not found"
msgstr "Абонент с таким номером телефона не найден" 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/recording/bug" % path
return "%s/monitor" % 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: class Meta:
db_table = 'cdr' db_table = 'cdr'
managed = False

12
dialing_app/templates/ext.html

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

27
dialing_app/templates/index.html

@ -17,16 +17,34 @@
<th>{% trans 'end' %}</th> <th>{% trans 'end' %}</th>
<th>{% trans 'disposition' %}</th> <th>{% trans 'disposition' %}</th>
</tr> </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> </thead>
<tbody> <tbody>
{% for log in logs %} {% for log in logs %}
<tr>
<tr>
{% with lurl=log.url %}
<td class="btn-group btn-group-xs btn-group-justify"> <td class="btn-group btn-group-xs btn-group-justify">
<button class="btn btn-default player-btn disabled"> <button class="btn btn-default player-btn disabled">
<span class="glyphicon glyphicon-play"></span> <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> </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> <span class="glyphicon glyphicon-download-alt"></span>
</a> </a>
</td> </td>
@ -38,7 +56,8 @@
<td>{{ log.answer|date:'d M, H:i:s' }}</td> <td>{{ log.answer|date:'d M, H:i:s' }}</td>
<td>{{ log.end|date:'d M, H:i:s' }}</td> <td>{{ log.end|date:'d M, H:i:s' }}</td>
<td>{{ log.locate_disposition }}</td> <td>{{ log.locate_disposition }}</td>
</tr>
{% endwith %}
</tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="9">{% trans 'Calls was not found' %}</td> <td colspan="9">{% trans 'Calls was not found' %}</td>

10
dialing_app/templates/vmail.html

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

4
dialing_app/urls.py

@ -4,6 +4,8 @@ from . import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.home, name='home'), 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'^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.shortcuts import render, redirect
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from guardian.decorators import permission_required_or_403 as permission_required from guardian.decorators import permission_required_or_403 as permission_required
from django.db.models import Q
from abonapp.models import Abon from abonapp.models import Abon
from mydefs import only_admins, pag_mn from mydefs import only_admins, pag_mn
@ -41,11 +42,36 @@ def to_abon(request, tel):
@login_required @login_required
@only_admins @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 = AsteriskCDR.objects.filter(userfield='request').order_by('-calldate')
cdr = pag_mn(request, cdr) cdr = pag_mn(request, cdr)
return render(request, 'vmail.html', { return render(request, 'vmail.html', {
'title': title, 'title': title,
'vmessages': cdr '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', 'chatbot',
'msg_app', 'msg_app',
'dialing_app', 'dialing_app',
'guardian'
'guardian',
'pinax_theme_bootstrap',
'bootstrapform',
'bootstrap3'
] ]
MIDDLEWARE_CLASSES = [ MIDDLEWARE_CLASSES = [

8
djing/urls.py

@ -1,7 +1,6 @@
from django.conf.urls import url, include from django.conf.urls import url, include
from django.contrib import admin
from django.conf import settings
from . import settings
from .views import home from .views import home
@ -17,13 +16,14 @@ urlpatterns = [
url(r'^tasks/', include('taskapp.urls', namespace='taskapp')), url(r'^tasks/', include('taskapp.urls', namespace='taskapp')),
url(r'^client/', include('clientsideapp.urls', namespace='client_side')), url(r'^client/', include('clientsideapp.urls', namespace='client_side')),
url(r'^msg/', include('msg_app.urls', namespace='msg_app')), 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: if settings.DEBUG:
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.contrib import admin
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += staticfiles_urlpatterns() 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): def add_tariff_range(self, tariff_list):
pass 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 -*- # -*- coding: utf-8 -*-
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from abonapp.models import Abon 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): def context_processor_additional_profile(request):
if request.user.is_staff or request.user.is_anonymous(): 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: 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 DotForm(forms.ModelForm):
class Meta: class Meta:
model = Dot model = Dot
fields = '__all__'
exclude = ['devices']
widgets = { 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.db import models
from django.utils.translation import ugettext_lazy as _
from devapp.models import Device
class Dot(models.Model): 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: class Meta:
db_table = 'dots' db_table = 'dots'
verbose_name = _('Map point')
verbose_name_plural = _('Map points')
permissions = (
('can_view', _('Can view')),
)
def __str__(self): def __str__(self):
return self.title 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 %}

140
mapapp/templates/maps/dot.html

@ -1,63 +1,103 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n %}
{% load bootstrap3 %}
{% block main %} {% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'mapapp:options' %}">{% trans 'Map settings' %}</a></li>
{% if dot.id %}
<li class="active">{{ dot.title }}</li>
{% 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>
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'mapapp:options' %}">Настройки карты</a></li>
<li class="active">{{ dot.title }}</li>
</ol>
{% include 'message_block.html' %}
{% include 'message_block.html' %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Точка топологии</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 %}
<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 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">{% trans 'Map point' %}</h3>
</div> </div>
<div class="panel-body">
<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>
{# title input #}
{% bootstrap_icon 'edit' as ic %}
{% bootstrap_field form.title 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>
{# longitude input #}
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.longitude addon_before=ic %}
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> Сохранить
</button>
<button type="reset" class="btn btn-sm btn-default">
<span class="glyphicon glyphicon-remove-circle"></span> Сбросить
</button>
</div>
{# latitude input #}
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.latitude addon_before=ic %}
{# attachment input #}
{% bootstrap_field form.attachment %}
</form>
</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-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>
</div> </div>
{% endif %}
</div>
{% endblock %}
{% 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> <h4>{{ dot.title }}</h4>
<div class="list-group"> <div class="list-group">
{% for dev in devs %} {% 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> </a>
{% empty %} {% empty %}
<li class="list-group-item">нет привязанных устройств</li>
<li class="list-group-item">{% trans 'Pinned devices not found' %}</li>
{% endfor %} {% endfor %}
</div> </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"> <div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <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>
<div class="modal-body"> <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>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-sm btn-primary"> <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>
<button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button>
</div> </div>
</form> </form>

38
mapapp/templates/maps/options.html

@ -1,24 +1,24 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n %}
{% block main %} {% block main %}
<ol class="breadcrumb"> <ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li> <li><span class="glyphicon glyphicon-home"></span></li>
<li class="active">Настройки карты</li>
<li class="active">{% trans 'Map settings' %}</li>
</ol> </ol>
{% include 'message_block.html' %} {% 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"> <div class="table-responsive">
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <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> </tr>
</thead> </thead>
<tbody> <tbody>
@ -27,33 +27,38 @@
<td>{{ dot.title }}</td> <td>{{ dot.title }}</td>
<td>{{ dot.longitude }}</td> <td>{{ dot.longitude }}</td>
<td>{{ dot.latitude }}</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"> class="btn btn-primary">
<span class="glyphicon glyphicon-edit"></span> <span class="glyphicon glyphicon-edit"></span>
</a> </a>
{% endif %} {% 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"> class="btn btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span>
<span class="glyphicon glyphicon-remove"></span>
</a> </a>
{% endif %} {% endif %}
</td> </td>
{% endif %}
</tr> </tr>
{% empty %} {% empty %}
<tr> <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> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <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"> <a href="{% url 'mapapp:add_dot' %}" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-plus"></span> <span class="glyphicon glyphicon-plus"></span>
</a> </a>
@ -62,6 +67,7 @@
</tfoot> </tfoot>
</table> </table>
</div> </div>
{% endwith %}
{% include 'toolbar_page.html' with pag=dots %} {% include 'toolbar_page.html' with pag=dots %}
{% endblock %} {% 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 %}

127
mapapp/templates/maps/ya_index.html

@ -1,4 +1,4 @@
{% extends 'base_no_lmenu.html' %}
{% extends 'base_no_lmenu.html' %}{% load i18n %}
{% block main %} {% block main %}
<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script> <script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script>
@ -8,7 +8,7 @@
function placemark_click(e){ function placemark_click(e){
var plcmrk = e.get('target'); var plcmrk = e.get('target');
plcmrk.properties.set('balloonContent', "Получаю инфу..");
plcmrk.properties.set('balloonContent', "{% trans 'Loading..' %}");
var html = $.ajax({ var html = $.ajax({
url: "{% url 'mapapp:dot_tooltip' %}", url: "{% url 'mapapp:dot_tooltip' %}",
data: {'d': plcmrk.properties._data.dot_id}, data: {'d': plcmrk.properties._data.dot_id},
@ -18,23 +18,48 @@
} }
function load_dots(r){ 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
}
);
dot.events.add('click', placemark_click);
myMap.geoObjects.add(dot);
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){ function add_dot(e){
var coords = e.get('coords'); var coords = e.get('coords');
$.get('{% url 'mapapp:modal_add_dot' %}', {'coords': coords.join(',')}, function(r){ $.get('{% url 'mapapp:modal_add_dot' %}', {'coords': coords.join(',')}, function(r){
show_ModalMyContent(r); show_ModalMyContent(r);
$('.form-ajax').ajform({'on_response': on_success_add_dot});
}); });
e.preventDefault(); e.preventDefault();
} }
@ -42,29 +67,93 @@
function init(){ function init(){
var win = $(window); var win = $(window);
$('#yamap')
.css('width', win.width()-5+'px')
.css('height', win.height()+'px');
myMap = new ymaps.Map("yamap", { myMap = new ymaps.Map("yamap", {
center: [45.449160, 34.735454], 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); $.getJSON("{% url 'mapapp:get_dots' %}", load_dots);
myMap.events.add('dblclick', add_dot); 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> </script>
<div id="yamap"></div>
<div id="yamap" class="col-sm-12"></div>
<style> <style>
#yamap{ #yamap{
margin-left: -15px; margin-left: -15px;
margin-top: -9px; 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> </style>
{% endblock %}
<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/add$', views.dot, name='add_dot'),
url(r'^options/(?P<did>\d+)/edit$', views.dot, name='edit_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+)/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'^get_dots$', views.get_dots, name='get_dots'),
url(r'^modal_add_dot$', views.modal_add_dot, name='modal_add_dot'), 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.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied 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 .models import Dot
from .forms import DotForm from .forms import DotForm
from mydefs import pag_mn
from mydefs import pag_mn, safe_int
from devapp.models import Device 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 @login_required
def home(request): def home(request):
if not request.user.is_superuser:
return redirect('/')
dots = Dot.objects.all() dots = Dot.objects.all()
groups = AbonGroup.objects.all()
return render(request, 'maps/ya_index.html', { return render(request, 'maps/ya_index.html', {
'dots': dots
'dots': dots,
'abon_groups': groups
}) })
@login_required @login_required
def options(request): def options(request):
if not request.user.is_superuser:
return redirect('/')
dots = Dot.objects.all() dots = Dot.objects.all()
dots = pag_mn(request, dots) dots = pag_mn(request, dots)
return render(request, 'maps/options.html', { return render(request, 'maps/options.html', {
@ -31,6 +40,8 @@ def options(request):
@login_required @login_required
def dot(request, did=0): def dot(request, did=0):
if not request.user.is_superuser:
return redirect('/')
try: try:
if did == 0: if did == 0:
dot = Dot() dot = Dot()
@ -42,12 +53,13 @@ def dot(request, did=0):
dot = Dot.objects.get(id=did) dot = Dot.objects.get(id=did)
if request.method == 'POST': if request.method == 'POST':
frm = DotForm(request.POST, instance=dot)
frm = DotForm(request.POST, request.FILES, instance=dot)
if frm.is_valid(): 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: else:
messages.error(request, 'ошибки в форме')
messages.error(request, _('fix form errors'))
else: else:
frm = DotForm(instance=dot) frm = DotForm(instance=dot)
@ -57,7 +69,7 @@ def dot(request, did=0):
}) })
except Dot.DoesNotExist: except Dot.DoesNotExist:
messages.error(request, 'Эта точка топологии не существует')
messages.error(request, _('Map point does not exist'))
return redirect('mapapp:options') return redirect('mapapp:options')
@ -68,49 +80,128 @@ def remove(request, did):
dot = Dot.objects.get(id=did) dot = Dot.objects.get(id=did)
title = dot.title title = dot.title
dot.delete() dot.delete()
messages.success(request, "Точка топологии '%s' успешно удалена" % title)
messages.success(request, _("Map point '%(title)s' has been deleted") % {'title': title})
except Dot.DoesNotExist: except Dot.DoesNotExist:
messages.error(request, 'Эта точка топологии не существует')
messages.error(request, _('Map point does not exist'))
return redirect('mapapp:options') return redirect('mapapp:options')
@login_required
def get_dots(request): 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 @login_required
@permission_required('mapapp.add_dot')
def modal_add_dot(request): 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': 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: else:
coords = request.GET.get('coords') 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', { return render_to_text('maps/modal_add_dot.html', {
'coords': coords
'coords': coords,
'form': frm
}, request=request) }, 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 @login_required
def dot_tooltip(request): def dot_tooltip(request):
if not request.user.is_superuser:
return render_to_text('403_for_modal.html')
d = request.GET.get('d') d = request.GET.get('d')
devs, dot = None, None devs, dot = None, None
try: try:
dot = Dot.objects.get(id=d) 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: except Dot.DoesNotExist:
pass pass
return render_to_text('maps/map_tooltip.html', { return render_to_text('maps/map_tooltip.html', {
'devs': devs, 'devs': devs,
'dot': dot '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 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 #: templates/msg_app/modal_new_conversation.html:19
msgid "for select multiple press ctrl and click on field" msgid "for select multiple press ctrl and click on field"
msgstr "Для выбора нескольких ывриантов зажмите ctrl и кликните вариант"
msgstr "Для выбора нескольких вариантов зажмите ctrl и кликните нужный вариант"
#: templates/msg_app/modal_new_conversation.html:28 #: templates/msg_app/modal_new_conversation.html:28
msgid "Add" msgid "Add"

26
msg_app/models.py

@ -1,6 +1,8 @@
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from accounts_app.models import UserProfile from accounts_app.models import UserProfile
from chatbot.telebot import send_notify
from chatbot.models import ChatException
class MessageError(Exception): class MessageError(Exception):
@ -146,7 +148,7 @@ class Conversation(models.Model):
objects = ConversationManager() objects = ConversationManager()
def get_messages(self): 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): def get_messages_new_count(self, account):
msgs = Message.objects.filter(conversation=self) msgs = Message.objects.filter(conversation=self)
@ -158,15 +160,19 @@ class Conversation(models.Model):
return messages[0] return messages[0]
def new_message(self, text, attachment, author, with_status=True): def new_message(self, text, attachment, author, with_status=True):
msg = Message.objects.create(
text=text, conversation=self, attachment=attachment, author=author
)
if with_status:
for participant in self.participants.all():
if participant == author:
continue
MessageStatus.objects.create(msg=msg, user=participant)
return msg
try:
msg = Message.objects.create(
text=text, conversation=self, attachment=attachment, author=author
)
if with_status:
for participant in self.participants.all():
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): def remove_message(self, msg):
if isinstance(msg, Message): if isinstance(msg, Message):

4
msg_app/templates/msg_app/chat.html

@ -16,12 +16,12 @@
<small>{{ conv.participants.count }} {% trans 'peoples' %}</small> <small>{{ conv.participants.count }} {% trans 'peoples' %}</small>
</h3> </h3>
</div> </div>
<div class="list-group">
<div class="list-group scroll-area">
{% with can_view_profile=perms.accounts_app.can_view_userprofile %} {% with can_view_profile=perms.accounts_app.can_view_userprofile %}
{% for msg in msg_list %} {% for msg in msg_list %}
{% with author=msg.author %} {% with author=msg.author %}
<div class="list-group-item clearfix">
<div class="list-group-item">
{% if msg.author == request.user %} {% 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"> <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> <span class="glyphicon glyphicon-remove"></span>

3
msg_app/urls.py

@ -6,5 +6,6 @@ urlpatterns = [
url(r'^$', views.home, name='home'), url(r'^$', views.home, name='home'),
url(r'^new$', views.new_conversation, name='new_conversation'), 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+)/$', 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.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib import messages from django.contrib import messages
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from chatbot.models import MessageQueue
from mydefs import pag_mn from mydefs import pag_mn
from .models import Conversation, MessageError, Message from .models import Conversation, MessageError, Message
@ -70,3 +73,17 @@ def remove_msg(request, conv_id, msg_id):
conversation_id = msg.conversation.pk conversation_id = msg.conversation.pk
msg.delete() msg.delete()
return redirect('msg_app:to_conversation', conversation_id) 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): class LogicError(Exception):
pass pass
def singleton(class_):
instances = {}
def getinstance(*args, **kwargs):
if class_ not in instances:
instances[class_] = class_(*args, **kwargs)
return instances[class_]
return getinstance

71
queue_mngr.py

@ -1,30 +1,55 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import sys
from rq import Connection, Worker
from pickle import loads
from pid.decorator import pidfile from pid.decorator import pidfile
import socket
import django 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: try:
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()
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()
from agent.commands.dhcp import dhcp_commit, dhcp_expiry, dhcp_release
serve()

5
requirements.txt

@ -12,6 +12,9 @@ xmltodict
mysqlclient mysqlclient
easysnmp easysnmp
rq
pid pid
django-guardian 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 { .table thead {
background-color: #ddd; 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{border-radius: 4px 4px 0 0;}
.modal-header.warning {background-color: #d2322d;} .modal-header.warning {background-color: #d2322d;}
.modal-header.success {background-color: #6ad245;} .modal-header.success {background-color: #6ad245;}
.modal-header.primary {background-color: #789cbb;} .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 { .main {
margin-top: 10px; margin-top: 10px;
/*display: table;*/
} }
.table-responsive thead { .table-responsive thead {
@ -246,7 +247,29 @@ button[data-toggle=offcanvas]{
fill-opacity: 0.3; 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); $('#modContent').html(content);
$('#modFrm').modal(); $('#modFrm').modal();
} }
function hide_ModalMyContent(){$('#modFrm').modal('hide');}
function show_Modal(title, content, type_class){ function show_Modal(title, content, type_class){
var cnt='<div class="modal-header '+type_class+'">' + var cnt='<div class="modal-header '+type_class+'">' +
'<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' + '<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-body">'+content+'</div>' +
'<div class="modal-footer"><button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button></div>'; '<div class="modal-footer"><button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button></div>';
show_ModalMyContent(cnt); show_ModalMyContent(cnt);
$('#loading').hide();
} }
function showErr(errContent) {show_Modal('Ошибка', errContent, 'warning');} 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');} function showPrimary(errContent) {show_Modal('Внимание!', errContent, 'primary');}
$(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) { $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
//loaderShow(false);
showErr(jqXHR.status + ': ' + jqXHR.statusText); showErr(jqXHR.status + ': ' + jqXHR.statusText);
}); });
@ -116,6 +117,109 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
})(jQuery); })(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 () { $(document).ready(function () {
@ -189,4 +293,9 @@ $(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip({container:'body'}); $('[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] [Service]
Type=simple Type=simple
ExecStart=/usr/bin/python3 ./telebot.py > /dev/null ExecStart=/usr/bin/python3 ./telebot.py > /dev/null
PIDFile=/run/telebot.py.pid
PIDFile=/run/djing_telebot.pid
WorkingDirectory=/var/www/djing WorkingDirectory=/var/www/djing
TimeoutSec=9 TimeoutSec=9
Restart=always 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 type - %s.') % task.get_mode_display() + '\n'
fulltext += task.descr if task.descr else '' fulltext += task.descr if task.descr else ''
print('task.state:', task.state)
if task.state == 'F' or task.state == 'C': if task.state == 'F' or task.state == 'C':
# Если задача завершена или провалена то отправляем одно оповещение автору # Если задача завершена или провалена то отправляем одно оповещение автору
try: try:
send_notify(fulltext, author)
send_notify(fulltext, author, tag='taskap')
except ChatException as e: except ChatException as e:
raise TaskException(e) raise TaskException(e)
else: else:
send_notify(fulltext, dst_account)
send_notify(fulltext, dst_account, tag='taskap')
except ChatException as e: except ChatException as e:
errors.append(e) errors.append(e)
if len(errors) > 0: 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:93
#: taskapp/templates/taskapp/tasklist_all.html:94 #: taskapp/templates/taskapp/tasklist_all.html:94
msgid "Add new task" msgid "Add new task"
msgstr "Добавьте новую задачу"
msgstr "Добавьте задачу"
#: taskapp/templates/taskapp/add_edit_task.html:27 #: taskapp/templates/taskapp/add_edit_task.html:27
#: taskapp/templates/taskapp/tasklist.html:13 #: taskapp/templates/taskapp/tasklist.html:13
@ -378,3 +378,9 @@ msgstr "Задачи, которые необходимо выполнить"
msgid "locality %s. Task type - %s. " msgid "locality %s. Task type - %s. "
msgstr "с. %s. Тип задачи - %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 = ( TASK_TYPES = (
('na', _('not chosen')), ('na', _('not chosen')),
('ic', _('ip conflict')),
('yt', _('yellow triangle')), ('yt', _('yellow triangle')),
('rc', _('red cross')), ('rc', _('red cross')),
('ls', _('weak speed')), ('ls', _('weak speed')),
@ -32,6 +33,7 @@ TASK_TYPES = (
('cr', _('router setup')), ('cr', _('router setup')),
('co', _('configure onu')), ('co', _('configure onu')),
('fc', _('crimp cable')), ('fc', _('crimp cable')),
('ni', _('Internet crash')),
('ot', _('other')) ('ot', _('other'))
) )

2
taskapp/templates/taskapp/add_edit_task.html

@ -21,7 +21,7 @@
{% else %} {% else %}
<form role="form" action="{% url 'taskapp:add' %}" method="post" enctype="multipart/form-data"> <form role="form" action="{% url 'taskapp:add' %}" method="post" enctype="multipart/form-data">
{% endif %} {% 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"> <div class="form-group">
<label for="id_descr">{% trans 'Description' %}</label> <label for="id_descr">{% trans 'Description' %}</label>

23
taskapp/templates/taskapp/footer_btns.html

@ -1,11 +1,14 @@
{% load i18n %} {% load i18n %}
{% 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">
<span class="glyphicon glyphicon-baby-formula"></span> {% trans 'View all tasks' %}
</a>
{% endif %}
<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">
<span class="glyphicon glyphicon-baby-formula"></span> {% trans 'View all tasks' %}
</a>
{% endif %}
</div>

10
taskapp/templates/taskapp/tasklist.html

@ -6,13 +6,13 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th>#</th>
<th class="hidden-xs">#</th>
<th class="col-sm-3">{% trans 'Name' %}</th> <th class="col-sm-3">{% trans 'Name' %}</th>
<th class="col-sm-1">{% trans 'Address' %}</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-1">{% trans 'The nature of the damage' %}</th>
<th class="col-sm-4">{% trans 'Description' %}</th> <th class="col-sm-4">{% trans 'Description' %}</th>
<th class="col-sm-1">{% trans 'Task author' %}</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> <th class="col-sm-1">{% trans 'Actions' %}</th>
</tr> </tr>
</thead> </thead>
@ -33,11 +33,11 @@
{% endif %} {% endif %}
{% 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 %} {% 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><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 %} {% else %}
<td>{% trans 'User does not exist' %}</td> <td>{% trans 'User does not exist' %}</td>
<td>---</td> <td>---</td>
@ -52,7 +52,7 @@
{% trans 'Author does not exist' %} {% trans 'Author does not exist' %}
{% endif %} {% endif %}
</td> </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"> <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' %}"> <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"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th>#</th>
<th class="hidden-xs">#</th>
<th class="col-sm-3">{% trans 'Name' %}</th> <th class="col-sm-3">{% trans 'Name' %}</th>
<th class="col-sm-1">{% trans 'Address' %}</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-1">{% trans 'The nature of the damage' %}</th>
<th class="col-sm-3">{% trans 'Description' %}</th> <th class="col-sm-3">{% trans 'Description' %}</th>
<th class="col-sm-1">{% trans 'Task author' %}</th> <th class="col-sm-1">{% trans 'Task author' %}</th>
<th class="col-sm-1">{% trans 'Condition' %}</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> <th class="col-sm-1">{% trans 'Actions' %}</th>
</tr> </tr>
</thead> </thead>
@ -46,7 +46,7 @@
{% endif %} {% endif %}
{% 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 %} {% 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> <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 %} {% endif %}
</td> </td>
<td>{{ task.get_state_display }}</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"> <td class="btn-group btn-group-sm btn-group-justified">
{% if perms.taskapp.change_task %} {% if perms.taskapp.change_task %}

10
taskapp/templates/taskapp/tasklist_failed.html

@ -6,13 +6,13 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th>#</th>
<th class="hidden-xs">#</th>
<th class="col-sm-3">{% trans 'Name' %}</th> <th class="col-sm-3">{% trans 'Name' %}</th>
<th class="col-sm-1">{% trans 'Address' %}</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-1">{% trans 'The nature of the damage' %}</th>
<th class="col-sm-4">{% trans 'Description' %}</th> <th class="col-sm-4">{% trans 'Description' %}</th>
<th class="col-sm-1">{% trans 'Task author' %}</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> <th class="col-sm-1">{% trans 'Actions' %}</th>
</tr> </tr>
</thead> </thead>
@ -33,7 +33,7 @@
{% endif %} {% endif %}
{% 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 %} {% 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> <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>{{ task.descr }}</td>
<td><a href="{% url 'acc_app:other_profile' task.author.pk %}" data-toggle="tooltip" <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> 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"> <a href="{% url 'taskapp:finish' task.pk %}" class="btn btn-success" title="{% trans 'Complete' %}" data-toggle="tooltip">
<span class="glyphicon glyphicon-ok"></span> <span class="glyphicon glyphicon-ok"></span>
</a> </a>

11
taskapp/templates/taskapp/tasklist_finish.html

@ -6,13 +6,13 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th>#</th>
<th class="hidden-xs">#</th>
<th class="col-sm-3">{% trans 'Name' %}</th> <th class="col-sm-3">{% trans 'Name' %}</th>
<th class="col-sm-2">{% trans 'Address' %}</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-1">{% trans 'The nature of the damage' %}</th>
<th class="col-sm-4">{% trans 'Description' %}</th> <th class="col-sm-4">{% trans 'Description' %}</th>
<th class="col-sm-1">{% trans 'Task author' %}</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> <th>#</th>
</tr> </tr>
</thead> </thead>
@ -33,7 +33,7 @@
{% endif %} {% endif %}
{% 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 %} {% 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> <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.get_mode_display }}</td>
<td>{{ task.descr }}</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> <td>
{% if perms.taskapp.change_task %} {% if perms.taskapp.change_task %}

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

Loading…
Cancel
Save