mirror of https://github.com/jumpserver/jumpserver
Merge branches 'guanghongwei' and 'wangyong' of gitcafe.com:ibuler/jumpserver into guanghongwei
Conflicts: connect.py jumpserver.conf jumpserver/api.pypull/6/head
commit
d8a6eba2f3
36
connect.py
36
connect.py
|
@ -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']:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
130
jasset/views.py
130
jasset/views.py
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
@ -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:
|
||||||
|
|
|
@ -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()
|
|
@ -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: "=";
|
||||||
|
}
|
|
@ -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
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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/">查看资产  </span><span class="label label-info pull-right">16/18</span></a></li>
|
<li class="host_list"><a href="/jasset/host_list/">查看资产  </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/">查看资产  </span><span class="label label-info pull-right">16/18</span></a></li>
|
<li class="host_list"><a href="/jasset/host_list/">查看资产  </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/">查看资产  </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 %}
|
Loading…
Reference in New Issue