Merge branches 'guanghongwei' and 'wangyong' of gitcafe.com:ibuler/jumpserver into guanghongwei

Conflicts:
	connect.py
	jumpserver.conf
	jumpserver/api.py
pull/6/head
guanghongwei 2015-03-21 13:10:38 +08:00
commit d8a6eba2f3
26 changed files with 2494 additions and 151 deletions

View File

@ -3,6 +3,7 @@
import socket import socket
import sys import sys
import os import os
import ast
import select import select
import time import time
from datetime import datetime from datetime import datetime
@ -24,8 +25,7 @@ django.setup()
from juser.models import User from juser.models import User
from jasset.models import Asset from jasset.models import Asset
from jlog.models import Log from jlog.models import Log
from jumpserver.api import user_perm_asset_api, PyCrypt, BASE_DIR, CONF, CRYPTOR, KEY from jumpserver.api import *
try: try:
import termios import termios
import tty import tty
@ -98,7 +98,13 @@ def log_record(username, host):
today_connect_log_dir = os.path.join(connect_log_dir, today) today_connect_log_dir = os.path.join(connect_log_dir, today)
log_filename = '%s_%s_%s.log' % (username, host, time_now) log_filename = '%s_%s_%s.log' % (username, host, time_now)
log_file_path = os.path.join(today_connect_log_dir, log_filename) log_file_path = os.path.join(today_connect_log_dir, log_filename)
dept_name = User.objects.get(username=username).dept
pid = os.getpid() pid = os.getpid()
ip_list = []
remote_ip = os.popen("who |grep `ps aux |gawk '{if ($2==%s) print $1}'` |gawk '{print $5}'|tr -d '()'" % pid).readlines()
for ip in remote_ip:
ip_list.append(ip.strip('\n'))
ip_list = ','.join(list(set(ip_list)))
if not os.path.isdir(today_connect_log_dir): if not os.path.isdir(today_connect_log_dir):
try: try:
@ -112,7 +118,7 @@ def log_record(username, host):
except IOError: except IOError:
raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir) raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir)
log = Log(user=username, host=host, log_path=log_file_path, start_time=datetime.now(), pid=pid) log = Log(user=username, host=host, remote_ip=ip_list, dept_name=dept_name, log_path=log_file_path, start_time=datetime.now(), pid=pid)
log_file.write('Starttime is %s\n' % datetime.now()) log_file.write('Starttime is %s\n' % datetime.now())
log.save() log.save()
return log_file, log return log_file, log
@ -173,6 +179,15 @@ def get_user_host(username):
return hosts_attr return hosts_attr
def get_user_hostgroup(username):
"""Get the hostgroups of under the user control."""
groups_attr = {}
group_all = user_perm_group_api(username)
for group in group_all:
groups_attr[group.name] = [group.id, group.comment]
return groups_attr
def get_connect_item(username, ip): def get_connect_item(username, ip):
asset = get_object(Asset, ip=ip) asset = get_object(Asset, ip=ip)
@ -222,8 +237,9 @@ def print_prompt():
msg = """\033[1;32m### Welcome Use JumpServer To Login. ### \033[0m msg = """\033[1;32m### Welcome Use JumpServer To Login. ### \033[0m
1) Type \033[32mIP ADDRESS\033[0m To Login. 1) Type \033[32mIP ADDRESS\033[0m To Login.
2) Type \033[32mP/p\033[0m To Print The Servers You Available. 2) Type \033[32mP/p\033[0m To Print The Servers You Available.
3) Type \033[32mE/e\033[0m To Execute Command On Several Servers. 3) Type \033[32mG/g\033[0m To Print The Server Groups You Available.
4) Type \033[32mQ/q\033[0m To Quit. 4) Type \033[32mE/e\033[0m To Execute Command On Several Servers.
5) Type \033[32mQ/q\033[0m To Quit.
""" """
print textwrap.dedent(msg) print textwrap.dedent(msg)
@ -236,6 +252,13 @@ def print_user_host(username):
print '%s -- %s' % (ip, hosts_attr[ip][1]) print '%s -- %s' % (ip, hosts_attr[ip][1])
def print_user_hostgroup(username):
group_attr = get_user_hostgroup(username)
groups = group_attr.keys()
for g in groups:
print '%s -- %s' % (g, group_attr[g][1])
def connect(username, password, host, port, login_name): def connect(username, password, host, port, login_name):
""" """
Connect server. Connect server.
@ -351,6 +374,9 @@ if __name__ == '__main__':
if option in ['P', 'p']: if option in ['P', 'p']:
print_user_host(LOGIN_NAME) print_user_host(LOGIN_NAME)
continue continue
elif option in ['G', 'g']:
print_user_hostgroup(LOGIN_NAME)
continue
elif option in ['E', 'e']: elif option in ['E', 'e']:
exec_cmd_servers(LOGIN_NAME) exec_cmd_servers(LOGIN_NAME)
elif option in ['Q', 'q']: elif option in ['Q', 'q']:

View File

@ -13,7 +13,6 @@ django.setup()
from juser.views import db_add_user, md5_crypt, CRYPTOR, db_add_group from juser.views import db_add_user, md5_crypt, CRYPTOR, db_add_group
from jasset.models import Asset, IDC, BisGroup from jasset.models import Asset, IDC, BisGroup
from juser.models import UserGroup, DEPT, User from juser.models import UserGroup, DEPT, User
from jasset.views import jasset_group_add
from jperm.models import CmdGroup from jperm.models import CmdGroup
from jlog.models import Log from jlog.models import Log
@ -123,18 +122,18 @@ def test_add_log():
end_time = datetime.datetime.now() end_time = datetime.datetime.now()
log_path = '/var/log/jumpserver/test.log' log_path = '/var/log/jumpserver/test.log'
host = '192.168.1.' + str(ip) host = '192.168.1.' + str(ip)
Log.objects.create(user=user, host=host, log_path=log_path, pid=168, start_time=start_time, Log.objects.create(user=user, host=host, remote_ip='8.8.8.8', dept_name='运维部', log_path=log_path, pid=168, start_time=start_time,
is_finished=1, log_finished=1, end_time=end_time) is_finished=1, log_finished=1, end_time=end_time)
if __name__ == '__main__': if __name__ == '__main__':
install() #install()
test_add_dept() #test_add_dept()
test_add_group() #test_add_group()
test_add_user() #test_add_user()
test_add_idc() #test_add_idc()
test_add_asset_group() #test_add_asset_group()
test_add_asset() #test_add_asset()
test_add_log() test_add_log()

135
docs/install.py Normal file
View File

@ -0,0 +1,135 @@
#coding:utf-8
import django
import os
import sys
import random
import datetime
sys.path.append('../')
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
django.setup()
from juser.views import db_add_user, md5_crypt, CRYPTOR, db_add_group
from jasset.models import Asset, IDC, BisGroup
from juser.models import UserGroup, DEPT, User
from jasset.views import jasset_group_add
from jperm.models import CmdGroup
from jlog.models import Log
def install():
IDC.objects.create(name='ALL', comment='ALL')
IDC.objects.create(name='默认', comment='默认')
DEPT.objects.create(name="默认", comment="默认部门")
DEPT.objects.create(name="超管部", comment="超级管理员部门")
dept = DEPT.objects.get(name='超管部')
dept2 = DEPT.objects.get(name='默认')
UserGroup.objects.create(name='ALL', dept=dept, comment='ALL')
UserGroup.objects.create(name='默认', dept=dept, comment='默认')
BisGroup.objects.create(name='ALL', dept=dept, comment='ALL')
BisGroup.objects.create(name='默认', dept=dept, comment='默认')
User(id=5000, username="admin", password=md5_crypt('admin'),
name='admin', email='admin@jumpserver.org', role='SU', is_active=True, dept=dept).save()
User(id=5001, username="group_admin", password=md5_crypt('group_admin'),
name='group_admin', email='group_admin@jumpserver.org', role='DA', is_active=True, dept=dept2).save()
def test_add_idc():
for i in range(1, 20):
name = 'IDC' + str(i)
IDC.objects.create(name=name, comment='')
print 'Add: %s' % name
def test_add_dept():
for i in range(1, 100):
name = 'DEPT' + str(i)
print "Add: %s" % name
DEPT.objects.create(name=name, comment=name)
def test_add_group():
dept_all = DEPT.objects.all()
for i in range(1, 100):
name = 'UserGroup' + str(i)
UserGroup.objects.create(name=name, dept=random.choice(dept_all), comment=name)
print 'Add: %s' % name
def test_add_cmd_group():
for i in range(1, 20):
name = 'CMD' + str(i)
cmd = '/sbin/ping%s, /sbin/ifconfig/' % str(i)
CmdGroup.objects.create(name=name, cmd=cmd, comment=name)
print 'Add: %s' % name
def test_add_user():
for i in range(1, 500):
username = "test" + str(i)
dept_all = DEPT.objects.all()
group_all = UserGroup.objects.all()
group_all_id = [group.id for group in group_all]
db_add_user(username=username,
password=md5_crypt(username),
dept=random.choice(dept_all),
name=username, email='%s@jumpserver.org' % username,
groups=[random.choice(group_all_id) for i in range(1, 4)], role='CU',
ssh_key_pwd=CRYPTOR.encrypt(username),
ldap_pwd=CRYPTOR.encrypt(username),
is_active=True,
date_joined=datetime.datetime.now())
print "Add: %s" % username
def test_add_asset_group():
dept = DEPT.objects.get(name='默认')
for i in range(1, 20):
name = 'AssetGroup' + str(i)
group = BisGroup(name=name, dept=dept, comment=name)
group.save()
print 'Add: %s' % name
def test_add_asset():
idc_all = IDC.objects.all()
test_idc = random.choice(idc_all)
bis_group_all = BisGroup.objects.all()
dept_all = DEPT.objects.all()
for i in range(1, 500):
ip = '192.168.1.' + str(i)
asset = Asset(ip=ip, port=22, login_type='L', idc=test_idc, is_active=True, comment='test')
asset.save()
asset.bis_group = [random.choice(bis_group_all) for i in range(2)]
asset.dept = [random.choice(dept_all) for i in range(2)]
print "Add: %s" % ip
def test_add_log():
li_date = []
today = datetime.date.today()
oneday = datetime.timedelta(days=1)
for i in range(0, 7):
today = today-oneday
li_date.append(today)
user_list = ['马云', '马化腾', '丁磊', '周鸿祎', '雷军', '柳传志', '陈天桥', '李彦宏', '李开复', '罗永浩']
for i in range(1, 1000):
user = random.choice(user_list)
ip = random.randint(1, 20)
start_time = random.choice(li_date)
end_time = datetime.datetime.now()
log_path = '/var/log/jumpserver/test.log'
host = '192.168.1.' + str(ip)
Log.objects.create(user=user, host=host, log_path=log_path, pid=168, start_time=start_time,
is_finished=1, log_finished=1, end_time=end_time)
if __name__ == '__main__':
install()

View File

@ -1,5 +1,7 @@
# coding:utf-8 # coding:utf-8
import ast
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.template import RequestContext from django.template import RequestContext
@ -9,8 +11,10 @@ from models import IDC, Asset, BisGroup
from juser.models import UserGroup, DEPT from juser.models import UserGroup, DEPT
from connect import PyCrypt, KEY from connect import PyCrypt, KEY
from jlog.models import Log from jlog.models import Log
from jumpserver.views import jasset_group_add, jasset_host_edit, pages from jumpserver.views import jasset_host_edit, pages
from jumpserver.api import asset_perm_api from jumpserver.api import asset_perm_api
from jumpserver.api import user_perm_group_api, require_login, require_super_user, \
require_admin, is_group_admin, is_super_user, is_common_user, get_user_dept
cryptor = PyCrypt(KEY) cryptor = PyCrypt(KEY)
@ -37,17 +41,13 @@ def f_add_host(ip, port, idc, jtype, group, dept, active, comment, username='',
comment=comment) comment=comment)
a.save() a.save()
jasset_group_add(ip, ip, 'P')
all_group = BisGroup.objects.get(name='ALL') all_group = BisGroup.objects.get(name='ALL')
private_group = BisGroup.objects.get(name=ip.strip())
for g in group: for g in group:
c = BisGroup.objects.get(name=g) c = BisGroup.objects.get(name=g)
groups.append(c) groups.append(c)
groups.extend([all_group, private_group]) groups.append(all_group)
print dept
for d in dept: for d in dept:
print d
p = DEPT.objects.get(name=d) p = DEPT.objects.get(name=d)
depts.append(p) depts.append(p)
@ -56,14 +56,20 @@ def f_add_host(ip, port, idc, jtype, group, dept, active, comment, username='',
a.save() a.save()
@require_admin
def add_host(request): def add_host(request):
login_types = {'L': 'LDAP', 'S': 'SSH_KEY', 'P': 'PASSWORD', 'M': 'MAP'} login_types = {'L': 'LDAP', 'M': 'MAP'}
header_title, path1, path2 = u'添加主机', u'资产管理', u'添加主机' header_title, path1, path2 = u'添加主机', u'资产管理', u'添加主机'
eidc = IDC.objects.all() eidc = IDC.objects.exclude(name='ALL')
if is_super_user(request):
edept = DEPT.objects.all() edept = DEPT.objects.all()
egroup = BisGroup.objects.filter(type='A') egroup = BisGroup.objects.exclude(name='ALL')
eusergroup = UserGroup.objects.all() eusergroup = UserGroup.objects.all()
elif is_group_admin(request):
dept_id = get_user_dept(request)
user_id = request.session.get('user_id')
edept = DEPT.objects.get(id=dept_id)
egroup = edept.bisgroup_set.all()
if request.method == 'POST': if request.method == 'POST':
j_ip = request.POST.get('j_ip') j_ip = request.POST.get('j_ip')
j_idc = request.POST.get('j_idc') j_idc = request.POST.get('j_idc')
@ -89,21 +95,20 @@ def add_host(request):
return render_to_response('jasset/host_add.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/host_add.html', locals(), context_instance=RequestContext(request))
@require_admin
def add_host_multi(request): def add_host_multi(request):
header_title, path1, path2 = u'批量添加主机', u'资产管理', u'批量添加主机' header_title, path1, path2 = u'批量添加主机', u'资产管理', u'批量添加主机'
login_types = {'LDAP': 'L', 'SSH_KEY': 'S', 'PASSWORD': 'P', 'MAP': 'M'} login_types = {'LDAP': 'L', 'MAP': 'M'}
j_group = [] dept_id = get_user_dept(request)
if request.method == 'POST': if request.method == 'POST':
multi_hosts = request.POST.get('j_multi').split('\n') multi_hosts = request.POST.get('j_multi').split('\n')
for host in multi_hosts: for host in multi_hosts:
if host == '': if host == '':
break break
j_ip, j_port, j_type, j_idc, j_groups, j_active, j_comment = host.split() j_ip, j_port, j_type, j_idc, j_groups, j_depts, j_active, j_comment = host.split()
j_type = login_types[j_type] j_type = login_types[j_type]
j_groups = j_groups.split(',') j_group = ast.literal_eval(j_groups)
for group in j_groups: j_dept = ast.literal_eval(j_depts)
g = group.strip('[]').encode('utf-8').strip()
j_group.append(g)
if Asset.objects.filter(ip=str(j_ip)): if Asset.objects.filter(ip=str(j_ip)):
emg = u'该IP %s 已存在!' % j_ip emg = u'该IP %s 已存在!' % j_ip
@ -113,9 +118,9 @@ def add_host_multi(request):
if j_type == 'M': if j_type == 'M':
j_user = request.POST.get('j_user') j_user = request.POST.get('j_user')
j_password = cryptor.encrypt(request.POST.get('j_password')) j_password = cryptor.encrypt(request.POST.get('j_password'))
f_add_host(j_ip, j_port, j_idc, j_type, j_group, j_active, j_comment, j_user, j_password) f_add_host(j_ip, j_port, j_idc, j_type, j_group, j_dept, j_active, j_comment, j_user, j_password)
else: else:
f_add_host(j_ip, j_port, j_idc, j_type, j_group, j_active, j_comment) f_add_host(j_ip, j_port, j_idc, j_type, j_group, j_dept, j_active, j_comment)
smg = u'批量添加添加成功' smg = u'批量添加添加成功'
return HttpResponseRedirect('/jasset/host_list/') return HttpResponseRedirect('/jasset/host_list/')
@ -123,6 +128,7 @@ def add_host_multi(request):
return render_to_response('jasset/host_add_multi.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/host_add_multi.html', locals(), context_instance=RequestContext(request))
@require_admin
def batch_host_edit(request): def batch_host_edit(request):
if request.method == 'POST': if request.method == 'POST':
len_table = request.POST.get('len_table') len_table = request.POST.get('len_table')
@ -158,22 +164,34 @@ def batch_host_edit(request):
return render_to_response('jasset/host_list.html') return render_to_response('jasset/host_list.html')
@require_login
def list_host(request): def list_host(request):
header_title, path1, path2 = u'查看主机', u'资产管理', u'查看主机' header_title, path1, path2 = u'查看主机', u'资产管理', u'查看主机'
login_types = {'L': 'LDAP', 'S': 'SSH_KEY', 'P': 'PASSWORD', 'M': 'MAP'} login_types = {'L': 'LDAP', 'M': 'MAP'}
keyword = request.GET.get('keyword', '') keyword = request.GET.get('keyword', '')
dept_id = get_user_dept(request)
dept = DEPT.objects.get(id=dept_id)
if is_super_user(request):
if keyword: if keyword:
posts = Asset.objects.filter(Q(ip__contains=keyword) | Q(idc__name__contains=keyword) | posts = Asset.objects.filter(Q(ip__contains=keyword) | Q(idc__name__contains=keyword) |
Q(bis_group__name__contains=keyword) | Q(comment__contains=keyword)).distinct().order_by('ip') Q(bis_group__name__contains=keyword) | Q(comment__contains=keyword)).distinct().order_by('ip')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
else: else:
posts = Asset.objects.all().order_by('ip') posts = Asset.objects.all().order_by('ip')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
elif is_group_admin(request):
if keyword:
posts = Asset.objects.filter(Q(ip__contains=keyword) | Q(idc__name__contains=keyword) |
Q(bis_group__name__contains=keyword) | Q(comment__contains=keyword)).filter(dept=dept).distinct().order_by('ip')
else:
posts = Asset.objects.all().filter(dept=dept).order_by('ip')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
elif is_common_user(request):
pass
return render_to_response('jasset/host_list.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/host_list.html', locals(), context_instance=RequestContext(request))
@require_admin
def host_del(request, offset): def host_del(request, offset):
if offset == 'multi': if offset == 'multi':
len_list = request.POST.get("len_list") len_list = request.POST.get("len_list")
@ -191,13 +209,14 @@ def host_del(request, offset):
return HttpResponseRedirect('/jasset/host_list/') return HttpResponseRedirect('/jasset/host_list/')
@require_admin
def host_edit(request): def host_edit(request):
actives = {1: u'激活', 0: u'禁用'} actives = {1: u'激活', 0: u'禁用'}
login_types = {'L': 'LDAP', 'M': 'MAP'} login_types = {'L': 'LDAP', 'M': 'MAP'}
header_title, path1, path2 = u'修改主机', u'资产管理', u'修改主机' header_title, path1, path2 = u'修改主机', u'资产管理', u'修改主机'
groups, e_group, e_dept, depts = [], [], [], [] groups, e_group, e_dept, depts = [], [], [], []
eidc = IDC.objects.all() eidc = IDC.objects.all()
egroup = BisGroup.objects.filter(type='A') egroup = BisGroup.objects.all()
edept = DEPT.objects.all() edept = DEPT.objects.all()
offset = request.GET.get('id') offset = request.GET.get('id')
for g in Asset.objects.get(id=int(offset)).bis_group.all(): for g in Asset.objects.get(id=int(offset)).bis_group.all():
@ -257,6 +276,7 @@ def host_edit(request):
return render_to_response('jasset/host_edit.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/host_edit.html', locals(), context_instance=RequestContext(request))
@require_login
def jlist_ip(request, offset): def jlist_ip(request, offset):
header_title, path1, path2 = u'主机详细信息', u'资产管理', u'主机详情' header_title, path1, path2 = u'主机详细信息', u'资产管理', u'主机详情'
login_types = {'L': 'LDAP', 'S': 'SSH_KEY', 'P': 'PASSWORD', 'M': 'MAP'} login_types = {'L': 'LDAP', 'S': 'SSH_KEY', 'P': 'PASSWORD', 'M': 'MAP'}
@ -266,6 +286,7 @@ def jlist_ip(request, offset):
return render_to_response('jasset/jlist_ip.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/jlist_ip.html', locals(), context_instance=RequestContext(request))
@require_super_user
def add_idc(request): def add_idc(request):
header_title, path1, path2 = u'添加IDC', u'资产管理', u'添加IDC' header_title, path1, path2 = u'添加IDC', u'资产管理', u'添加IDC'
if request.method == 'POST': if request.method == 'POST':
@ -281,6 +302,7 @@ def add_idc(request):
return render_to_response('jasset/idc_add.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/idc_add.html', locals(), context_instance=RequestContext(request))
@require_admin
def list_idc(request): def list_idc(request):
header_title, path1, path2 = u'查看IDC', u'资产管理', u'查看IDC' header_title, path1, path2 = u'查看IDC', u'资产管理', u'查看IDC'
keyword = request.GET.get('keyword', '') keyword = request.GET.get('keyword', '')
@ -292,6 +314,7 @@ def list_idc(request):
return render_to_response('jasset/idc_list.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/idc_list.html', locals(), context_instance=RequestContext(request))
@require_super_user
def edit_idc(request): def edit_idc(request):
header_title, path1, path2 = u'编辑IDC', u'资产管理', u'编辑IDC' header_title, path1, path2 = u'编辑IDC', u'资产管理', u'编辑IDC'
edit = 1 edit = 1
@ -320,6 +343,7 @@ def edit_idc(request):
return render_to_response('jasset/idc_add.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/idc_add.html', locals(), context_instance=RequestContext(request))
@require_super_user
def del_idc(request, offset): def del_idc(request, offset):
if offset == 'multi': if offset == 'multi':
len_list = request.POST.get("len_list") len_list = request.POST.get("len_list")
@ -333,10 +357,17 @@ def del_idc(request, offset):
return HttpResponseRedirect('/jasset/idc_list/') return HttpResponseRedirect('/jasset/idc_list/')
@require_admin
def add_group(request): def add_group(request):
header_title, path1, path2 = u'添加主机组', u'资产管理', u'添加主机组' header_title, path1, path2 = u'添加主机组', u'资产管理', u'添加主机组'
if is_super_user(request):
posts = Asset.objects.all() posts = Asset.objects.all()
edept = DEPT.objects.all() edept = DEPT.objects.all()
elif is_group_admin(request):
dept_id = get_user_dept(request)
dept = DEPT.objects.get(id=dept_id)
posts = Asset.objects.filter(dept=dept)
edept = DEPT.objects.get(id=dept_id)
if request.method == 'POST': if request.method == 'POST':
j_group = request.POST.get('j_group') j_group = request.POST.get('j_group')
j_dept = request.POST.get('j_dept') j_dept = request.POST.get('j_dept')
@ -348,9 +379,8 @@ def add_group(request):
emg = u'该主机组已存在!' emg = u'该主机组已存在!'
return render_to_response('jasset/group_add.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/group_add.html', locals(), context_instance=RequestContext(request))
else: else:
BisGroup.objects.create(name=j_group, comment=j_comment, type='A') BisGroup.objects.create(name=j_group, dept=j_dept, comment=j_comment)
group = BisGroup.objects.get(name=j_group) group = BisGroup.objects.get(name=j_group)
group.dept = j_dept
for host in j_hosts: for host in j_hosts:
g = Asset.objects.get(id=host) g = Asset.objects.get(id=host)
group.asset_set.add(g) group.asset_set.add(g)
@ -359,25 +389,44 @@ def add_group(request):
return render_to_response('jasset/group_add.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/group_add.html', locals(), context_instance=RequestContext(request))
@require_admin
def list_group(request): def list_group(request):
header_title, path1, path2 = u'查看主机组', u'资产管理', u'查看主机组' header_title, path1, path2 = u'查看主机组', u'资产管理', u'查看主机组'
dept_id = get_user_dept(request)
dept = DEPT.objects.get(id=dept_id)
keyword = request.GET.get('keyword', '') keyword = request.GET.get('keyword', '')
if is_super_user(request):
if keyword: if keyword:
posts = BisGroup.objects.filter(Q(name__contains=keyword) | Q(comment__contains=keyword)) posts = BisGroup.objects.exclude(name='ALL').filter(Q(name__contains=keyword) | Q(comment__contains=keyword))
else: else:
posts = BisGroup.objects.all().order_by('id') posts = BisGroup.objects.exclude(name='ALL').order_by('id')
elif is_group_admin(request):
if keyword:
posts = BisGroup.objects.filter(Q(name__contains=keyword) | Q(comment__contains=keyword)).filter(dept=dept)
else:
posts = BisGroup.objects.filter(dept=dept).order_by('id')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
return render_to_response('jasset/group_list.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/group_list.html', locals(), context_instance=RequestContext(request))
@require_admin
def edit_group(request): def edit_group(request):
header_title, path1, path2 = u'编辑主机组', u'资产管理', u'编辑主机组' header_title, path1, path2 = u'编辑主机组', u'资产管理', u'编辑主机组'
group_id = request.GET.get('id') group_id = request.GET.get('id')
group = BisGroup.objects.get(id=group_id) group = BisGroup.objects.get(id=group_id)
all = Asset.objects.all() all = Asset.objects.all()
dept_id = get_user_dept(request)
eposts = contact_list = Asset.objects.filter(bis_group=group).order_by('ip') eposts = contact_list = Asset.objects.filter(bis_group=group).order_by('ip')
if is_super_user(request):
edept = DEPT.objects.all()
posts = [g for g in all if g not in eposts] posts = [g for g in all if g not in eposts]
elif is_group_admin(request):
dept = DEPT.objects.get(id=dept_id)
all_dept = Asset.objects.filter(dept=dept)
posts = [g for g in all_dept if g not in eposts]
if request.method == 'POST': if request.method == 'POST':
j_group = request.POST.get('j_group') j_group = request.POST.get('j_group')
j_hosts = request.POST.getlist('j_hosts') j_hosts = request.POST.getlist('j_hosts')
@ -394,30 +443,43 @@ def edit_group(request):
return render_to_response('jasset/group_add.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/group_add.html', locals(), context_instance=RequestContext(request))
@require_admin
def detail_group(request): def detail_group(request):
header_title, path1, path2 = u'主机组详情', u'资产管理', u'主机组详情' header_title, path1, path2 = u'主机组详情', u'资产管理', u'主机组详情'
login_types = {'L': 'LDAP', 'S': 'SSH_KEY', 'P': 'PASSWORD', 'M': 'MAP'} login_types = {'L': 'LDAP', 'S': 'SSH_KEY', 'P': 'PASSWORD', 'M': 'MAP'}
dept_id = get_user_dept(request)
dept = DEPT.objects.get(id=dept_id)
group_id = request.GET.get('id') group_id = request.GET.get('id')
group_name = BisGroup.objects.get(id=group_id).name group_name = BisGroup.objects.get(id=group_id).name
b = BisGroup.objects.get(id=group_id) b = BisGroup.objects.get(id=group_id)
if is_super_user(request):
posts = Asset.objects.filter(bis_group=b).order_by('ip') posts = Asset.objects.filter(bis_group=b).order_by('ip')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
elif is_group_admin(request):
posts = Asset.objects.filter(bis_group=b).filter(dept=dept).order_by('ip')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
return render_to_response('jasset/group_detail.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/group_detail.html', locals(), context_instance=RequestContext(request))
def detail_idc(request): def detail_idc(request):
header_title, path1, path2 = u'IDC详情', u'资产管理', u'IDC详情' header_title, path1, path2 = u'IDC详情', u'资产管理', u'IDC详情'
login_types = {'L': 'LDAP', 'S': 'SSH_KEY', 'P': 'PASSWORD', 'M': 'MAP'} login_types = {'L': 'LDAP', 'M': 'MAP'}
idc_id = request.GET.get('id') idc_id = request.GET.get('id')
idc_name = IDC.objects.get(id=idc_id).name idc_name = IDC.objects.get(id=idc_id).name
b = IDC.objects.get(id=idc_id) b = IDC.objects.get(id=idc_id)
dept_id = get_user_dept(request)
dept = DEPT.objects.get(id=dept_id)
if is_super_user(request):
posts = Asset.objects.filter(idc=b).order_by('ip') posts = Asset.objects.filter(idc=b).order_by('ip')
elif is_group_admin(request):
posts = Asset.objects.filter(idc=b).filter(dept=dept).order_by('ip')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
return render_to_response('jasset/idc_detail.html', locals(), context_instance=RequestContext(request)) return render_to_response('jasset/idc_detail.html', locals(), context_instance=RequestContext(request))
@require_admin
def group_del_host(request, offset): def group_del_host(request, offset):
if request.method == 'POST': if request.method == 'POST':
group_name = request.POST.get('group_name') group_name = request.POST.get('group_name')
@ -439,6 +501,7 @@ def group_del_host(request, offset):
return HttpResponseRedirect('/jasset/%s_detail/?id=%s' % (offset, group.id)) return HttpResponseRedirect('/jasset/%s_detail/?id=%s' % (offset, group.id))
@require_admin
def group_del(request, offset): def group_del(request, offset):
if offset == 'multi': if offset == 'multi':
len_list = request.POST.get("len_list") len_list = request.POST.get("len_list")
@ -454,10 +517,17 @@ def group_del(request, offset):
def host_search(request): def host_search(request):
keyword = request.GET.get('keyword') keyword = request.GET.get('keyword')
login_types = {'L': 'LDAP', 'S': 'SSH_KEY', 'P': 'PASSWORD', 'M': 'MAP'} login_types = {'L': 'LDAP', 'M': 'MAP'}
dept_id = get_user_dept(request)
dept = DEPT.objects.get(id=dept_id)
if is_super_user(request):
posts = Asset.objects.filter(Q(ip__contains=keyword) | Q(idc__name__contains=keyword) | posts = Asset.objects.filter(Q(ip__contains=keyword) | Q(idc__name__contains=keyword) |
Q(bis_group__name__contains=keyword) | Q( Q(bis_group__name__contains=keyword) | Q(
comment__contains=keyword)).distinct().order_by('ip') comment__contains=keyword)).distinct().order_by('ip')
elif is_group_admin(request):
posts = Asset.objects.filter(Q(ip__contains=keyword) | Q(idc__name__contains=keyword) |
Q(bis_group__name__contains=keyword) | Q(
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) 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)) return render_to_response('jasset/host_search.html', locals(), context_instance=RequestContext(request))

View File

@ -4,6 +4,8 @@ from django.db import models
class Log(models.Model): class Log(models.Model):
user = models.CharField(max_length=20, null=True) user = models.CharField(max_length=20, null=True)
host = models.CharField(max_length=20, null=True) host = models.CharField(max_length=20, null=True)
remote_ip = models.CharField(max_length=100)
dept_name = models.CharField(max_length=20)
log_path = models.CharField(max_length=100) log_path = models.CharField(max_length=100)
start_time = models.DateTimeField(null=True) start_time = models.DateTimeField(null=True)
pid = models.IntegerField(max_length=10) pid = models.IntegerField(max_length=10)

View File

@ -5,12 +5,15 @@ from datetime import datetime
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse from django.http import HttpResponse
from django.template import RequestContext
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
from connect import BASE_DIR from connect import BASE_DIR
from jlog.models import Log from jlog.models import Log
from jumpserver.views import pages from jumpserver.views import pages
from juser.models import User, DEPT
from jumpserver.api import get_user_dept, is_super_user, is_group_admin, is_common_user
CONF = ConfigParser.ConfigParser() CONF = ConfigParser.ConfigParser()
CONF.read('%s/jumpserver.conf' % BASE_DIR) CONF.read('%s/jumpserver.conf' % BASE_DIR)
@ -20,30 +23,61 @@ def log_list_online(request):
header_title, path1, path2 = u'查看日志', u'查看日志', u'在线用户' header_title, path1, path2 = u'查看日志', u'查看日志', u'在线用户'
keyword = request.GET.get('keyword') keyword = request.GET.get('keyword')
web_socket_host = CONF.get('websocket', 'web_socket_host') web_socket_host = CONF.get('websocket', 'web_socket_host')
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: if keyword:
posts = contact_list = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ posts = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \
.filter(is_finished=0).order_by('-start_time') .filter(is_finished=0).order_by('-start_time')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
else: else:
posts = Log.objects.filter(is_finished=0).order_by('-start_time') posts = Log.objects.filter(is_finished=0).order_by('-start_time')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
return render_to_response('jlog/log_online.html', locals()) elif is_group_admin(request):
if keyword:
posts = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \
.filter(is_finished=0).filter(dept_name=dept_name).order_by('-start_time')
else:
posts = Log.objects.filter(is_finished=0).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):
posts = Log.objects.filter(is_finished=0).filter(user=username).order_by('-start_time')
return render_to_response('jlog/log_online.html', locals(), context_instance=RequestContext(request))
def log_list_offline(request): def log_list_offline(request):
header_title, path1, path2 = u'查看日志', u'查看日志', u'历史记录' header_title, path1, path2 = u'查看日志', u'查看日志', u'历史记录'
keyword = request.GET.get('keyword') keyword = request.GET.get('keyword')
web_socket_host = CONF.get('websocket', 'web_socket_host') web_socket_host = CONF.get('websocket', 'web_socket_host')
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: if keyword:
posts = contact_list = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ posts = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \
.filter(is_finished=1).order_by('-start_time') .filter(is_finished=1).order_by('-start_time')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
else: else:
posts = Log.objects.filter(is_finished=1).order_by('-start_time') 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) contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
return render_to_response('jlog/log_offline.html', locals()) 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):
posts = Log.objects.filter(is_finished=1).filter(user=username).order_by('-start_time')
return render_to_response('jlog/log_offline.html', locals(), context_instance=RequestContext(request))
def log_kill(request, offset): def log_kill(request, offset):
@ -51,7 +85,7 @@ def log_kill(request, offset):
if pid: if pid:
os.kill(int(pid), 9) os.kill(int(pid), 9)
Log.objects.filter(pid=pid).update(is_finished=1, end_time=datetime.now()) Log.objects.filter(pid=pid).update(is_finished=1, end_time=datetime.now())
return HttpResponseRedirect('jlog/log_offline.html', locals()) return HttpResponseRedirect('jlog/log_offline.html', locals(), context_instance=RequestContext(request))
def log_history(request): def log_history(request):
@ -69,13 +103,23 @@ def log_history(request):
def log_search(request): def log_search(request):
keyword = request.GET.get('keyword') keyword = request.GET.get('keyword')
env = request.GET.get('env') env = request.GET.get('env')
dept_id = get_user_dept(request)
dept_name = DEPT.objects.get(id=dept_id).name
if is_super_user(request):
if env == 'online': if env == 'online':
posts = contact_list = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ posts = contact_list = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \
.filter(is_finished=0).order_by('-start_time') .filter(is_finished=0).order_by('-start_time')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
elif env == 'offline': elif env == 'offline':
posts = contact_list = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \ posts = contact_list = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \
.filter(is_finished=1).order_by('-start_time') .filter(is_finished=1).order_by('-start_time')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
return render_to_response('jlog/log_search.html', locals()) elif is_group_admin(request):
if env == 'online':
posts = contact_list = Log.objects.filter(Q(user__contains=keyword) | Q(host__contains=keyword)) \
.filter(is_finished=0).filter(dept_name=dept_name).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(dept_name=dept_name).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))

View File

@ -29,6 +29,15 @@ LOGIN_NAME = getpass.getuser()
LDAP_ENABLE = CONF.getint('ldap', 'ldap_enable') LDAP_ENABLE = CONF.getint('ldap', 'ldap_enable')
# def user_perm_group_api(username):
# user = User.objects.get(username=username)
# if user:
# perm_list = []
# user_group_all = user.group.all()
# for user_group in user_group_all:
# perm_list.extend(user_group.perm_set.all())
class LDAPMgmt(): class LDAPMgmt():
def __init__(self, def __init__(self,
host_url, host_url,
@ -177,12 +186,27 @@ def is_super_user(request):
def is_group_admin(request): def is_group_admin(request):
print request.session.get('role_id'), type(request.session.get('role_id'))
if request.session.get('role_id') == 1: if request.session.get('role_id') == 1:
return True return True
else: else:
return False return False
def is_common_user(request):
if request.session.get('role_id') == 0:
return True
else:
return False
def get_user_dept(request):
user_id = request.session.get('user_id')
if user_id:
user_dept = User.objects.get(id=user_id).dept
return user_dept.id
def api_user(request): def api_user(request):
hosts = Log.objects.filter(is_finished=0).count() hosts = Log.objects.filter(is_finished=0).count()
users = Log.objects.filter(is_finished=0).values('user').distinct().count() users = Log.objects.filter(is_finished=0).values('user').distinct().count()

View File

@ -1,4 +1,5 @@
from juser.models import User from juser.models import User
from jasset.models import Asset
def name_proc(request): def name_proc(request):
@ -6,8 +7,11 @@ def name_proc(request):
role_id = request.session.get('role_id') role_id = request.session.get('role_id')
user_total_num = User.objects.all().count() user_total_num = User.objects.all().count()
user_active_num = User.objects.filter(is_active=True).count() user_active_num = User.objects.filter(is_active=True).count()
host_total_num = Asset.objects.all().count()
host_active_num = Asset.objects.filter(is_active=True).count()
request.session.set_expiry(3600) request.session.set_expiry(3600)
return {'session_user_id': user_id, 'session_role_id': role_id, return {'session_user_id': user_id, 'session_role_id': role_id,
'user_total_num': user_total_num, 'user_active_num': user_active_num} 'user_total_num': user_total_num, 'user_active_num': user_active_num,
'host_total_num': host_total_num, 'host_active_num': host_active_num}

396
jumpserver/ssh.py Normal file
View File

@ -0,0 +1,396 @@
#!/usr/bin/env python
import socket
import sys
import os
import ast
import select
import time
from datetime import datetime
import paramiko
import struct
import fcntl
import signal
import textwrap
import django
import getpass
import fnmatch
import optparse
import readline
from multiprocessing import Pool
from ConfigParser import ConfigParser
from django.core.exceptions import ObjectDoesNotExist
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
django.setup()
from juser.models import User
from jasset.models import Asset
from jlog.models import Log
from jumpserver.views import PyCrypt
from jumpserver.api import user_perm_asset_api, user_perm_group_api
try:
import termios
import tty
except ImportError:
print '\033[1;31mOnly postfix supported.\033[0m'
time.sleep(3)
sys.exit()
CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
BASE_DIR = os.path.dirname(CURRENT_DIR)
CONF = ConfigParser()
CONF.read(os.path.join(BASE_DIR, 'jumpserver.conf'))
LOG_DIR = os.path.join(BASE_DIR, 'logs')
SSH_KEY_DIR = os.path.join(BASE_DIR, 'keys')
SERVER_KEY_DIR = os.path.join(SSH_KEY_DIR, 'server')
KEY = CONF.get('web', 'key')
LOGIN_NAME = getpass.getuser()
def color_print(msg, color='blue'):
"""Print colorful string."""
color_msg = {'blue': '\033[1;36m%s\033[0m',
'green': '\033[1;32m%s\033[0m',
'red': '\033[1;31m%s\033[0m'}
print color_msg.get(color, 'blue') % msg
def color_print_exit(msg, color='red'):
"""Print colorful string and exit."""
color_print(msg, color=color)
time.sleep(2)
sys.exit()
class ServerError(Exception):
pass
def get_win_size():
"""This function use to get the size of the windows!"""
if 'TIOCGWINSZ' in dir(termios):
TIOCGWINSZ = termios.TIOCGWINSZ
else:
TIOCGWINSZ = 1074295912L # Assume
s = struct.pack('HHHH', 0, 0, 0, 0)
x = fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ, s)
return struct.unpack('HHHH', x)[0:2]
def set_win_size(sig, data):
"""This function use to set the window size of the terminal!"""
try:
win_size = get_win_size()
channel.resize_pty(height=win_size[0], width=win_size[1])
except:
pass
def get_object(model, **kwargs):
try:
the_object = model.objects.get(**kwargs)
except ObjectDoesNotExist:
raise ServerError('Object get %s failed.' % str(kwargs.values()))
return the_object
def log_record(username, host):
"""Logging user command and output."""
connect_log_dir = os.path.join(LOG_DIR, 'connect')
timestamp_start = int(time.time())
today = time.strftime('%Y%m%d', time.localtime(timestamp_start))
time_now = time.strftime('%H%M%S', time.localtime(timestamp_start))
today_connect_log_dir = os.path.join(connect_log_dir, today)
log_filename = '%s_%s_%s.log' % (username, host, time_now)
log_file_path = os.path.join(today_connect_log_dir, log_filename)
pid = os.getpid()
ip_list = []
remote_ip = os.popen("who |grep `ps aux |gawk '{if ($2==%s) print $1}'` |gawk '{print $5}'|tr -d '()'" % pid).readlines()
for ip in remote_ip:
ip_list.append(ip.strip('\n'))
ip_list = ','.join(list(set(ip_list)))
if not os.path.isdir(today_connect_log_dir):
try:
os.makedirs(today_connect_log_dir)
os.chmod(today_connect_log_dir, 0777)
except OSError:
raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, connect_log_dir))
try:
log_file = open(log_file_path, 'a')
except IOError:
raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir)
log = Log(user=username, host=host, remote_ip=ip_list, log_path=log_file_path, start_time=datetime.now(), pid=pid)
log_file.write('Starttime is %s\n' % datetime.now())
log.save()
return log_file, log
def posix_shell(chan, username, host):
"""
Use paramiko channel connect server interactive.
"""
log_file, log = log_record(username, host)
old_tty = termios.tcgetattr(sys.stdin)
try:
tty.setraw(sys.stdin.fileno())
tty.setcbreak(sys.stdin.fileno())
chan.settimeout(0.0)
while True:
try:
r, w, e = select.select([chan, sys.stdin], [], [])
except:
pass
if chan in r:
try:
x = chan.recv(1024)
if len(x) == 0:
break
sys.stdout.write(x)
sys.stdout.flush()
log_file.write(x)
log_file.flush()
except socket.timeout:
pass
if sys.stdin in r:
x = os.read(sys.stdin.fileno(), 1)
if len(x) == 0:
break
chan.send(x)
finally:
timestamp_end = time.time()
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
log_file.write('Endtime is %s' % datetime.now())
log_file.close()
log.is_finished = True
log.log_finished = False
log.end_time = datetime.now()
log.save()
def get_user_host(username):
"""Get the hosts of under the user control."""
hosts_attr = {}
asset_all = user_perm_asset_api(username)
for asset in asset_all:
hosts_attr[asset.ip] = [asset.id, asset.comment]
return hosts_attr
def get_user_hostgroup(username):
"""Get the hostgroups of under the user control."""
groups_attr = {}
group_all = user_perm_group_api(username)
for group in group_all:
groups_attr[group.name] = [group.id, group.comment]
return groups_attr
def get_connect_item(username, ip):
cryptor = PyCrypt(KEY)
asset = get_object(Asset, ip=ip)
port = asset.port
if not asset.is_active:
raise ServerError('Host %s is not active.' % ip)
user = get_object(User, username=username)
if not user.is_active:
raise ServerError('User %s is not active.' % username)
login_type_dict = {
'L': user.ldap_pwd,
}
if asset.login_type in login_type_dict:
password = cryptor.decrypt(login_type_dict[asset.login_type])
return username, password, ip, port
elif asset.login_type == 'M':
username = asset.username
password = cryptor.decrypt(asset.password)
return username, password, ip, port
else:
raise ServerError('Login type is not in ["L", "M"]')
def verify_connect(username, part_ip):
hosts_attr = get_user_host(username)
hosts = hosts_attr.keys()
ip_matched = [ip for ip in hosts if part_ip in ip]
if len(ip_matched) > 1:
for ip in ip_matched:
print '%s -- %s' % (ip, hosts_attr[ip][1])
elif len(ip_matched) < 1:
color_print('No Permission or No host.', 'red')
else:
username, password, host, port = get_connect_item(username, ip_matched[0])
print username, password, host, port
connect(username, password, host, port, LOGIN_NAME)
def print_prompt():
msg = """\033[1;32m### Welcome Use JumpServer To Login. ### \033[0m
1) Type \033[32mIP ADDRESS\033[0m To Login.
2) Type \033[32mP/p\033[0m To Print The Servers You Available.
3) Type \033[32mG/g\033[0m To Print The Server Groups You Available.
4) Type \033[32mE/e\033[0m To Execute Command On Several Servers.
5) Type \033[32mQ/q\033[0m To Quit.
"""
print textwrap.dedent(msg)
def print_user_host(username):
hosts_attr = get_user_host(username)
hosts = hosts_attr.keys()
hosts.sort()
for ip in hosts:
print '%s -- %s' % (ip, hosts_attr[ip][1])
def print_user_hostgroup(username):
group_attr = get_user_hostgroup(username)
groups = group_attr.keys()
for g in groups:
print '%s -- %s' % (g, group_attr[g][1])
def connect(username, password, host, port, login_name):
"""
Connect server.
"""
ps1 = "PS1='[\u@%s \W]\$ '\n" % host
login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % host
# Make a ssh connection
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(host, port=port, username=username, password=password, compress=True)
except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException:
raise ServerError('Authentication Error.')
except socket.error:
raise ServerError('Connect SSH Socket Port Error, Please Correct it.')
# Make a channel and set windows size
global channel
win_size = get_win_size()
channel = ssh.invoke_shell(height=win_size[0], width=win_size[1])
#channel.resize_pty(height=win_size[0], width=win_size[1])
try:
signal.signal(signal.SIGWINCH, set_win_size)
except:
pass
# Set PS1 and msg it
channel.send(ps1)
channel.send(login_msg)
# Make ssh interactive tunnel
posix_shell(channel, login_name, host)
# Shutdown channel socket
channel.close()
ssh.close()
def remote_exec_cmd(ip, port, username, password, cmd):
try:
time.sleep(5)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ip, port, username, password, timeout=5)
stdin, stdout, stderr = ssh.exec_command("bash -l -c '%s'" % cmd)
out = stdout.readlines()
err = stderr.readlines()
color_print('%s:' %ip, 'blue')
for i in out:
color_print(" " * 4 + i.strip(), 'green')
for j in err:
color_print(" " * 4 + j.strip(), 'red')
ssh.close()
except Exception as e:
color_print(ip + ':', 'blue')
color_print(str(e), 'red')
def multi_remote_exec_cmd(hosts, username, cmd):
pool = Pool(processes=5)
for host in hosts:
username, password, ip, port = get_connect_item(username, host)
pool.apply_async(remote_exec_cmd, (ip, port, username, password, cmd))
pool.close()
pool.join()
def exec_cmd_servers(username):
hosts = []
color_print("Input the Host IP(s),Separated by Commas, q/Q to Quit.\n \
You can choose in the following IP(s), Use Linux / Unix glob.", 'green')
print_user_host(LOGIN_NAME)
while True:
inputs = raw_input('\033[1;32mip(s)>: \033[0m')
if inputs in ['q', 'Q']:
break
get_hosts = get_user_host(username).keys()
for host in get_hosts:
if fnmatch.fnmatch(host, inputs):
hosts.append(host.strip())
if len(hosts) == 0:
color_print("Check again, Not matched any ip!", 'red')
continue
else:
print "You matched ip: %s" % hosts
color_print("Input the Command , The command will be Execute on servers, q/Q to quit.", 'green')
while True:
cmd = raw_input('\033[1;32mCmd(s): \033[0m')
if cmd in ['q', 'Q']:
break
exec_log_dir = os.path.join(LOG_DIR, 'exec_cmds')
if not os.path.isdir(exec_log_dir):
os.mkdir(exec_log_dir)
os.chmod(exec_log_dir, 0777)
filename = "%s/%s.log" % (exec_log_dir, time.strftime('%Y%m%d'))
f = open(filename, 'a')
f.write("DateTime: %s User: %s Host: %s Cmds: %s\n" %
(time.strftime('%Y/%m/%d %H:%M:%S'), username, hosts, cmd))
multi_remote_exec_cmd(hosts, username, cmd)
def help():
global p, options, arguments
usage = "usage: %prog '' [options] arg1 [options] arg2"
p = optparse.OptionParser(usage=usage)
p.add_option('-p', '--host', help = "Print The Servers You Available.")
p.add_option('-g', '--group', help = "Print The Server Groups You Available.")
options, arguments = p.parse_args()
def main():
help()
if options.host:
pass
elif options.group:
pass
else:
try:
verify_connect(LOGIN_NAME, sys.argv[1])
except ServerError, e:
color_print(e, 'red')
if __name__ == '__main__':
main()

View File

@ -169,22 +169,6 @@ def string_length(string, length):
return '%s ...' % string[0:length] return '%s ...' % string[0:length]
@register.filter(name='filter_private')
def filter_private(group):
agroup = []
pattern = re.compile(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
p = BisGroup.objects.get(name='ALL')
for g in group:
if not pattern.match(g.name):
agroup.append(g)
try:
agroup.remove(p)
except ValueError:
pass
return agroup
@register.filter(name='to_name') @register.filter(name='to_name')
def to_name(user_id): def to_name(user_id):
try: try:

395
ssh.py Normal file
View File

@ -0,0 +1,395 @@
#!/usr/bin/env python
import socket
import sys
import os
import ast
import select
import time
from datetime import datetime
import paramiko
import struct
import fcntl
import signal
import textwrap
import django
import getpass
import fnmatch
import optparse
import readline
from multiprocessing import Pool
from ConfigParser import ConfigParser
from django.core.exceptions import ObjectDoesNotExist
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
django.setup()
from juser.models import User
from jasset.models import Asset
from jlog.models import Log
from jumpserver.views import PyCrypt
from jumpserver.api import user_perm_asset_api, user_perm_group_api
try:
import termios
import tty
except ImportError:
print '\033[1;31mOnly postfix supported.\033[0m'
time.sleep(3)
sys.exit()
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
CONF = ConfigParser()
CONF.read(os.path.join(BASE_DIR, 'jumpserver.conf'))
LOG_DIR = os.path.join(BASE_DIR, 'logs')
SSH_KEY_DIR = os.path.join(BASE_DIR, 'keys')
SERVER_KEY_DIR = os.path.join(SSH_KEY_DIR, 'server')
KEY = CONF.get('web', 'key')
LOGIN_NAME = getpass.getuser()
def color_print(msg, color='blue'):
"""Print colorful string."""
color_msg = {'blue': '\033[1;36m%s\033[0m',
'green': '\033[1;32m%s\033[0m',
'red': '\033[1;31m%s\033[0m'}
print color_msg.get(color, 'blue') % msg
def color_print_exit(msg, color='red'):
"""Print colorful string and exit."""
color_print(msg, color=color)
time.sleep(2)
sys.exit()
class ServerError(Exception):
pass
def get_win_size():
"""This function use to get the size of the windows!"""
if 'TIOCGWINSZ' in dir(termios):
TIOCGWINSZ = termios.TIOCGWINSZ
else:
TIOCGWINSZ = 1074295912L # Assume
s = struct.pack('HHHH', 0, 0, 0, 0)
x = fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ, s)
return struct.unpack('HHHH', x)[0:2]
def set_win_size(sig, data):
"""This function use to set the window size of the terminal!"""
try:
win_size = get_win_size()
channel.resize_pty(height=win_size[0], width=win_size[1])
except:
pass
def get_object(model, **kwargs):
try:
the_object = model.objects.get(**kwargs)
except ObjectDoesNotExist:
raise ServerError('Object get %s failed.' % str(kwargs.values()))
return the_object
def log_record(username, host):
"""Logging user command and output."""
connect_log_dir = os.path.join(LOG_DIR, 'connect')
timestamp_start = int(time.time())
today = time.strftime('%Y%m%d', time.localtime(timestamp_start))
time_now = time.strftime('%H%M%S', time.localtime(timestamp_start))
today_connect_log_dir = os.path.join(connect_log_dir, today)
log_filename = '%s_%s_%s.log' % (username, host, time_now)
log_file_path = os.path.join(today_connect_log_dir, log_filename)
pid = os.getpid()
ip_list = []
remote_ip = os.popen("who |grep `ps aux |gawk '{if ($2==%s) print $1}'` |gawk '{print $5}'|tr -d '()'" % pid).readlines()
for ip in remote_ip:
ip_list.append(ip.strip('\n'))
ip_list = ','.join(list(set(ip_list)))
if not os.path.isdir(today_connect_log_dir):
try:
os.makedirs(today_connect_log_dir)
os.chmod(today_connect_log_dir, 0777)
except OSError:
raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, connect_log_dir))
try:
log_file = open(log_file_path, 'a')
except IOError:
raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir)
log = Log(user=username, host=host, remote_ip=ip_list, log_path=log_file_path, start_time=datetime.now(), pid=pid)
log_file.write('Starttime is %s\n' % datetime.now())
log.save()
return log_file, log
def posix_shell(chan, username, host):
"""
Use paramiko channel connect server interactive.
"""
log_file, log = log_record(username, host)
old_tty = termios.tcgetattr(sys.stdin)
try:
tty.setraw(sys.stdin.fileno())
tty.setcbreak(sys.stdin.fileno())
chan.settimeout(0.0)
while True:
try:
r, w, e = select.select([chan, sys.stdin], [], [])
except:
pass
if chan in r:
try:
x = chan.recv(1024)
if len(x) == 0:
break
sys.stdout.write(x)
sys.stdout.flush()
log_file.write(x)
log_file.flush()
except socket.timeout:
pass
if sys.stdin in r:
x = os.read(sys.stdin.fileno(), 1)
if len(x) == 0:
break
chan.send(x)
finally:
timestamp_end = time.time()
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
log_file.write('Endtime is %s' % datetime.now())
log_file.close()
log.is_finished = True
log.log_finished = False
log.end_time = datetime.now()
log.save()
def get_user_host(username):
"""Get the hosts of under the user control."""
hosts_attr = {}
asset_all = user_perm_asset_api(username)
for asset in asset_all:
hosts_attr[asset.ip] = [asset.id, asset.comment]
return hosts_attr
def get_user_hostgroup(username):
"""Get the hostgroups of under the user control."""
groups_attr = {}
group_all = user_perm_group_api(username)
for group in group_all:
groups_attr[group.name] = [group.id, group.comment]
return groups_attr
def get_connect_item(username, ip):
cryptor = PyCrypt(KEY)
asset = get_object(Asset, ip=ip)
port = asset.port
if not asset.is_active:
raise ServerError('Host %s is not active.' % ip)
user = get_object(User, username=username)
if not user.is_active:
raise ServerError('User %s is not active.' % username)
login_type_dict = {
'L': user.ldap_pwd,
}
if asset.login_type in login_type_dict:
password = cryptor.decrypt(login_type_dict[asset.login_type])
return username, password, ip, port
elif asset.login_type == 'M':
username = asset.username
password = cryptor.decrypt(asset.password)
return username, password, ip, port
else:
raise ServerError('Login type is not in ["L", "M"]')
def verify_connect(username, part_ip):
hosts_attr = get_user_host(username)
hosts = hosts_attr.keys()
ip_matched = [ip for ip in hosts if part_ip in ip]
if len(ip_matched) > 1:
for ip in ip_matched:
print '%s -- %s' % (ip, hosts_attr[ip][1])
elif len(ip_matched) < 1:
color_print('No Permission or No host.', 'red')
else:
username, password, host, port = get_connect_item(username, ip_matched[0])
print username, password, host, port
connect(username, password, host, port, LOGIN_NAME)
def print_prompt():
msg = """\033[1;32m### Welcome Use JumpServer To Login. ### \033[0m
1) Type \033[32mIP ADDRESS\033[0m To Login.
2) Type \033[32mP/p\033[0m To Print The Servers You Available.
3) Type \033[32mG/g\033[0m To Print The Server Groups You Available.
4) Type \033[32mE/e\033[0m To Execute Command On Several Servers.
5) Type \033[32mQ/q\033[0m To Quit.
"""
print textwrap.dedent(msg)
def print_user_host(username):
hosts_attr = get_user_host(username)
hosts = hosts_attr.keys()
hosts.sort()
for ip in hosts:
print '%s -- %s' % (ip, hosts_attr[ip][1])
def print_user_hostgroup(username):
group_attr = get_user_hostgroup(username)
groups = group_attr.keys()
for g in groups:
print '%s -- %s' % (g, group_attr[g][1])
def connect(username, password, host, port, login_name):
"""
Connect server.
"""
ps1 = "PS1='[\u@%s \W]\$ '\n" % host
login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % host
# Make a ssh connection
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(host, port=port, username=username, password=password, compress=True)
except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException:
raise ServerError('Authentication Error.')
except socket.error:
raise ServerError('Connect SSH Socket Port Error, Please Correct it.')
# Make a channel and set windows size
global channel
win_size = get_win_size()
channel = ssh.invoke_shell(height=win_size[0], width=win_size[1])
#channel.resize_pty(height=win_size[0], width=win_size[1])
try:
signal.signal(signal.SIGWINCH, set_win_size)
except:
pass
# Set PS1 and msg it
channel.send(ps1)
channel.send(login_msg)
# Make ssh interactive tunnel
posix_shell(channel, login_name, host)
# Shutdown channel socket
channel.close()
ssh.close()
def remote_exec_cmd(ip, port, username, password, cmd):
try:
time.sleep(5)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ip, port, username, password, timeout=5)
stdin, stdout, stderr = ssh.exec_command("bash -l -c '%s'" % cmd)
out = stdout.readlines()
err = stderr.readlines()
color_print('%s:' %ip, 'blue')
for i in out:
color_print(" " * 4 + i.strip(), 'green')
for j in err:
color_print(" " * 4 + j.strip(), 'red')
ssh.close()
except Exception as e:
color_print(ip + ':', 'blue')
color_print(str(e), 'red')
def multi_remote_exec_cmd(hosts, username, cmd):
pool = Pool(processes=5)
for host in hosts:
username, password, ip, port = get_connect_item(username, host)
pool.apply_async(remote_exec_cmd, (ip, port, username, password, cmd))
pool.close()
pool.join()
def exec_cmd_servers(username):
hosts = []
color_print("Input the Host IP(s),Separated by Commas, q/Q to Quit.\n \
You can choose in the following IP(s), Use Linux / Unix glob.", 'green')
print_user_host(LOGIN_NAME)
while True:
inputs = raw_input('\033[1;32mip(s)>: \033[0m')
if inputs in ['q', 'Q']:
break
get_hosts = get_user_host(username).keys()
for host in get_hosts:
if fnmatch.fnmatch(host, inputs):
hosts.append(host.strip())
if len(hosts) == 0:
color_print("Check again, Not matched any ip!", 'red')
continue
else:
print "You matched ip: %s" % hosts
color_print("Input the Command , The command will be Execute on servers, q/Q to quit.", 'green')
while True:
cmd = raw_input('\033[1;32mCmd(s): \033[0m')
if cmd in ['q', 'Q']:
break
exec_log_dir = os.path.join(LOG_DIR, 'exec_cmds')
if not os.path.isdir(exec_log_dir):
os.mkdir(exec_log_dir)
os.chmod(exec_log_dir, 0777)
filename = "%s/%s.log" % (exec_log_dir, time.strftime('%Y%m%d'))
f = open(filename, 'a')
f.write("DateTime: %s User: %s Host: %s Cmds: %s\n" %
(time.strftime('%Y/%m/%d %H:%M:%S'), username, hosts, cmd))
multi_remote_exec_cmd(hosts, username, cmd)
def help():
global p, options, arguments
usage = "usage: %prog '' [options] arg1 [options] arg2"
p = optparse.OptionParser(usage=usage)
p.add_option('-p', '--host', help = "Print The Servers You Available.")
p.add_option('-g', '--group', help = "Print The Server Groups You Available.")
options, arguments = p.parse_args()
def main():
help()
if options.host:
pass
elif options.group:
pass
else:
try:
verify_connect(LOGIN_NAME, sys.argv[1])
except ServerError, e:
color_print(e, 'red')
if __name__ == '__main__':
main()

