Browse Source

make map functionality is more usable

devel
Dmitry 8 years ago
parent
commit
377ae2dcbc
  1. 12
      accounts_app/templates/accounts/ext.htm
  2. 14
      accounts_app/templates/accounts/settings/test.html
  3. 8
      bugs.txt
  4. 28
      devapp/forms.py
  5. 72
      devapp/migrations/0004_auto_20171103_0006.py
  6. 53
      devapp/models.py
  7. 84
      devapp/templates/devapp/add_dev.html
  8. 145
      devapp/templates/devapp/dev.html
  9. 24
      devapp/templates/devapp/devices.html
  10. 82
      devapp/templates/devapp/fix_dev_group.html
  11. 33
      devapp/templates/devapp/manage_ports/modal_add_edit_port.html
  12. 1
      devapp/urls.py
  13. 52
      devapp/views.py
  14. 12
      global_context_processors.py
  15. 7
      mapapp/forms.py
  16. 167
      mapapp/locale/ru/LC_MESSAGES/django.po
  17. 45
      mapapp/migrations/0002_auto_20171103_0006.py
  18. 15
      mapapp/models.py
  19. 70
      mapapp/templates/maps/add_device.html
  20. 138
      mapapp/templates/maps/dot.html
  21. 62
      mapapp/templates/maps/index.html
  22. 19
      mapapp/templates/maps/map_tooltip.html
  23. 20
      mapapp/templates/maps/modal_add_device.html
  24. 28
      mapapp/templates/maps/modal_add_dot.html
  25. 38
      mapapp/templates/maps/options.html
  26. 7
      mapapp/templates/maps/preload_devices_tmpl.html
  27. 124
      mapapp/templates/maps/ya_index.html
  28. 6
      mapapp/urls.py
  29. 149
      mapapp/views.py
  30. 5
      requirements.txt
  31. 48
      static/clientside/ISPlaylist.m3u
  32. 22
      static/css/custom.css
  33. BIN
      static/img/loading.gif
  34. 60
      static/js/my.js
  35. 20
      systemd_units/do_backup.sh
  36. 20
      systemd_units/webdav_backup.py
  37. 2
      taskapp/templates/taskapp/add_edit_task.html
  38. 8
      templates/403.html
  39. 12
      templates/403_for_modal.html
  40. 14
      templates/all_base.html
  41. 12
      templates/base.html

12
accounts_app/templates/accounts/ext.htm

@ -20,23 +20,23 @@
{% else %} {% else %}
<img alt="ava" src="/static/img/user_ava.gif"/> <img alt="ava" src="/static/img/user_ava.gif"/>
{% endif %} {% endif %}
<div class="caption btn-group btn-group-sm">
<div class="caption btn-group btn-group-sm btn-group-justified">
{% 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 %}

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>

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
! Проверить дату завершения услуги

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'
}) })
} }

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'),
),
]

53
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,34 @@ class DeviceDBException(Exception):
pass pass
class DeviceManager(models.Manager):
@staticmethod
def wrap_monitoring_info(devices_queryset):
nag_url = getattr(settings, 'NAGIOS_URL')
if nag_url:
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):
return
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)
objects = DeviceManager()
class Meta: class Meta:
db_table = 'dev' db_table = 'dev'
@ -44,8 +63,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]
@ -65,9 +86,9 @@ class Device(models.Model):
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)

84
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>
{% 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-globe"></span></span>
{{ form.ip_address }}{{ form.ip_address.errors }}
</div>
</div>
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.mac_addr addon_before=ic %}
<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>
{% bootstrap_icon 'comment' as ic %}
{% bootstrap_field form.comment addon_before=ic %}
<div class="form-group">
<label for="id_comment">{% trans 'Comment' %}</label>
{% bootstrap_icon 'hdd' as ic %}
{% bootstrap_field form.devtype addon_before=ic %}
<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>
<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 'lock' as ic %}
{% bootstrap_field form.man_passw addon_before=ic %}
<div class="form-group">
<label for="id_man_passw">{% trans 'SNMP password' %}</label>
<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>
<div class="form-group">
<label for="id_map_dot">{% trans 'Map point' %}</label>
<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,11 @@
</div> </div>
</div> </div>
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">
<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>

145
devapp/templates/devapp/dev.html

