From 1dddd98741e1ab95e5293c388fab0ec8fb57145c Mon Sep 17 00:00:00 2001 From: halcyon <864072399@qq.com> Date: Tue, 24 Mar 2015 17:04:25 +0800 Subject: [PATCH 1/6] bug --- jumpserver.conf | 8 ++++---- templates/jasset/group_add.html | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/jumpserver.conf b/jumpserver.conf index a24c2190a..8de09fc8b 100644 --- a/jumpserver.conf +++ b/jumpserver.conf @@ -9,10 +9,10 @@ database = jumpserver [ldap] ldap_enable = 1 -host_url = ldap://127.0.0.1:389 -base_dn = dc=jumpserver, dc=org -root_dn = cn=admin,dc=jumpserver,dc=org -root_pw = secret234 +host_url = ldap://192.168.8.230:389 +base_dn = dc=fengxing, dc=com +root_dn = cn=admin,dc=fengxing,dc=com +root_pw = 123456 [websocket] web_socket_host = 127.0.0.1:3000 diff --git a/templates/jasset/group_add.html b/templates/jasset/group_add.html index 837c95f78..d3defd781 100644 --- a/templates/jasset/group_add.html +++ b/templates/jasset/group_add.html @@ -54,9 +54,9 @@
-
- - +
+ +
From e3b2be0261f3bc44e483e3e656b04b9f7840f2db Mon Sep 17 00:00:00 2001 From: halcyon <864072399@qq.com> Date: Tue, 24 Mar 2015 18:34:00 +0800 Subject: [PATCH 2/6] bugs --- jasset/urls.py | 1 - jasset/views.py | 19 ++++++++++++++----- jumpserver/api.py | 9 +++++++-- templates/jasset/host_add_multi.html | 2 +- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/jasset/urls.py b/jasset/urls.py index ae8b89612..c37330264 100644 --- a/jasset/urls.py +++ b/jasset/urls.py @@ -3,7 +3,6 @@ from django.conf.urls import patterns, include, url from jasset.views import * urlpatterns = patterns('', - url(r'^$', index), url(r'^host_add/$', add_host), url(r"^host_add_multi/$", add_host_multi), url(r'^host_list/$', list_host), diff --git a/jasset/views.py b/jasset/views.py index 79c415cb3..f3552ed82 100644 --- a/jasset/views.py +++ b/jasset/views.py @@ -8,19 +8,19 @@ from django.template import RequestContext from django.shortcuts import render_to_response from models import IDC, Asset, BisGroup -from juser.models import UserGroup, DEPT +from juser.models import UserGroup, DEPT, User from connect import PyCrypt, KEY from jlog.models import Log from jumpserver.views import jasset_host_edit, pages -from jumpserver.api import asset_perm_api -from jumpserver.api import user_perm_group_api, require_login, require_super_user, \ +from jumpserver.api import asset_perm_api, validate +from jumpserver.api import require_login, require_super_user, \ require_admin, is_group_admin, is_super_user, is_common_user, get_user_dept cryptor = PyCrypt(KEY) -def index(request): - return render_to_response('jasset/jasset.html', ) +class RaiseError(Exception): + pass def f_add_host(ip, port, idc, jtype, group, dept, active, comment, username='', password=''): @@ -70,6 +70,7 @@ def add_host(request): user_id = request.session.get('user_id') edept = DEPT.objects.get(id=dept_id) egroup = edept.bisgroup_set.all() + if request.method == 'POST': j_ip = request.POST.get('j_ip') j_idc = request.POST.get('j_idc') @@ -80,6 +81,14 @@ def add_host(request): j_comment = request.POST.get('j_comment') j_dept = request.POST.getlist('j_dept') + try: + if is_group_admin(request) and not validate(request, asset_group=j_group): + print validate(request, asset_group=j_group), 'hello' + emg = u'滚Y' + raise RaiseError(emg) + except RaiseError: + pass + if Asset.objects.filter(ip=str(j_ip)): emg = u'该IP %s 已存在!' % j_ip return render_to_response('jasset/host_add.html', locals(), context_instance=RequestContext(request)) diff --git a/jumpserver/api.py b/jumpserver/api.py index 55f31945c..131dc78d1 100644 --- a/jumpserver/api.py +++ b/jumpserver/api.py @@ -321,10 +321,15 @@ def validate(request, user_group=None, user=None, asset_group=None, asset=None): if asset_group: dept_asset_groups = dept.bisgroup_set.all() asset_groups = [] - for asset_group_id in asset_group: - asset_groups.extend(BisGroup.objects.filter(id=asset_group_id)) + for asset_group_name in asset_group: + asset_groups.extend(BisGroup.objects.filter(name=asset_group_name)) + + if len(asset_groups) == 0: + print 'hehe' + return False if not set(asset_groups).issubset(set(dept_asset_groups)): + print 'not in' return False if asset: diff --git a/templates/jasset/host_add_multi.html b/templates/jasset/host_add_multi.html index 4fd629071..5df6bc89a 100644 --- a/templates/jasset/host_add_multi.html +++ b/templates/jasset/host_add_multi.html @@ -44,7 +44,7 @@ {% endif %}

按照文本框内主机信息格式填写, 多台主机回车换行

-
+
From 408e4a54d8624095c6f6b0c04fb81af26c85aacc Mon Sep 17 00:00:00 2001 From: halcyon <864072399@qq.com> Date: Wed, 25 Mar 2015 19:02:14 +0800 Subject: [PATCH 3/6] bugs --- jasset/urls.py | 1 - jasset/views.py | 49 +++++++++++++++++++++------------ jumpserver/api.py | 24 +++++++++------- jumpserver/views.py | 13 +++++++-- templates/jasset/host_list.html | 2 +- 5 files changed, 57 insertions(+), 32 deletions(-) diff --git a/jasset/urls.py b/jasset/urls.py index c37330264..7e8912a67 100644 --- a/jasset/urls.py +++ b/jasset/urls.py @@ -22,5 +22,4 @@ urlpatterns = patterns('', url(r'^host_del/(\w+)/$', host_del), url(r'^host_edit/$', host_edit), url(r'^host_edit/batch/$', batch_host_edit), - url(r'^test/$', test), ) \ No newline at end of file diff --git a/jasset/views.py b/jasset/views.py index f3552ed82..64d5b848c 100644 --- a/jasset/views.py +++ b/jasset/views.py @@ -3,12 +3,13 @@ import ast from django.db.models import Q +from django.http import Http404 from django.http import HttpResponseRedirect from django.template import RequestContext from django.shortcuts import render_to_response from models import IDC, Asset, BisGroup -from juser.models import UserGroup, DEPT, User +from juser.models import UserGroup, DEPT from connect import PyCrypt, KEY from jlog.models import Log from jumpserver.views import jasset_host_edit, pages @@ -81,13 +82,9 @@ def add_host(request): j_comment = request.POST.get('j_comment') j_dept = request.POST.getlist('j_dept') - try: - if is_group_admin(request) and not validate(request, asset_group=j_group): - print validate(request, asset_group=j_group), 'hello' - emg = u'滚Y' - raise RaiseError(emg) - except RaiseError: - pass + if is_group_admin(request) and not validate(request, asset_group=j_group, edept=j_dept): + emg = u'添加失败,您无权操作!' + return render_to_response('jasset/host_add.html', locals(), context_instance=RequestContext(request)) if Asset.objects.filter(ip=str(j_ip)): emg = u'该IP %s 已存在!' % j_ip @@ -145,6 +142,7 @@ def batch_host_edit(request): j_id = "editable[" + str(i) + "][j_id]" j_ip = "editable[" + str(i) + "][j_ip]" j_port = "editable[" + str(i) + "][j_port]" + j_dept = "editable[" + str(i) + "][j_dept]" j_idc = "editable[" + str(i) + "][j_idc]" j_type = "editable[" + str(i) + "][j_type]" j_group = "editable[" + str(i) + "][j_group]" @@ -154,11 +152,18 @@ def batch_host_edit(request): j_id = request.POST.get(j_id).strip() j_ip = request.POST.get(j_ip).strip() j_port = request.POST.get(j_port).strip() + j_dept = request.POST.getlist(j_dept).strip() j_idc = request.POST.get(j_idc).strip() j_type = request.POST.get(j_type).strip() j_group = request.POST.getlist(j_group) j_active = request.POST.get(j_active).strip() j_comment = request.POST.get(j_comment).strip() + print j_dept, j_group + # + # if is_group_admin(request) and not validate(request, asset=[j_id]): + # emg = u'删除失败,您无权操作!' + # print 'hehe' + # return HttpResponseRedirect('/jasset/host_list/') if j_type == 'M': j_user = "editable[" + str(i) + "][j_user]" @@ -166,9 +171,9 @@ def batch_host_edit(request): j_user = request.POST.get(j_user).strip() password = request.POST.get(j_password).strip() j_password = cryptor.encrypt(password) - jasset_host_edit(j_id, j_ip, j_idc, j_port, j_type, j_group, j_active, j_comment, j_user, j_password) + jasset_host_edit(j_id, j_ip, j_idc, j_port, j_type, j_group, j_dept, j_active, j_comment, j_user, j_password) else: - jasset_host_edit(j_id, j_ip, j_idc, j_port, j_type, j_group, j_active, j_comment) + jasset_host_edit(j_id, j_ip, j_idc, j_port, j_type, j_group, j_dept, j_active, j_comment) return render_to_response('jasset/host_list.html') @@ -207,11 +212,17 @@ def host_del(request, offset): for i in range(int(len_list)): key = "id_list[" + str(i) + "]" jid = request.POST.get(key) + if is_group_admin(request) and not validate(request, asset=[jid]): + emg = u'删除失败,您无权操作!' + return HttpResponseRedirect('/jasset/host_list/') a = Asset.objects.get(id=jid).ip Asset.objects.filter(id=jid).delete() BisGroup.objects.filter(name=a).delete() else: jid = int(offset) + if is_group_admin(request) and not validate(request, asset=[jid]): + emg = u'删除失败,您无权操作!' + return HttpResponseRedirect('/jasset/host_list/') a = Asset.objects.get(id=jid).ip BisGroup.objects.filter(name=a).delete() Asset.objects.filter(id=jid).delete() @@ -243,8 +254,12 @@ def host_edit(request): j_active = request.POST.get('j_active') j_comment = request.POST.get('j_comment') j_idc = IDC.objects.get(name=j_idc) + + if is_group_admin(request) and not validate(request, asset_group=j_group, edept=j_dept): + emg = u'修改失败,您无权操作!' + return render_to_response('jasset/host_edit.html', locals(), context_instance=RequestContext(request)) + for group in j_group: - print group c = BisGroup.objects.get(name=group) groups.append(c) @@ -382,8 +397,12 @@ def add_group(request): j_dept = request.POST.get('j_dept') j_hosts = request.POST.getlist('j_hosts') j_comment = request.POST.get('j_comment') - j_dept = DEPT.objects.get(name=j_dept) + if is_group_admin(request) and not validate(request, asset=j_hosts, edept=[j_dept]): + emg = u'添加失败,您无权操作!' + return render_to_response('jasset/group_add.html', locals(), context_instance=RequestContext(request)) + + j_dept = DEPT.objects.get(name=j_dept) if BisGroup.objects.filter(name=j_group): emg = u'该主机组已存在!' return render_to_response('jasset/group_add.html', locals(), context_instance=RequestContext(request)) @@ -539,8 +558,4 @@ def host_search(request): comment__contains=keyword)).filter(dept=dept).distinct().order_by('ip') contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - return render_to_response('jasset/host_search.html', locals(), context_instance=RequestContext(request)) - - -def test(request): - return render_to_response('jasset/test.html', locals()) + return render_to_response('jasset/host_search.html', locals(), context_instance=RequestContext(request)) \ No newline at end of file diff --git a/jumpserver/api.py b/jumpserver/api.py index 131dc78d1..537843aed 100644 --- a/jumpserver/api.py +++ b/jumpserver/api.py @@ -299,8 +299,12 @@ def asset_perm_api(asset): return user_permed_list -def validate(request, user_group=None, user=None, asset_group=None, asset=None): +def validate(request, user_group=None, user=None, asset_group=None, asset=None, edept=None): dept = get_session_user_dept(request)[1] + if edept: + if dept.name != edept[0]: + return False + if user_group: dept_user_groups = dept.usergroup_set.all() user_groups = [] @@ -321,24 +325,24 @@ def validate(request, user_group=None, user=None, asset_group=None, asset=None): if asset_group: dept_asset_groups = dept.bisgroup_set.all() asset_groups = [] - for asset_group_name in asset_group: - asset_groups.extend(BisGroup.objects.filter(name=asset_group_name)) + for asset_group_name in dept_asset_groups: + asset_groups.extend(asset_group_name.name) if len(asset_groups) == 0: - print 'hehe' return False - if not set(asset_groups).issubset(set(dept_asset_groups)): - print 'not in' + if not set(asset_group).issubset(set(asset_groups)): return False if asset: dept_assets = dept.asset_set.all() - assets = [] - for asset_id in asset: - assets.extend(asset_id) + assets, eassets = [], [] + for asset_id in dept_assets: + eassets.append(int(asset_id.id)) + for i in asset: + assets.append(int(i)) - if not set(assets).issubset(dept_assets): + if not set(assets).issubset(eassets): return False return True \ No newline at end of file diff --git a/jumpserver/views.py b/jumpserver/views.py index a52e99881..7fe3417b0 100644 --- a/jumpserver/views.py +++ b/jumpserver/views.py @@ -6,6 +6,7 @@ from django.db.models import Count from django.shortcuts import render_to_response from django.template import RequestContext from jasset.models import IDC +from juser.models import DEPT from jumpserver.api import * @@ -84,13 +85,18 @@ def jasset_group_add(name, comment, jtype): smg = u'业务组%s添加成功' % name -def jasset_host_edit(j_id, j_ip, j_idc, j_port, j_type, j_group, j_active, j_comment, j_user='', j_password=''): - groups = [] +def jasset_host_edit(j_id, j_ip, j_idc, j_port, j_type, j_group, j_dept, j_active, j_comment, j_user='', j_password=''): + groups, depts = [], [] is_active = {u'是': '1', u'否': '2'} - login_types = {'LDAP': 'L', 'SSH_KEY': 'S', 'PASSWORD': 'P', 'MAP': 'M'} + login_types = {'LDAP': 'L', 'MAP': 'M'} for group in j_group[0].split(): c = BisGroup.objects.get(name=group.strip()) groups.append(c) + print j_dept + for d in j_dept[0].split(): + p = DEPT.objects.get(name=d.strip()) + depts.append(p) + j_type = login_types[j_type] j_idc = IDC.objects.get(name=j_idc) a = Asset.objects.get(id=j_id) @@ -112,6 +118,7 @@ def jasset_host_edit(j_id, j_ip, j_idc, j_port, j_type, j_group, j_active, j_com a.comment = j_comment a.save() a.bis_group = groups + a.dept = depts a.save() diff --git a/templates/jasset/host_list.html b/templates/jasset/host_list.html index 9bcfaa152..cb75b3c3d 100644 --- a/templates/jasset/host_list.html +++ b/templates/jasset/host_list.html @@ -69,7 +69,7 @@ {{ post.port }} {{ login_types|get_item:post.login_type }} {{ post.idc.name }} - {{ post.dept.all | group_str2 }} + {{ post.dept.all | group_str2 }} {{ post.bis_group.all | group_str2 }} {{ post.is_active|bool2str }} From 359f70b9f9f31fa963bded6da7079dee264c4065 Mon Sep 17 00:00:00 2001 From: halcyon <864072399@qq.com> Date: Thu, 26 Mar 2015 18:42:52 +0800 Subject: [PATCH 4/6] apply --- jasset/views.py | 11 ++- jlog/views.py | 26 ++++- jperm/models.py | 18 +++- jperm/urls.py | 2 + jperm/views.py | 59 ++++++++++- templates/jasset/host_list.html | 6 +- templates/jlog/log_offline.html | 8 +- templates/jlog/log_online.html | 12 ++- templates/jlog/user_history.html | 163 +++++++++++++++++++++++++++++++ templates/jperm/perm_apply.html | 127 ++++++++++++++++++++++++ templates/jperm/perm_log.html | 93 ++++++++++++++++++ templates/nav.html | 16 +-- 12 files changed, 518 insertions(+), 23 deletions(-) create mode 100644 templates/jlog/user_history.html create mode 100644 templates/jperm/perm_apply.html create mode 100644 templates/jperm/perm_log.html diff --git a/jasset/views.py b/jasset/views.py index 64d5b848c..a33c6cfa9 100644 --- a/jasset/views.py +++ b/jasset/views.py @@ -13,9 +13,7 @@ from juser.models import UserGroup, DEPT from connect import PyCrypt, KEY from jlog.models import Log from jumpserver.views import jasset_host_edit, pages -from jumpserver.api import asset_perm_api, validate -from jumpserver.api import require_login, require_super_user, \ - require_admin, is_group_admin, is_super_user, is_common_user, get_user_dept +from jumpserver.api import * cryptor = PyCrypt(KEY) @@ -201,7 +199,11 @@ def list_host(request): contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) elif is_common_user(request): - pass + user_id = request.session.get('user_id') + username = User.objects.get(id=user_id).name + posts = user_perm_asset_api(username) + contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) + print posts, username return render_to_response('jasset/host_list.html', locals(), context_instance=RequestContext(request)) @@ -212,6 +214,7 @@ def host_del(request, offset): for i in range(int(len_list)): key = "id_list[" + str(i) + "]" jid = request.POST.get(key) + print jid if is_group_admin(request) and not validate(request, asset=[jid]): emg = u'删除失败,您无权操作!' return HttpResponseRedirect('/jasset/host_list/') diff --git a/jlog/views.py b/jlog/views.py index 59a448b93..33d32b83c 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -45,7 +45,12 @@ def log_list_online(request): contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) elif is_common_user(request): - posts = Log.objects.filter(is_finished=0).filter(user=username).order_by('-start_time') + if keyword: + posts = Log.objects.filter(user=username).filter(Q(user__contains=keyword) | Q(host__contains=keyword))\ + .filter(is_finished=0).order_by('-start_time') + else: + posts = Log.objects.filter(is_finished=0).filter(user=username).order_by('-start_time') + contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) return render_to_response('jlog/log_online.html', locals(), context_instance=RequestContext(request)) @@ -75,8 +80,12 @@ def log_list_offline(request): contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) elif is_common_user(request): - posts = Log.objects.filter(is_finished=1).filter(user=username).order_by('-start_time') - + if keyword: + posts = Log.objects.filter(user=username).filter(Q(user__contains=keyword) | Q(host__contains=keyword))\ + .filter(is_finished=1).order_by('-start_time') + else: + posts = Log.objects.filter(is_finished=1).filter(user=username).order_by('-start_time') + contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) return render_to_response('jlog/log_offline.html', locals(), context_instance=RequestContext(request)) @@ -105,6 +114,8 @@ def log_search(request): env = request.GET.get('env') dept_id = get_user_dept(request) dept_name = DEPT.objects.get(id=dept_id).name + user_id = request.session.get('user_id') + username = User.objects.get(id=user_id).username if is_super_user(request): if env == 'online': posts = contact_list = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ @@ -122,4 +133,13 @@ def log_search(request): posts = contact_list = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ .filter(is_finished=1).filter(dept_name=dept_name).order_by('-start_time') contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) + + elif is_common_user(request): + if env == 'online': + posts = contact_list = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ + .filter(is_finished=0).filter(user=username).order_by('-start_time') + elif env == 'offline': + posts = contact_list = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ + .filter(is_finished=1).filter(user=username).order_by('-start_time') + contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) return render_to_response('jlog/log_search.html', locals(), context_instance=RequestContext(request)) diff --git a/jperm/models.py b/jperm/models.py index 624df9743..6cfcceb76 100644 --- a/jperm/models.py +++ b/jperm/models.py @@ -1,3 +1,5 @@ +import datetime + from django.db import models from juser.models import UserGroup, DEPT from jasset.models import Asset, BisGroup @@ -30,4 +32,18 @@ class SudoPerm(models.Model): comment = models.CharField(max_length=30, null=True, blank=True) def __unicode__(self): - return self.name \ No newline at end of file + return self.name + + +class Apply(models.Model): + applyer = models.CharField(max_length=20) + approver = models.CharField(max_length=20) + dept = models.CharField(max_length=20) + bisgroup = models.CharField(max_length=500) + asset = models.CharField(max_length=500) + comment = models.TextField(blank=True, null=True) + date_add = models.DateTimeField(default=datetime.datetime.now(), null=True) + date_end = models.DateTimeField(null=True) + + def __unicode__(self): + return self.applyer diff --git a/jperm/urls.py b/jperm/urls.py index 624f0bcca..9a840afe4 100644 --- a/jperm/urls.py +++ b/jperm/urls.py @@ -24,4 +24,6 @@ urlpatterns = patterns('jperm.views', (r'^cmd_list/$', 'cmd_list'), (r'^cmd_del/$', 'cmd_del'), (r'^cmd_edit/$', 'cmd_edit'), + (r'^apply/$', 'perm_apply'), + (r'^apply/online/$', 'perm_apply_log'), ) diff --git a/jperm/views.py b/jperm/views.py index 99a39f33b..0308af278 100644 --- a/jperm/views.py +++ b/jperm/views.py @@ -5,7 +5,7 @@ from django.http import HttpResponseRedirect, HttpResponse from django.template import RequestContext from juser.models import User, UserGroup, DEPT from jasset.models import Asset, BisGroup -from jperm.models import Perm, SudoPerm, CmdGroup +from jperm.models import Perm, SudoPerm, CmdGroup, Apply from django.core.paginator import Paginator, EmptyPage, InvalidPage from django.db.models import Q from jumpserver.views import LDAP_ENABLE, ldap_conn, CONF, page_list_return, pages @@ -525,3 +525,60 @@ def cmd_del(request): if cmd_group: cmd_group[0].delete() return HttpResponseRedirect('/jperm/cmd_list/') + + +@require_login +def perm_apply(request): + header_title, path1, path2 = u'主机权限申请', u'权限管理', u'申请主机' + user_id = request.session.get('user_id') + username = User.objects.get(id=user_id).username + dept_id = get_user_dept(request) + deptname = DEPT.objects.get(id=dept_id).name + dept = DEPT.objects.get(id=dept_id) + posts = Asset.objects.filter(dept=dept) + egroup = dept.bisgroup_set.all() + + if request.method == 'POST': + applyer = request.POST.get('applyer') + dept = request.POST.get('dept') + group = request.POST.getlist('group') + hosts = request.POST.getlist('hosts') + comment = request.POST.get('comment') + + Apply.objects.create(applyer=applyer, dept=dept, bisgroup=group, asset=hosts, comment=comment) + print applyer, dept, group, hosts, comment + return render_to_response('jperm/perm_apply.html', locals(), context_instance=RequestContext(request)) + return render_to_response('jperm/perm_apply.html', locals(), context_instance=RequestContext(request)) + + +def perm_apply_log(request): + header_title, path1, path2 = u'权限申请记录', u'权限管理', u'申请记录' + keyword = request.GET.get('keyword') + dept_id = get_user_dept(request) + dept_name = DEPT.objects.get(id=dept_id).name + user_id = request.session.get('user_id') + username = User.objects.get(id=user_id).username + if is_super_user(request): + if keyword: + posts = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ + .filter(is_finished=1).order_by('-start_time') + else: + posts = Log.objects.filter(is_finished=1).order_by('-start_time') + contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) + + elif is_group_admin(request): + if keyword: + posts = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ + .filter(is_finished=1).filter(dept_name=dept_name).order_by('-start_time') + else: + posts = Log.objects.filter(is_finished=1).filter(dept_name=dept_name).order_by('-start_time') + contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) + + elif is_common_user(request): + if keyword: + posts = Apply.objects.filter(applyer=username).filter(Q(applyer__contains=keyword) | Q(asset__contains=keyword))\ + .order_by('-date_add') + else: + posts = Apply.objects.filter(applyer=username).order_by('-date_add') + contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) + return render_to_response('jperm/perm_log.html', locals(), context_instance=RequestContext(request)) diff --git a/templates/jasset/host_list.html b/templates/jasset/host_list.html index cb75b3c3d..c57546abf 100644 --- a/templates/jasset/host_list.html +++ b/templates/jasset/host_list.html @@ -76,8 +76,10 @@ {{ post.comment }} 详情 - 编辑 - 删除 + {% ifnotequal session_role_id 0 %} + 编辑 + 删除 + {% endifnotequal %} {% endfor %} diff --git a/templates/jlog/log_offline.html b/templates/jlog/log_offline.html index 970f8ac6d..c859024d0 100644 --- a/templates/jlog/log_offline.html +++ b/templates/jlog/log_offline.html @@ -80,7 +80,9 @@ 所属部门 登录主机 来源IP - 命令统计 + {% ifnotequal session_role_id 0 %} + 命令统计 + {% endifnotequal %} 登录时间 结束时间 @@ -93,7 +95,9 @@ {{ post.dept_name }} {{ post.host }} {{ post.remote_ip }} - 命令统计 + {% ifnotequal session_role_id 0 %} + 命令统计 + {% endifnotequal %} {{ post.start_time|date:"Y-m-d H:i:s"}} {{ post.end_time|date:"Y-m-d H:i:s" }} diff --git a/templates/jlog/log_online.html b/templates/jlog/log_online.html index 6945e60b3..c63408ac2 100644 --- a/templates/jlog/log_online.html +++ b/templates/jlog/log_online.html @@ -80,8 +80,10 @@ 所属部门 登录主机 来源IP - 实时监控 - 阻断 + {% ifnotequal session_role_id 0 %} + 实时监控 + 阻断 + {% endifnotequal %} 登录时间 @@ -93,8 +95,10 @@ {{ post.dept_name }} {{ post.host }} {{ post.remote_ip }} - 监控 - + {% ifnotequal session_role_id 0 %} + 监控 + + {% endifnotequal %} {{ post.start_time|date:"Y-m-d H:i:s" }} {% endfor %} diff --git a/templates/jlog/user_history.html b/templates/jlog/user_history.html new file mode 100644 index 000000000..4e166b52e --- /dev/null +++ b/templates/jlog/user_history.html @@ -0,0 +1,163 @@ +{% extends 'base.html' %} +{% block content %} +{% include 'nav_cat_bar.html' %} + + +
+
+
+
+
+
用户{{ username }}日志详细信息列表
+ +
+ +
+
+ +
+
+
+ + + + + + + + + + + + + + + {% for post in contacts.object_list %} + + + + + + + + + + {% endfor %} + +
用户名 所属部门 登录主机 来源IP 登录时间 结束时间
{{ post.user }} {{ post.dept_name }} {{ post.host }} {{ post.remote_ip }} {{ post.start_time|date:"Y-m-d H:i:s"}} {{ post.end_time|date:"Y-m-d H:i:s" }}
+
+
+
+ {% include 'paginator.html' %} +
+
+
+
+
+
+
+ + +{##} + +{% endblock %} \ No newline at end of file diff --git a/templates/jperm/perm_apply.html b/templates/jperm/perm_apply.html new file mode 100644 index 000000000..181e176cf --- /dev/null +++ b/templates/jperm/perm_apply.html @@ -0,0 +1,127 @@ +{% extends 'base.html' %} +{% block content %} +{% include 'nav_cat_bar.html' %} + +
+
+
+
+
+
填写要申请主机的基本信息
+ +
+ +
+ {% if emg %} +
{{ emg }}
+ {% endif %} + {% if smg %} +
{{ smg }}
+ {% endif %} +
+ {% csrf_token %} +
+
+
+ +
+
+
+
+ + +
+
+ +
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+ + +
+
+
+

已选中主机

+
+ +
+
+
+
+ + +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+ +{% endblock content %} \ No newline at end of file diff --git a/templates/jperm/perm_log.html b/templates/jperm/perm_log.html new file mode 100644 index 000000000..3b71002e6 --- /dev/null +++ b/templates/jperm/perm_log.html @@ -0,0 +1,93 @@ +{% extends 'base.html' %} +{% block content %} +{% include 'nav_cat_bar.html' %} +
+
+
+
+
+
用户权限申请详细信息列表
+ +
+ +
+
+ +
+
+
+ + + + + + + + + + + + + + + {% for post in contacts.object_list %} + + + + + + + + + + + + + {% endfor %} + +
申请人 所属部门 申请主机组 申请主机 申请时间 批准时间 备注
{{ post.applyer }} {{ post.dept }} {{ post.bisgroup }} {{ post.asset }} {{ post.date_add|date:"Y-m-d H:i:s"}} {{ post.date_end|date:"Y-m-d H:i:s" }} {{ post.comment }}
+
+
+
+ {% include 'paginator.html' %} +
+
+
+
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/templates/nav.html b/templates/nav.html index b3aecd104..93cb66523 100644 --- a/templates/nav.html +++ b/templates/nav.html @@ -137,14 +137,18 @@
  • 个人信息
  • -
  • - 查看主机 +
  • + 查看主机
  • -
  • - 申请主机 +
  • + 权限申请 +
  • -
  • - 登录历史 +
  • + 登录历史
  • From e1b9134d7f9c8e42e5c680fd6abbde97314c49c6 Mon Sep 17 00:00:00 2001 From: halcyon <864072399@qq.com> Date: Wed, 1 Apr 2015 18:58:10 +0800 Subject: [PATCH 5/6] 0402 --- jperm/models.py | 1 + jperm/urls.py | 2 +- jperm/views.py | 78 +++++++++++++++------- jumpserver.conf | 6 ++ jumpserver/settings.py | 7 ++ jumpserver/templatetags/mytags.py | 6 ++ templates/jperm/perm_log_offline.html | 96 +++++++++++++++++++++++++++ templates/jperm/perm_log_online.html | 93 ++++++++++++++++++++++++++ templates/nav.html | 4 +- 9 files changed, 266 insertions(+), 27 deletions(-) create mode 100644 templates/jperm/perm_log_offline.html create mode 100644 templates/jperm/perm_log_online.html diff --git a/jperm/models.py b/jperm/models.py index 6cfcceb76..a865d005a 100644 --- a/jperm/models.py +++ b/jperm/models.py @@ -42,6 +42,7 @@ class Apply(models.Model): bisgroup = models.CharField(max_length=500) asset = models.CharField(max_length=500) comment = models.TextField(blank=True, null=True) + status = models.IntegerField(max_length=2) date_add = models.DateTimeField(default=datetime.datetime.now(), null=True) date_end = models.DateTimeField(null=True) diff --git a/jperm/urls.py b/jperm/urls.py index 9a840afe4..c15807260 100644 --- a/jperm/urls.py +++ b/jperm/urls.py @@ -25,5 +25,5 @@ urlpatterns = patterns('jperm.views', (r'^cmd_del/$', 'cmd_del'), (r'^cmd_edit/$', 'cmd_edit'), (r'^apply/$', 'perm_apply'), - (r'^apply/online/$', 'perm_apply_log'), + (r'^apply_show/(\w+)/$', 'perm_apply_log'), ) diff --git a/jperm/views.py b/jperm/views.py index 0308af278..e91c8b670 100644 --- a/jperm/views.py +++ b/jperm/views.py @@ -1,5 +1,10 @@ # coding: utf-8 +import ast +import datetime + + +from django.core.mail import send_mail from django.shortcuts import render_to_response from django.http import HttpResponseRedirect, HttpResponse from django.template import RequestContext @@ -537,6 +542,7 @@ def perm_apply(request): dept = DEPT.objects.get(id=dept_id) posts = Asset.objects.filter(dept=dept) egroup = dept.bisgroup_set.all() + mail_address = 'wangyong@fun.tv' if request.method == 'POST': applyer = request.POST.get('applyer') @@ -544,41 +550,65 @@ def perm_apply(request): group = request.POST.getlist('group') hosts = request.POST.getlist('hosts') comment = request.POST.get('comment') - - Apply.objects.create(applyer=applyer, dept=dept, bisgroup=group, asset=hosts, comment=comment) print applyer, dept, group, hosts, comment + url = 'http://127.0.0.1:8000/jperm/apply/exec/?id=' + mail_title = '权限申请' + mail_msg = """ + Hi,%s: + 有新的权限申请, 详情如下: + 申请人: %s + 申请主机组: %s + 申请的主机: %s + 申请时间: %s + 申请说明: %s + 请及时审批, 审批完成后点击以下链接,告知各位。 + %s + """ % (u'123', applyer, group, hosts, datetime.datetime.now(), comment, url) + send_mail(mail_title, mail_msg, 'jkfunshion@fun.tv', [mail_address], fail_silently=False) + smg = "提交成功,已转交运维上线。" + + Apply.objects.create(applyer=applyer, dept=dept, bisgroup=group, asset=hosts, status=0, comment=comment) return render_to_response('jperm/perm_apply.html', locals(), context_instance=RequestContext(request)) return render_to_response('jperm/perm_apply.html', locals(), context_instance=RequestContext(request)) -def perm_apply_log(request): +def get_apply_posts(request, status, username, dept_name, keyword=None): + if is_super_user(request): + if keyword: + posts = Apply.objects.filter(Q(applyer__contains=keyword) | Q(approver__contains=keyword)) \ + .filter(status=status).order_by('-date_add') + else: + posts = Apply.objects.filter(status=status).order_by('-date_add') + + elif is_group_admin(request): + if keyword: + posts = Apply.objects.filter(Q(applyer__contains=keyword) | Q(approver__contains=keyword)) \ + .filter(status=status).filter(dept=dept_name).order_by('-date_add') + else: + posts = Log.objects.filter(status=status).filter(dept=dept_name).order_by('-date_add') + + elif is_common_user(request): + if keyword: + posts = Apply.objects.filter(applyer=username).filter(status=status).filter(Q(applyer__contains=keyword) | + Q(asset__contains=keyword)).order_by('-date_add') + else: + posts = Apply.objects.filter(applyer=username).filter(status=status).order_by('-date_add') + return posts + + +def perm_apply_log(request, offset): header_title, path1, path2 = u'权限申请记录', u'权限管理', u'申请记录' keyword = request.GET.get('keyword') dept_id = get_user_dept(request) dept_name = DEPT.objects.get(id=dept_id).name user_id = request.session.get('user_id') username = User.objects.get(id=user_id).username - if is_super_user(request): - if keyword: - posts = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ - .filter(is_finished=1).order_by('-start_time') - else: - posts = Log.objects.filter(is_finished=1).order_by('-start_time') + if offset == 'online': + posts = get_apply_posts(request, 0, username, dept_name, keyword) contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) + return render_to_response('jperm/perm_log_online.html', locals(), context_instance=RequestContext(request)) - elif is_group_admin(request): - if keyword: - posts = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ - .filter(is_finished=1).filter(dept_name=dept_name).order_by('-start_time') - else: - posts = Log.objects.filter(is_finished=1).filter(dept_name=dept_name).order_by('-start_time') + elif offset == 'offline': + posts = get_apply_posts(request, 1, username, dept_name, keyword) contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - - elif is_common_user(request): - if keyword: - posts = Apply.objects.filter(applyer=username).filter(Q(applyer__contains=keyword) | Q(asset__contains=keyword))\ - .order_by('-date_add') - else: - posts = Apply.objects.filter(applyer=username).order_by('-date_add') - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - return render_to_response('jperm/perm_log.html', locals(), context_instance=RequestContext(request)) + return render_to_response('jperm/perm_log_offline.html', locals(), context_instance=RequestContext(request)) diff --git a/jumpserver.conf b/jumpserver.conf index 8de09fc8b..95250fbea 100644 --- a/jumpserver.conf +++ b/jumpserver.conf @@ -20,3 +20,9 @@ web_socket_host = 127.0.0.1:3000 [web] key = 88aaaf7ffe3c6c04 +[mail] +email_host = 'mail.funshion.com' +email_port = '25' +email_host_user = 'jkfunshion' +email_host_password = 'jkmail%' +email_use_tls = False \ No newline at end of file diff --git a/jumpserver/settings.py b/jumpserver/settings.py index 489577dc1..db905dfde 100644 --- a/jumpserver/settings.py +++ b/jumpserver/settings.py @@ -121,3 +121,10 @@ USE_TZ = False STATIC_URL = '/static/' +# mail config +EMAIL_HOST = config.get('mail', 'email_host') +EMAIL_PORT = config.get('mail', 'email_port') +EMAIL_HOST_USER = config.get('mail', 'email_host_user') +EMAIL_HOST_PASSWORD = config.get('mail', 'email_host_password') +EMAIL_USE_TLS = config.get('mail', 'email_use_tls') + diff --git a/jumpserver/templatetags/mytags.py b/jumpserver/templatetags/mytags.py index 1f264f4e4..b414dfc3d 100644 --- a/jumpserver/templatetags/mytags.py +++ b/jumpserver/templatetags/mytags.py @@ -1,6 +1,7 @@ # coding: utf-8 import re +import ast import time from django import template @@ -160,6 +161,11 @@ def group_type_to_str(type_name): return group_types.get(type_name) +@register.filter(name='ast_to_list') +def ast_to_list(lis): + return ast.literal_eval(lis) + + # @register.filter(name='perm_asset_count') # def perm_asset_count(user_id): # return len(perm_user_asset(user_id)) diff --git a/templates/jperm/perm_log_offline.html b/templates/jperm/perm_log_offline.html new file mode 100644 index 000000000..6839d32ab --- /dev/null +++ b/templates/jperm/perm_log_offline.html @@ -0,0 +1,96 @@ +{% extends 'base.html' %} +{% load mytags %} +{% block content %} +{% include 'nav_cat_bar.html' %} +
    +
    +
    +
    +
    +
    用户权限申请详细信息列表
    + +
    + +
    +
    + +
    +
    +
    + + + + + + + + + + + + + + + + {% for post in contacts.object_list %} + + + + + + + + + + + + + + {% endfor %} + +
    申请人 所属部门 申请主机组 申请主机 批准人 申请时间 批准时间 备注
    {{ post.applyer }} {{ post.dept }} {% for i in post.bisgroup|ast_to_list %} {{ i }} {% endfor %} {% for i in post.asset|ast_to_list %} {{ i }} {% endfor %} {{ post.approver }} {{ post.date_add|date:"Y-m-d H:i:s"}} {{ post.date_end|date:"Y-m-d H:i:s" }} {{ post.comment }}
    +
    +
    +
    + {% include 'paginator.html' %} +
    +
    +
    +
    +
    +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/templates/jperm/perm_log_online.html b/templates/jperm/perm_log_online.html new file mode 100644 index 000000000..cae9b204e --- /dev/null +++ b/templates/jperm/perm_log_online.html @@ -0,0 +1,93 @@ +{% extends 'base.html' %} +{% block content %} +{% include 'nav_cat_bar.html' %} +
    +
    +
    +
    +
    +
    用户权限申请详细信息列表
    + +
    + +
    +
    + +
    +
    +
    + + + + + + + + + + + + + + + {% for post in contacts.object_list %} + + + + + + + + + + + + + {% endfor %} + +
    申请人 所属部门 申请主机组 申请主机 申请时间 批准时间 备注
    {{ post.applyer }} {{ post.dept }} {{ post.bisgroup }} {{ post.asset }} {{ post.date_add|date:"Y-m-d H:i:s"}} {{ post.date_end|date:"Y-m-d H:i:s" }} {{ post.comment }}
    +
    +
    +
    + {% include 'paginator.html' %} +
    +
    +
    +
    +
    +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/templates/nav.html b/templates/nav.html index 93cb66523..745c07d5e 100644 --- a/templates/nav.html +++ b/templates/nav.html @@ -143,8 +143,8 @@
  • 权限申请
  • From b35f1e61740b02a6aa1131ba61e7a514f41cbf59 Mon Sep 17 00:00:00 2001 From: halcyon <864072399@qq.com> Date: Thu, 2 Apr 2015 18:32:43 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E6=9D=83=E9=99=90=E7=94=B3=E8=AF=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jperm/models.py | 3 ++ jperm/urls.py | 1 + jperm/views.py | 45 ++++++++++++++++++++++------ jumpserver/settings.py | 13 ++++---- templates/jperm/perm_apply.html | 8 +++++ templates/jperm/perm_apply_exec.html | 40 +++++++++++++++++++++++++ 6 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 templates/jperm/perm_apply_exec.html diff --git a/jperm/models.py b/jperm/models.py index a865d005a..4c21013fd 100644 --- a/jperm/models.py +++ b/jperm/models.py @@ -1,5 +1,7 @@ import datetime +from uuidfield import UUIDField + from django.db import models from juser.models import UserGroup, DEPT from jasset.models import Asset, BisGroup @@ -36,6 +38,7 @@ class SudoPerm(models.Model): class Apply(models.Model): + uuid = UUIDField(auto=True) applyer = models.CharField(max_length=20) approver = models.CharField(max_length=20) dept = models.CharField(max_length=20) diff --git a/jperm/urls.py b/jperm/urls.py index c15807260..77d0ce5ea 100644 --- a/jperm/urls.py +++ b/jperm/urls.py @@ -26,4 +26,5 @@ urlpatterns = patterns('jperm.views', (r'^cmd_edit/$', 'cmd_edit'), (r'^apply/$', 'perm_apply'), (r'^apply_show/(\w+)/$', 'perm_apply_log'), + (r'^apply_exec/$', 'perm_apply_exec'), ) diff --git a/jperm/views.py b/jperm/views.py index e91c8b670..2a3c59279 100644 --- a/jperm/views.py +++ b/jperm/views.py @@ -1,6 +1,8 @@ # coding: utf-8 -import ast +import sys +reload(sys) +sys.setdefaultencoding('utf8') import datetime @@ -542,17 +544,25 @@ def perm_apply(request): dept = DEPT.objects.get(id=dept_id) posts = Asset.objects.filter(dept=dept) egroup = dept.bisgroup_set.all() - mail_address = 'wangyong@fun.tv' + dept_da = User.objects.filter(dept_id=dept_id, role='DA') if request.method == 'POST': applyer = request.POST.get('applyer') dept = request.POST.get('dept') + da = request.POST.get('da') group = request.POST.getlist('group') hosts = request.POST.getlist('hosts') comment = request.POST.get('comment') - print applyer, dept, group, hosts, comment - url = 'http://127.0.0.1:8000/jperm/apply/exec/?id=' - mail_title = '权限申请' + da = User.objects.get(id=da) + mail_address = da.email + mail_title = '%s - 权限申请' % username + # print da.username, applyer, group, hosts, datetime.datetime.now(), comment, url + group_lis = ', '.join(group) + hosts_lis = ', '.join(hosts) + time_now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + Apply.objects.create(applyer=applyer, dept=dept, bisgroup=group, asset=hosts, status=0, comment=comment) + uuid = Apply.objects.get(applyer=applyer, asset=hosts, comment=comment).uuid + url = "http://127.0.0.1:8000/jperm/apply_exec/?uuid=%s" % uuid mail_msg = """ Hi,%s: 有新的权限申请, 详情如下: @@ -563,15 +573,32 @@ def perm_apply(request): 申请说明: %s 请及时审批, 审批完成后点击以下链接,告知各位。 %s - """ % (u'123', applyer, group, hosts, datetime.datetime.now(), comment, url) - send_mail(mail_title, mail_msg, 'jkfunshion@fun.tv', [mail_address], fail_silently=False) - smg = "提交成功,已转交运维上线。" + """ % (da.username, applyer, group_lis, hosts_lis, time_now, comment, url) - Apply.objects.create(applyer=applyer, dept=dept, bisgroup=group, asset=hosts, status=0, comment=comment) + send_mail(mail_title, mail_msg, 'jkfunshion@fun.tv', [mail_address], fail_silently=False) + smg = "提交成功,已发邮件通知部门管理员。" return render_to_response('jperm/perm_apply.html', locals(), context_instance=RequestContext(request)) return render_to_response('jperm/perm_apply.html', locals(), context_instance=RequestContext(request)) +def perm_apply_exec(request): + uuid = request.GET.get('uuid') + p_apply = Apply.objects.filter(uuid=str(uuid)) + q_apply = Apply.objects.get(uuid=str(uuid)) + if p_apply: + user = User.objects.get(username=q_apply.applyer) + mail_address = user.email + time_now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + p_apply.update(status=1, date_end=time_now) + mail_title = '%s - 权限审批完成' % q_apply.applyer + mail_msg = """ + Hi,%s: + 您所申请的权限已由 %s 在 %s 审批完成, 请登录验证。 + """ % (q_apply.applyer, q_apply.approver, time_now) + send_mail(mail_title, mail_msg, 'jkfunshion@fun.tv', [mail_address], fail_silently=False) + return render_to_response('jperm/perm_apply_exec.html', locals(), context_instance=RequestContext(request)) + + def get_apply_posts(request, status, username, dept_name, keyword=None): if is_super_user(request): if keyword: diff --git a/jumpserver/settings.py b/jumpserver/settings.py index db905dfde..50b6ae0db 100644 --- a/jumpserver/settings.py +++ b/jumpserver/settings.py @@ -23,6 +23,13 @@ DB_USER = config.get('db', 'user') DB_PASSWORD = config.get('db', 'password') DB_DATABASE = config.get('db', 'database') +# mail config +EMAIL_HOST = 'mail.funshion.com' +EMAIL_PORT = '25' +EMAIL_HOST_USER = 'jkfunshion' +EMAIL_HOST_PASSWORD = 'jkmail%' +EMAIL_USE_TLS = False + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ @@ -121,10 +128,4 @@ USE_TZ = False STATIC_URL = '/static/' -# mail config -EMAIL_HOST = config.get('mail', 'email_host') -EMAIL_PORT = config.get('mail', 'email_port') -EMAIL_HOST_USER = config.get('mail', 'email_host_user') -EMAIL_HOST_PASSWORD = config.get('mail', 'email_host_password') -EMAIL_USE_TLS = config.get('mail', 'email_use_tls') diff --git a/templates/jperm/perm_apply.html b/templates/jperm/perm_apply.html index 181e176cf..52a01fb6c 100644 --- a/templates/jperm/perm_apply.html +++ b/templates/jperm/perm_apply.html @@ -45,6 +45,14 @@
  • +
    +
    +
    + {% for da in dept_da %} + + {% endfor %} +
    +
    diff --git a/templates/jperm/perm_apply_exec.html b/templates/jperm/perm_apply_exec.html new file mode 100644 index 000000000..c02be7aaf --- /dev/null +++ b/templates/jperm/perm_apply_exec.html @@ -0,0 +1,40 @@ +{% extends 'base.html' %} +{% block content %} +{% include 'nav_cat_bar.html' %} + +
    +
    +
    +
    +
    +
    填写要申请主机的基本信息
    + +
    + +
    +

    授权完成, 已邮件通知申请人, 五秒钟后关闭页面

    +
    +
    +
    +
    +
    + +{% endblock content %} \ No newline at end of file