View File

@ -0,0 +1,977 @@
/*!
* FullCalendar v2.2.0 Stylesheet
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2013 Adam Shaw
*/
.fc {
direction: ltr;
text-align: left;
}
.fc-rtl {
text-align: right;
}
body .fc { /* extra precedence to overcome jqui */
font-size: 1em;
}
/* Colors
--------------------------------------------------------------------------------------------------*/
.fc-unthemed th,
.fc-unthemed td,
.fc-unthemed hr,
.fc-unthemed thead,
.fc-unthemed tbody,
.fc-unthemed .fc-row,
.fc-unthemed .fc-popover {
border-color: #ddd;
}
.fc-unthemed .fc-popover {
background-color: #fff;
}
.fc-unthemed hr,
.fc-unthemed .fc-popover .fc-header {
background: #eee;
}
.fc-unthemed .fc-popover .fc-header .fc-close {
color: #666;
}
.fc-unthemed .fc-today {
background: #fcf8e3;
}
.fc-highlight { /* when user is selecting cells */
background: #bce8f1;
opacity: .3;
filter: alpha(opacity=30); /* for IE */
}
.fc-bgevent { /* default look for background events */
background: rgb(143, 223, 130);
opacity: .3;
filter: alpha(opacity=30); /* for IE */
}
.fc-nonbusiness { /* default look for non-business-hours areas */
/* will inherit .fc-bgevent's styles */
background: #ccc;
}
/* Icons (inline elements with styled text that mock arrow icons)
--------------------------------------------------------------------------------------------------*/
.fc-icon {
display: inline-block;
font-size: 2em;
line-height: .5em;
height: .5em; /* will make the total height 1em */
font-family: "Courier New", Courier, monospace;
}
.fc-icon-left-single-arrow:after {
content: "\02039";
font-weight: bold;
}
.fc-icon-right-single-arrow:after {
content: "\0203A";
font-weight: bold;
}
.fc-icon-left-double-arrow:after {
content: "\000AB";
}
.fc-icon-right-double-arrow:after {
content: "\000BB";
}
.fc-icon-x:after {
content: "\000D7";
}
/* Buttons (styled <button> tags, normalized to work cross-browser)
--------------------------------------------------------------------------------------------------*/
.fc button {
/* force height to include the border and padding */
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
/* dimensions */
margin: 0;
height: 2.1em;
padding: 0 .6em;
/* text & cursor */
font-size: 1em; /* normalize */
white-space: nowrap;
cursor: pointer;
}
/* Firefox has an annoying inner border */
.fc button::-moz-focus-inner { margin: 0; padding: 0; }
.fc-state-default { /* non-theme */
border: 1px solid;
}
.fc-state-default.fc-corner-left { /* non-theme */
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.fc-state-default.fc-corner-right { /* non-theme */
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
/* icons in buttons */
.fc button .fc-icon { /* non-theme */
position: relative;
top: .05em; /* seems to be a good adjustment across browsers */
margin: 0 .1em;
}
/*
button states
borrowed from twitter bootstrap (http://twitter.github.com/bootstrap/)
*/
.fc-state-default {
background-color: #f5f5f5;
background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
background-repeat: repeat-x;
border-color: #e6e6e6 #e6e6e6 #bfbfbf;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
color: #333;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
}
.fc-state-hover,
.fc-state-down,
.fc-state-active,
.fc-state-disabled {
color: #333333;
background-color: #e6e6e6;
}
.fc-state-hover {
color: #333333;
text-decoration: none;
background-position: 0 -15px;
-webkit-transition: background-position 0.1s linear;
-moz-transition: background-position 0.1s linear;
-o-transition: background-position 0.1s linear;
transition: background-position 0.1s linear;
}
.fc-state-down,
.fc-state-active {
background-color: #cccccc;
background-image: none;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
}
.fc-state-disabled {
cursor: default;
background-image: none;
opacity: 0.65;
filter: alpha(opacity=65);
box-shadow: none;
}
/* Buttons Groups
--------------------------------------------------------------------------------------------------*/
.fc-button-group {
display: inline-block;
}
/*
every button that is not first in a button group should scootch over one pixel and cover the
previous button's border...
*/
.fc .fc-button-group > * { /* extra precedence b/c buttons have margin set to zero */
float: left;
margin: 0 0 0 -1px;
}
.fc .fc-button-group > :first-child { /* same */
margin-left: 0;
}
/* Popover
--------------------------------------------------------------------------------------------------*/
.fc-popover {
position: absolute;
box-shadow: 0 2px 6px rgba(0,0,0,.15);
}
.fc-popover .fc-header {
padding: 2px 4px;
}
.fc-popover .fc-header .fc-title {
margin: 0 2px;
}
.fc-popover .fc-header .fc-close {
cursor: pointer;
}
.fc-ltr .fc-popover .fc-header .fc-title,
.fc-rtl .fc-popover .fc-header .fc-close {
float: left;
}
.fc-rtl .fc-popover .fc-header .fc-title,
.fc-ltr .fc-popover .fc-header .fc-close {
float: right;
}
/* unthemed */
.fc-unthemed .fc-popover {
border-width: 1px;
border-style: solid;
}
.fc-unthemed .fc-popover .fc-header .fc-close {
font-size: 25px;
margin-top: 4px;
}
/* jqui themed */
.fc-popover > .ui-widget-header + .ui-widget-content {
border-top: 0; /* where they meet, let the header have the border */
}
/* Misc Reusable Components
--------------------------------------------------------------------------------------------------*/
.fc hr {
height: 0;
margin: 0;
padding: 0 0 2px; /* height is unreliable across browsers, so use padding */
border-style: solid;
border-width: 1px 0;
}
.fc-clear {
clear: both;
}
.fc-bg,
.fc-bgevent-skeleton,
.fc-highlight-skeleton,
.fc-helper-skeleton {
/* these element should always cling to top-left/right corners */
position: absolute;
top: 0;
left: 0;
right: 0;
}
.fc-bg {
bottom: 0; /* strech bg to bottom edge */
}
.fc-bg table {
height: 100%; /* strech bg to bottom edge */
}
/* Tables
--------------------------------------------------------------------------------------------------*/
.fc table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
border-spacing: 0;
font-size: 1em; /* normalize cross-browser */
}
.fc th {
text-align: center;
}
.fc th,
.fc td {
border-style: solid;
border-width: 1px;
padding: 0;
vertical-align: top;
}
.fc td.fc-today {
border-style: double; /* overcome neighboring borders */
}
/* Fake Table Rows
--------------------------------------------------------------------------------------------------*/
.fc .fc-row { /* extra precedence to overcome themes w/ .ui-widget-content forcing a 1px border */
/* no visible border by default. but make available if need be (scrollbar width compensation) */
border-style: solid;
border-width: 0;
}
.fc-row table {
/* don't put left/right border on anything within a fake row.
the outer tbody will worry about this */
border-left: 0 hidden transparent;
border-right: 0 hidden transparent;
/* no bottom borders on rows */
border-bottom: 0 hidden transparent;
}
.fc-row:first-child table {
border-top: 0 hidden transparent; /* no top border on first row */
}
/* Day Row (used within the header and the DayGrid)
--------------------------------------------------------------------------------------------------*/
.fc-row {
position: relative;
}
.fc-row .fc-bg {
z-index: 1;
}
/* highlighting cells & background event skeleton */
.fc-row .fc-bgevent-skeleton,
.fc-row .fc-highlight-skeleton {
bottom: 0; /* stretch skeleton to bottom of row */
}
.fc-row .fc-bgevent-skeleton table,
.fc-row .fc-highlight-skeleton table {
height: 100%; /* stretch skeleton to bottom of row */
}
.fc-row .fc-highlight-skeleton td,
.fc-row .fc-bgevent-skeleton td {
border-color: transparent;
}
.fc-row .fc-bgevent-skeleton {
z-index: 2;
}
.fc-row .fc-highlight-skeleton {
z-index: 3;
}
/*
row content (which contains day/week numbers and events) as well as "helper" (which contains
temporary rendered events).
*/
.fc-row .fc-content-skeleton {
position: relative;
z-index: 4;
padding-bottom: 2px; /* matches the space above the events */
}
.fc-row .fc-helper-skeleton {
z-index: 5;
}
.fc-row .fc-content-skeleton td,
.fc-row .fc-helper-skeleton td {
/* see-through to the background below */
background: none; /* in case <td>s are globally styled */
border-color: transparent;
/* don't put a border between events and/or the day number */
border-bottom: 0;
}
.fc-row .fc-content-skeleton tbody td, /* cells with events inside (so NOT the day number cell) */
.fc-row .fc-helper-skeleton tbody td {
/* don't put a border between event cells */
border-top: 0;
}
/* Scrolling Container
--------------------------------------------------------------------------------------------------*/
.fc-scroller { /* this class goes on elements for guaranteed vertical scrollbars */
overflow-y: scroll;
overflow-x: hidden;
}
.fc-scroller > * { /* we expect an immediate inner element */
position: relative; /* re-scope all positions */
width: 100%; /* hack to force re-sizing this inner element when scrollbars appear/disappear */
overflow: hidden; /* don't let negative margins or absolute positioning create further scroll */
}
/* Global Event Styles
--------------------------------------------------------------------------------------------------*/
.fc-event {
position: relative; /* for resize handle and other inner positioning */
display: block; /* make the <a> tag block */
font-size: .85em;
line-height: 1.3;
border-radius: 3px;
border: 1px solid #3a87ad; /* default BORDER color */
background-color: #3a87ad; /* default BACKGROUND color */
font-weight: normal; /* undo jqui's ui-widget-header bold */
}
/* overpower some of bootstrap's and jqui's styles on <a> tags */
.fc-event,
.fc-event:hover,
.ui-widget .fc-event {
color: #fff; /* default TEXT color */
text-decoration: none; /* if <a> has an href */
}
.fc-event[href],
.fc-event.fc-draggable {
cursor: pointer; /* give events with links and draggable events a hand mouse pointer */
}
.fc-not-allowed, /* causes a "warning" cursor. applied on body */
.fc-not-allowed .fc-event { /* to override an event's custom cursor */
cursor: not-allowed;
}
/* DayGrid events
----------------------------------------------------------------------------------------------------
We use the full "fc-day-grid-event" class instead of using descendants because the event won't
be a descendant of the grid when it is being dragged.
*/
.fc-day-grid-event {
margin: 1px 2px 0; /* spacing between events and edges */
padding: 0 1px;
}
/* events that are continuing to/from another week. kill rounded corners and butt up against edge */
.fc-ltr .fc-day-grid-event.fc-not-start,
.fc-rtl .fc-day-grid-event.fc-not-end {
margin-left: 0;
border-left-width: 0;
padding-left: 1px; /* replace the border with padding */
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.fc-ltr .fc-day-grid-event.fc-not-end,
.fc-rtl .fc-day-grid-event.fc-not-start {
margin-right: 0;
border-right-width: 0;
padding-right: 1px; /* replace the border with padding */
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.fc-day-grid-event > .fc-content { /* force events to be one-line tall */
white-space: nowrap;
overflow: hidden;
}
.fc-day-grid-event .fc-time {
font-weight: bold;
}
/* resize handle (outside of fc-content, so can go outside of bounds) */
.fc-day-grid-event .fc-resizer {
position: absolute;
top: 0;
bottom: 0;
width: 7px;
}
.fc-ltr .fc-day-grid-event .fc-resizer {
right: -3px;
cursor: e-resize;
}
.fc-rtl .fc-day-grid-event .fc-resizer {
left: -3px;
cursor: w-resize;
}
/* Event Limiting
--------------------------------------------------------------------------------------------------*/
/* "more" link that represents hidden events */
a.fc-more {
margin: 1px 3px;
font-size: .85em;
cursor: pointer;
text-decoration: none;
}
a.fc-more:hover {
text-decoration: underline;
}
.fc-limited { /* rows and cells that are hidden because of a "more" link */
display: none;
}
/* popover that appears when "more" link is clicked */
.fc-day-grid .fc-row {
z-index: 1; /* make the "more" popover one higher than this */
}
.fc-more-popover {
z-index: 2;
width: 220px;
}
.fc-more-popover .fc-event-container {
padding: 10px;
}
/* Toolbar
--------------------------------------------------------------------------------------------------*/
.fc-toolbar {
text-align: center;
margin-bottom: 1em;
}
.fc-toolbar .fc-left {
float: left;
}
.fc-toolbar .fc-right {
float: right;
}
.fc-toolbar .fc-center {
display: inline-block;
}
/* the things within each left/right/center section */
.fc .fc-toolbar > * > * { /* extra precedence to override button border margins */
float: left;
margin-left: .75em;
}
/* the first thing within each left/center/right section */
.fc .fc-toolbar > * > :first-child { /* extra precedence to override button border margins */
margin-left: 0;
}
/* title text */
.fc-toolbar h2 {
margin: 0;
}
/* button layering (for border precedence) */
.fc-toolbar button {
position: relative;
}
.fc-toolbar .fc-state-hover,
.fc-toolbar .ui-state-hover {
z-index: 2;
}
.fc-toolbar .fc-state-down {
z-index: 3;
}
.fc-toolbar .fc-state-active,
.fc-toolbar .ui-state-active {
z-index: 4;
}
.fc-toolbar button:focus {
z-index: 5;
}
/* View Structure
--------------------------------------------------------------------------------------------------*/
/* undo twitter bootstrap's box-sizing rules. normalizes positioning techniques */
/* don't do this for the toolbar because we'll want bootstrap to style those buttons as some pt */
.fc-view-container *,
.fc-view-container *:before,
.fc-view-container *:after {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.fc-view, /* scope positioning and z-index's for everything within the view */
.fc-view > table { /* so dragged elements can be above the view's main element */
position: relative;
z-index: 1;
}
/* BasicView
--------------------------------------------------------------------------------------------------*/
/* day row structure */
.fc-basicWeek-view .fc-content-skeleton,
.fc-basicDay-view .fc-content-skeleton {
/* we are sure there are no day numbers in these views, so... */
padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */
padding-bottom: 1em; /* ensure a space at bottom of cell for user selecting/clicking */
}
.fc-basic-view tbody .fc-row {
min-height: 4em; /* ensure that all rows are at least this tall */
}
/* a "rigid" row will take up a constant amount of height because content-skeleton is absolute */
.fc-row.fc-rigid {
overflow: hidden;
}
.fc-row.fc-rigid .fc-content-skeleton {
position: absolute;
top: 0;
left: 0;
right: 0;
}
/* week and day number styling */
.fc-basic-view .fc-week-number,
.fc-basic-view .fc-day-number {
padding: 0 2px;
}
.fc-basic-view td.fc-week-number span,
.fc-basic-view td.fc-day-number {
padding-top: 2px;
padding-bottom: 2px;
}
.fc-basic-view .fc-week-number {
text-align: center;
}
.fc-basic-view .fc-week-number span {
/* work around the way we do column resizing and ensure a minimum width */
display: inline-block;
min-width: 1.25em;
}
.fc-ltr .fc-basic-view .fc-day-number {
text-align: right;
}
.fc-rtl .fc-basic-view .fc-day-number {
text-align: left;
}
.fc-day-number.fc-other-month {
opacity: 0.3;
filter: alpha(opacity=30); /* for IE */
/* opacity with small font can sometimes look too faded
might want to set the 'color' property instead
making day-numbers bold also fixes the problem */
}
/* AgendaView all-day area
--------------------------------------------------------------------------------------------------*/
.fc-agenda-view .fc-day-grid {
position: relative;
z-index: 2; /* so the "more.." popover will be over the time grid */
}
.fc-agenda-view .fc-day-grid .fc-row {
min-height: 3em; /* all-day section will never get shorter than this */
}
.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton {
padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */
padding-bottom: 1em; /* give space underneath events for clicking/selecting days */
}
/* TimeGrid axis running down the side (for both the all-day area and the slot area)
--------------------------------------------------------------------------------------------------*/
.fc .fc-axis { /* .fc to overcome default cell styles */
vertical-align: middle;
padding: 0 4px;
white-space: nowrap;
}
.fc-ltr .fc-axis {
text-align: right;
}
.fc-rtl .fc-axis {
text-align: left;
}
.ui-widget td.fc-axis {
font-weight: normal; /* overcome jqui theme making it bold */
}
/* TimeGrid Structure
--------------------------------------------------------------------------------------------------*/
.fc-time-grid-container, /* so scroll container's z-index is below all-day */
.fc-time-grid { /* so slats/bg/content/etc positions get scoped within here */
position: relative;
z-index: 1;
}
.fc-time-grid {
min-height: 100%; /* so if height setting is 'auto', .fc-bg stretches to fill height */
}
.fc-time-grid table { /* don't put outer borders on slats/bg/content/etc */
border: 0 hidden transparent;
}
.fc-time-grid > .fc-bg {
z-index: 1;
}
.fc-time-grid .fc-slats,
.fc-time-grid > hr { /* the <hr> AgendaView injects when grid is shorter than scroller */
position: relative;
z-index: 2;
}
.fc-time-grid .fc-bgevent-skeleton,
.fc-time-grid .fc-content-skeleton {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.fc-time-grid .fc-bgevent-skeleton {
z-index: 3;
}
.fc-time-grid .fc-highlight-skeleton {
z-index: 4;
}
.fc-time-grid .fc-content-skeleton {
z-index: 5;
}
.fc-time-grid .fc-helper-skeleton {
z-index: 6;
}
/* TimeGrid Slats (lines that run horizontally)
--------------------------------------------------------------------------------------------------*/
.fc-slats td {
height: 1.5em;
border-bottom: 0; /* each cell is responsible for its top border */
}
.fc-slats .fc-minor td {
border-top-style: dotted;
}
.fc-slats .ui-widget-content { /* for jqui theme */
background: none; /* see through to fc-bg */
}
/* TimeGrid Highlighting Slots
--------------------------------------------------------------------------------------------------*/
.fc-time-grid .fc-highlight-container { /* a div within a cell within the fc-highlight-skeleton */
position: relative; /* scopes the left/right of the fc-highlight to be in the column */
}
.fc-time-grid .fc-highlight {
position: absolute;
left: 0;
right: 0;
/* top and bottom will be in by JS */
}
/* TimeGrid Event Containment
--------------------------------------------------------------------------------------------------*/
.fc-time-grid .fc-event-container, /* a div within a cell within the fc-content-skeleton */
.fc-time-grid .fc-bgevent-container { /* a div within a cell within the fc-bgevent-skeleton */
position: relative;
}
.fc-ltr .fc-time-grid .fc-event-container { /* space on the sides of events for LTR (default) */
margin: 0 2.5% 0 2px;
}
.fc-rtl .fc-time-grid .fc-event-container { /* space on the sides of events for RTL */
margin: 0 2px 0 2.5%;
}
.fc-time-grid .fc-event,
.fc-time-grid .fc-bgevent {
position: absolute;
z-index: 1; /* scope inner z-index's */
}
.fc-time-grid .fc-bgevent {
/* background events always span full width */
left: 0;
right: 0;
}
/* TimeGrid Event Styling
----------------------------------------------------------------------------------------------------
We use the full "fc-time-grid-event" class instead of using descendants because the event won't
be a descendant of the grid when it is being dragged.
*/
.fc-time-grid-event.fc-not-start { /* events that are continuing from another day */
/* replace space made by the top border with padding */
border-top-width: 0;
padding-top: 1px;
/* remove top rounded corners */
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.fc-time-grid-event.fc-not-end {
/* replace space made by the top border with padding */
border-bottom-width: 0;
padding-bottom: 1px;
/* remove bottom rounded corners */
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.fc-time-grid-event {
overflow: hidden; /* don't let the bg flow over rounded corners */
}
.fc-time-grid-event > .fc-content { /* contains the time and title, but no bg and resizer */
position: relative;
z-index: 2; /* above the bg */
}
.fc-time-grid-event .fc-time,
.fc-time-grid-event .fc-title {
padding: 0 1px;
}
.fc-time-grid-event .fc-time {
font-size: .85em;
white-space: nowrap;
}
.fc-time-grid-event .fc-bg {
z-index: 1;
background: #fff;
opacity: .25;
filter: alpha(opacity=25); /* for IE */
}
/* short mode, where time and title are on the same line */
.fc-time-grid-event.fc-short .fc-content {
/* don't wrap to second line (now that contents will be inline) */
white-space: nowrap;
}
.fc-time-grid-event.fc-short .fc-time,
.fc-time-grid-event.fc-short .fc-title {
/* put the time and title on the same line */
display: inline-block;
vertical-align: top;
}
.fc-time-grid-event.fc-short .fc-time span {
display: none; /* don't display the full time text... */
}
.fc-time-grid-event.fc-short .fc-time:before {
content: attr(data-start); /* ...instead, display only the start time */
}
.fc-time-grid-event.fc-short .fc-time:after {
content: "\000A0-\000A0"; /* seperate with a dash, wrapped in nbsp's */
}
.fc-time-grid-event.fc-short .fc-title {
font-size: .85em; /* make the title text the same size as the time */
padding: 0; /* undo padding from above */
}
/* resizer */
.fc-time-grid-event .fc-resizer {
position: absolute;
z-index: 3; /* above content */
left: 0;
right: 0;
bottom: 0;
height: 8px;
overflow: hidden;
line-height: 8px;
font-size: 11px;
font-family: monospace;
text-align: center;
cursor: s-resize;
}
.fc-time-grid-event .fc-resizer:after {
content: "=";
}

View File

@ -0,0 +1,202 @@
/*!
* FullCalendar v2.2.0 Print Stylesheet
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2013 Adam Shaw
*/
/*
* Include this stylesheet on your page to get a more printer-friendly calendar.
* When including this stylesheet, use the media='print' attribute of the <link> tag.
* Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
*/
.fc {
max-width: 100% !important;
}
/* Global Event Restyling
--------------------------------------------------------------------------------------------------*/
.fc-event {
background: #fff !important;
color: #000 !important;
page-break-inside: avoid;
}
.fc-event .fc-resizer {
display: none;
}
/* Table & Day-Row Restyling
--------------------------------------------------------------------------------------------------*/
th,
td,
hr,
thead,
tbody,
.fc-row {
border-color: #ccc !important;
background: #fff !important;
}
/* kill the overlaid, absolutely-positioned common components */
.fc-bg,
.fc-bgevent-skeleton,
.fc-highlight-skeleton,
.fc-helper-skeleton {
display: none;
}
/* don't force a min-height on rows (for DayGrid) */
.fc tbody .fc-row {
height: auto !important; /* undo height that JS set in distributeHeight */
min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */
}
.fc tbody .fc-row .fc-content-skeleton {
position: static; /* undo .fc-rigid */
padding-bottom: 0 !important; /* use a more border-friendly method for this... */
}
.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */
padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */
}
.fc tbody .fc-row .fc-content-skeleton table {
/* provides a min-height for the row, but only effective for IE, which exaggerates this value,
making it look more like 3em. for other browers, it will already be this tall */
height: 1em;
}
/* Undo month-view event limiting. Display all events and hide the "more" links
--------------------------------------------------------------------------------------------------*/
.fc-more-cell,
.fc-more {
display: none !important;
}
.fc tr.fc-limited {
display: table-row !important;
}
.fc td.fc-limited {
display: table-cell !important;
}
.fc-popover {
display: none; /* never display the "more.." popover in print mode */
}
/* TimeGrid Restyling
--------------------------------------------------------------------------------------------------*/
/* undo the min-height 100% trick used to fill the container's height */
.fc-time-grid {
min-height: 0 !important;
}
/* don't display the side axis at all ("all-day" and time cells) */
.fc-agenda-view .fc-axis {
display: none;
}
/* don't display the horizontal lines */
.fc-slats,
.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */
display: none !important; /* important overrides inline declaration */
}
/* let the container that holds the events be naturally positioned and create real height */
.fc-time-grid .fc-content-skeleton {
position: static;
}
/* in case there are no events, we still want some height */
.fc-time-grid .fc-content-skeleton table {
height: 4em;
}
/* kill the horizontal spacing made by the event container. event margins will be done below */
.fc-time-grid .fc-event-container {
margin: 0 !important;
}
/* TimeGrid *Event* Restyling
--------------------------------------------------------------------------------------------------*/
/* naturally position events, vertically stacking them */
.fc-time-grid .fc-event {
position: static !important;
margin: 3px 2px !important;
}
/* for events that continue to a future day, give the bottom border back */
.fc-time-grid .fc-event.fc-not-end {
border-bottom-width: 1px !important;
}
/* indicate the event continues via "..." text */
.fc-time-grid .fc-event.fc-not-end:after {
content: "...";
}
/* for events that are continuations from previous days, give the top border back */
.fc-time-grid .fc-event.fc-not-start {
border-top-width: 1px !important;
}
/* indicate the event is a continuation via "..." text */
.fc-time-grid .fc-event.fc-not-start:before {
content: "...";
}
/* time */
/* undo a previous declaration and let the time text span to a second line */
.fc-time-grid .fc-event .fc-time {
white-space: normal !important;
}
/* hide the the time that is normally displayed... */
.fc-time-grid .fc-event .fc-time span {
display: none;
}
/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */
.fc-time-grid .fc-event .fc-time:after {
content: attr(data-full);
}
/* Vertical Scroller & Containers
--------------------------------------------------------------------------------------------------*/
/* kill the scrollbars and allow natural height */
.fc-scroller,
.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */
.fc-time-grid-container { /* */
overflow: visible !important;
height: auto !important;
}
/* kill the horizontal border/padding used to compensate for scrollbars */
.fc-row {
border: 0 !important;
margin: 0 !important;
}
/* Button Controls
--------------------------------------------------------------------------------------------------*/
.fc-button-group,
.fc button {
display: none; /* don't display any button-related controls */
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,8 @@
<script src="/static/js/plugins/slimscroll/jquery.slimscroll.min.js"></script> <script src="/static/js/plugins/slimscroll/jquery.slimscroll.min.js"></script>
<script src="/static/js/bootstrap-dialog.js"></script> <script src="/static/js/bootstrap-dialog.js"></script>
<script src="/static/js/mindmup-editabletable.js"></script> <script src="/static/js/mindmup-editabletable.js"></script>
<script src="/static/js/plugins/fullcalendar/moment.min.js"></script>
<script src="/static/js/plugins/fullcalendar/fullcalendar.min.js"></script>
<!-- Custom and plugin javascript --> <!-- Custom and plugin javascript -->
<script src="/static/js/inspinia.js"></script> <script src="/static/js/inspinia.js"></script>

View File

@ -7,7 +7,7 @@
<div class="col-lg-3"> <div class="col-lg-3">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span class="label label-success pull-right">Monthly</span> <span class="label label-success pull-right">Users</span>
<h5>用户总数</h5> <h5>用户总数</h5>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
@ -20,7 +20,7 @@
<div class="col-lg-3"> <div class="col-lg-3">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span class="label label-info pull-right">Annual</span> <span class="label label-info pull-right">Hosts</span>
<h5>主机总数</h5> <h5>主机总数</h5>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
@ -34,7 +34,7 @@
<div class="col-lg-3"> <div class="col-lg-3">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span class="label label-primary pull-right">Today</span> <span class="label label-primary pull-right">Online</span>
<h5>实时在线用户</h5> <h5>实时在线用户</h5>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
@ -48,13 +48,13 @@
<div class="col-lg-3"> <div class="col-lg-3">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span class="label label-danger pull-right">Low value</span> <span class="label label-danger pull-right">Connected</span>
<h5>已连接服务器</h5> <h5>已连接服务器</h5>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<h1 class="no-margins"><a href="/jlog/log_list/online/"> <span id="online_hosts"></span></a></h1> <h1 class="no-margins"><a href="/jlog/log_list/online/"> <span id="online_hosts"></span></a></h1>
<div class="stat-percent font-bold text-danger">38% <i class="fa fa-level-down"></i></div> <div class="stat-percent font-bold text-danger">38% <i class="fa fa-level-down"></i></div>
<small>Connect host</small> <small>Connected host</small>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load mytags %}
{% block content %} {% block content %}
{% include 'nav_cat_bar.html' %} {% include 'nav_cat_bar.html' %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
@ -20,6 +21,7 @@
<div class="col-sm-8" name="group_id" value="{{ post.id }}"><input type="text" value="{{ group.name }}" placeholder="网站" name="j_group" class="form-control"></div> <div class="col-sm-8" name="group_id" value="{{ post.id }}"><input type="text" value="{{ group.name }}" placeholder="网站" name="j_group" class="form-control"></div>
</div> </div>
{% ifequal session_role_id 2 %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<div class="form-group"> <div class="form-group">
<label for="j_dept" class="col-lg-2 control-label">所属部门<span class="red-fonts">*</span></label> <label for="j_dept" class="col-lg-2 control-label">所属部门<span class="red-fonts">*</span></label>
@ -31,6 +33,15 @@
</select> </select>
</div> </div>
</div> </div>
{% endifequal %}
{% ifequal session_role_id 1 %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<label for="j_dept" class="col-lg-2 control-label">所属部门<span class="red-fonts" style="">*</span></label>
<div class="col-sm-8"><input type="text" name="j_dept" value="{{ edept.name }}" class="form-control" readonly="readonly"></div>
</div>
{% endifequal %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<div class="form-group"> <div class="form-group">
@ -43,9 +54,9 @@
</select> </select>
</div> </div>
<div class="col-sm-1"> <div class="col-sm-1">
<div class="btn-group" style="margin-top: 50px;"> <div class="fc-button-group" style="margin-top: 50px;">
<button type="button" class="btn btn-white" onclick="move('groups', 'groups_selected')"><i class="fa fa-chevron-right"></i></button> <button type="button" class="fc-button fc-state-default" onclick="move('groups', 'groups_selected')"><span class="fc-icon fc-icon-right-single-arrow"></span></button>
<button type="button" class="btn btn-white" onclick="move_left('groups_selected', 'groups')"><i class="fa fa-chevron-left"></i> </button> <button type="button" class="fc-button fc-state-default" onclick="move_left('groups_selected', 'groups')"><span class="fc-icon fc-icon-left-single-arrow"></span></button>
</div> </div>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load mytags %}
{% block content %} {% block content %}
{% include 'nav_cat_bar.html' %} {% include 'nav_cat_bar.html' %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
@ -84,6 +85,7 @@
</div> </div>
</div> </div>
{% ifequal session_role_id 2 %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<div class="form-group"> <div class="form-group">
<label for="j_dept" class="col-lg-2 control-label">所属部门<span class="red-fonts">*</span></label> <label for="j_dept" class="col-lg-2 control-label">所属部门<span class="red-fonts">*</span></label>
@ -95,6 +97,14 @@
</select> </select>
</div> </div>
</div> </div>
{% endifequal %}
{% ifequal session_role_id 1 %}
<div class="hr-line-dashed"></div>
<div class="form-group"><label class="col-sm-2 control-label"> 所属部门 </label>
<div class="col-sm-8"><input type="text" name="j_dept" value="{{ edept.name }}" class="form-control" readonly="readonly"></div>
</div>
{% endifequal %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<div class="form-group"> <div class="form-group">
@ -108,16 +118,6 @@
</div> </div>
</div> </div>
<!--<div class="hr-line-dashed"></div>-->
<!--<div class="form-group">-->
<!--<label for="j_group" class="col-sm-2 control-label"> 所属用户组<span class="red-fonts">*</span> </label>-->
<!--<div class="col-sm-8">-->
<!--{% for g in eusergroup %}-->
<!--<label class="checkbox-inline"><input type="checkbox" id="j_usergroup" value="{{ g }}" name="j_usergroup"> {{ g }} </label>-->
<!--{% endfor %}-->
<!--</div>-->
<!--</div>-->
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<div class="form-group"><label class="col-sm-2 control-label"> 是否激活<span class="red-fonts">*</span> </label> <div class="form-group"><label class="col-sm-2 control-label"> 是否激活<span class="red-fonts">*</span> </label>
<div class="col-sm-8"> <div class="col-sm-8">

View File

@ -44,7 +44,7 @@
{% endif %} {% endif %}
<h4>按照文本框内主机信息格式填写, 多台主机回车换行</h4> <h4>按照文本框内主机信息格式填写, 多台主机回车换行</h4>
<form id="assetMulti" method="post" class="form-horizontal"> <form id="assetMulti" method="post" class="form-horizontal">
<div><textarea id="j_multi" name="j_multi" type="text" placeholder="192.168.1.1 22 LDAP 北京联通 [网站,数据库] 1 网站服务器" class="form-control" style="width:700px;height:500px"></textarea></div> <div><textarea id="j_multi" name="j_multi" type="text" placeholder="192.168.1.1 22 LDAP 北京联通 [网站,数据库] 运维部 1 网站服务器" class="form-control" style="width:700px;height:500px">192.168.1.1 22 LDAP 北京联通 ['网站','数据库'] ['运维部','测试部'] 1 网站服务器</textarea></div>
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-4 col-sm-offset-4"> <div class="col-sm-4 col-sm-offset-4">

View File

@ -34,7 +34,7 @@
<td> {{ post.port }} </td> <td> {{ post.port }} </td>
<td> {{ login_types|get_item:post.login_type }} </td> <td> {{ login_types|get_item:post.login_type }} </td>
<td class="text-center"> {{ post.idc.name }} </td> <td class="text-center"> {{ post.idc.name }} </td>
<td class="text-center">{% for group in post.bis_group.all|filter_private %} {{ group }} {% endfor %}</td> <td class="text-center">{% for group in post.bis_group.all %} {{ group }} {% endfor %}</td>
<td class="text-center"> {{ post.date_added|date:"Y-m-d H:i:s" }} </td> <td class="text-center"> {{ post.date_added|date:"Y-m-d H:i:s" }} </td>
<td class="text-center"> {{ post.comment }} </td> <td class="text-center"> {{ post.comment }} </td>
</tr> </tr>

View File

@ -77,7 +77,9 @@
<thead> <thead>
<tr> <tr>
<th class="text-center"> 用户名 </th> <th class="text-center"> 用户名 </th>
<th class="text-center"> 所属部门 </th>
<th class="text-center"> 登录主机 </th> <th class="text-center"> 登录主机 </th>
<th class="text-center"> 来源IP </th>
<th class="text-center"> 命令统计 </th> <th class="text-center"> 命令统计 </th>
<th class="text-center"> 登录时间 </th> <th class="text-center"> 登录时间 </th>
<th class="text-center"> 结束时间 </th> <th class="text-center"> 结束时间 </th>
@ -88,7 +90,9 @@
{% for post in contacts.object_list %} {% for post in contacts.object_list %}
<tr class="gradeX"> <tr class="gradeX">
<td class="text-center" id="username"> {{ post.user }} </td> <td class="text-center" id="username"> {{ post.user }} </td>
<td class="text-center" id="dept"> {{ post.dept_name }} </td>
<td class="text-center" id="ip"> {{ post.host }} </td> <td class="text-center" id="ip"> {{ post.host }} </td>
<td class="text-center" id="remote_ip"> {{ post.remote_ip }} </td>
<td class="text-center"><a href="/jlog/history/?id={{ post.id }}" class="log_command"> 命令统计 </td> <td class="text-center"><a href="/jlog/history/?id={{ post.id }}" class="log_command"> 命令统计 </td>
<td class="text-center" id="start_time"> {{ post.start_time|date:"Y-m-d H:i:s"}} </td> <td class="text-center" id="start_time"> {{ post.start_time|date:"Y-m-d H:i:s"}} </td>
<td class="text-center" id="end_time"> {{ post.end_time|date:"Y-m-d H:i:s" }} </td> <td class="text-center" id="end_time"> {{ post.end_time|date:"Y-m-d H:i:s" }} </td>

View File

@ -77,7 +77,9 @@
<thead> <thead>
<tr> <tr>
<th class="text-center"> 用户名 </th> <th class="text-center"> 用户名 </th>
<th class="text-center"> 所属部门 </th>
<th class="text-center"> 登录主机 </th> <th class="text-center"> 登录主机 </th>
<th class="text-center"> 来源IP </th>
<th class="text-center"> 实时监控 </th> <th class="text-center"> 实时监控 </th>
<th class="text-center"> 阻断 </th> <th class="text-center"> 阻断 </th>
<th class="text-center"> 登录时间 </th> <th class="text-center"> 登录时间 </th>
@ -88,7 +90,9 @@
{% for post in contacts.object_list %} {% for post in contacts.object_list %}
<tr class="gradeX"> <tr class="gradeX">
<td id="username" class="text-center"> {{ post.user }} </td> <td id="username" class="text-center"> {{ post.user }} </td>
<td id="ip" class="text-center"> {{ post.dept_name }} </td>
<td id="ip" class="text-center"> {{ post.host }} </td> <td id="ip" class="text-center"> {{ post.host }} </td>
<td id="ip" class="text-center"> {{ post.remote_ip }} </td>
<td class="text-center"><a class="monitor" filename="{{ post.log_path }}"> 监控 </a></td> <td class="text-center"><a class="monitor" filename="{{ post.log_path }}"> 监控 </a></td>
<td class="text-center"><input type="button" id="cut" class="btn btn-danger btn-xs" name="cut" value="阻断" onclick='cut("{{ post.pid }}")' /></td> <td class="text-center"><input type="button" id="cut" class="btn btn-danger btn-xs" name="cut" value="阻断" onclick='cut("{{ post.pid }}")' /></td>
<td class="text-center"> {{ post.start_time|date:"Y-m-d H:i:s" }} </td> <td class="text-center"> {{ post.start_time|date:"Y-m-d H:i:s" }} </td>
@ -145,9 +149,9 @@
var regx = /\x1B\[([0-9]{1,3}((;[0-9]{1,3})*)?)?[m|K]/g; var regx = /\x1B\[([0-9]{1,3}((;[0-9]{1,3})*)?)?[m|K]/g;
// tag.append('<p>'+escapeString(obj.content.replace(regx,''))+'</p>'); // tag.append('<p>'+escapeString(obj.content.replace(regx,''))+'</p>');
if (option == 'new') { if (option == 'new') {
tag.append('<p>' + escapeString(obj.content) + '</p>'); tag.append('<p style="margin: 2px">' + escapeString(obj.content) + '</p>');
} else if (option == 'exist') { } else if (option == 'exist') {
tag.append('<p>' + exsit_message + '</p>'); tag.append('<p style="margin: 0">' + exsit_message + '</p>');
} }
tag.animate({ scrollTop: tag[0].scrollHeight}, 1); tag.animate({ scrollTop: tag[0].scrollHeight}, 1);
}); });

View File

@ -3,7 +3,9 @@
<thead> <thead>
<tr> <tr>
<th class="text-center"> 用户名 </th> <th class="text-center"> 用户名 </th>
<th class="text-center"> 所属部门 </th>
<th class="text-center"> 登录主机 </th> <th class="text-center"> 登录主机 </th>
<th class="text-center"> 来源IP </th>
<th class="text-center"> 命令统计 </th> <th class="text-center"> 命令统计 </th>
<th class="text-center"> 登录时间 </th> <th class="text-center"> 登录时间 </th>
<th class="text-center"> 结束时间 </th> <th class="text-center"> 结束时间 </th>
@ -14,7 +16,9 @@
{% for post in contacts.object_list %} {% for post in contacts.object_list %}
<tr class="gradeX"> <tr class="gradeX">
<td class="text-center" id="username"> {{ post.user }} </td> <td class="text-center" id="username"> {{ post.user }} </td>
<td class="text-center" id="ip"> {{ post.dept_name }} </td>
<td class="text-center" id="ip"> {{ post.host }} </td> <td class="text-center" id="ip"> {{ post.host }} </td>
<td class="text-center" id="ip"> {{ post.remote_ip }} </td>
<td class="text-center"><a href="/jlog/history/?id={{ post.id }}" class="log_command"> 命令统计 </td> <td class="text-center"><a href="/jlog/history/?id={{ post.id }}" class="log_command"> 命令统计 </td>
<td class="text-center" id="start_time"> {{ post.start_time|date:"Y-m-d H:i:s"}} </td> <td class="text-center" id="start_time"> {{ post.start_time|date:"Y-m-d H:i:s"}} </td>
<td class="text-center" id="end_time"> {{ post.end_time|date:"Y-m-d H:i:s" }} </td> <td class="text-center" id="end_time"> {{ post.end_time|date:"Y-m-d H:i:s" }} </td>

View File

@ -6,3 +6,4 @@
<link href="/static/css/colorbox.css" rel="stylesheet"> <link href="/static/css/colorbox.css" rel="stylesheet">
<link href="/static/css/vaildator/jquery.validator.css" rel="stylesheet"> <link href="/static/css/vaildator/jquery.validator.css" rel="stylesheet">
<link href="/static/css/magnific/magnific-popup.css" rel="stylesheet"> <link href="/static/css/magnific/magnific-popup.css" rel="stylesheet">
<link href="/static/css/plugins/fullcalendar/fullcalendar.css" rel="stylesheet">

View File

@ -27,7 +27,7 @@
<a><i class="fa fa-cube"></i> <span class="nav-label">资产管理</span><span class="fa arrow"></span></a> <a><i class="fa fa-cube"></i> <span class="nav-label">资产管理</span><span class="fa arrow"></span></a>
<ul class="nav nav-second-level"> <ul class="nav nav-second-level">
<li class="host_add host_add_multi"><a href="/jasset/host_add/">添加资产</a></li> <li class="host_add host_add_multi"><a href="/jasset/host_add/">添加资产</a></li>
<li class="host_list"><a href="/jasset/host_list/">查看资产&nbsp&nbsp</span><span class="label label-info pull-right">16/18</span></a></li> <li class="host_list"><a href="/jasset/host_list/">查看资产&nbsp&nbsp</span><span class="label label-info pull-right">{{ host_active_num }}/{{ host_total_num}}</span></a></li>
<li class="jgroup_add"><a href="/jasset/jgroup_add/">添加主机组</a></li> <li class="jgroup_add"><a href="/jasset/jgroup_add/">添加主机组</a></li>
<li class="jgroup_list group_detail"><a href="/jasset/jgroup_list/">查看主机组</a></li> <li class="jgroup_list group_detail"><a href="/jasset/jgroup_list/">查看主机组</a></li>
<li class="idc_add"><a href="/jasset/idc_add/">添加IDC</a></li> <li class="idc_add"><a href="/jasset/idc_add/">添加IDC</a></li>
@ -100,12 +100,11 @@
<li id="jasset"> <li id="jasset">
<a><i class="fa fa-cube"></i> <span class="nav-label">资产管理</span><span class="fa arrow"></span></a> <a><i class="fa fa-cube"></i> <span class="nav-label">资产管理</span><span class="fa arrow"></span></a>
<ul class="nav nav-second-level"> <ul class="nav nav-second-level">
<li id="host_add"><a href="/jasset/host_add/">添加资产</a></li> <li class="host_add host_add_multi"><a href="/jasset/host_add/">添加资产</a></li>
<li id="host_list"><a href="/jasset/host_list/">查看资产&nbsp&nbsp</span><span class="label label-info pull-right">16/18</span></a></li> <li class="host_list"><a href="/jasset/host_list/">查看资产&nbsp&nbsp</span><span class="label label-info pull-right">{{ host_active_num }}/{{ host_total_num}}</span></a></li>
<li id="jgroup_add"><a href="/jasset/jgroup_add/">添加主机组</a></li> <li class="jgroup_add"><a href="/jasset/jgroup_add/">添加主机组</a></li>
<li id="jgroup_list"><a href="/jasset/jgroup_list/">查看主机组</a></li> <li class="jgroup_list group_detail"><a href="/jasset/jgroup_list/">查看主机组</a></li>
<li id="idc_add"><a href="/jasset/idc_add/">添加IDC</a></li> <li class="idc_list idc_detail"><a href="/jasset/idc_list/">查看IDC</a></li>
<li id="idc_list"><a href="/jasset/idc_list/">查看IDC</a></li>
</ul> </ul>
</li> </li>
<li id="jperm"> <li id="jperm">
@ -142,3 +141,48 @@
</div> </div>
</nav> </nav>
{% endifequal %} {% endifequal %}
{% ifequal session_role_id 0 %}
<nav class="navbar-default navbar-static-side" role="navigation">
<div class="sidebar-collapse">
<ul class="nav" id="side-menu">
{% include 'nav_li_profile.html' %}
<li>
<a href="/"><i class="fa fa-th-large"></i> <span class="nav-label">仪表盘</span><span class="label label-info pull-right"></span></a>
</li>
<li id="juser">
<a href="#"><i class="fa fa-rebel"></i> <span class="nav-label">用户管理</span><span class="fa arrow"></span></a>
<ul class="nav nav-second-level">
<li id="user_list"><a href="/juser/user_list/">查看用户<span class="label {% ifequal user_active_num user_total_num %}label-primary {% else %}label-warning {% endifequal %}pull-right">{{ user_active_num }}/{{ user_total_num }}</span></a></li>
</ul>
</li>
<li id="jasset">
<a><i class="fa fa-cube"></i> <span class="nav-label">资产管理</span><span class="fa arrow"></span></a>
<ul class="nav nav-second-level">
<li class="host_list"><a href="/jasset/host_list/">查看资产&nbsp&nbsp</span><span class="label label-info pull-right">16/18</span></a></li>
<li class="jgroup_list group_detail"><a href="/jasset/jgroup_list/">查看主机组</a></li>
<li class="idc_list idc_detail"><a href="/jasset/idc_list/">查看IDC</a></li>
</ul>
</li>
<li id="jlog">
<a href="#"><i class="fa fa-files-o"></i> <span class="nav-label">日志审计</span><span class="fa arrow"></span></a>
<ul class="nav nav-second-level">
<li id="log_list"><a href="/jlog/log_list/online/">查看日志</a></li>
<li id="log_detail"><a href="/jlog/log_detail/">日志分析</a></li>
</ul>
</li>
<li>
<a href="#"><i class="fa fa-download"></i> <span class="nav-label">上传下载</span><span class="fa arrow"></span></a>
<ul class="nav nav-second-level">
<li><a href="/file/upload/">文件上传</a></li>
<li><a href="/file/download/">文件下载</a></li>
</ul>
</li>
<li class="special_link">
<a href="http://www.jumpserver.org" target="_blank"><i class="fa fa-database"></i> <span class="nav-label">访问官网</span></a>
</li>
</ul>
</div>
</nav>
{% endifequal %}