@ -1,114 +1,67 @@
{% 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="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>
<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-hdd"></span></span>
{{ form.devtype }}{{ form.devtype.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_man_passw">{% trans 'SNMP password' %}</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-lock"></span></span>
{{ form.man_passw }}{{ form.man_passw.errors }}
</div>
</div>
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.mac_addr addon_before=ic %}
<div class="form-group">
<label for="id_map_dot">{% trans 'Map point' %}</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-map-marker"></span></span>
{{ form.map_dot }}{{ form.devtype.errors }}
</div>
</div>
{% bootstrap_icon 'hdd' as ic %}
{% bootstrap_field form.devtype addon_before=ic %}
<div class="form-group">
<label for="id_user_group">{% trans 'User group' %}</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-subscript"></span></span>
{{ form.user_group }}{{ form.user_group.errors }}
</div>
</div>
{% bootstrap_icon 'subscript' as ic %}
{% bootstrap_field form.user_group addon_before=ic %}
<div class="form-group">
<label for="id_parent_dev">{% trans 'Parent device' %}</label>
<div class="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">
<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' %}
<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> </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>
</form>
</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>
</form>
</div> </div>
</div>
{% endblock %} {% endblock %}

24
devapp/templates/devapp/devices.html

@ -15,21 +15,23 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th>#</th>
<th> <th>
<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>
<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>{% trans 'Mac address' %}</th>
<th width="250">
<a href="{% url 'devapp:devs' group.pk %}?order_by=devtype&dir={{ dir|default:"down" }}">
<th>{% trans 'Monitoring output' %}</th>
<th>
<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 %}
@ -42,9 +44,17 @@
{% 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.current_state == '0' %}
<span class="glyphicon glyphicon-ok-circle text-success"></span>
{% else %}
<span class="glyphicon glyphicon-exclamation-sign text-danger"></span>
{% 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>{{ dev.mon.plugin_output }}</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-sm">
{% if can_del_dev %} {% if can_del_dev %}
@ -61,7 +71,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 +79,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
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 %}

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>

1
devapp/urls.py

@ -17,5 +17,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)
] ]

52
devapp/views.py

@ -25,8 +25,7 @@ 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')
devs = Device.objects.filter(user_group=grp).select_related('user_group').only('comment', 'mac_addr', 'devtype', 'user_group', 'pk', 'ip_address')
# фильтр # фильтр
dr, field = order_helper(request) dr, field = order_helper(request)
@ -36,7 +35,7 @@ def devices(request, grp):
devs = pag_mn(request, devs) devs = pag_mn(request, devs)
return render(request, 'devapp/devices.html', { return render(request, 'devapp/devices.html', {
'devices': devs,
'devices': Device.objects.wrap_monitoring_info(devs),
'dir': dr, 'dir': dr,
'order_by': request.GET.get('order_by'), 'order_by': request.GET.get('order_by'),
'group': group 'group': group
@ -95,10 +94,16 @@ def dev(request, grp, devid=0):
if frm.is_valid(): if frm.is_valid():
ndev = frm.save() ndev = frm.save()
messages.success(request, _('Device info has been saved')) messages.success(request, _('Device info has been saved'))
return redirect('devapp:edit', grp, ndev.pk)
return redirect('devapp:edit', ndev.user_group.pk, ndev.pk)
else: else:
try: try:
already_dev = Device.objects.get(mac_addr=request.POST.get('mac_addr')) 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: except Device.DoesNotExist:
pass pass
messages.error(request, _('Form is invalid, check fields and try again')) messages.error(request, _('Form is invalid, check fields and try again'))
@ -125,7 +130,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 +143,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 +182,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'),
@ -306,6 +311,11 @@ def devview(request, did):
ports = None ports = None
uptime = 0 uptime = 0
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):
@ -354,6 +364,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 +390,29 @@ 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
})

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

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

124
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,92 @@
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;
}
#yapanel{
position: absolute;
right: 12px;
top: 5px;
}
.list-group-item{
padding: 3px 15px;
} }
</style> </style>
<div class="panel panel-default hidden-sm hidden-xs" id="yapanel">
<div class="panel-heading">
<h4 class="modal-title">{% trans 'Layers' %}</h4>
</div>
<div class="list-group">
{% for grp in abon_groups %}
<button type="button" data-href="{% url 'mapapp:resolve_dots_by_group' grp.pk %}" class="list-group-item mapbtns">{{ grp.title }}</button>
{% endfor %}
</div>
<div class="panel-footer">
<a href="#" onclick="showAll();" class="btn btn-default">
<span class="glyphicon glyphicon-eye-open"></span> {% trans 'Show all' %}
</a>
</div>
</div>
{% endblock %} {% 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
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

22
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,22 @@ 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;
}

