mirror of https://github.com/jumpserver/jumpserver
fix push role bug
parent
2829a7ad31
commit
34ccaeb10f
|
@ -7,7 +7,7 @@ from jumpserver.models import Setting
|
|||
from jasset.forms import AssetForm, IdcForm
|
||||
from jasset.models import Asset, IDC, AssetGroup, ASSET_TYPE, ASSET_STATUS
|
||||
from jperm.perm_api import get_group_asset_perm
|
||||
from jperm.ansible_api import Tasks, MyRunner
|
||||
from jperm.ansible_api import MyRunner
|
||||
from jperm.perm_api import gen_resource
|
||||
|
||||
|
||||
|
|
|
@ -119,8 +119,8 @@ class MyRunner(MyInventory):
|
|||
super(MyRunner, self).__init__(*args, **kwargs)
|
||||
self.results_raw = {}
|
||||
|
||||
def run(self, module_name='shell', module_args='', timeout=10, forks=10, pattern='',
|
||||
sudo=False, sudo_user='root', sudo_pass=''):
|
||||
def run(self, module_name='shell', module_args='', timeout=10, forks=10, pattern='*',
|
||||
become=False, become_method='sudo', become_user='root', become_pass=''):
|
||||
"""
|
||||
run module from andible ad-hoc.
|
||||
module_name: ansible module_name
|
||||
|
@ -132,10 +132,10 @@ class MyRunner(MyInventory):
|
|||
inventory=self.inventory,
|
||||
pattern=pattern,
|
||||
forks=forks,
|
||||
become=sudo,
|
||||
become_method='sudo',
|
||||
become_user=sudo_user,
|
||||
become_pass=sudo_pass
|
||||
become=become,
|
||||
become_method=become_method,
|
||||
become_user=become_user,
|
||||
become_pass=become_pass
|
||||
)
|
||||
self.results_raw = hoc.run()
|
||||
return self.results_raw
|
||||
|
@ -156,7 +156,7 @@ class MyRunner(MyInventory):
|
|||
for host, info in contacted.items():
|
||||
if info.get('failed'):
|
||||
result['failed'][host] = info.get('msg') + info.get('stderr', '')
|
||||
elif info.get('stderr'):
|
||||
elif info.get('stderr') and info.get('module_name') in ['shell', 'command', 'raw']:
|
||||
result['failed'][host] = info.get('stderr') + str(info.get('warnings'))
|
||||
else:
|
||||
result['ok'][host] = info.get('stdout')
|
||||
|
@ -265,60 +265,21 @@ class Command(MyInventory):
|
|||
return self.results_raw.get("dark")
|
||||
|
||||
|
||||
class Tasks(Command):
|
||||
class MyTask(MyRunner):
|
||||
"""
|
||||
this is a tasks object for include the common command.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Tasks, self).__init__(*args, **kwargs)
|
||||
|
||||
def __run(self,
|
||||
module_args,
|
||||
module_name="command",
|
||||
timeout=5,
|
||||
forks=10,
|
||||
group='default_group',
|
||||
pattern='*',
|
||||
):
|
||||
"""
|
||||
run command from andible ad-hoc.
|
||||
command : 必须是一个需要执行的命令字符串, 比如
|
||||
'uname -a'
|
||||
"""
|
||||
hoc = Runner(module_name=module_name,
|
||||
module_args=module_args,
|
||||
timeout=timeout,
|
||||
inventory=self.inventory,
|
||||
subset=group,
|
||||
pattern=pattern,
|
||||
forks=forks,
|
||||
become=False,
|
||||
)
|
||||
|
||||
self.results = hoc.run()
|
||||
return {"msg": self.msg, "result": self.results}
|
||||
|
||||
@property
|
||||
def msg(self):
|
||||
"""
|
||||
get the contacted and dark msg
|
||||
"""
|
||||
msg = {}
|
||||
for result in ["contacted", "dark"]:
|
||||
all = self.results.get(result)
|
||||
for key, value in all.iteritems():
|
||||
if value.get("msg"):
|
||||
msg[key] = value.get("msg")
|
||||
return msg
|
||||
super(MyTask, self).__init__(*args, **kwargs)
|
||||
|
||||
def push_key(self, user, key_path):
|
||||
"""
|
||||
push the ssh authorized key to target.
|
||||
"""
|
||||
module_args = 'user="%s" key="{{ lookup("file", "%s") }}" state=present' % (user, key_path)
|
||||
self.__run(module_args, "authorized_key")
|
||||
self.run("authorized_key", module_args, become=True)
|
||||
|
||||
return {"status": "failed", "msg": self.msg} if self.msg else {"status": "ok"}
|
||||
return self.results
|
||||
|
||||
def push_multi_key(self, **user_info):
|
||||
"""
|
||||
|
@ -345,9 +306,9 @@ class Tasks(Command):
|
|||
push the ssh authorized key to target.
|
||||
"""
|
||||
module_args = 'user="%s" key="{{ lookup("file", "%s") }}" state="absent"' % (user, key_path)
|
||||
self.__run(module_args, "authorized_key")
|
||||
self.run("authorized_key", module_args, become=True)
|
||||
|
||||
return {"status": "failed", "msg": self.msg} if self.msg else {"status": "ok"}
|
||||
return self.results
|
||||
|
||||
def add_user(self, username, password=''):
|
||||
"""
|
||||
|
@ -358,9 +319,9 @@ class Tasks(Command):
|
|||
module_args = 'name=%s shell=/bin/bash password=%s' % (username, encrypt_pass)
|
||||
else:
|
||||
module_args = 'name=%s shell=/bin/bash' % username
|
||||
self.__run(module_args, "user")
|
||||
self.run("user", module_args, become=True)
|
||||
|
||||
return {"status": "failed", "msg": self.msg} if self.msg else {"status": "ok"}
|
||||
return self.results
|
||||
|
||||
def add_multi_user(self, **user_info):
|
||||
"""
|
||||
|
@ -387,85 +348,10 @@ class Tasks(Command):
|
|||
"""
|
||||
delete a host user.
|
||||
"""
|
||||
module_args = 'name=%s state=absent remove=yes move_home=yes force=yes' % (username)
|
||||
self.__run(module_args,
|
||||
"user",)
|
||||
module_args = 'name=%s state=absent remove=yes move_home=yes force=yes' % username
|
||||
self.run("user", module_args, become=True)
|
||||
|
||||
return {"status": "failed","msg": self.msg} if self.msg else {"status": "ok"}
|
||||
|
||||
def add_init_users(self):
|
||||
"""
|
||||
add initail users: SA, DBA, DEV
|
||||
"""
|
||||
results = {}
|
||||
action = results["action_info"] = {}
|
||||
users = {"SA": get_rand_pass(), "DBA": get_rand_pass(), "DEV": get_rand_pass()}
|
||||
for user, password in users.iteritems():
|
||||
ret = self.add_user(user, password)
|
||||
action[user] = ret
|
||||
results["user_info"] = users
|
||||
|
||||
return results
|
||||
|
||||
def del_init_users(self):
|
||||
"""
|
||||
delete initail users: SA, DBA, DEV
|
||||
"""
|
||||
results = {}
|
||||
action = results["action_info"] = {}
|
||||
for user in ["SA", "DBA", "DEV"]:
|
||||
ret = self.del_user(user)
|
||||
action[user] = ret
|
||||
return results
|
||||
|
||||
def get_host_info(self):
|
||||
"""
|
||||
use the setup module get host informations
|
||||
:return:
|
||||
all_ip is list
|
||||
processor_count is int
|
||||
system_dist_version is string
|
||||
system_type is string
|
||||
disk is dict (device_name: device_size}
|
||||
system_dist is string
|
||||
processor_type is string
|
||||
default_ip is string
|
||||
hostname is string
|
||||
product_sn is string
|
||||
memory_total is int (MB)
|
||||
default_mac is string
|
||||
product_name is string
|
||||
"""
|
||||
self.__run('', 'setup')
|
||||
|
||||
result = {}
|
||||
all = self.results.get("contacted")
|
||||
for key, value in all.iteritems():
|
||||
setup =value.get("ansible_facts")
|
||||
# get disk informations
|
||||
disk_all = setup.get("ansible_devices")
|
||||
disk_need = {}
|
||||
for disk_name, disk_info in disk_all.iteritems():
|
||||
if disk_name.startswith('sd') or disk_name.startswith('hd'):
|
||||
disk_need[disk_name] = disk_info.get("size")
|
||||
|
||||
result[key] = {
|
||||
"all_ip": setup.get("ansible_all_ipv4_addresses"),
|
||||
"hostname" : setup.get("ansible_hostname"),
|
||||
"default_ip": setup.get("ansible_default_ipv4").get("address"),
|
||||
"default_mac": setup.get("ansible_default_ipv4").get("macaddress"),
|
||||
"product_name": setup.get("ansible_product_name"),
|
||||
"processor_type": ' '.join(setup.get("ansible_processor")),
|
||||
"processor_count": setup.get("ansible_processor_count"),
|
||||
"memory_total": setup.get("ansible_memtotal_mb"),
|
||||
"disk": disk_need,
|
||||
"system_type": setup.get("ansible_system"),
|
||||
"system_dist": setup.get("ansible_distribution"),
|
||||
"system_dist_verion": setup.get("ansible_distribution_major_version"),
|
||||
"product_sn": setup.get("ansible_product_serial")
|
||||
}
|
||||
|
||||
return {"failed": self.msg, "ok": result}
|
||||
return self.results
|
||||
|
||||
def push_sudo_file(self, file_path):
|
||||
"""
|
||||
|
@ -473,8 +359,9 @@ class Tasks(Command):
|
|||
:return:
|
||||
"""
|
||||
module_args1 = file_path
|
||||
ret = self.__run(module_args1, "script")
|
||||
return ret
|
||||
self.run("script", module_args1, become=True)
|
||||
print self.results_raw
|
||||
return self.results
|
||||
|
||||
|
||||
class CustomAggregateStats(callbacks.AggregateStats):
|
||||
|
|
|
@ -302,20 +302,6 @@ def get_role_push_host(role):
|
|||
return asset_pushed, asset_no_push
|
||||
|
||||
|
||||
@require_role('user')
|
||||
def perm_role_get(request):
|
||||
asset_id = request.GET.get('id', 0)
|
||||
if asset_id:
|
||||
asset = get_object(Asset, id=asset_id)
|
||||
if asset:
|
||||
role = user_have_perm(request.user, asset=asset)
|
||||
return HttpResponse(','.join([i.name for i in role]))
|
||||
else:
|
||||
roles = get_group_user_perm(request.user).get('role').keys()
|
||||
return HttpResponse(','.join(i.name for i in roles))
|
||||
return HttpResponse('error')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print get_role_info(1)
|
||||
|
||||
|
|
|
@ -11,9 +11,8 @@ from jperm.models import PermRole, PermRule, PermSudo, PermPush
|
|||
from jumpserver.models import Setting
|
||||
|
||||
from jperm.utils import updates_dict, gen_keys, get_rand_pass, get_add_sudo_script
|
||||
from jperm.ansible_api import Tasks
|
||||
from jperm.ansible_api import MyTask
|
||||
from jperm.perm_api import get_role_info, get_role_push_host
|
||||
|
||||
from jumpserver.api import my_render, get_object, CRYPTOR
|
||||
|
||||
|
||||
|
@ -211,8 +210,6 @@ def perm_rule_delete(request):
|
|||
# 根据rule_id 取得rule对象
|
||||
rule_id = request.POST.get("id")
|
||||
rule_obj = PermRule.objects.get(id=rule_id)
|
||||
print rule_id, rule_obj
|
||||
print rule_obj.name
|
||||
rule_obj.delete()
|
||||
return HttpResponse(u"删除授权规则:%s" % rule_obj.name)
|
||||
else:
|
||||
|
@ -423,25 +420,18 @@ def perm_role_push(request):
|
|||
# 调用Ansible API 进行推送
|
||||
password_push = True if request.POST.get("use_password") else False
|
||||
key_push = True if request.POST.get("use_publicKey") else False
|
||||
task = Tasks(push_resource)
|
||||
task = MyTask(push_resource)
|
||||
ret = {}
|
||||
ret_failed = {}
|
||||
|
||||
# 因为要先建立用户,所以password 是必选项,而push key是在 password也完成的情况下的 可选项
|
||||
# 1. 以password 方式推送角色
|
||||
if password_push:
|
||||
ret["password_push"] = task.add_user(role.name, CRYPTOR.decrypt(role.password))
|
||||
if ret["password_push"].get("status") != "success":
|
||||
ret_failed = ret["password_push"].get('msg')
|
||||
|
||||
# 2. 以秘钥 方式推送角色
|
||||
# 1. 以秘钥 方式推送角色
|
||||
if key_push:
|
||||
ret["password_push"] = task.add_user(role.name)
|
||||
if ret["password_push"].get("status") != "ok":
|
||||
ret_failed = ret["password_push"].get('msg')
|
||||
ret["pass_push"] = task.add_user(role.name, CRYPTOR.decrypt(role.password))
|
||||
ret["key_push"] = task.push_key(role.name, os.path.join(role.key_path, 'id_rsa.pub'))
|
||||
if ret["key_push"].get("status") != "ok":
|
||||
ret_failed = ret["key_push"].get('msg')
|
||||
|
||||
# 2. 推送账号密码
|
||||
elif password_push:
|
||||
ret["pass_push"] = task.add_user(role.name, CRYPTOR.decrypt(role.password))
|
||||
|
||||
# 3. 推送sudo配置文件
|
||||
if password_push or key_push:
|
||||
|
@ -451,16 +441,32 @@ def perm_role_push(request):
|
|||
role_chosen_aliase[role.name] = ','.join(sudo.name for sudo in sudo_alias if sudo.name)
|
||||
add_sudo_script = get_add_sudo_script(role_chosen_aliase, sudo_alias)
|
||||
ret['sudo'] = task.push_sudo_file(add_sudo_script)
|
||||
|
||||
if ret['sudo'].get('msg'):
|
||||
ret_failed = ret['sudo'].get('msg')
|
||||
os.remove(add_sudo_script)
|
||||
|
||||
logger.debug('推送role结果: %s' % ret)
|
||||
logger.debug('推送role错误: %s' % ret_failed)
|
||||
success_asset = {}
|
||||
failed_asset = {}
|
||||
logger.debug(ret)
|
||||
for push_type, result in ret.items():
|
||||
if result.get('failed'):
|
||||
for hostname, info in result.get('failed').items():
|
||||
if hostname in failed_asset.keys():
|
||||
if info in failed_asset.get(hostname):
|
||||
failed_asset[hostname] += info
|
||||
else:
|
||||
failed_asset[hostname] = info
|
||||
|
||||
for push_type, result in ret.items():
|
||||
if result.get('ok'):
|
||||
for hostname, info in result.get('ok').items():
|
||||
if hostname in failed_asset.keys():
|
||||
continue
|
||||
elif hostname in success_asset.keys():
|
||||
if str(info) in success_asset.get(hostname, ''):
|
||||
success_asset[hostname] += str(info)
|
||||
else:
|
||||
success_asset[hostname] = str(info)
|
||||
|
||||
success_asset = []
|
||||
failed_asset = []
|
||||
# 推送成功 回写push表
|
||||
for asset in calc_assets:
|
||||
push_check = PermPush.objects.filter(role=role, asset=asset)
|
||||
|
@ -470,20 +476,18 @@ def perm_role_push(request):
|
|||
def func(**kwargs):
|
||||
PermPush(**kwargs).save()
|
||||
|
||||
if ret_failed.get(asset.hostname):
|
||||
failed_asset.append(asset)
|
||||
if failed_asset.get(asset.hostname):
|
||||
func(is_password=password_push, is_public_key=key_push, role=role, asset=asset, success=False,
|
||||
result=ret_failed.get(asset.hostname))
|
||||
result=failed_asset.get(asset.hostname))
|
||||
else:
|
||||
success_asset.append(asset)
|
||||
func(is_password=password_push, is_public_key=key_push, role=role, asset=asset, success=True)
|
||||
|
||||
if not failed_asset:
|
||||
msg = u'角色 %s 推送成功[ %s ]' % (role.name, ','.join([asset.hostname for asset in success_asset]))
|
||||
msg = u'角色 %s 推送成功[ %s ]' % (role.name, ','.join(success_asset.keys()))
|
||||
else:
|
||||
error = u'角色 %s 推送失败 [ %s ], 推送成功 [ %s ]' % (role.name,
|
||||
','.join([asset.hostname for asset in failed_asset]),
|
||||
','.join([asset.hostname for asset in success_asset]))
|
||||
','.join(failed_asset.keys()),
|
||||
','.join(success_asset.keys()))
|
||||
return my_render('jperm/perm_role_push.html', locals(), request)
|
||||
|
||||
|
||||
|
@ -586,11 +590,29 @@ def perm_sudo_delete(request):
|
|||
def perm_role_recycle(request):
|
||||
role_id = request.GET.get('role_id')
|
||||
asset_ids = request.GET.get('asset_id').split(',')
|
||||
assets = []
|
||||
for asset_id in asset_ids:
|
||||
asset = get_object(Asset, id=asset_id)
|
||||
assets.append(asset)
|
||||
role = get_object(PermRole, id=role_id)
|
||||
PermPush.objects.filter(asset=asset, role=role).delete()
|
||||
|
||||
res = gen_resource(assets)
|
||||
task = MyTask(res)
|
||||
|
||||
return HttpResponse('删除成功')
|
||||
|
||||
|
||||
@require_role('user')
|
||||
def perm_role_get(request):
|
||||
asset_id = request.GET.get('id', 0)
|
||||
if asset_id:
|
||||
asset = get_object(Asset, id=asset_id)
|
||||
if asset:
|
||||
role = user_have_perm(request.user, asset=asset)
|
||||
return HttpResponse(','.join([i.name for i in role]))
|
||||
else:
|
||||
roles = get_group_user_perm(request.user).get('role').keys()
|
||||
return HttpResponse(','.join(i.name for i in roles))
|
||||
return HttpResponse('error')
|
||||
|
||||
|
|
|
@ -302,8 +302,6 @@ def upload(request):
|
|||
illegal_asset = set(asset_select).issubset(set(assets))
|
||||
return HttpResponse('没有权限的服务器 %s' % ','.join([asset.hostname for asset in illegal_asset]))
|
||||
|
||||
|
||||
|
||||
for upload_file in upload_files:
|
||||
file_path = '%s/%s' % (upload_dir, upload_file.name)
|
||||
with open(file_path, 'w') as f:
|
||||
|
@ -320,11 +318,11 @@ def upload(request):
|
|||
filename=' '.join([f.name for f in upload_files]), type='upload', remote_ip=remote_ip,
|
||||
result=ret).save()
|
||||
if ret.get('failed'):
|
||||
error = '上传目录: %s <br> 上传失败: [ %s ] <br>上传成功 [ %s ]' % (upload_dir,
|
||||
error = u'上传目录: %s <br> 上传失败: [ %s ] <br>上传成功 [ %s ]' % (upload_dir,
|
||||
', '.join(ret.get('failed').keys()),
|
||||
', '.join(ret.get('ok').keys()))
|
||||
return HttpResponse(error, status=500)
|
||||
msg = '上传目录: %s <br> 传送成功 [ %s ]' % (upload_dir, ', '.join(ret.get('ok')).keys())
|
||||
msg = u'上传目录: %s <br> 传送成功 [ %s ]' % (upload_dir, ', '.join(ret.get('ok').keys()))
|
||||
return HttpResponse(msg)
|
||||
return my_render('upload.html', locals(), request)
|
||||
|
||||
|
@ -345,7 +343,7 @@ def download(request):
|
|||
|
||||
if not set(asset_select).issubset(set(assets)):
|
||||
illegal_asset = set(asset_select).issubset(set(assets))
|
||||
return HttpResponse('没有权限的服务器 %s' % ','.join([asset.hostname for asset in illegal_asset]))
|
||||
return HttpResponse(u'没有权限的服务器 %s' % ','.join([asset.hostname for asset in illegal_asset]))
|
||||
|
||||
res = gen_resource({'user': user, 'asset': asset_select})
|
||||
runner = MyRunner(res)
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load mytags %}
|
||||
{% block content %}
|
||||
{% include 'nav_cat_bar.html' %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label label-primary"><b>{{ log.id }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<div class="text-left">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-navy">ID</td>
|
||||
<td>{{ log.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-navy">用户名</td>
|
||||
<td>{{ log.user }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-navy">来源IP</td>
|
||||
<td>{{ log.remote_ip }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-navy">类型</td>
|
||||
<td>{{ log.type }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-navy">日期</td>
|
||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-navy">文件</td>
|
||||
<td>
|
||||
<table class="table">
|
||||
{% for file_name in file_list %}
|
||||
{% if file_name %}
|
||||
<tr>
|
||||
<td>{{ file_name }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-navy">主机</td>
|
||||
<td>
|
||||
<table class="table">
|
||||
{% for asset_name in assets_hostname %}
|
||||
{% if asset_name %}
|
||||
<tr>
|
||||
<td>{{ asset_name }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>结果</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content inspinia-timeline">
|
||||
<div>
|
||||
<div class="text-left">
|
||||
<table class="table">
|
||||
{% for result, info in result.items %}
|
||||
{% for host, msg in info.items %}
|
||||
{% ifequal result 'failed' %}
|
||||
<tr>
|
||||
<td class="text-navy" style="color: #ed5565">{{ host }}</td>
|
||||
<td>{{ msg }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td class="text-navy">{{ host }}</td>
|
||||
<td>{{ msg }}</td>
|
||||
</tr>
|
||||
{% endifequal %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#show').click(function(){
|
||||
$('#last').css('display', 'none');
|
||||
$('#all').css('display', 'block');
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
Loading…
Reference in New Issue