BIN
static/img/loading.gif

After

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

60
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,61 @@ $(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);
$(document).ready(function () { $(document).ready(function () {
@ -189,4 +245,6 @@ $(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip({container:'body'}); $('[data-toggle="tooltip"]').tooltip({container:'body'});
$('.btn_ajloader').ajloader({'dst_block': '#id_block_devices'});
}); });

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))

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>

8
templates/403.html

@ -1,8 +1,8 @@
<!DOCTYPE html>
<!DOCTYPE html>{% load i18n %}
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head lang="ru"> <head lang="ru">
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>В доступе отказано</title>
<title>{% trans 'Permission denied' %}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
@ -36,8 +36,8 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-6 col-md-offset-3"> <div class="col-md-6 col-md-offset-3">
<div align="center"><h1>403 - Нет доступа</h1></div>
<div align="center"><p>У вас нет прав для выполнения действий на этой странице</p></div>
<div align="center"><h1>{% trans '403 - Permission denied' %}</h1></div>
<div align="center"><p>{% trans 'You have no permissions for that page' %}</p></div>
</div> </div>
</div> </div>
</div> </div>

12
templates/403_for_modal.html

@ -0,0 +1,12 @@
{% load i18n %}
<div class="modal-header warning">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-lock"></span>{% trans '403 - Permission denied' %}</h4>
</div>
<div class="modal-body">
<div align="center"><h1>{% trans '403 - Permission denied' %}</h1></div>
<div align="center"><p>{% trans 'You have no permissions for that page' %}</p></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button>
</div>

14
templates/all_base.html

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru">
<html lang="ru_RU">
<head> <head>
<title>InternetService - Админка</title> <title>InternetService - Админка</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
@ -14,6 +14,12 @@
</head> </head>
<body> <body>
<div id="loading">
<div class="gif">
<img src="/static/img/loading.gif"/>
</div>
</div>
<!-- Modal --> <!-- Modal -->
<div class="modal fade" id="modFrm" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal fade" id="modFrm" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
@ -39,8 +45,10 @@
{% url 'acc_app:other_profile' request.user.id as home_url %} {% url 'acc_app:other_profile' request.user.id as home_url %}
<li{% if home_url == request.path %} class="active"{% endif %}><a href="{{ home_url }}">Главная</a></li> <li{% if home_url == request.path %} class="active"{% endif %}><a href="{{ home_url }}">Главная</a></li>
{% url 'mapapp:home' as map_url %}
<li{% if map_url == request.path %} class="active"{% endif %}><a href="{{ map_url }}" target="_blank">Карта</a></li>
{% if request.user.is_superuser %}
{% url 'mapapp:home' as map_url %}
<li{% if map_url == request.path %} class="active"{% endif %}><a href="{{ map_url }}" target="_blank">Карта</a></li>
{% endif %}
<!--<li><a href="{ % url 'statistics:home' %}" target="_blank">График траффика</a></li>---> <!--<li><a href="{ % url 'statistics:home' %}" target="_blank">График траффика</a></li>--->

12
templates/base.html

@ -47,11 +47,13 @@
<span class="glyphicon glyphicon-usd"></span> тарифы <span class="glyphicon glyphicon-usd"></span> тарифы
</a></li> </a></li>
{% url 'mapapp:options' as mapapp_ops %}
<li{% if mapapp_ops in request.path %} class="active"{% endif %}>
<a href="{{ mapapp_ops }}">
<span class="glyphicon glyphicon-map-marker"></span> Настройки карты
</a></li>
{% if request.user.is_superuser %}
{% url 'mapapp:options' as mapapp_ops %}
<li{% if mapapp_ops in request.path %} class="active"{% endif %}>
<a href="{{ mapapp_ops }}">
<span class="glyphicon glyphicon-map-marker"></span> Настройки карты
</a></li>
{% endif %}
{% url 'msg_app:home' as privmsg_home %} {% url 'msg_app:home' as privmsg_home %}
<li{% if privmsg_home in request.path %} class="active"{% endif %}> <li{% if privmsg_home in request.path %} class="active"{% endif %}>

Loading…
Cancel
Save