merge update...

pull/26/head
yumaojun 2015-11-14 23:07:08 +08:00
commit 012950cb68
71 changed files with 11586 additions and 1228 deletions

BIN
.DS_Store vendored

Binary file not shown.

3
.gitignore vendored
View File

@ -1,7 +1,8 @@
*.py[cod]
.idea
test.py
.DS_Store
db.sqlite3
# C extensions
*.so

View File

@ -8,19 +8,33 @@ sys.setdefaultencoding('utf8')
import os
import re
import time
import datetime
import textwrap
import getpass
import readline
import django
from multiprocessing import Pool
import paramiko
import struct, fcntl, signal, socket, select, fnmatch
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
if django.get_version() != '1.6':
django.setup()
from jumpserver.api import ServerError, User, Asset, Jtty, get_object
from jumpserver.api import logger
from jumpserver.api import BisGroup as AssetGroup
from jumpserver.api import ServerError, User, Asset, AssetGroup, get_object
from jumpserver.api import logger, is_dir, Log, TtyLog
from jumpserver.settings import log_dir
try:
import termios
import tty
except ImportError:
print '\033[1;31m仅支持类Unix系统 Only unix like supported.\033[0m'
time.sleep(3)
sys.exit()
VIM_FLAG = False
VIM_COMMAND = ''
SSH_TTY = ''
login_user = get_object(User, username=getpass.getuser())
@ -82,6 +96,397 @@ def verify_connect(user, option):
jtty.connect()
def check_vim_status(command, ssh):
global SSH_TTY
print command
if command == '':
return True
else:
command_str= 'ps -ef |grep "%s" | grep "%s"|grep -v grep |wc -l' % (command,SSH_TTY)
print command_str
stdin, stdout, stderr = ssh.exec_command(command_str)
ps_num = stdout.read()
print ps_num
if int(ps_num) == 0:
return True
else:
return False
def deal_command(str_r, ssh):
"""
处理命令中特殊字符
"""
str_r = re.sub('\x07','',str_r) #删除响铃
patch_char = re.compile('\x08\x1b\[C') #删除方向左右一起的按键
while patch_char.search(str_r):
str_r = patch_char.sub('', str_r.rstrip())
result_command = '' #最后的结果
pattern_str = '' #模式中间中的字符串
backspace_num = 0 #光标移动的个数
reach_backspace_flag = False #没有检测到光标键则为true
end_flag = False
while str_r:
tmp = re.match(r'\w', str_r)
if tmp:
if reach_backspace_flag:
pattern_str += str(tmp.group(0))
str_r = str_r[1:]
continue
else:
result_command += str(tmp.group(0))
str_r = str_r[1:]
continue
tmp = re.match(r'\x1b\[K[\x08]*', str_r)
if tmp:
if backspace_num > 0:
if backspace_num > len(result_command) :
result_command += pattern_str
result_command = result_command[0:-backspace_num]
else:
result_command = result_command[0:-backspace_num]
result_command += pattern_str
del_len = len(str(tmp.group(0)))-3
if del_len > 0:
result_command = result_command[0:-del_len]
reach_backspace_flag = False
backspace_num =0
pattern_str=''
str_r = str_r[len(str(tmp.group(0))):]
continue
if re.match(r'\x08', str_r):
backspace_num += 1
reach_backspace_flag = True
str_r = str_r[1:]
if len(str_r) == 0:
end_flag = True
continue
if reach_backspace_flag :
pattern_str += str_r[0]
else :
result_command += str_r[0]
str_r = str_r[1:]
if backspace_num > 0 and not end_flag:
result_command = result_command[:-backspace_num]
result_command += pattern_str
control_char = re.compile(r"""
\x1b[ #%()*+\-.\/]. |
\r | #匹配 回车符(CR)
(?:\x1b\[|\x9b) [ -?]* [@-~] | #匹配 控制顺序描述符(CSI)... Cmd
(?:\x1b\]|\x9d) .*? (?:\x1b\\|[\a\x9c]) | \x07 | #匹配 操作系统指令(OSC)...终止符或振铃符(ST|BEL)
(?:\x1b[P^_]|[\x90\x9e\x9f]) .*? (?:\x1b\\|\x9c) | #匹配 设备控制串或私讯或应用程序命令(DCS|PM|APC)...终止符(ST)
\x1b. #匹配 转义过后的字符
[\x80-\x9f] #匹配 所有控制字符
""", re.X)
result_command = control_char.sub('', result_command.strip())
global VIM_FLAG
global VIM_COMMAND
if not VIM_FLAG:
if result_command.startswith('vim') or result_command.startswith('vi') :
VIM_FLAG = True
VIM_COMMAND = result_command
return result_command.decode('utf8',"ignore")
else:
if check_vim_status(VIM_COMMAND, ssh):
VIM_FLAG = False
VIM_COMMAND=''
return result_command.decode('utf8',"ignore")
else:
return ''
def newline_code_in(strings):
for i in ['\r', '\r\n', '\n']:
if i in strings:
#print "new line"
return True
return False
class Tty(object):
"""
A virtual tty class
一个虚拟终端类实现连接ssh和记录日志基类
"""
def __init__(self, username, asset_name):
self.username = username
self.asset_name = asset_name
self.ip = None
self.port = 22
self.channel = None
self.user = None
self.asset = None
self.role = None
self.ssh = None
self.connect_info = None
self.login_type = 'ssh'
@staticmethod
def is_output(strings):
newline_char = ['\n', '\r', '\r\n']
for char in newline_char:
if char in strings:
return True
return False
@staticmethod
def remove_control_char(str_r):
"""
处理日志特殊字符
"""
control_char = re.compile(r"""
\x1b[ #%()*+\-.\/]. |
\r | #匹配 回车符(CR)
(?:\x1b\[|\x9b) [ -?]* [@-~] | #匹配 控制顺序描述符(CSI)... Cmd
(?:\x1b\]|\x9d) .*? (?:\x1b\\|[\a\x9c]) | \x07 | #匹配 操作系统指令(OSC)...终止符或振铃符(ST|BEL)
(?:\x1b[P^_]|[\x90\x9e\x9f]) .*? (?:\x1b\\|\x9c) | #匹配 设备控制串或私讯或应用程序命令(DCS|PM|APC)...终止符(ST)
\x1b. #匹配 转义过后的字符
[\x80-\x9f] #匹配 所有控制字符
""", re.X)
backspace = re.compile(r"[^\b][\b]")
line_filtered = control_char.sub('', str_r.rstrip())
while backspace.search(line_filtered):
line_filtered = backspace.sub('', line_filtered)
return line_filtered
def get_log_file(self):
"""
Logging user command and output.
记录用户的日志
"""
tty_log_dir = os.path.join(log_dir, 'tty')
timestamp_start = int(time.time())
date_start = time.strftime('%Y%m%d', time.localtime(timestamp_start))
time_start = time.strftime('%H%M%S', time.localtime(timestamp_start))
today_connect_log_dir = os.path.join(tty_log_dir, date_start)
log_file_path = os.path.join(today_connect_log_dir, '%s_%s_%s' % (self.username, self.asset_name, time_start))
try:
is_dir(today_connect_log_dir, mode=0777)
except OSError:
raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, tty_log_dir))
try:
log_file_f = open(log_file_path + '.log', 'a')
log_time_f = open(log_file_path + '.time', 'a')
except IOError:
raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir)
if self.login_type == 'ssh':
pid = os.getpid()
remote_ip = os.popen("who -m | awk '{ print $5 }'").read().strip('()\n')
log = Log(user=self.username, host=self.asset_name, remote_ip=remote_ip,
log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid)
else:
remote_ip = 'Web'
log = Log(user=self.username, host=self.asset_name, remote_ip=remote_ip,
log_path=log_file_path, start_time=datetime.datetime.now(), pid=0)
log.save()
log.pid = log.id
log.save()
log_file_f.write('Start at %s\n' % datetime.datetime.now())
log.save()
return log_file_f, log_time_f, log
def get_connect_info(self):
"""
获取需要登陆的主机的信息和映射用户的账号密码
"""
# 1. get ip, port
# 2. get 映射用户
# 3. get 映射用户的账号密码或者key
# self.connect_info = {'user': '', 'asset': '', 'ip': '', 'port': 0, 'role_name': '', 'role_pass': '', 'role_key': ''}
self.connect_info = {'user': 'a', 'asset': 'b', 'ip': '127.0.0.1', 'port': 22, 'role_name': 'root', 'role_pass': '', 'role_key': '/root/.ssh/id_rsa.bak'}
return self.connect_info
def get_connection(self):
"""
获取连接成功后的ssh
"""
connect_info = self.get_connect_info()
# 发起ssh连接请求 Make a ssh connection
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
if connect_info.get('role_pass'):
ssh.connect(connect_info.get('ip'),
port=connect_info.get('port'),
username=connect_info.get('role_name'),
password=connect_info.get('role_pass'),
look_for_keys=False)
else:
ssh.connect(connect_info.get('ip'),
port=connect_info.get('port'),
username=connect_info.get('role_name'),
key_filename=connect_info.get('role_key'),
look_for_keys=False)
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.')
else:
self.ssh = ssh
return ssh
class SshTty(Tty):
"""
A virtual tty class
一个虚拟终端类实现连接ssh和记录日志
"""
@staticmethod
def get_win_size():
"""
This function use to get the size of the windows!
获得terminal窗口大小
"""
if 'TIOCGWINSZ' in dir(termios):
TIOCGWINSZ = termios.TIOCGWINSZ
else:
TIOCGWINSZ = 1074295912L
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(self, sig, data):
"""
This function use to set the window size of the terminal!
设置terminal窗口大小
"""
try:
win_size = self.get_win_size()
self.channel.resize_pty(height=win_size[0], width=win_size[1])
except Exception:
pass
def posix_shell(self):
"""
Use paramiko channel connect server interactive.
使用paramiko模块的channel连接后端进入交互式
"""
log_file_f, log_time_f, log = self.get_log_file()
old_tty = termios.tcgetattr(sys.stdin)
pre_timestamp = time.time()
input_r = ''
input_mode = False
try:
tty.setraw(sys.stdin.fileno())
tty.setcbreak(sys.stdin.fileno())
self.channel.settimeout(0.0)
while True:
try:
r, w, e = select.select([self.channel, sys.stdin], [], [])
except Exception:
pass
if self.channel in r:
try:
x = self.channel.recv(1024)
if len(x) == 0:
break
sys.stdout.write(x)
sys.stdout.flush()
now_timestamp = time.time()
log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(x)))
log_file_f.write(x)
pre_timestamp = now_timestamp
log_file_f.flush()
log_time_f.flush()
if input_mode and not self.is_output(x):
input_r += x
except socket.timeout:
pass
if sys.stdin in r:
x = os.read(sys.stdin.fileno(), 1)
if not input_mode:
input_mode = True
if str(x) in ['\r', '\n', '\r\n']:
# input_r = deal_command(input_r,ssh)
input_r = self.remove_control_char(input_r)
TtyLog(log=log, datetime=datetime.datetime.now(), cmd=input_r).save()
input_r = ''
input_mode = False
if len(x) == 0:
break
self.channel.send(x)
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
log_file_f.write('End time is %s' % datetime.datetime.now())
log_file_f.close()
log.is_finished = True
log.end_time = datetime.datetime.now()
log.save()
def connect(self):
"""
Connect server.
连接服务器
"""
ps1 = "PS1='[\u@%s \W]\$ '\n" % self.ip
login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % self.ip
# 发起ssh连接请求 Make a ssh connection
ssh = self.get_connection()
# 获取连接的隧道并设置窗口大小 Make a channel and set windows size
global channel
win_size = self.get_win_size()
self.channel = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1], term='xterm')
try:
signal.signal(signal.SIGWINCH, self.set_win_size)
except:
pass
# 设置PS1并提示 Set PS1 and msg it
#channel.send(ps1)
#channel.send(login_msg)
# channel.send('echo ${SSH_TTY}\n')
# global SSH_TTY
# while not channel.recv_ready():
# time.sleep(1)
# tmp = channel.recv(1024)
#print 'ok'+tmp+'ok'
# SSH_TTY = re.search(r'(?<=/dev/).*', tmp).group().strip()
# SSH_TTY = ''
channel.send('clear\n')
# Make ssh interactive tunnel
self.posix_shell()
# Shutdown channel socket
channel.close()
ssh.close()
def execute(self, cmd):
"""
execute cmd on the asset
执行命令
"""
pass
def print_prompt():
"""
Print prompt
@ -98,76 +503,6 @@ def print_prompt():
print textwrap.dedent(msg)
# 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):
# color_print("You can choose in the following IP(s), Use glob or ips split by comma. q/Q to PreLayer.", 'green')
# user.get_asset_info(printable=True)
# while True:
# hosts = []
# inputs = raw_input('\033[1;32mip(s)>: \033[0m')
# if inputs in ['q', 'Q']:
# break
# get_hosts = login_user.get_asset_info().keys()
#
# if ',' in inputs:
# ips_input = inputs.split(',')
# for host in ips_input:
# if host in get_hosts:
# hosts.append(host)
# else:
# 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 main():
"""
he he

28
docs/initial_data.yaml Normal file
View File

@ -0,0 +1,28 @@
- model: juser.user
pk: 5000
fields:
username: admin
name: admin
password: pbkdf2_sha256$20000$jBIDGPB2j5JT$orxqGgzzjzykColYm1BswPjgHOiERjZkcgkuVIkD2Hc=
email: admin@jumpserver.org
role: SU
is_active: 1
- model: juser.user
pk: 5001
fields:
username: group_admin
name: group_admin
password: pbkdf2_sha256$20000$ttObUWd15q10$NJoyZf2OZz9oiw2g4j2TkTh9zGgyVDRFdUkhn8X0nB0=
email: group_admin@jumpserver.org
role: GA
is_active: 1
- model: juser.usergroup
pk: 1
fields:
name: ALL
comment: ALL
- model: juser.usergroup
pk: 2
fields:
name: 默认
comment: 默认

View File

@ -60,12 +60,12 @@ class AssetGroup(models.Model):
class Asset(models.Model):
ip = models.GenericIPAddressField(unique=True)
port = models.IntegerField(blank=True, null=True)
port = models.IntegerField()
group = models.ManyToManyField(AssetGroup)
username = models.CharField(max_length=20, blank=True, null=True)
password = models.CharField(max_length=80, blank=True, null=True)
use_default = models.BooleanField(default=True)
date_added = models.DateTimeField(auto_now=True)
use_default_auth = models.BooleanField(default=True)
date_added = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
comment = models.CharField(max_length=100, blank=True, null=True)

68
jlog/log_api.py Normal file
View File

@ -0,0 +1,68 @@
# coding: utf-8
from argparse import ArgumentParser, FileType
from contextlib import closing
from codecs import open as copen
from json import dumps
from math import ceil
from os.path import basename, dirname, exists, join
from struct import unpack
from subprocess import Popen
from sys import platform, prefix, stderr
from tempfile import NamedTemporaryFile
from jinja2 import FileSystemLoader, Template
from jinja2.environment import Environment
from jumpserver.api import BASE_DIR
DEFAULT_TEMPLATE = join(BASE_DIR, 'templates', 'jlog', 'static.jinja2')
def escapeString(string):
string = string.encode('unicode_escape').decode('utf-8')
string = string.replace("'", "\\'")
string = '\'' + string + '\''
return string
def getTiming(timef):
timing = None
with closing(timef):
timing = [l.strip().split(' ') for l in timef]
timing = [(int(ceil(float(r[0]) * 1000)), int(r[1])) for r in timing]
return timing
def scriptToJSON(scriptf, timing=None):
ret = []
with closing(scriptf):
scriptf.readline() # ignore first header line from script file
offset = 0
for t in timing:
data = escapeString(scriptf.read(t[1]))
offset += t[0]
ret.append((data, offset))
return dumps(ret)
def renderTemplate(script_path, time_file_path, dimensions=(24, 80), templatename=DEFAULT_TEMPLATE):
with copen(script_path, encoding='utf-8', errors='replace') as scriptf:
with open(time_file_path) as timef:
timing = getTiming(timef)
json = scriptToJSON(scriptf, timing)
fsl = FileSystemLoader(dirname(templatename), 'utf-8')
e = Environment()
e.loader = fsl
templatename = basename(templatename)
rendered = e.get_template(templatename).render(json=json,
dimensions=dimensions)
return rendered

View File

@ -5,12 +5,10 @@ class Log(models.Model):
user = 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)
start_time = models.DateTimeField(null=True)
pid = models.IntegerField()
is_finished = models.BooleanField(default=False)
handle_finished = models.BooleanField(default=False)
end_time = models.DateTimeField(null=True)
def __unicode__(self):
@ -20,4 +18,11 @@ class Log(models.Model):
class Alert(models.Model):
msg = models.CharField(max_length=20)
time = models.DateTimeField(null=True)
is_finished = models.BigIntegerField(default=False)
is_finished = models.BigIntegerField(default=False)
class TtyLog(models.Model):
log = models.ForeignKey(Log)
datetime = models.DateTimeField()
cmd = models.CharField(max_length=200)

View File

@ -3,9 +3,10 @@ from django.conf.urls import patterns, include, url
from jlog.views import *
urlpatterns = patterns('',
url(r'^$', log_list),
url(r'^log_list/(\w+)/$', log_list),
url(r'^log_kill/', log_kill),
url(r'^history/$', log_history),
url(r'^search/$', log_search),
)
url(r'^$', log_list),
url(r'^log_list/(\w+)/$', log_list),
url(r'^history/$', log_history),
url(r'^log_kill/', log_kill),
url(r'^record/$', log_record),
url(r'^web_terminal/$', web_terminal),
)

View File

@ -4,78 +4,62 @@ from django.template import RequestContext
from django.shortcuts import render_to_response
from jumpserver.api import *
from jasset.views import httperror
from django.http import HttpResponseNotFound
from jlog.log_api import renderTemplate
CONF = ConfigParser()
CONF.read('%s/jumpserver.conf' % BASE_DIR)
from models import Log
from jumpserver.settings import web_socket_host
def get_user_info(request, offset):
""" 获取用户信息及环境 """
env_dic = {'online': 0, 'offline': 1}
env = env_dic[offset]
keyword = request.GET.get('keyword', '')
user_info = get_session_user_info(request)
user_id, username = user_info[0:2]
dept_id, dept_name = user_info[3:5]
ret = [request, keyword, env, username, dept_name]
return ret
def get_user_log(ret_list):
""" 获取不同类型用户日志记录 """
request, keyword, env, username, dept_name = ret_list
post_all = Log.objects.filter(is_finished=env).order_by('-start_time')
post_keyword_all = Log.objects.filter(Q(user__contains=keyword) |
Q(host__contains=keyword)) \
.filter(is_finished=env).order_by('-start_time')
if is_super_user(request):
if keyword:
posts = post_keyword_all
else:
posts = post_all
elif is_group_admin(request):
if keyword:
posts = post_keyword_all.filter(dept_name=dept_name)
else:
posts = post_all.filter(dept_name=dept_name)
elif is_common_user(request):
if keyword:
posts = post_keyword_all.filter(user=username)
else:
posts = post_all.filter(user=username)
return posts
@require_login
@require_role('admin')
def log_list(request, offset):
""" 显示日志 """
header_title, path1, path2 = u'查看日志', u'查看日志', u'在线用户'
keyword = request.GET.get('keyword', '')
web_socket_host = CONF.get('websocket', 'web_socket_host')
posts = get_user_log(get_user_info(request, offset))
header_title, path1 = u'审计', u'操作审计'
date_seven_day = request.GET.get('start', '')
date_now_str = request.GET.get('end', '')
username_list = request.GET.getlist('username', [])
host_list = request.GET.getlist('host', [])
cmd = request.GET.get('cmd', '')
print date_seven_day, date_now_str
if offset == 'online':
posts = Log.objects.filter(is_finished=False).order_by('-start_time')
else:
posts = Log.objects.filter(is_finished=True).order_by('-start_time')
username_all = set([log.user for log in Log.objects.all()])
ip_all = set([log.host for log in Log.objects.all()])
if date_seven_day and date_now_str:
datetime_start = datetime.datetime.strptime(date_seven_day + ' 00:00:01', '%m/%d/%Y %H:%M:%S')
datetime_end = datetime.datetime.strptime(date_now_str + ' 23:59:59', '%m/%d/%Y %H:%M:%S')
posts = posts.filter(start_time__gte=datetime_start).filter(start_time__lte=datetime_end)
if username_list:
posts = posts.filter(user__in=username_list)
if host_list:
posts = posts.filter(host__in=host_list)
if cmd:
log_id_list = set([log.log_id for log in TtyLog.objects.filter(cmd__contains=cmd)])
posts = posts.filter(id__in=log_id_list)
else:
date_now = datetime.datetime.now()
date_now_str = date_now.strftime('%m/%d/%Y')
date_seven_day = (date_now + datetime.timedelta(days=-7)).strftime('%m/%d/%Y')
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
web_monitor_uri = 'ws://%s/monitor' % web_socket_host
web_kill_uri = 'http://%s/kill' % web_socket_host
return render_to_response('jlog/log_%s.html' % offset, locals(), context_instance=RequestContext(request))
@require_admin
@require_role('admin')
def log_kill(request):
""" 杀掉connect进程 """
pid = request.GET.get('id', '')
log = Log.objects.filter(pid=pid)
if log:
log = log[0]
dept_name = log.dept_name
deptname = get_session_user_info(request)[4]
if is_group_admin(request) and dept_name != deptname:
return httperror(request, u'Kill失败, 您无权操作!')
try:
os.kill(int(pid), 9)
except OSError:
@ -86,35 +70,44 @@ def log_kill(request):
return HttpResponseNotFound(u'没有此进程!')
@require_login
@require_role('admin')
def log_history(request):
""" 命令历史记录 """
log_id = request.GET.get('id', 0)
log = Log.objects.filter(id=log_id)
if log:
log = log[0]
tty_logs = log.ttylog_set.all()
if tty_logs:
content = ''
for tty_log in tty_logs:
content += '%s: %s\n' % (tty_log.datetime.strftime('%Y-%m-%d %H:%M:%S'), tty_log.cmd)
return HttpResponse(content)
return HttpResponse('无日志记录!')
@require_role('admin')
def log_record(request):
log_id = request.GET.get('id', 0)
log = Log.objects.filter(id=int(log_id))
if log:
log = log[0]
dept_name = log.dept_name
deptname = get_session_user_info(request)[4]
if is_group_admin(request) and dept_name != deptname:
return httperror(request, '查看失败, 您无权查看!')
elif is_common_user(request):
return httperror(request, '查看失败, 您无权查看!')
log_his = "%s.his" % log.log_path
if os.path.isfile(log_his):
f = open(log_his)
content = f.read()
log_file = log.log_path + '.log'
log_time = log.log_path + '.time'
if os.path.isfile(log_file) and os.path.isfile(log_time):
content = renderTemplate(log_file, log_time)
return HttpResponse(content)
else:
return httperror(request, '无日志记录, 请查看日志处理脚本是否开启!')
return HttpResponse('无日志记录!')
@require_login
def log_search(request):
""" 日志搜索 """
offset = request.GET.get('env', '')
keyword = request.GET.get('keyword', '')
posts = get_user_log(get_user_info(request, offset))
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))
def web_terminal(request):
#username = get_session.get('username', '')
token = request.COOKIES.get('sessionid')
username = request.user.username
asset_name = '127.0.0.1'
web_terminal_uri = 'ws://%s/terminal?username=%s&asset_name=%s&token=%s' % (web_socket_host, username, asset_name, token)
return render_to_response('jlog/web_terminal.html', locals())

View File

@ -20,7 +20,9 @@ root_dn = cn=admin,dc=jumpserver,dc=org
root_pw = secret234
[websocket]
web_socket_host = 1127.0.0.1:3000
web_socket_host = 127.0.0.1:3000
[mail]
mail_enable = 1
@ -28,4 +30,4 @@ email_host = smtp.exmail.qq.com
email_port = 25
email_host_user = noreply@jumpserver.org
email_host_password = jumpserver1234
email_use_tls = False
email_use_tls = True

View File

@ -1,8 +1,6 @@
# coding: utf-8
import os, sys, time
from ConfigParser import ConfigParser
import getpass
import os, sys, time, re
from Crypto.Cipher import AES
import crypt
from binascii import b2a_hex, a2b_hex
@ -10,16 +8,15 @@ import hashlib
import datetime
import random
import subprocess
import paramiko
import struct, fcntl, signal,socket, select, fnmatch
from settings import *
from django.core.paginator import Paginator, EmptyPage, InvalidPage
from django.http import HttpResponse, Http404
from django.template import RequestContext
from juser.models import User, UserGroup
from jasset.models import Asset, AssetGroup
from jasset.models import AssetAlias
from jlog.models import Log
# from jlog.models import Log
from jlog.models import Log, TtyLog
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
@ -27,29 +24,6 @@ from django.core.mail import send_mail
import json
import logging
try:
import termios
import tty
except ImportError:
print '\033[1;31m仅支持类Unix系统 Only unix like supported.\033[0m'
time.sleep(3)
sys.exit()
BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
CONF = ConfigParser()
CONF.read(os.path.join(BASE_DIR, 'jumpserver.conf'))
LOG_DIR = os.path.join(BASE_DIR, 'logs')
JLOG_FILE = os.path.join(LOG_DIR, 'jumpserver.log')
SSH_KEY_DIR = os.path.join(BASE_DIR, 'keys')
# SERVER_KEY_DIR = os.path.join(SSH_KEY_DIR, 'server')
KEY = CONF.get('base', 'key')
LOGIN_NAME = getpass.getuser()
# LDAP_ENABLE = CONF.getint('ldap', 'ldap_enable')
URL = CONF.get('base', 'url')
MAIL_ENABLE = CONF.get('mail', 'mail_enable')
MAIL_FROM = CONF.get('mail', 'email_host_user')
log_dir = os.path.join(BASE_DIR, 'logs')
def set_log(level):
@ -69,75 +43,6 @@ def set_log(level):
return logger_f
# class LDAPMgmt():
# """
# LDAP class for add, select, del, update
# LDAP 管理类,增删改查
# """
# def __init__(self,
# host_url,
# base_dn,
# root_cn,
# root_pw):
# self.ldap_host = host_url
# self.ldap_base_dn = base_dn
# self.conn = ldap.initialize(host_url)
# self.conn.set_option(ldap.OPT_REFERRALS, 0)
# self.conn.protocol_version = ldap.VERSION3
# self.conn.simple_bind_s(root_cn, root_pw)
#
# def list(self, filter, scope=ldap.SCOPE_SUBTREE, attr=None):
# """
# query
# 查询
# """
# result = {}
# try:
# ldap_result = self.conn.search_s(self.ldap_base_dn, scope, filter, attr)
# for entry in ldap_result:
# name, data = entry
# for k, v in data.items():
# print '%s: %s' % (k, v)
# result[k] = v
# return result
# except ldap.LDAPError, e:
# print e
#
# def add(self, dn, attrs):
# """
# add
# 添加
# """
# try:
# ldif = modlist.addModlist(attrs)
# self.conn.add_s(dn, ldif)
# except ldap.LDAPError, e:
# print e
#
# def modify(self, dn, attrs):
# """
# modify
# 更改
# """
# try:
# attr_s = []
# for k, v in attrs.items():
# attr_s.append((2, k, v))
# self.conn.modify_s(dn, attr_s)
# except ldap.LDAPError, e:
# print e
#
# def delete(self, dn):
# """
# delete
# 删除
# """
# try:
# self.conn.delete_s(dn)
# except ldap.LDAPError, e:
# print e
def page_list_return(total, current=1):
"""
page
@ -146,7 +51,7 @@ def page_list_return(total, current=1):
min_page = current - 2 if current - 4 > 0 else 1
max_page = min_page + 4 if min_page + 4 < total else total
return range(min_page, max_page+1)
return range(min_page, max_page + 1)
def pages(post_objects, request):
@ -181,208 +86,6 @@ def pages(post_objects, request):
return post_objects, paginator, page_objects, page_range, current_page, show_first, show_end
class Jtty(object):
"""
A virtual tty class
一个虚拟终端类实现连接ssh和记录日志
"""
def __init__(self, user, asset):
self.chan = None
self.username = user.username
self.ip = asset.ip
self.user = user
self.asset = asset
@staticmethod
def get_win_size():
"""
This function use to get the size of the windows!
获得terminal窗口大小
"""
if 'TIOCGWINSZ' in dir(termios):
TIOCGWINSZ = termios.TIOCGWINSZ
else:
TIOCGWINSZ = 1074295912L
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(self, sig, data):
"""
This function use to set the window size of the terminal!
设置terminal窗口大小
"""
try:
win_size = self.get_win_size()
self.chan.resize_pty(height=win_size[0], width=win_size[1])
except Exception:
pass
def log_record(self):
"""
Logging user command and output.
记录用户的日志
"""
tty_log_dir = os.path.join(log_dir, 'tty')
timestamp_start = int(time.time())
date_start = time.strftime('%Y%m%d', time.localtime(timestamp_start))
time_start = time.strftime('%H%M%S', time.localtime(timestamp_start))
log_filename = '%s_%s_%s.log' % (self.username, self.ip, time_start)
today_connect_log_dir = os.path.join(tty_log_dir, date_start)
log_file_path = os.path.join(today_connect_log_dir, log_filename)
dept_name = self.user.dept.name
pid = os.getpid()
pts = os.popen("ps axu | grep %s | grep -v grep | awk '{ print $7 }'" % pid).read().strip()
ip_list = os.popen("who | grep %s | awk '{ print $5 }'" % pts).read().strip('()\n')
try:
is_dir(today_connect_log_dir)
except OSError:
raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, tty_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=self.username, host=self.ip, remote_ip=ip_list, dept_name=dept_name,
log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid)
log_file.write('Start time is %s\n' % datetime.datetime.now())
log.save()
return log_file, log
def posix_shell(self):
"""
Use paramiko channel connect server interactive.
使用paramiko模块的channel连接后端进入交互式
"""
log_file, log = self.log_record()
old_tty = termios.tcgetattr(sys.stdin)
try:
tty.setraw(sys.stdin.fileno())
tty.setcbreak(sys.stdin.fileno())
self.chan.settimeout(0.0)
while True:
try:
r, w, e = select.select([self.chan, sys.stdin], [], [])
except Exception:
pass
if self.chan in r:
try:
x = self.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
self.chan.send(x)
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
log_file.write('End time is %s' % datetime.datetime.now())
log_file.close()
log.is_finished = True
log.handle_finished = False
log.end_time = datetime.datetime.now()
log.save()
def get_connect_item(self):
"""
get args for connect: ip, port, username, passwd
获取连接需要的参数也就是服务ip, 端口, 用户账号和密码
"""
if not self.asset.is_active:
raise ServerError('该主机被禁用 Host %s is not active.' % self.ip)
if not self.user.is_active:
raise ServerError('该用户被禁用 User %s is not active.' % self.username)
login_type_dict = {
'L': self.user.ldap_pwd,
}
if self.asset.login_type in login_type_dict:
password = CRYPTOR.decrypt(login_type_dict[self.asset.login_type])
return self.username, password, self.ip, int(self.asset.port)
elif self.asset.login_type == 'M':
username = self.asset.username
password = CRYPTOR.decrypt(self.asset.password)
return username, password, self.ip, int(self.asset.port)
else:
raise ServerError('不支持的服务器登录方式 Login type is not in ["L", "M"]')
def get_connection(self):
"""
Get the ssh connection for reuse
获取连接套接字
"""
username, password, ip, port = self.get_connect_item()
logger.debug("username: %s, password: %s, ip: %s, port: %s" % (username, password, ip, port))
# 发起ssh连接请求 Make a ssh connection
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(ip, 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.')
else:
return ssh
def connect(self):
"""
Connect server.
连接服务器
"""
ps1 = "PS1='[\u@%s \W]\$ '\n" % self.ip
login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % self.asset.ip
# 发起ssh连接请求 Make a ssh connection
ssh = self.get_connection()
# 获取连接的隧道并设置窗口大小 Make a channel and set windows size
global channel
win_size = self.get_win_size()
self.chan = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1])
try:
signal.signal(signal.SIGWINCH, self.set_win_size)
except:
pass
# 设置PS1并提示 Set PS1 and msg it
channel.send(ps1)
channel.send(login_msg)
# Make ssh interactive tunnel
self.posix_shell()
# Shutdown channel socket
channel.close()
ssh.close()
def execute(self, cmd):
"""
execute cmd on the asset
执行命令
"""
pass
class PyCrypt(object):
"""
@ -404,7 +107,7 @@ class PyCrypt(object):
symbol = '!@$%^&*()_'
salt_list = []
if especial:
for i in range(length-4):
for i in range(length - 4):
salt_list.append(random.choice(salt_key))
for i in range(4):
salt_list.append(random.choice(symbol))
@ -493,19 +196,24 @@ def require_role(role='user'):
decorator for require user role in ["super", "admin", "user"]
要求用户是某种角色 ["super", "admin", "user"]的装饰器
"""
def _deco(func):
def __deco(request, *args, **kwargs):
if role == 'user':
if not request.session.get('user_id'):
return HttpResponseRedirect('/login/')
elif role == 'admin':
if request.session.get('role_id', 0) < 1:
if not request.user.is_authenticated():
return HttpResponseRedirect('/login/')
if role == 'admin':
# if request.session.get('role_id', 0) < 1:
if request.user.role == 'CU':
return HttpResponseRedirect('/')
elif role == 'super':
if request.session.get('role_id', 0) < 2:
# if request.session.get('role_id', 0) < 2:
if request.user.role in ['CU', 'GA']:
return HttpResponseRedirect('/')
return func(request, *args, **kwargs)
return __deco
return _deco
@ -514,8 +222,8 @@ def is_role_request(request, role='user'):
require this request of user is right
要求请求角色正确
"""
role_all = {'user': 0, 'admin': 1, 'super': 2}
if request.session.get('role_id') == role_all.get(role, 0):
role_all = {'user': 'CU', 'admin': 'GA', 'super': 'SU'}
if request.user.role == role_all.get(role, 'CU'):
return True
else:
return False
@ -526,13 +234,14 @@ def get_session_user_dept(request):
get department of the user in session
获取session中用户的部门
"""
user_id = request.session.get('user_id', 0)
print '#' * 20
print user_id
user = User.objects.filter(id=user_id)
if user:
user = user[0]
return user, None
# user_id = request.session.get('user_id', 0)
# print '#' * 20
# print user_id
# user = User.objects.filter(id=user_id)
# if user:
# user = user[0]
# return user, None
return request.user, None
@require_role
@ -541,18 +250,18 @@ def get_session_user_info(request):
get the user info of the user in session, for example id, username etc.
获取用户的信息
"""
user_id = request.session.get('user_id', 0)
user = get_object(User, id=user_id)
if user:
return [user.id, user.username, user]
# user_id = request.session.get('user_id', 0)
# user = get_object(User, id=user_id)
# if user:
# return [user.id, user.username, user]
return [request.user.id, request.user.username, request.user]
def get_user_dept(request):
"""
get the user dept id
获取用户的部门id
"""
user_id = request.session.get('user_id')
user_id = request.user.id
if user_id:
user_dept = User.objects.get(id=user_id).dept
return user_dept.id
@ -588,7 +297,7 @@ def validate(request, user_group=None, user=None, asset_group=None, asset=None,
if edept:
if dept.id != int(edept[0]):
return False
if user_group:
dept_user_groups = dept.usergroup_set.all()
user_group_ids = []
@ -705,15 +414,5 @@ def my_render(template, data, request):
CRYPTOR = PyCrypt(KEY)
logger = set_log(log_level)
# if LDAP_ENABLE:
# LDAP_HOST_URL = CONF.get('ldap', 'host_url')
# LDAP_BASE_DN = CONF.get('ldap', 'base_dn')
# LDAP_ROOT_DN = CONF.get('ldap', 'root_dn')
# LDAP_ROOT_PW = CONF.get('ldap', 'root_pw')
# ldap_conn = LDAPMgmt(LDAP_HOST_URL, LDAP_BASE_DN, LDAP_ROOT_DN, LDAP_ROOT_PW)
# else:
# ldap_conn = None
log_level = CONF.get('base', 'log')
logger = set_log(log_level)

View File

@ -4,8 +4,9 @@ from jumpserver.api import *
def name_proc(request):
user_id = request.session.get('user_id')
role_id = request.session.get('role_id')
user_id = request.user.id
# role_id = request.session.get('role_id')
role_id = {'SU':2,'GA':1,'CU':0}.get(request.user.role,0)
# if role_id == 2:
user_total_num = User.objects.all().count()
user_active_num = User.objects.filter().count()

View File

@ -6,7 +6,8 @@ from django.db import models
class Setting(models.Model):
name = models.CharField(max_length=100)
default_user = models.CharField(max_length=100, null=True, blank=True)
default_port = models.IntegerField(null=True, blank=True)
default_port = models.IntegerField(max_length=10, null=True, blank=True)
default_password = models.CharField(max_length=100, null=True, blank=True)
default_pri_key_path = models.CharField(max_length=100, null=True, blank=True)
class Meta:

View File

@ -11,12 +11,11 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import ConfigParser
import djcelery
import getpass
djcelery.setup_loader()
config = ConfigParser.ConfigParser()
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
config.read(os.path.join(BASE_DIR, 'jumpserver.conf'))
@ -25,13 +24,29 @@ DB_PORT = config.getint('db', 'port')
DB_USER = config.get('db', 'user')
DB_PASSWORD = config.get('db', 'password')
DB_DATABASE = config.get('db', 'database')
AUTH_USER_MODEL = 'juser.User'
# mail config
MAIL_ENABLE = config.get('mail', 'mail_enable')
EMAIL_HOST = config.get('mail', 'email_host')
EMAIL_PORT = config.get('mail', 'email_port')
EMAIL_HOST_USER = config.get('mail', 'email_host_user')
EMAIL_HOST_PASSWORD = config.get('mail', 'email_host_password')
EMAIL_USE_TLS = config.getboolean('mail', 'email_use_tls')
EMAIL_TIMEOUT = 5
# ======== Log ==========
LOG = False
LOG_DIR = os.path.join(BASE_DIR, 'logs')
JLOG_FILE = os.path.join(LOG_DIR, 'jumpserver.log')
SSH_KEY_DIR = os.path.join(BASE_DIR, 'keys')
# SERVER_KEY_DIR = os.path.join(SSH_KEY_DIR, 'server')
KEY = config.get('base', 'key')
LOGIN_NAME = getpass.getuser()
# LDAP_ENABLE = CONF.getint('ldap', 'ldap_enable')
URL = config.get('base', 'url')
log_dir = os.path.join(BASE_DIR, 'logs')
log_level = config.get('base', 'log')
web_socket_host = config.get('websocket', 'web_socket_host')
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
@ -46,7 +61,6 @@ TEMPLATE_DEBUG = True
ALLOWED_HOSTS = ['0.0.0.0/8']
BROKER_URL = 'django://'
# Application definition
INSTALLED_APPS = (
@ -57,21 +71,20 @@ INSTALLED_APPS = (
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'djcelery',
'kombu.transport.django',
'jumpserver',
'juser',
'jasset',
'jperm',
'jlog',
'jlog',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
#'django.middleware.csrf.CsrfViewMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
#'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
# 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
@ -95,6 +108,12 @@ DATABASES = {
}
}
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
@ -103,14 +122,14 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'jumpserver.context_processors.name_proc'
'jumpserver.context_processors.name_proc',
)
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, 'templates'),
)
#STATIC_ROOT = os.path.join(BASE_DIR, 'static')
# STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
@ -133,5 +152,3 @@ USE_TZ = False
# https://docs.djangoproject.com/en/1.7/howto/static-files/
STATIC_URL = '/static/'

View File

@ -8,16 +8,17 @@ urlpatterns = patterns('',
(r'^skin_config/$', 'jumpserver.views.skin_config'),
(r'^install/$', 'jumpserver.views.install'),
(r'^base/$', 'jumpserver.views.base'),
(r'^login/$', 'jumpserver.views.login'),
(r'^logout/$', 'jumpserver.views.logout'),
(r'^login/$', 'jumpserver.views.Login'),
(r'^logout/$', 'jumpserver.views.Logout'),
(r'^file/upload/$', 'jumpserver.views.upload'),
(r'^file/download/$', 'jumpserver.views.download'),
(r'^setting', 'jumpserver.views.setting'),
(r'^error/$', 'jumpserver.views.httperror'),
(r'^juser/', include('juser.urls')),
(r'^jasset/', include('jasset.urls')),
# (r'^jlog/', include('jlog.urls')),
(r'^jlog/', include('jlog.urls')),
(r'^jperm/', include('jperm.urls')),
(r'^node_auth/', 'jumpserver.views.node_auth'),
(r'test2', 'jumpserver.views.test2'),
)

View File

@ -13,43 +13,70 @@ from django.http import HttpResponse
import paramiko
from jumpserver.api import *
from jumpserver.models import Setting
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from settings import BASE_DIR
from jlog.models import Log
def getDaysByNum(num):
"""
输出格式:([datetime.date(2015, 11, 6), datetime.date(2015, 11, 8)], ['11-06', '11-08'])
"""
today = datetime.date.today()
oneday = datetime.timedelta(days=1)
li_date, li_str = [], []
date_li, date_str = [], []
for i in range(0, num):
today = today-oneday
li_date.append(today)
li_str.append(str(today)[5:10])
li_date.reverse()
li_str.reverse()
t = (li_date, li_str)
return t
date_li.append(today)
date_str.append(str(today)[5:10])
date_li.reverse()
date_str.reverse()
return date_li, date_str
def get_data(data, items, option):
dic = {}
li_date, li_str = getDaysByNum(7)
for item in items:
li = []
name = item[option]
if option == 'user':
option_data = data.filter(user=name)
elif option == 'host':
option_data = data.filter(host=name)
for t in li_date:
year, month, day = t.year, t.month, t.day
times = option_data.filter(start_time__year=year, start_time__month=month, start_time__day=day).count()
li.append(times)
dic[name] = li
return dic
def get_data(x, y, z):
pass
def get_data_by_day(date_li, item):
data_li = []
for d in date_li:
logs = Log.objects.filter(start_time__year=d.year,
start_time__month=d.month,
start_time__day=d.day)
if item == 'user':
data_li.append(set([log.user for log in logs]))
elif item == 'asset':
data_li.append(set([log.host for log in logs]))
elif item == 'login':
data_li.append(logs)
else:
pass
return data_li
def get_count_by_day(date_li, item):
data_li = get_data_by_day(date_li, item)
data_count_li = []
for data in data_li:
data_count_li.append(len(data))
return data_count_li
def get_count_by_date(date_li, item):
data_li = get_data_by_day(date_li, item)
data_count_tmp = []
for data in data_li:
data_count_tmp.extend(list(data))
return len(set(data_count_tmp))
@require_role(role='user')
def index_cu(request):
user_id = request.session.get('user_id')
user_id = request.user.id
user = get_object(User, id=user_id)
login_types = {'L': 'LDAP', 'M': 'MAP'}
username = user.username
@ -64,7 +91,6 @@ def index_cu(request):
new_posts.append(post_five)
post_five = []
new_posts.append(post_five)
return render_to_response('index_cu.html', locals(), context_instance=RequestContext(request))
@ -78,6 +104,7 @@ def index(request):
return index_cu(request)
elif is_role_request(request, 'super'):
# dashboard 显示汇总
users = User.objects.all()
hosts = Asset.objects.all()
online = Log.objects.filter(is_finished=0)
@ -85,68 +112,52 @@ def index(request):
online_user = online.values('user').distinct()
active_users = User.objects.filter(is_active=1)
active_hosts = Asset.objects.filter(is_active=1)
# 一个月历史汇总
date_li, date_str = getDaysByNum(30)
date_month = repr(date_str)
active_user_per_month = str(get_count_by_day(date_li, 'user'))
active_asset_per_month = str(get_count_by_day(date_li, 'asset'))
active_login_per_month = str(get_count_by_day(date_li, 'login'))
# 活跃用户资产图
active_user_month = get_count_by_date(date_li, 'user')
disabled_user_count = len(users.filter(is_active=False))
inactive_user_month = len(users) - active_user_month
active_asset_month = get_count_by_date(date_li, 'asset')
disabled_asset_count = len(hosts.filter(is_active=False)) if hosts.filter(is_active=False) else 0
inactive_asset_month = len(hosts) - active_asset_month if len(hosts) > active_asset_month else 0
# 一周top10用户和主机
week_data = Log.objects.filter(start_time__range=[from_week, datetime.datetime.now()])
user_top_ten = week_data.values('user').annotate(times=Count('user')).order_by('-times')[:10]
host_top_ten = week_data.values('host').annotate(times=Count('host')).order_by('-times')[:10]
elif is_role_request(request, 'admin'):
return index_cu(request)
# user = get_session_user_info(request)[2]
# users = User.objects.filter(dept=dept)
# hosts = Asset.objects.filter(dept=dept)
# online = Log.objects.filter(dept_name=dept_name, is_finished=0)
# online_host = online.values('host').distinct()
# online_user = online.values('user').distinct()
# active_users = users.filter(is_active=1)
# active_hosts = hosts.filter(is_active=1)
# week_data = Log.objects.filter(dept_name=dept_name, start_time__range=[from_week, datetime.datetime.now()])
for user_info in user_top_ten:
username = user_info.get('user')
last = Log.objects.filter(user=username).latest('start_time')
user_info['last'] = last
# percent of dashboard
if users.count() == 0:
percent_user, percent_online_user = '0%', '0%'
else:
percent_user = format(active_users.count() / users.count(), '.0%')
percent_online_user = format(online_user.count() / users.count(), '.0%')
if hosts.count() == 0:
percent_host, percent_online_host = '0%', '0%'
else:
percent_host = format(active_hosts.count() / hosts.count(), '.0%')
percent_online_host = format(online_host.count() / hosts.count(), '.0%')
for host_info in host_top_ten:
host = host_info.get('host')
last = Log.objects.filter(host=host).latest('start_time')
host_info['last'] = last
user_top_ten = week_data.values('user').annotate(times=Count('user')).order_by('-times')[:10]
host_top_ten = week_data.values('host').annotate(times=Count('host')).order_by('-times')[:10]
user_dic, host_dic = get_data(week_data, user_top_ten, 'user'), get_data(week_data, host_top_ten, 'host')
# 一周top5
week_users = week_data.values('user').distinct().count()
week_hosts = week_data.count()
# a week data
week_users = week_data.values('user').distinct().count()
week_hosts = week_data.count()
user_top_five = week_data.values('user').annotate(times=Count('user')).order_by('-times')[:5]
color = ['label-success', 'label-info', 'label-primary', 'label-default', 'label-warnning']
user_top_five = week_data.values('user').annotate(times=Count('user')).order_by('-times')[:5]
color = ['label-success', 'label-info', 'label-primary', 'label-default', 'label-warnning']
# 最后10次权限申请
# perm apply latest 10
# perm_apply_10 = Apply.objects.order_by('-date_add')[:10]
# perm apply latest 10
# perm_apply_10 = Apply.objects.order_by('-date_add')[:10]
# 最后10次登陆
login_10 = Log.objects.order_by('-start_time')[:10]
login_more_10 = Log.objects.order_by('-start_time')[10:21]
# latest 10 login
login_10 = Log.objects.order_by('-start_time')[:10]
login_more_10 = Log.objects.order_by('-start_time')[10:21]
# a week top 10
for user_info in user_top_ten:
username = user_info.get('user')
last = Log.objects.filter(user=username).latest('start_time')
user_info['last'] = last
top = {'user': '活跃用户数', 'host': '活跃主机数', 'times': '登录次数'}
top_dic = {}
for key, value in top.items():
li = []
for t in li_date:
year, month, day = t.year, t.month, t.day
if key != 'times':
times = week_data.filter(start_time__year=year, start_time__month=month, start_time__day=day).values(key).distinct().count()
else:
times = week_data.filter(start_time__year=year, start_time__month=month, start_time__day=day).count()
li.append(times)
top_dic[value] = li
return render_to_response('index.html', locals(), context_instance=RequestContext(request))
@ -193,69 +204,95 @@ def is_latest():
pass
def login(request):
def Login(request):
"""登录界面"""
if request.session.get('username'):
error = ''
if request.user.is_authenticated():
return HttpResponseRedirect('/')
if request.method == 'GET':
return render_to_response('login.html')
else:
username = request.POST.get('username')
password = request.POST.get('password')
user_filter = User.objects.filter(username=username)
if user_filter:
user = user_filter[0]
if PyCrypt.md5_crypt(password) == user.password:
request.session['user_id'] = user.id
user_filter.update(last_login=datetime.datetime.now())
if user.role == 'SU':
request.session['role_id'] = 2
elif user.role == 'GA':
request.session['role_id'] = 1
else:
request.session['role_id'] = 0
response = HttpResponseRedirect('/', )
response.set_cookie('username', username, expires=604800)
response.set_cookie('seed', PyCrypt.md5_crypt(password), expires=604800)
return response
if username and password:
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
# c = {}
# c.update(csrf(request))
# request.session['csrf_token'] = str(c.get('csrf_token'))
# user_filter = User.objects.filter(username=username)
# if user_filter:
# user = user_filter[0]
# if PyCrypt.md5_crypt(password) == user.password:
# request.session['user_id'] = user.id
# user_filter.update(last_login=datetime.datetime.now())
if user.role == 'SU':
request.session['role_id'] = 2
elif user.role == 'GA':
request.session['role_id'] = 1
else:
request.session['role_id'] = 0
return HttpResponseRedirect('/', )
# response.set_cookie('username', username, expires=604800)
# response.set_cookie('seed', PyCrypt.md5_crypt(password), expires=604800)
# return response
else:
error = '密码错误,请重新输入。'
else:
error = '用户不存在。'
error = '用户名或密码错误'
return render_to_response('login.html', {'error': error})
def logout(request):
def Logout(request):
request.session.delete()
logout(request)
return HttpResponseRedirect('/login/')
def setting(request):
header_title, path1 = '项目设置', '设置'
setting_r = get_object(Setting, name='default')
setting_default = get_object(Setting, name='default')
if request.method == "POST":
username = request.POST.get('username', '')
port = request.POST.get('port', '')
private_key = request.POST.get('key', '')
setting_raw = request.POST.get('setting', '')
if setting_raw == 'default':
username = request.POST.get('username', '')
port = request.POST.get('port', '')
password = request.POST.get('password', '')
private_key = request.POST.get('key', '')
if '' in [username, port, private_key]:
return HttpResponse('所填内容不能为空')
else:
settings = get_object(Setting, id=1)
private_key_path = os.path.join(BASE_DIR, 'keys', 'default', 'default_private_key.pem')
with open(private_key_path, 'w') as f:
f.write(private_key)
os.chmod(private_key_path, 0600)
if settings:
Setting.objects.filter(name='default').update(default_user=username, default_port=port,
default_pri_key_path=private_key_path)
if '' in [username, port] and ('' in password or '' in private_key):
return HttpResponse('所填内容不能为空, 且密码和私钥填一个')
else:
setting_r = Setting(name='default', default_user=username, default_port=port,
default_pri_key_path=private_key_path).save()
private_key_path = os.path.join(BASE_DIR, 'keys', 'default', 'default_private_key.pem')
if private_key:
with open(private_key_path, 'w') as f:
f.write(private_key)
os.chmod(private_key_path, 0600)
if setting_default:
if password != setting_default.default_password:
password_encode = CRYPTOR.encrypt(password)
else:
password_encode = password
Setting.objects.filter(name='default').update(default_user=username, default_port=port,
default_password=password_encode,
default_pri_key_path=private_key_path)
else:
password_encode = CRYPTOR.encrypt(password)
setting_r = Setting(name='default', default_user=username, default_port=port,
default_password=password_encode,
default_pri_key_path=private_key_path).save()
msg = "设置成功"
return my_render('setting.html', locals(), request)
def test2(request):
return my_render('test2.html', locals(), request)
#
# def filter_ajax_api(request):
# attr = request.GET.get('attr', 'user')

View File

@ -1,39 +1,27 @@
# coding: utf-8
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserGroup(models.Model):
name = models.CharField(max_length=80, unique=True)
# assets = models.TextField(max_length=1000, verbose_name="Assets", default='')
# asset_groups = models.CharField(max_length=1000, verbose_name="Asset Groups", default='')
comment = models.CharField(max_length=160, blank=True, null=True)
def __unicode__(self):
return self.name
# def get_user(self):
# return self.user_set.all()
#
# def update(self, **kwargs):
# for key, value in kwargs.items():
# self.__setattr__(key, value)
# self.save()
class User(models.Model):
class User(AbstractUser):
USER_ROLE_CHOICES = (
('SU', 'SuperUser'),
('GA', 'GroupAdmin'),
('CU', 'CommonUser'),
)
username = models.CharField(max_length=80, unique=True)
password = models.CharField(max_length=100)
name = models.CharField(max_length=80)
email = models.EmailField(max_length=75)
role = models.CharField(max_length=2, choices=USER_ROLE_CHOICES, default='CU')
uuid = models.CharField(max_length=100)
role = models.CharField(max_length=2, choices=USER_ROLE_CHOICES, default='CU')
group = models.ManyToManyField(UserGroup)
ssh_key_pwd = models.CharField(max_length=200)
is_active = models.BooleanField(default=True)
@ -41,94 +29,10 @@ class User(models.Model):
date_joined = models.DateTimeField(null=True)
def __unicode__(self):
return self.username
# def get_asset_group(self):
# """
# Get user host_groups.
# 获取用户有权限的主机组
# """
# host_group_list = []
# perm_list = []
# user_group_all = self.group.all()
# for user_group in user_group_all:
# perm_list.extend(user_group.perm_set.all())
#
# for perm in perm_list:
# host_group_list.append(perm.asset_group)
#
# return host_group_list
#
# def get_asset_group_info(self, printable=False):
# """
# Get or print asset group info
# 获取或打印用户授权资产组
# """
# asset_groups_info = {}
# asset_groups = self.get_asset_group()
#
# for asset_group in asset_groups:
# asset_groups_info[asset_group.id] = [asset_group.name, asset_group.comment]
#
# if printable:
# for group_id in asset_groups_info:
# if asset_groups_info[group_id][1]:
# print "[%3s] %s -- %s" % (group_id,
# asset_groups_info[group_id][0],
# asset_groups_info[group_id][1])
# else:
# print "[%3s] %s" % (group_id, asset_groups_info[group_id][0])
# print ''
# else:
# return asset_groups_info
#
# def get_asset(self):
# """
# Get the assets of under the user control.
# 获取主机列表
# """
# assets = []
# asset_groups = self.get_asset_group()
#
# for asset_group in asset_groups:
# assets.extend(asset_group.asset_set.all())
#
# return assets
#
# def get_asset_info(self, printable=False):
# """
# Get or print the user asset info
# 获取或打印用户资产信息
# """
# from jasset.models import AssetAlias
# assets_info = {}
# assets = self.get_asset()
#
# for asset in assets:
# asset_alias = AssetAlias.objects.filter(user=self, asset=asset)
# if asset_alias and asset_alias[0].alias != '':
# assets_info[asset.ip] = [asset.id, asset.ip, str(asset_alias[0].alias)]
# else:
# assets_info[asset.ip] = [asset.id, asset.ip, str(asset.comment)]
#
# if printable:
# ips = assets_info.keys()
# ips.sort()
# for ip in ips:
# if assets_info[ip][2]:
# print '%-15s -- %s' % (ip, assets_info[ip][2])
# else:
# print '%-15s' % ip
# print ''
# else:
# return assets_info
#
# def update(self, **kwargs):
# for key, value in kwargs.items():
# self.__setattr__(key, value)
# self.save()
class AdminGroup(models.Model):
"""
@ -141,5 +45,3 @@ class AdminGroup(models.Model):
def __unicode__(self):
return '%s: %s' % (self.user.username, self.group.name)

View File

@ -22,6 +22,6 @@ urlpatterns = patterns('juser.views',
(r'^profile/$', 'profile'),
(r'^change_info/$', 'change_info'),
(r'^regen_ssh_key/$', 'regen_ssh_key'),
(r'^chg_role/$', 'chg_role'),
(r'^change_role/$', 'chg_role'),
(r'^down_key/$', 'down_key'),
)

View File

@ -5,6 +5,7 @@ from subprocess import call
from juser.models import AdminGroup
from jumpserver.api import *
from jumpserver.settings import BASE_DIR, EMAIL_HOST_USER as MAIL_FROM
def group_add_user(group, user_id=None, username=None):
@ -60,6 +61,7 @@ def db_add_user(**kwargs):
admin_groups = kwargs.pop('admin_groups')
role = kwargs.get('role', 'CU')
user = User(**kwargs)
user.set_password(kwargs.get('password'))
user.save()
if groups_post:
group_select = []
@ -84,10 +86,10 @@ def db_update_user(**kwargs):
groups_post = kwargs.pop('groups')
admin_groups_post = kwargs.pop('admin_groups')
user_id = kwargs.pop('user_id')
user = User.objects.filter(id=user_id)
user = User.objects.get(id=user_id)
if user:
user.update(**kwargs)
user = user[0]
user.set_password(kwargs.pop('password'))
user.save()
else:
return None

View File

@ -2,25 +2,25 @@
# Author: Guanghongwei
# Email: ibuler@qq.com
import random
from Crypto.PublicKey import RSA
# import random
# from Crypto.PublicKey import RSA
import uuid as uuid_r
from django.db.models import Q
from django.template import RequestContext
from django.db.models import ObjectDoesNotExist
from jumpserver.settings import EMAIL_HOST_USER
from juser.user_api import *
from jperm.perm_api import _public_perm_api, perm_user_api, user_permed
MAIL_FROM = EMAIL_HOST_USER
def chg_role(request):
role = {'SU': 2, 'DA': 1, 'CU': 0}
user, dept = get_session_user_dept(request)
role = {'SU': 2, 'GA': 1, 'CU': 0}
if request.session['role_id'] > 0:
request.session['role_id'] = 0
elif request.session['role_id'] == 0:
request.session['role_id'] = role.get(user.role, 0)
request.session['role_id'] = role.get(request.user.role, 0)
return HttpResponseRedirect('/')
@ -168,8 +168,8 @@ def user_add(request):
if '' in [username, password, ssh_key_pwd, name, role]:
error = u'带*内容不能为空'
raise ServerError
user_test = get_object(User, username=username)
if user_test:
check_user_is_exist = User.objects.filter(username=username)
if check_user_is_exist:
error = u'用户 %s 已存在' % username
raise ServerError
@ -178,10 +178,10 @@ def user_add(request):
else:
try:
user = db_add_user(username=username, name=name,
password=CRYPTOR.md5_crypt(password),
password=password,
email=email, role=role, uuid=uuid,
groups=groups, admin_groups=admin_groups,
ssh_key_pwd=CRYPTOR.md5_crypt(ssh_key_pwd),
ssh_key_pwd=ssh_key_pwd,
is_active=is_active,
date_joined=datetime.datetime.now())
server_add_user(username, password, ssh_key_pwd, ssh_key_login_need)
@ -233,10 +233,10 @@ def user_list(request):
@require_role(role='user')
def user_detail(request):
header_title, path1, path2 = '用户详情', '用户管理', '用户详情'
if request.session.get('role_id') == 0:
user_id = request.session.get('user_id')
else:
user_id = request.GET.get('id', '')
# if request.session.get('role_id') == 0:
# user_id = request.user.id
# else:
# user_id = request.GET.get('id', '')
# if request.session.get('role_id') == 1:
# user, dept = get_session_user_dept(request)
# if not validate(request, user=[user_id]):
@ -244,9 +244,9 @@ def user_detail(request):
# if not user_id:
# return HttpResponseRedirect('/juser/user_list/')
user = get_object(User, id=user_id)
if user:
pass
# user = get_object(User, id=user_id)
# if user:
# pass
# asset_group_permed = user.get_asset_group()
# logs_last = Log.objects.filter(user=user.name).order_by('-start_time')[0:10]
# logs_all = Log.objects.filter(user=user.name).order_by('-start_time')
@ -257,8 +257,14 @@ def user_detail(request):
@require_role(role='admin')
def user_del(request):
user_ids = request.GET.get('id', '')
user_id_list = user_ids.split(',')
if request.method == "GET":
user_ids = request.GET.get('id', '')
user_id_list = user_ids.split(',')
elif request.method == "POST":
user_ids = request.POST.get('id', '')
user_id_list = user_ids.split(',')
else:
return HttpResponse('错误请求')
for user_id in user_id_list:
user = get_object(User, id=user_id)
if user:
@ -370,11 +376,11 @@ def user_edit(request):
else:
return HttpResponseRedirect('/juser/user_list/')
if password != user.password:
password_decode = password
password = CRYPTOR.md5_crypt(password)
else:
password_decode = None
# if password != user.password:
# password_decode = password
# password = CRYPTOR.md5_crypt(password)
# else:
# password_decode = None
db_update_user(user_id=user_id,
password=password,
@ -409,7 +415,10 @@ def user_edit_adm(request):
def profile(request):
user_id = request.session.get('user_id')
a = request.user.id
a = request.user.groups
user_id = request.user.id
if not user_id:
return HttpResponseRedirect('/')
user = User.objects.get(id=user_id)
@ -418,7 +427,7 @@ def profile(request):
def change_info(request):
header_title, path1, path2 = '修改信息', '用户管理', '修改个人信息'
user_id = request.session.get('user_id')
user_id = request.user.id
user = get_object(User, id=user_id)
error = ''
if not user:
@ -436,10 +445,11 @@ def change_info(request):
error = '密码须大于6位'
if not error:
if password != user.password:
password = CRYPTOR.md5_crypt(password)
# if password != user.password:
# password = CRYPTOR.md5_crypt(password)
user.update(name=name, password=password, email=email)
user.update(name=name, email=email)
user.set_password(password)
msg = '修改成功'
return render_to_response('juser/change_info.html', locals(), context_instance=RequestContext(request))
@ -465,7 +475,7 @@ def down_key(request):
user_id = request.GET.get('id')
if is_role_request(request, 'user'):
user_id = request.session.get('user_id')
user_id = request.user.id
if user_id:
user = get_object(User, id=user_id)

294
run_websocket.py Normal file
View File

@ -0,0 +1,294 @@
# coding: utf-8
import time
import datetime
import json
import os
import sys
import os.path
import threading
import urllib
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
import tornado.httpserver
import tornado.gen
import tornado.httpclient
from tornado.websocket import WebSocketClosedError
from tornado.options import define, options
from pyinotify import WatchManager, Notifier, ProcessEvent, IN_DELETE, IN_CREATE, IN_MODIFY, AsyncNotifier
# from gevent import monkey
# monkey.patch_all()
# import gevent
# from gevent.socket import wait_read, wait_write
import struct, fcntl, signal, socket, select, fnmatch
import paramiko
from connect import Tty
from connect import TtyLog, Log
try:
import simplejson as json
except ImportError:
import json
define("port", default=3000, help="run on the given port", type=int)
define("host", default='0.0.0.0', help="run port on", type=str)
def require_auth(func):
def _deco(request, *args, **kwargs):
username = request.get_argument('username', '')
asset_name = request.get_argument('asset_name', '')
token = request.get_argument('token', '')
print username, asset_name, token
client = tornado.httpclient.HTTPClient()
# response = client.fetch('http://some/url') + urllib.urlencode({'username': username,
# 'asset_name': asset_name, 'token': token})
# return request.close()
return func(request, *args, **kwargs)
return _deco
class MyThread(threading.Thread):
def __init__(self, *args, **kwargs):
super(MyThread, self).__init__(*args, **kwargs)
def run(self):
try:
super(MyThread, self).run()
except WebSocketClosedError:
pass
class EventHandler(ProcessEvent):
def __init__(self, client=None):
self.client = client
def process_IN_CREATE(self, event):
print "Create file:%s." % os.path.join(event.path, event.name)
def process_IN_DELETE(self, event):
print "Delete file:%s." % os.path.join(event.path, event.name)
def process_IN_MODIFY(self, event):
print "Modify file:%s." % os.path.join(event.path, event.name)
self.client.write_message(f.read())
def file_monitor(path='.', client=None):
wm = WatchManager()
mask = IN_DELETE | IN_CREATE | IN_MODIFY
notifier = AsyncNotifier(wm, EventHandler(client))
wm.add_watch(path, mask, auto_add=True, rec=True)
if not os.path.isfile(path):
print "You should monitor a file"
sys.exit(3)
else:
print "now starting monitor %s." % path
global f
f = open(path, 'r')
st_size = os.stat(path)[6]
f.seek(st_size)
while True:
try:
notifier.process_events()
if notifier.check_events():
notifier.read_events()
except KeyboardInterrupt:
print "keyboard Interrupt."
notifier.stop()
break
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r'/monitor', MonitorHandler),
(r'/terminal', WebTerminalHandler),
(r'/kill', WebTerminalKillHandler),
]
setting = {
'cookie_secret': 'DFksdfsasdfkasdfFKwlwfsdfsa1204mx',
'template_path': os.path.join(os.path.dirname(__file__), 'templates'),
'static_path': os.path.join(os.path.dirname(__file__), 'static'),
'debug': True,
}
tornado.web.Application.__init__(self, handlers, **setting)
class MonitorHandler(tornado.websocket.WebSocketHandler):
clients = []
threads = []
def __init__(self, *args, **kwargs):
self.file_path = None
super(self.__class__, self).__init__(*args, **kwargs)
def check_origin(self, origin):
return True
@require_auth
def open(self):
# 获取监控的path
self.file_path = self.get_argument('file_path', '')
MonitorHandler.clients.append(self)
thread = MyThread(target=file_monitor, args=('%s.log' % self.file_path, self))
MonitorHandler.threads.append(thread)
self.stream.set_nodelay(True)
try:
for t in MonitorHandler.threads:
if t.is_alive():
continue
t.setDaemon(True)
t.start()
except WebSocketClosedError:
client_index = MonitorHandler.clients.index(self)
MonitorHandler.threads[client_index].stop()
MonitorHandler.clients.remove(self)
MonitorHandler.threads.remove(MonitorHandler.threads[client_index])
print len(MonitorHandler.threads), len(MonitorHandler.clients)
def on_message(self, message):
# 监控日志,发生变动发向客户端
pass
def on_close(self):
# 客户端主动关闭
# self.close()
print "Close websocket."
client_index = MonitorHandler.clients.index(self)
MonitorHandler.clients.remove(self)
MonitorHandler.threads.remove(MonitorHandler.threads[client_index])
class WebTty(Tty):
def __init__(self, *args, **kwargs):
super(WebTty, self).__init__(*args, **kwargs)
self.login_type = 'web'
self.ws = None
self.input_r = ''
self.input_mode = False
class WebTerminalKillHandler(tornado.web.RequestHandler):
def get(self):
ws_id = self.get_argument('id')
Log.objects.filter(id=ws_id).update(is_finished=True)
for ws in WebTerminalHandler.clients:
print ws.id
if ws.id == int(ws_id):
print "killed"
ws.log.save()
ws.close()
print len(WebTerminalHandler.clients)
class WebTerminalHandler(tornado.websocket.WebSocketHandler):
tasks = []
clients = []
def __init__(self, *args, **kwargs):
self.term = None
self.channel = None
self.log_file_f = None
self.log_time_f = None
self.log = None
self.id = 0
super(WebTerminalHandler, self).__init__(*args, **kwargs)
def check_origin(self, origin):
return True
@require_auth
def open(self):
asset_name = self.get_argument('asset_name', '')
username = self.get_argument('username', '')
token = self.get_argument('token', '')
print asset_name, username, token
self.term = WebTty('a', 'b')
self.term.get_connection()
self.channel = self.term.ssh.invoke_shell(term='xterm')
WebTerminalHandler.tasks.append(MyThread(target=self.forward_outbound))
WebTerminalHandler.clients.append(self)
for t in WebTerminalHandler.tasks:
if t.is_alive():
continue
t.setDaemon(True)
t.start()
def on_message(self, message):
data = json.loads(message)
if not data:
return
if data.get('data'):
self.term.input_mode = True
if str(data['data']) in ['\r', '\n', '\r\n']:
TtyLog(log=self.log, datetime=datetime.datetime.now(), cmd=self.term.remove_control_char(self.term.input_r)).save()
self.term.input_r = ''
self.term.input_mode = False
self.channel.send(data['data'])
def on_close(self):
print 'On_close'
if self in WebTerminalHandler.clients:
WebTerminalHandler.clients.remove(self)
try:
self.log_file_f.write('End time is %s' % datetime.datetime.now())
self.log.is_finished = True
self.log.end_time = datetime.datetime.now()
self.log.save()
self.close()
except AttributeError:
pass
def forward_outbound(self):
self.log_file_f, self.log_time_f, self.log = self.term.get_log_file()
self.id = self.log.id
try:
data = ''
pre_timestamp = time.time()
while True:
r, w, e = select.select([self.channel, sys.stdin], [], [])
if self.channel in r:
recv = self.channel.recv(1024)
if not len(recv):
return
data += recv
try:
self.write_message(json.dumps({'data': data}))
now_timestamp = time.time()
self.log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(data)))
self.log_file_f.write(data)
pre_timestamp = now_timestamp
self.log_file_f.flush()
self.log_time_f.flush()
if self.term.input_mode and not self.term.is_output(data):
self.term.input_r += data
data = ''
except UnicodeDecodeError:
pass
finally:
self.close()
if __name__ == '__main__':
tornado.options.parse_command_line()
app = Application()
server = tornado.httpserver.HTTPServer(app)
server.bind(options.port, options.host)
# server.listen(options.port)
server.start(num_processes=1)
tornado.ioloop.IOLoop.instance().start()

BIN
static/.DS_Store vendored

Binary file not shown.

689
static/css/plugins/bootstrap.min.css vendored Normal file
View File

@ -0,0 +1,689 @@
article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
audio:not([controls]){display:none;}
html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
a:hover,a:active{outline:0;}
sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}
sup{top:-0.5em;}
sub{bottom:-0.25em;}
img{height:auto;border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;}
button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}
button,input{*overflow:visible;line-height:normal;}
button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}
button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}
textarea{overflow:auto;vertical-align:top;}
.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";}
.clearfix:after{clear:both;}
.hide-text{overflow:hidden;text-indent:100%;white-space:nowrap;}
.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;}
body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333333;background-color:#ffffff;}
a{color:#0088cc;text-decoration:none;}
a:hover{color:#005580;text-decoration:underline;}
.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";}
.row:after{clear:both;}
[class*="span"]{float:left;margin-left:20px;}
.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
.span12{width:940px;}
.span11{width:860px;}
.span10{width:780px;}
.span9{width:700px;}
.span8{width:620px;}
.span7{width:540px;}
.span6{width:460px;}
.span5{width:380px;}
.span4{width:300px;}
.span3{width:220px;}
.span2{width:140px;}
.span1{width:60px;}
.offset12{margin-left:980px;}
.offset11{margin-left:900px;}
.offset10{margin-left:820px;}
.offset9{margin-left:740px;}
.offset8{margin-left:660px;}
.offset7{margin-left:580px;}
.offset6{margin-left:500px;}
.offset5{margin-left:420px;}
.offset4{margin-left:340px;}
.offset3{margin-left:260px;}
.offset2{margin-left:180px;}
.offset1{margin-left:100px;}
.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";}
.row-fluid:after{clear:both;}
.row-fluid>[class*="span"]{float:left;margin-left:2.127659574%;}
.row-fluid>[class*="span"]:first-child{margin-left:0;}
.row-fluid > .span12{width:99.99999998999999%;}
.row-fluid > .span11{width:91.489361693%;}
.row-fluid > .span10{width:82.97872339599999%;}
.row-fluid > .span9{width:74.468085099%;}
.row-fluid > .span8{width:65.95744680199999%;}
.row-fluid > .span7{width:57.446808505%;}
.row-fluid > .span6{width:48.93617020799999%;}
.row-fluid > .span5{width:40.425531911%;}
.row-fluid > .span4{width:31.914893614%;}
.row-fluid > .span3{width:23.404255317%;}
.row-fluid > .span2{width:14.89361702%;}
.row-fluid > .span1{width:6.382978723%;}
.container{margin-left:auto;margin-right:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";}
.container:after{clear:both;}
.container-fluid{padding-left:20px;padding-right:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";}
.container-fluid:after{clear:both;}
p{margin:0 0 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;}p small{font-size:11px;color:#999999;}
.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;}
h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999999;}
h1{font-size:30px;line-height:36px;}h1 small{font-size:18px;}
h2{font-size:24px;line-height:36px;}h2 small{font-size:18px;}
h3{line-height:27px;font-size:18px;}h3 small{font-size:14px;}
h4,h5,h6{line-height:18px;}
h4{font-size:14px;}h4 small{font-size:12px;}
h5{font-size:12px;}
h6{font-size:11px;color:#999999;text-transform:uppercase;}
.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eeeeee;}
.page-header h1{line-height:1;}
ul,ol{padding:0;margin:0 0 9px 25px;}
ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
ul{list-style:disc;}
ol{list-style:decimal;}
li{line-height:18px;}
ul.unstyled,ol.unstyled{margin-left:0;list-style:none;}
dl{margin-bottom:18px;}
dt,dd{line-height:18px;}
dt{font-weight:bold;line-height:17px;}
dd{margin-left:9px;}
.dl-horizontal dt{float:left;clear:left;width:120px;text-align:right;}
.dl-horizontal dd{margin-left:130px;}
hr{margin:18px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;}
strong{font-weight:bold;}
em{font-style:italic;}
.muted{color:#999999;}
abbr[title]{border-bottom:1px dotted #ddd;cursor:help;}
abbr.initialism{font-size:90%;text-transform:uppercase;}
blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;}
blockquote small{display:block;line-height:18px;color:#999999;}blockquote small:before{content:'\2014 \00A0';}
blockquote.pull-right{float:right;padding-left:0;padding-right:15px;border-left:0;border-right:5px solid #eeeeee;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;}
q:before,q:after,blockquote:before,blockquote:after{content:"";}
address{display:block;margin-bottom:18px;line-height:18px;font-style:normal;}
small{font-size:100%;}
cite{font-style:normal;}
code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;}
pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.025px;line-height:18px;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;white-space:pre;white-space:pre-wrap;word-break:break-all;word-wrap:break-word;}pre.prettyprint{margin-bottom:18px;}
pre code{padding:0;color:inherit;background-color:transparent;border:0;}
.pre-scrollable{max-height:340px;overflow-y:scroll;}
form{margin:0 0 18px;}
fieldset{padding:0;margin:0;border:0;}
legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333333;border:0;border-bottom:1px solid #eee;}legend small{font-size:13.5px;color:#999999;}
label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:18px;}
input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;}
label{display:block;margin-bottom:5px;color:#333333;}
input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555555;border:1px solid #cccccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
.uneditable-textarea{width:auto;height:auto;}
label input,label textarea,label select{display:block;}
input[type="image"],input[type="checkbox"],input[type="radio"]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;cursor:pointer;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;border:0 \9;}
input[type="image"]{border:0;}
input[type="file"]{width:auto;padding:initial;line-height:initial;border:initial;background-color:#ffffff;background-color:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
input[type="button"],input[type="reset"],input[type="submit"]{width:auto;height:auto;}
select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px;}
input[type="file"]{line-height:18px \9;}
select{width:220px;background-color:#ffffff;}
select[multiple],select[size]{height:auto;}
input[type="image"]{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
textarea{height:auto;}
input[type="hidden"]{display:none;}
.radio,.checkbox{padding-left:18px;}
.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;}
.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;}
.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;}
.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;}
input,textarea{-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;}
input:focus,textarea:focus{border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);outline:0;outline:thin dotted \9;}
input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
.input-mini{width:60px;}
.input-small{width:90px;}
.input-medium{width:150px;}
.input-large{width:210px;}
.input-xlarge{width:270px;}
.input-xxlarge{width:530px;}
input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{float:none;margin-left:0;}
input,textarea,.uneditable-input{margin-left:0;}
input.span12, textarea.span12, .uneditable-input.span12{width:930px;}
input.span11, textarea.span11, .uneditable-input.span11{width:850px;}
input.span10, textarea.span10, .uneditable-input.span10{width:770px;}
input.span9, textarea.span9, .uneditable-input.span9{width:690px;}
input.span8, textarea.span8, .uneditable-input.span8{width:610px;}
input.span7, textarea.span7, .uneditable-input.span7{width:530px;}
input.span6, textarea.span6, .uneditable-input.span6{width:450px;}
input.span5, textarea.span5, .uneditable-input.span5{width:370px;}
input.span4, textarea.span4, .uneditable-input.span4{width:290px;}
input.span3, textarea.span3, .uneditable-input.span3{width:210px;}
input.span2, textarea.span2, .uneditable-input.span2{width:130px;}
input.span1, textarea.span1, .uneditable-input.span1{width:50px;}
input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#eeeeee;border-color:#ddd;cursor:not-allowed;}
.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;}
.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;border-color:#c09853;}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e;}
.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;}
.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;}
.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;border-color:#b94a48;}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392;}
.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;}
.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;}
.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;border-color:#468847;}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b;}
.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;}
input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
.form-actions{padding:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#eeeeee;border-top:1px solid #ddd;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";}
.form-actions:after{clear:both;}
.uneditable-input{display:block;background-color:#ffffff;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
:-moz-placeholder{color:#999999;}
::-webkit-input-placeholder{color:#999999;}
.help-block,.help-inline{color:#555555;}
.help-block{display:block;margin-bottom:9px;}
.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;}
.input-prepend,.input-append{margin-bottom:5px;}.input-prepend input,.input-append input,.input-prepend select,.input-append select,.input-prepend .uneditable-input,.input-append .uneditable-input{*margin-left:0;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}.input-prepend input:focus,.input-append input:focus,.input-prepend select:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{position:relative;z-index:2;}
.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc;}
.input-prepend .add-on,.input-append .add-on{display:inline-block;width:auto;min-width:16px;height:18px;padding:4px 5px;font-weight:normal;line-height:18px;text-align:center;text-shadow:0 1px 0 #ffffff;vertical-align:middle;background-color:#eeeeee;border:1px solid #ccc;}
.input-prepend .add-on,.input-append .add-on,.input-prepend .btn,.input-append .btn{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.input-prepend .active,.input-append .active{background-color:#a9dba9;border-color:#46a546;}
.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;}
.input-append input,.input-append select .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.input-append .uneditable-input{border-left-color:#eee;border-right-color:#ccc;}
.input-append .add-on,.input-append .btn{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
.search-query{padding-left:14px;padding-right:14px;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;}
.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;margin-bottom:0;}
.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;}
.form-search label,.form-inline label{display:inline-block;}
.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;}
.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;}
.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-left:0;margin-right:3px;}
.control-group{margin-bottom:9px;}
legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate;}
.form-horizontal .control-group{margin-bottom:18px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";}
.form-horizontal .control-group:after{clear:both;}
.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right;}
.form-horizontal .controls{margin-left:160px;*display:inline-block;*margin-left:0;*padding-left:20px;}
.form-horizontal .help-block{margin-top:9px;margin-bottom:0;}
.form-horizontal .form-actions{padding-left:160px;}
table{max-width:100%;border-collapse:collapse;border-spacing:0;background-color:transparent;}
.table{width:100%;margin-bottom:18px;}.table th,.table td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;}
.table th{font-weight:bold;}
.table thead th{vertical-align:bottom;}
.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;}
.table tbody+tbody{border-top:2px solid #dddddd;}
.table-condensed th,.table-condensed td{padding:4px 5px;}
.table-bordered{border:1px solid #dddddd;border-left:0;border-collapse:separate;*border-collapse:collapsed;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;}
.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;}
.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;}
.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;}
.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;}
.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;}
.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5;}
table .span1{float:none;width:44px;margin-left:0;}
table .span2{float:none;width:124px;margin-left:0;}
table .span3{float:none;width:204px;margin-left:0;}
table .span4{float:none;width:284px;margin-left:0;}
table .span5{float:none;width:364px;margin-left:0;}
table .span6{float:none;width:444px;margin-left:0;}
table .span7{float:none;width:524px;margin-left:0;}
table .span8{float:none;width:604px;margin-left:0;}
table .span9{float:none;width:684px;margin-left:0;}
table .span10{float:none;width:764px;margin-left:0;}
table .span11{float:none;width:844px;margin-left:0;}
table .span12{float:none;width:924px;margin-left:0;}
table .span13{float:none;width:1004px;margin-left:0;}
table .span14{float:none;width:1084px;margin-left:0;}
table .span15{float:none;width:1164px;margin-left:0;}
table .span16{float:none;width:1244px;margin-left:0;}
table .span17{float:none;width:1324px;margin-left:0;}
table .span18{float:none;width:1404px;margin-left:0;}
table .span19{float:none;width:1484px;margin-left:0;}
table .span20{float:none;width:1564px;margin-left:0;}
table .span21{float:none;width:1644px;margin-left:0;}
table .span22{float:none;width:1724px;margin-left:0;}
table .span23{float:none;width:1804px;margin-left:0;}
table .span24{float:none;width:1884px;margin-left:0;}
[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;*margin-right:.3em;}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0;}
.icon-white{background-image:url("../img/glyphicons-halflings-white.png");}
.icon-glass{background-position:0 0;}
.icon-music{background-position:-24px 0;}
.icon-search{background-position:-48px 0;}
.icon-envelope{background-position:-72px 0;}
.icon-heart{background-position:-96px 0;}
.icon-star{background-position:-120px 0;}
.icon-star-empty{background-position:-144px 0;}
.icon-user{background-position:-168px 0;}
.icon-film{background-position:-192px 0;}
.icon-th-large{background-position:-216px 0;}
.icon-th{background-position:-240px 0;}
.icon-th-list{background-position:-264px 0;}
.icon-ok{background-position:-288px 0;}
.icon-remove{background-position:-312px 0;}
.icon-zoom-in{background-position:-336px 0;}
.icon-zoom-out{background-position:-360px 0;}
.icon-off{background-position:-384px 0;}
.icon-signal{background-position:-408px 0;}
.icon-cog{background-position:-432px 0;}
.icon-trash{background-position:-456px 0;}
.icon-home{background-position:0 -24px;}
.icon-file{background-position:-24px -24px;}
.icon-time{background-position:-48px -24px;}
.icon-road{background-position:-72px -24px;}
.icon-download-alt{background-position:-96px -24px;}
.icon-download{background-position:-120px -24px;}
.icon-upload{background-position:-144px -24px;}
.icon-inbox{background-position:-168px -24px;}
.icon-play-circle{background-position:-192px -24px;}
.icon-repeat{background-position:-216px -24px;}
.icon-refresh{background-position:-240px -24px;}
.icon-list-alt{background-position:-264px -24px;}
.icon-lock{background-position:-287px -24px;}
.icon-flag{background-position:-312px -24px;}
.icon-headphones{background-position:-336px -24px;}
.icon-volume-off{background-position:-360px -24px;}
.icon-volume-down{background-position:-384px -24px;}
.icon-volume-up{background-position:-408px -24px;}
.icon-qrcode{background-position:-432px -24px;}
.icon-barcode{background-position:-456px -24px;}
.icon-tag{background-position:0 -48px;}
.icon-tags{background-position:-25px -48px;}
.icon-book{background-position:-48px -48px;}
.icon-bookmark{background-position:-72px -48px;}
.icon-print{background-position:-96px -48px;}
.icon-camera{background-position:-120px -48px;}
.icon-font{background-position:-144px -48px;}
.icon-bold{background-position:-167px -48px;}
.icon-italic{background-position:-192px -48px;}
.icon-text-height{background-position:-216px -48px;}
.icon-text-width{background-position:-240px -48px;}
.icon-align-left{background-position:-264px -48px;}
.icon-align-center{background-position:-288px -48px;}
.icon-align-right{background-position:-312px -48px;}
.icon-align-justify{background-position:-336px -48px;}
.icon-list{background-position:-360px -48px;}
.icon-indent-left{background-position:-384px -48px;}
.icon-indent-right{background-position:-408px -48px;}
.icon-facetime-video{background-position:-432px -48px;}
.icon-picture{background-position:-456px -48px;}
.icon-pencil{background-position:0 -72px;}
.icon-map-marker{background-position:-24px -72px;}
.icon-adjust{background-position:-48px -72px;}
.icon-tint{background-position:-72px -72px;}
.icon-edit{background-position:-96px -72px;}
.icon-share{background-position:-120px -72px;}
.icon-check{background-position:-144px -72px;}
.icon-move{background-position:-168px -72px;}
.icon-step-backward{background-position:-192px -72px;}
.icon-fast-backward{background-position:-216px -72px;}
.icon-backward{background-position:-240px -72px;}
.icon-play{background-position:-264px -72px;}
.icon-pause{background-position:-288px -72px;}
.icon-stop{background-position:-312px -72px;}
.icon-forward{background-position:-336px -72px;}
.icon-fast-forward{background-position:-360px -72px;}
.icon-step-forward{background-position:-384px -72px;}
.icon-eject{background-position:-408px -72px;}
.icon-chevron-left{background-position:-432px -72px;}
.icon-chevron-right{background-position:-456px -72px;}
.icon-plus-sign{background-position:0 -96px;}
.icon-minus-sign{background-position:-24px -96px;}
.icon-remove-sign{background-position:-48px -96px;}
.icon-ok-sign{background-position:-72px -96px;}
.icon-question-sign{background-position:-96px -96px;}
.icon-info-sign{background-position:-120px -96px;}
.icon-screenshot{background-position:-144px -96px;}
.icon-remove-circle{background-position:-168px -96px;}
.icon-ok-circle{background-position:-192px -96px;}
.icon-ban-circle{background-position:-216px -96px;}
.icon-arrow-left{background-position:-240px -96px;}
.icon-arrow-right{background-position:-264px -96px;}
.icon-arrow-up{background-position:-289px -96px;}
.icon-arrow-down{background-position:-312px -96px;}
.icon-share-alt{background-position:-336px -96px;}
.icon-resize-full{background-position:-360px -96px;}
.icon-resize-small{background-position:-384px -96px;}
.icon-plus{background-position:-408px -96px;}
.icon-minus{background-position:-433px -96px;}
.icon-asterisk{background-position:-456px -96px;}
.icon-exclamation-sign{background-position:0 -120px;}
.icon-gift{background-position:-24px -120px;}
.icon-leaf{background-position:-48px -120px;}
.icon-fire{background-position:-72px -120px;}
.icon-eye-open{background-position:-96px -120px;}
.icon-eye-close{background-position:-120px -120px;}
.icon-warning-sign{background-position:-144px -120px;}
.icon-plane{background-position:-168px -120px;}
.icon-calendar{background-position:-192px -120px;}
.icon-random{background-position:-216px -120px;}
.icon-comment{background-position:-240px -120px;}
.icon-magnet{background-position:-264px -120px;}
.icon-chevron-up{background-position:-288px -120px;}
.icon-chevron-down{background-position:-313px -119px;}
.icon-retweet{background-position:-336px -120px;}
.icon-shopping-cart{background-position:-360px -120px;}
.icon-folder-close{background-position:-384px -120px;}
.icon-folder-open{background-position:-408px -120px;}
.icon-resize-vertical{background-position:-432px -119px;}
.icon-resize-horizontal{background-position:-456px -118px;}
.dropdown{position:relative;}
.dropdown-toggle{*margin-bottom:-3px;}
.dropdown-toggle:active,.open .dropdown-toggle{outline:0;}
.caret{display:inline-block;width:0;height:0;vertical-align:top;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000000;opacity:0.3;filter:alpha(opacity=30);content:"";}
.dropdown .caret{margin-top:8px;margin-left:2px;}
.dropdown:hover .caret,.open.dropdown .caret{opacity:1;filter:alpha(opacity=100);}
.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;float:left;display:none;min-width:160px;padding:4px 0;margin:0;list-style:none;background-color:#ffffff;border-color:#ccc;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:1px;-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;*border-right-width:2px;*border-bottom-width:2px;}.dropdown-menu.pull-right{right:0;left:auto;}
.dropdown-menu .divider{height:1px;margin:8px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;*width:100%;*margin:-5px 0 5px;}
.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#333333;white-space:nowrap;}
.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;background-color:#0088cc;}
.dropdown.open{*z-index:1000;}.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);}
.dropdown.open .dropdown-menu{display:block;}
.pull-right .dropdown-menu{left:auto;right:0;}
.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"\2191";}
.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;}
.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;}
.collapse{-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-ms-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;position:relative;overflow:hidden;height:0;}.collapse.in{height:auto;}
.close{float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover{color:#000000;text-decoration:none;opacity:0.4;filter:alpha(opacity=40);cursor:pointer;}
.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 10px 4px;margin-bottom:0;font-size:13px;line-height:18px;color:#333333;text-align:center;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);vertical-align:middle;background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-ms-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(top, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);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);filter:progid:dximagetransform.microsoft.gradient(enabled=false);border:1px solid #cccccc;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);cursor:pointer;*margin-left:.3em;}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;}
.btn:active,.btn.active{background-color:#cccccc \9;}
.btn:first-child{*margin-left:0;}
.btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-ms-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;}
.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
.btn.active,.btn:active{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);background-color:#e6e6e6;background-color:#d9d9d9 \9;outline:0;}
.btn.disabled,.btn[disabled]{cursor:default;background-image:none;background-color:#e6e6e6;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
.btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
.btn-large [class^="icon-"]{margin-top:1px;}
.btn-small{padding:5px 9px;font-size:11px;line-height:16px;}
.btn-small [class^="icon-"]{margin-top:-1px;}
.btn-mini{padding:2px 6px;font-size:11px;line-height:14px;}
.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);color:#ffffff;}
.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);}
.btn-primary{background-color:#0074cc;background-image:-moz-linear-gradient(top, #0088cc, #0055cc);background-image:-ms-linear-gradient(top, #0088cc, #0055cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0055cc));background-image:-webkit-linear-gradient(top, #0088cc, #0055cc);background-image:-o-linear-gradient(top, #0088cc, #0055cc);background-image:linear-gradient(top, #0088cc, #0055cc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0055cc', GradientType=0);border-color:#0055cc #0055cc #003580;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#0055cc;}
.btn-primary:active,.btn-primary.active{background-color:#004099 \9;}
.btn-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;}
.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;}
.btn-danger{background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-ms-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(top, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;}
.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;}
.btn-success{background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-ms-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(top, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;}
.btn-success:active,.btn-success.active{background-color:#408140 \9;}
.btn-info{background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-ms-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(top, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;}
.btn-info:active,.btn-info.active{background-color:#24748c \9;}
.btn-inverse{background-color:#414141;background-image:-moz-linear-gradient(top, #555555, #222222);background-image:-ms-linear-gradient(top, #555555, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#222222));background-image:-webkit-linear-gradient(top, #555555, #222222);background-image:-o-linear-gradient(top, #555555, #222222);background-image:linear-gradient(top, #555555, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222222;}
.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;}
button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;}
button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;}
button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;}
button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;}
.btn-group{position:relative;*zoom:1;*margin-left:.3em;}.btn-group:before,.btn-group:after{display:table;content:"";}
.btn-group:after{clear:both;}
.btn-group:first-child{*margin-left:0;}
.btn-group+.btn-group{margin-left:5px;}
.btn-toolbar{margin-top:9px;margin-bottom:9px;}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;}
.btn-group .btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.btn-group .btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
.btn-group .btn:last-child,.btn-group .dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
.btn-group .btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;}
.btn-group .btn.large:last-child,.btn-group .large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;}
.btn-group .btn:hover,.btn-group .btn:focus,.btn-group .btn:active,.btn-group .btn.active{z-index:2;}
.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;}
.btn-group .dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);*padding-top:3px;*padding-bottom:3px;}
.btn-group .btn-mini.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:1px;*padding-bottom:1px;}
.btn-group .btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px;}
.btn-group .btn-large.dropdown-toggle{padding-left:12px;padding-right:12px;}
.btn-group.open{*z-index:1000;}.btn-group.open .dropdown-menu{display:block;margin-top:1px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);}
.btn .caret{margin-top:7px;margin-left:0;}
.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100);}
.btn-mini .caret{margin-top:5px;}
.btn-small .caret{margin-top:6px;}
.btn-large .caret{margin-top:6px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:0.75;filter:alpha(opacity=75);}
.alert{padding:8px 35px 8px 14px;margin-bottom:18px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#c09853;}
.alert-heading{color:inherit;}
.alert .close{position:relative;top:-2px;right:-21px;line-height:18px;}
.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;}
.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;}
.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;}
.alert-block{padding-top:14px;padding-bottom:14px;}
.alert-block>p,.alert-block>ul{margin-bottom:0;}
.alert-block p+p{margin-top:5px;}
.nav{margin-left:0;margin-bottom:18px;list-style:none;}
.nav>li>a{display:block;}
.nav>li>a:hover{text-decoration:none;background-color:#eeeeee;}
.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;}
.nav li+.nav-header{margin-top:9px;}
.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;}
.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
.nav-list>li>a{padding:3px 15px;}
.nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;}
.nav-list [class^="icon-"]{margin-right:2px;}
.nav-list .divider{height:1px;margin:8px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;*width:100%;*margin:-5px 0 5px;}
.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";}
.nav-tabs:after,.nav-pills:after{clear:both;}
.nav-tabs>li,.nav-pills>li{float:left;}
.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;}
.nav-tabs{border-bottom:1px solid #ddd;}
.nav-tabs>li{margin-bottom:-1px;}
.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:18px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;}
.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#0088cc;}
.nav-stacked>li{float:none;}
.nav-stacked>li>a{margin-right:0;}
.nav-tabs.nav-stacked{border-bottom:0;}
.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}
.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}
.nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;}
.nav-pills.nav-stacked>li>a{margin-bottom:3px;}
.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;}
.nav-tabs .dropdown-menu,.nav-pills .dropdown-menu{margin-top:1px;border-width:1px;}
.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{border-top-color:#0088cc;border-bottom-color:#0088cc;margin-top:6px;}
.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580;}
.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333333;border-bottom-color:#333333;}
.nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;}
.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;}
.nav .open .caret,.nav .open.active .caret,.nav .open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);}
.tabs-stacked .open>a:hover{border-color:#999999;}
.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";}
.tabbable:after{clear:both;}
.tab-content{display:table;width:100%;}
.tabs-below .nav-tabs,.tabs-right .nav-tabs,.tabs-left .nav-tabs{border-bottom:0;}
.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
.tab-content>.active,.pill-content>.active{display:block;}
.tabs-below .nav-tabs{border-top:1px solid #ddd;}
.tabs-below .nav-tabs>li{margin-top:-1px;margin-bottom:0;}
.tabs-below .nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below .nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;}
.tabs-below .nav-tabs .active>a,.tabs-below .nav-tabs .active>a:hover{border-color:transparent #ddd #ddd #ddd;}
.tabs-left .nav-tabs>li,.tabs-right .nav-tabs>li{float:none;}
.tabs-left .nav-tabs>li>a,.tabs-right .nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;}
.tabs-left .nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;}
.tabs-left .nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
.tabs-left .nav-tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;}
.tabs-left .nav-tabs .active>a,.tabs-left .nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;}
.tabs-right .nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;}
.tabs-right .nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
.tabs-right .nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;}
.tabs-right .nav-tabs .active>a,.tabs-right .nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;}
.navbar{*position:relative;*z-index:2;overflow:visible;margin-bottom:18px;}
.navbar-inner{padding-left:20px;padding-right:20px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);}
.navbar .container{width:auto;}
.btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);}.btn-navbar:hover,.btn-navbar:active,.btn-navbar.active,.btn-navbar.disabled,.btn-navbar[disabled]{background-color:#222222;}
.btn-navbar:active,.btn-navbar.active{background-color:#080808 \9;}
.btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);}
.btn-navbar .icon-bar+.icon-bar{margin-top:3px;}
.nav-collapse.collapse{height:auto;}
.navbar{color:#999999;}.navbar .brand:hover{text-decoration:none;}
.navbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#ffffff;}
.navbar .navbar-text{margin-bottom:0;line-height:40px;}
.navbar .btn,.navbar .btn-group{margin-top:5px;}
.navbar .btn-group .btn{margin-top:0;}
.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";}
.navbar-form:after{clear:both;}
.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;}
.navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0;}
.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;}
.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;}
.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;background-color:#626262;border:1px solid #151515;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query:-moz-placeholder{color:#cccccc;}
.navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;}
.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;}
.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;}
.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
.navbar-fixed-top{top:0;}
.navbar-fixed-bottom{bottom:0;}
.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;}
.navbar .nav.pull-right{float:right;}
.navbar .nav>li{display:block;float:left;}
.navbar .nav>li>a{float:none;padding:10px 10px 11px;line-height:19px;color:#999999;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}
.navbar .nav>li>a:hover{background-color:transparent;color:#ffffff;text-decoration:none;}
.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#ffffff;text-decoration:none;background-color:#222222;}
.navbar .divider-vertical{height:40px;width:1px;margin:0 9px;overflow:hidden;background-color:#222222;border-right:1px solid #333333;}
.navbar .nav.pull-right{margin-left:10px;margin-right:0;}
.navbar .dropdown-menu{margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.navbar .dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;}
.navbar .dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;}
.navbar-fixed-bottom .dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;}
.navbar-fixed-bottom .dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;}
.navbar .nav .dropdown-toggle .caret,.navbar .nav .open.dropdown .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
.navbar .nav .active .caret{opacity:1;filter:alpha(opacity=100);}
.navbar .nav .open>.dropdown-toggle,.navbar .nav .active>.dropdown-toggle,.navbar .nav .open.active>.dropdown-toggle{background-color:transparent;}
.navbar .nav .active>.dropdown-toggle:hover{color:#ffffff;}
.navbar .nav.pull-right .dropdown-menu,.navbar .nav .dropdown-menu.pull-right{left:auto;right:0;}.navbar .nav.pull-right .dropdown-menu:before,.navbar .nav .dropdown-menu.pull-right:before{left:auto;right:12px;}
.navbar .nav.pull-right .dropdown-menu:after,.navbar .nav .dropdown-menu.pull-right:after{left:auto;right:13px;}
.breadcrumb{padding:7px 14px;margin:0 0 18px;list-style:none;background-color:#fbfbfb;background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;}
.breadcrumb .divider{padding:0 5px;color:#999999;}
.breadcrumb .active a{color:#333333;}
.pagination{height:36px;margin:18px 0;}
.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
.pagination li{display:inline;}
.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0;}
.pagination a:hover,.pagination .active a{background-color:#f5f5f5;}
.pagination .active a{color:#999999;cursor:default;}
.pagination .disabled span,.pagination .disabled a,.pagination .disabled a:hover{color:#999999;background-color:transparent;cursor:default;}
.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
.pagination-centered{text-align:center;}
.pagination-right{text-align:right;}
.pager{margin-left:0;margin-bottom:18px;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";}
.pager:after{clear:both;}
.pager li{display:inline;}
.pager a{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
.pager a:hover{text-decoration:none;background-color:#f5f5f5;}
.pager .next a{float:right;}
.pager .previous a{float:left;}
.pager .disabled a,.pager .disabled a:hover{color:#999999;background-color:#fff;cursor:default;}
.modal-open .dropdown-menu{z-index:2050;}
.modal-open .dropdown.open{*z-index:2050;}
.modal-open .popover{z-index:2060;}
.modal-open .tooltip{z-index:2070;}
.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;}
.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);}
.modal{position:fixed;top:50%;left:50%;z-index:1050;overflow:auto;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
.modal.fade.in{top:50%;}
.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;}
.modal-body{overflow-y:auto;max-height:400px;padding:15px;}
.modal-form{margin-bottom:0;}
.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";}
.modal-footer:after{clear:both;}
.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;}
.modal-footer .btn-group .btn+.btn{margin-left:-1px;}
.tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);}
.tooltip.top{margin-top:-2px;}
.tooltip.right{margin-left:2px;}
.tooltip.bottom{margin-top:2px;}
.tooltip.left{margin-left:-2px;}
.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.tooltip-arrow{position:absolute;width:0;height:0;}
.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;}.popover.top{margin-top:-5px;}
.popover.right{margin-left:5px;}
.popover.bottom{margin-top:5px;}
.popover.left{margin-left:-5px;}
.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
.popover .arrow{position:absolute;width:0;height:0;}
.popover-inner{padding:3px;width:280px;overflow:hidden;background:#000000;background:rgba(0, 0, 0, 0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);}
.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;}
.popover-content{padding:14px;background-color:#ffffff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;}
.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";}
.thumbnails:after{clear:both;}
.thumbnails>li{float:left;margin:0 0 18px 20px;}
.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}
a.thumbnail:hover{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;}
.thumbnail .caption{padding:9px;}
.label{padding:1px 4px 2px;font-size:10.998px;font-weight:bold;line-height:13px;color:#ffffff;vertical-align:middle;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
.label:hover{color:#ffffff;text-decoration:none;}
.label-important{background-color:#b94a48;}
.label-important:hover{background-color:#953b39;}
.label-warning{background-color:#f89406;}
.label-warning:hover{background-color:#c67605;}
.label-success{background-color:#468847;}
.label-success:hover{background-color:#356635;}
.label-info{background-color:#3a87ad;}
.label-info:hover{background-color:#2d6987;}
.label-inverse{background-color:#333333;}
.label-inverse:hover{background-color:#1a1a1a;}
.badge{padding:1px 9px 2px;font-size:12.025px;font-weight:bold;white-space:nowrap;color:#ffffff;background-color:#999999;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;}
.badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;}
.badge-error{background-color:#b94a48;}
.badge-error:hover{background-color:#953b39;}
.badge-warning{background-color:#f89406;}
.badge-warning:hover{background-color:#c67605;}
.badge-success{background-color:#468847;}
.badge-success:hover{background-color:#356635;}
.badge-info{background-color:#3a87ad;}
.badge-info:hover{background-color:#2d6987;}
.badge-inverse{background-color:#333333;}
.badge-inverse:hover{background-color:#1a1a1a;}
@-webkit-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}.progress{overflow:hidden;height:18px;margin-bottom:18px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-ms-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(top, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.progress .bar{width:0%;height:18px;color:#ffffff;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-ms-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(top, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-ms-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;}
.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;}
.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;}
.progress-danger .bar{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);}
.progress-danger.progress-striped .bar{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
.progress-success .bar{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);}
.progress-success.progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
.progress-info .bar{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);}
.progress-info.progress-striped .bar{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
.progress-warning .bar{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);}
.progress-warning.progress-striped .bar{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
.accordion{margin-bottom:18px;}
.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.accordion-heading{border-bottom:0;}
.accordion-heading .accordion-toggle{display:block;padding:8px 15px;}
.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;}
.carousel{position:relative;margin-bottom:18px;line-height:1;}
.carousel-inner{overflow:hidden;width:100%;position:relative;}
.carousel .item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-ms-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;}
.carousel .item>img{display:block;line-height:1;}
.carousel .active,.carousel .next,.carousel .prev{display:block;}
.carousel .active{left:0;}
.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;}
.carousel .next{left:100%;}
.carousel .prev{left:-100%;}
.carousel .next.left,.carousel .prev.right{left:0;}
.carousel .active.left{left:-100%;}
.carousel .active.right{left:100%;}
.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;}
.carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);}
.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:10px 15px 5px;background:#333333;background:rgba(0, 0, 0, 0.75);}
.carousel-caption h4,.carousel-caption p{color:#ffffff;}
.hero-unit{padding:60px;margin-bottom:30px;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;}
.hero-unit p{font-size:18px;font-weight:200;line-height:27px;color:inherit;}
.pull-right{float:right;}
.pull-left{float:left;}
.hide{display:none;}
.show{display:block;}
.invisible{visibility:hidden;}

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

View File

@ -0,0 +1,429 @@
/*!
Chosen, a Select Box Enhancer for jQuery and Prototype
by Patrick Filler for Harvest, http://getharvest.com
Version 1.1.0
Full source at https://github.com/harvesthq/chosen
Copyright (c) 2011 Harvest http://getharvest.com
MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
This file is generated by `grunt build`, do not edit it by hand.
*/
/* @group Base */
.chosen-container {
position: relative;
display: inline-block;
vertical-align: middle;
font-size: 13px;
zoom: 1;
*display: inline;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.chosen-container .chosen-drop {
position: absolute;
top: 100%;
left: -9999px;
z-index: 1010;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
border: 1px solid #aaa;
border-top: 0;
background: #fff;
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
}
.chosen-container.chosen-with-drop .chosen-drop {
left: 0;
}
.chosen-container a {
cursor: pointer;
}
/* @end */
/* @group Single Chosen */
.chosen-container-single .chosen-single {
position: relative;
display: block;
overflow: hidden;
padding: 0 0 0 8px;
height: 23px;
border: 1px solid #aaa;
border-radius: 5px;
background-color: #fff;
background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4));
background: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background: linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background-clip: padding-box;
box-shadow: 0 0 3px white inset, 0 1px 1px rgba(0, 0, 0, 0.1);
color: #444;
text-decoration: none;
white-space: nowrap;
line-height: 24px;
}
.chosen-container-single .chosen-default {
color: #999;
}
.chosen-container-single .chosen-single span {
display: block;
overflow: hidden;
margin-right: 26px;
text-overflow: ellipsis;
white-space: nowrap;
}
.chosen-container-single .chosen-single-with-deselect span {
margin-right: 38px;
}
.chosen-container-single .chosen-single abbr {
position: absolute;
top: 6px;
right: 26px;
display: block;
width: 12px;
height: 12px;
background: url('chosen-sprite.png') -42px 1px no-repeat;
font-size: 1px;
}
.chosen-container-single .chosen-single abbr:hover {
background-position: -42px -10px;
}
.chosen-container-single.chosen-disabled .chosen-single abbr:hover {
background-position: -42px -10px;
}
.chosen-container-single .chosen-single div {
position: absolute;
top: 0;
right: 0;
display: block;
width: 18px;
height: 100%;
}
.chosen-container-single .chosen-single div b {
display: block;
width: 100%;
height: 100%;
background: url('chosen-sprite.png') no-repeat 0px 2px;
}
.chosen-container-single .chosen-search {
position: relative;
z-index: 1010;
margin: 0;
padding: 3px 4px;
white-space: nowrap;
}
.chosen-container-single .chosen-search input[type="text"] {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin: 1px 0;
padding: 4px 20px 4px 5px;
width: 100%;
height: auto;
outline: 0;
border: 1px solid #aaa;
background: white url('chosen-sprite.png') no-repeat 100% -20px;
background: url('chosen-sprite.png') no-repeat 100% -20px;
font-size: 1em;
font-family: sans-serif;
line-height: normal;
border-radius: 0;
}
.chosen-container-single .chosen-drop {
margin-top: -1px;
border-radius: 0 0 4px 4px;
background-clip: padding-box;
}
.chosen-container-single.chosen-container-single-nosearch .chosen-search {
position: absolute;
left: -9999px;
}
/* @end */
/* @group Results */
.chosen-container .chosen-results {
position: relative;
overflow-x: hidden;
overflow-y: auto;
margin: 0 4px 4px 0;
padding: 0 0 0 4px;
max-height: 240px;
-webkit-overflow-scrolling: touch;
}
.chosen-container .chosen-results li {
display: none;
margin: 0;
padding: 5px 6px;
list-style: none;
line-height: 15px;
-webkit-touch-callout: none;
}
.chosen-container .chosen-results li.active-result {
display: list-item;
cursor: pointer;
}
.chosen-container .chosen-results li.disabled-result {
display: list-item;
color: #ccc;
cursor: default;
}
.chosen-container .chosen-results li.highlighted {
background-color: #3875d7;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc));
background-image: -webkit-linear-gradient(#3875d7 20%, #2a62bc 90%);
background-image: -moz-linear-gradient(#3875d7 20%, #2a62bc 90%);
background-image: -o-linear-gradient(#3875d7 20%, #2a62bc 90%);
background-image: linear-gradient(#3875d7 20%, #2a62bc 90%);
color: #fff;
}
.chosen-container .chosen-results li.no-results {
display: list-item;
background: #f4f4f4;
}
.chosen-container .chosen-results li.group-result {
display: list-item;
font-weight: bold;
cursor: default;
}
.chosen-container .chosen-results li.group-option {
padding-left: 15px;
}
.chosen-container .chosen-results li em {
font-style: normal;
text-decoration: underline;
}
/* @end */
/* @group Multi Chosen */
.chosen-container-multi .chosen-choices {
-moz-box-sizing: border-box;
background-color: #FFFFFF;
border: 1px solid #CBD5DD;
border-radius: 2px;
cursor: text;
height: auto !important;
margin: 0;
min-height: 30px;
overflow: hidden;
padding: 2px;
position: relative;
width: 100%;
}
.chosen-container-multi .chosen-choices li {
float: left;
list-style: none;
}
.chosen-container-multi .chosen-choices li.search-field {
margin: 0;
padding: 0;
white-space: nowrap;
}
.chosen-container-multi .chosen-choices li.search-field input[type="text"] {
margin: 1px 0;
padding: 5px;
height: 25px;
outline: 0;
border: 0 !important;
background: transparent !important;
box-shadow: none;
color: #666;
font-size: 100%;
font-family: sans-serif;
line-height: normal;
border-radius: 0;
}
.chosen-container-multi .chosen-choices li.search-field .default {
color: #999;
}
.chosen-container-multi .chosen-choices li.search-choice {
position: relative;
margin: 3px 0 3px 5px;
padding: 3px 20px 3px 5px;
border: 1px solid #aaa;
border-radius: 3px;
background-color: #e4e4e4;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
background-image: -webkit-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -moz-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -o-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-clip: padding-box;
box-shadow: 0 0 2px white inset, 0 1px 0 rgba(0, 0, 0, 0.05);
color: #333;
line-height: 13px;
cursor: default;
}
.chosen-container-multi .chosen-choices li.search-choice .search-choice-close {
position: absolute;
top: 4px;
right: 3px;
display: block;
width: 12px;
height: 12px;
background: url('chosen-sprite.png') -42px 1px no-repeat;
font-size: 1px;
}
.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover {
background-position: -42px -10px;
}
.chosen-container-multi .chosen-choices li.search-choice-disabled {
padding-right: 5px;
border: 1px solid #ccc;
background-color: #e4e4e4;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
color: #666;
}
.chosen-container-multi .chosen-choices li.search-choice-focus {
background: #d4d4d4;
}
.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close {
background-position: -42px -10px;
}
.chosen-container-multi .chosen-results {
margin: 0;
padding: 0;
}
.chosen-container-multi .chosen-drop .result-selected {
display: list-item;
color: #ccc;
cursor: default;
}
/* @end */
/* @group Active */
.chosen-container-active .chosen-single {
border: 1px solid #5897fb;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
.chosen-container-active.chosen-with-drop .chosen-single {
border: 1px solid #aaa;
-moz-border-radius-bottomright: 0;
border-bottom-right-radius: 0;
-moz-border-radius-bottomleft: 0;
border-bottom-left-radius: 0;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #eeeeee), color-stop(80%, #ffffff));
background-image: -webkit-linear-gradient(#eeeeee 20%, #ffffff 80%);
background-image: -moz-linear-gradient(#eeeeee 20%, #ffffff 80%);
background-image: -o-linear-gradient(#eeeeee 20%, #ffffff 80%);
background-image: linear-gradient(#eeeeee 20%, #ffffff 80%);
box-shadow: 0 1px 0 #fff inset;
}
.chosen-container-active.chosen-with-drop .chosen-single div {
border-left: none;
background: transparent;
}
.chosen-container-active.chosen-with-drop .chosen-single div b {
background-position: -18px 2px;
}
.chosen-container-active .chosen-choices {
border: 1px solid #5897fb;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
.chosen-container-active .chosen-choices li.search-field input[type="text"] {
color: #111 !important;
}
/* @end */
/* @group Disabled Support */
.chosen-disabled {
opacity: 0.5 !important;
cursor: default;
}
.chosen-disabled .chosen-single {
cursor: default;
}
.chosen-disabled .chosen-choices .search-choice .search-choice-close {
cursor: default;
}
/* @end */
/* @group Right to Left */
.chosen-rtl {
text-align: right;
}
.chosen-rtl .chosen-single {
overflow: visible;
padding: 0 8px 0 0;
}
.chosen-rtl .chosen-single span {
margin-right: 0;
margin-left: 26px;
direction: rtl;
}
.chosen-rtl .chosen-single-with-deselect span {
margin-left: 38px;
}
.chosen-rtl .chosen-single div {
right: auto;
left: 3px;
}
.chosen-rtl .chosen-single abbr {
right: auto;
left: 26px;
}
.chosen-rtl .chosen-choices li {
float: right;
}
.chosen-rtl .chosen-choices li.search-field input[type="text"] {
direction: rtl;
}
.chosen-rtl .chosen-choices li.search-choice {
margin: 3px 5px 3px 0;
padding: 3px 5px 3px 19px;
}
.chosen-rtl .chosen-choices li.search-choice .search-choice-close {
right: auto;
left: 4px;
}
.chosen-rtl.chosen-container-single-nosearch .chosen-search,
.chosen-rtl .chosen-drop {
left: 9999px;
}
.chosen-rtl.chosen-container-single .chosen-results {
margin: 0 0 4px 4px;
padding: 0 4px 0 0;
}
.chosen-rtl .chosen-results li.group-option {
padding-right: 15px;
padding-left: 0;
}
.chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div {
border-right: none;
}
.chosen-rtl .chosen-search input[type="text"] {
padding: 4px 5px 4px 20px;
background: white url('chosen-sprite.png') no-repeat -30px -20px;
background: url('chosen-sprite.png') no-repeat -30px -20px;
direction: rtl;
}
.chosen-rtl.chosen-container-single .chosen-single div b {
background-position: 6px 2px;
}
.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b {
background-position: -12px 2px;
}
/* @end */
/* @group Retina compatibility */
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-resolution: 144dpi) {
.chosen-rtl .chosen-search input[type="text"],
.chosen-container-single .chosen-single abbr,
.chosen-container-single .chosen-single div b,
.chosen-container-single .chosen-search input[type="text"],
.chosen-container-multi .chosen-choices .search-choice .search-choice-close,
.chosen-container .chosen-results-scroll-down span,
.chosen-container .chosen-results-scroll-up span {
background-image: url('chosen-sprite@2x.png') !important;
background-size: 52px 37px !important;
background-repeat: no-repeat !important;
}
}
/* @end */

View File

@ -0,0 +1,789 @@
/*!
* Datepicker for Bootstrap
*
* Copyright 2012 Stefan Petre
* Improvements by Andrew Rowls
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
.datepicker {
padding: 4px;
border-radius: 4px;
direction: ltr;
/*.dow {
border-top: 1px solid #ddd !important;
}*/
}
.datepicker-inline {
width: 220px;
}
.datepicker.datepicker-rtl {
direction: rtl;
}
.datepicker.datepicker-rtl table tr td span {
float: right;
}
.datepicker-dropdown {
top: 0;
left: 0;
}
.datepicker-dropdown:before {
content: '';
display: inline-block;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-top: 0;
border-bottom-color: rgba(0, 0, 0, 0.2);
position: absolute;
}
.datepicker-dropdown:after {
content: '';
display: inline-block;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #fff;
border-top: 0;
position: absolute;
}
.datepicker-dropdown.datepicker-orient-left:before {
left: 6px;
}
.datepicker-dropdown.datepicker-orient-left:after {
left: 7px;
}
.datepicker-dropdown.datepicker-orient-right:before {
right: 6px;
}
.datepicker-dropdown.datepicker-orient-right:after {
right: 7px;
}
.datepicker-dropdown.datepicker-orient-top:before {
top: -7px;
}
.datepicker-dropdown.datepicker-orient-top:after {
top: -6px;
}
.datepicker-dropdown.datepicker-orient-bottom:before {
bottom: -7px;
border-bottom: 0;
border-top: 7px solid #999;
}
.datepicker-dropdown.datepicker-orient-bottom:after {
bottom: -6px;
border-bottom: 0;
border-top: 6px solid #fff;
}
.datepicker > div {
display: none;
}
.datepicker.days div.datepicker-days {
display: block;
}
.datepicker.months div.datepicker-months {
display: block;
}
.datepicker.years div.datepicker-years {
display: block;
}
.datepicker table {
margin: 0;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.datepicker table tr td,
.datepicker table tr th {
text-align: center;
width: 30px;
height: 30px;
border-radius: 4px;
border: none;
}
.table-striped .datepicker table tr td,
.table-striped .datepicker table tr th {
background-color: transparent;
}
.datepicker table tr td.day:hover,
.datepicker table tr td.day.focused {
background: #eeeeee;
cursor: pointer;
}
.datepicker table tr td.old,
.datepicker table tr td.new {
color: #999999;
}
.datepicker table tr td.disabled,
.datepicker table tr td.disabled:hover {
background: none;
color: #999999;
cursor: default;
}
.datepicker table tr td.today,
.datepicker table tr td.today:hover,
.datepicker table tr td.today.disabled,
.datepicker table tr td.today.disabled:hover {
color: #000000;
background-color: #ffdb99;
border-color: #ffb733;
}
.datepicker table tr td.today:hover,
.datepicker table tr td.today:hover:hover,
.datepicker table tr td.today.disabled:hover,
.datepicker table tr td.today.disabled:hover:hover,
.datepicker table tr td.today:focus,
.datepicker table tr td.today:hover:focus,
.datepicker table tr td.today.disabled:focus,
.datepicker table tr td.today.disabled:hover:focus,
.datepicker table tr td.today:active,
.datepicker table tr td.today:hover:active,
.datepicker table tr td.today.disabled:active,
.datepicker table tr td.today.disabled:hover:active,
.datepicker table tr td.today.active,
.datepicker table tr td.today:hover.active,
.datepicker table tr td.today.disabled.active,
.datepicker table tr td.today.disabled:hover.active,
.open .dropdown-toggle.datepicker table tr td.today,
.open .dropdown-toggle.datepicker table tr td.today:hover,
.open .dropdown-toggle.datepicker table tr td.today.disabled,
.open .dropdown-toggle.datepicker table tr td.today.disabled:hover {
color: #000000;
background-color: #ffcd70;
border-color: #f59e00;
}
.datepicker table tr td.today:active,
.datepicker table tr td.today:hover:active,
.datepicker table tr td.today.disabled:active,
.datepicker table tr td.today.disabled:hover:active,
.datepicker table tr td.today.active,
.datepicker table tr td.today:hover.active,
.datepicker table tr td.today.disabled.active,
.datepicker table tr td.today.disabled:hover.active,
.open .dropdown-toggle.datepicker table tr td.today,
.open .dropdown-toggle.datepicker table tr td.today:hover,
.open .dropdown-toggle.datepicker table tr td.today.disabled,
.open .dropdown-toggle.datepicker table tr td.today.disabled:hover {
background-image: none;
}
.datepicker table tr td.today.disabled,
.datepicker table tr td.today:hover.disabled,
.datepicker table tr td.today.disabled.disabled,
.datepicker table tr td.today.disabled:hover.disabled,
.datepicker table tr td.today[disabled],
.datepicker table tr td.today:hover[disabled],
.datepicker table tr td.today.disabled[disabled],
.datepicker table tr td.today.disabled:hover[disabled],
fieldset[disabled] .datepicker table tr td.today,
fieldset[disabled] .datepicker table tr td.today:hover,
fieldset[disabled] .datepicker table tr td.today.disabled,
fieldset[disabled] .datepicker table tr td.today.disabled:hover,
.datepicker table tr td.today.disabled:hover,
.datepicker table tr td.today:hover.disabled:hover,
.datepicker table tr td.today.disabled.disabled:hover,
.datepicker table tr td.today.disabled:hover.disabled:hover,
.datepicker table tr td.today[disabled]:hover,
.datepicker table tr td.today:hover[disabled]:hover,
.datepicker table tr td.today.disabled[disabled]:hover,
.datepicker table tr td.today.disabled:hover[disabled]:hover,
fieldset[disabled] .datepicker table tr td.today:hover,
fieldset[disabled] .datepicker table tr td.today:hover:hover,
fieldset[disabled] .datepicker table tr td.today.disabled:hover,
fieldset[disabled] .datepicker table tr td.today.disabled:hover:hover,
.datepicker table tr td.today.disabled:focus,
.datepicker table tr td.today:hover.disabled:focus,
.datepicker table tr td.today.disabled.disabled:focus,
.datepicker table tr td.today.disabled:hover.disabled:focus,
.datepicker table tr td.today[disabled]:focus,
.datepicker table tr td.today:hover[disabled]:focus,
.datepicker table tr td.today.disabled[disabled]:focus,
.datepicker table tr td.today.disabled:hover[disabled]:focus,
fieldset[disabled] .datepicker table tr td.today:focus,
fieldset[disabled] .datepicker table tr td.today:hover:focus,
fieldset[disabled] .datepicker table tr td.today.disabled:focus,
fieldset[disabled] .datepicker table tr td.today.disabled:hover:focus,
.datepicker table tr td.today.disabled:active,
.datepicker table tr td.today:hover.disabled:active,
.datepicker table tr td.today.disabled.disabled:active,
.datepicker table tr td.today.disabled:hover.disabled:active,
.datepicker table tr td.today[disabled]:active,
.datepicker table tr td.today:hover[disabled]:active,
.datepicker table tr td.today.disabled[disabled]:active,
.datepicker table tr td.today.disabled:hover[disabled]:active,
fieldset[disabled] .datepicker table tr td.today:active,
fieldset[disabled] .datepicker table tr td.today:hover:active,
fieldset[disabled] .datepicker table tr td.today.disabled:active,
fieldset[disabled] .datepicker table tr td.today.disabled:hover:active,
.datepicker table tr td.today.disabled.active,
.datepicker table tr td.today:hover.disabled.active,
.datepicker table tr td.today.disabled.disabled.active,
.datepicker table tr td.today.disabled:hover.disabled.active,
.datepicker table tr td.today[disabled].active,
.datepicker table tr td.today:hover[disabled].active,
.datepicker table tr td.today.disabled[disabled].active,
.datepicker table tr td.today.disabled:hover[disabled].active,
fieldset[disabled] .datepicker table tr td.today.active,
fieldset[disabled] .datepicker table tr td.today:hover.active,
fieldset[disabled] .datepicker table tr td.today.disabled.active,
fieldset[disabled] .datepicker table tr td.today.disabled:hover.active {
background-color: #ffdb99;
border-color: #ffb733;
}
.datepicker table tr td.today:hover:hover {
color: #000;
}
.datepicker table tr td.today.active:hover {
color: #fff;
}
.datepicker table tr td.range,
.datepicker table tr td.range:hover,
.datepicker table tr td.range.disabled,
.datepicker table tr td.range.disabled:hover {
background: #eeeeee;
border-radius: 0;
}
.datepicker table tr td.range.today,
.datepicker table tr td.range.today:hover,
.datepicker table tr td.range.today.disabled,
.datepicker table tr td.range.today.disabled:hover {
color: #000000;
background-color: #f7ca77;
border-color: #f1a417;
border-radius: 0;
}
.datepicker table tr td.range.today:hover,
.datepicker table tr td.range.today:hover:hover,
.datepicker table tr td.range.today.disabled:hover,
.datepicker table tr td.range.today.disabled:hover:hover,
.datepicker table tr td.range.today:focus,
.datepicker table tr td.range.today:hover:focus,
.datepicker table tr td.range.today.disabled:focus,
.datepicker table tr td.range.today.disabled:hover:focus,
.datepicker table tr td.range.today:active,
.datepicker table tr td.range.today:hover:active,
.datepicker table tr td.range.today.disabled:active,
.datepicker table tr td.range.today.disabled:hover:active,
.datepicker table tr td.range.today.active,
.datepicker table tr td.range.today:hover.active,
.datepicker table tr td.range.today.disabled.active,
.datepicker table tr td.range.today.disabled:hover.active,
.open .dropdown-toggle.datepicker table tr td.range.today,
.open .dropdown-toggle.datepicker table tr td.range.today:hover,
.open .dropdown-toggle.datepicker table tr td.range.today.disabled,
.open .dropdown-toggle.datepicker table tr td.range.today.disabled:hover {
color: #000000;
background-color: #f4bb51;
border-color: #bf800c;
}
.datepicker table tr td.range.today:active,
.datepicker table tr td.range.today:hover:active,
.datepicker table tr td.range.today.disabled:active,
.datepicker table tr td.range.today.disabled:hover:active,
.datepicker table tr td.range.today.active,
.datepicker table tr td.range.today:hover.active,
.datepicker table tr td.range.today.disabled.active,
.datepicker table tr td.range.today.disabled:hover.active,
.open .dropdown-toggle.datepicker table tr td.range.today,
.open .dropdown-toggle.datepicker table tr td.range.today:hover,
.open .dropdown-toggle.datepicker table tr td.range.today.disabled,
.open .dropdown-toggle.datepicker table tr td.range.today.disabled:hover {
background-image: none;
}
.datepicker table tr td.range.today.disabled,
.datepicker table tr td.range.today:hover.disabled,
.datepicker table tr td.range.today.disabled.disabled,
.datepicker table tr td.range.today.disabled:hover.disabled,
.datepicker table tr td.range.today[disabled],
.datepicker table tr td.range.today:hover[disabled],
.datepicker table tr td.range.today.disabled[disabled],
.datepicker table tr td.range.today.disabled:hover[disabled],
fieldset[disabled] .datepicker table tr td.range.today,
fieldset[disabled] .datepicker table tr td.range.today:hover,
fieldset[disabled] .datepicker table tr td.range.today.disabled,
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover,
.datepicker table tr td.range.today.disabled:hover,
.datepicker table tr td.range.today:hover.disabled:hover,
.datepicker table tr td.range.today.disabled.disabled:hover,
.datepicker table tr td.range.today.disabled:hover.disabled:hover,
.datepicker table tr td.range.today[disabled]:hover,
.datepicker table tr td.range.today:hover[disabled]:hover,
.datepicker table tr td.range.today.disabled[disabled]:hover,
.datepicker table tr td.range.today.disabled:hover[disabled]:hover,
fieldset[disabled] .datepicker table tr td.range.today:hover,
fieldset[disabled] .datepicker table tr td.range.today:hover:hover,
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover,
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:hover,
.datepicker table tr td.range.today.disabled:focus,
.datepicker table tr td.range.today:hover.disabled:focus,
.datepicker table tr td.range.today.disabled.disabled:focus,
.datepicker table tr td.range.today.disabled:hover.disabled:focus,
.datepicker table tr td.range.today[disabled]:focus,
.datepicker table tr td.range.today:hover[disabled]:focus,
.datepicker table tr td.range.today.disabled[disabled]:focus,
.datepicker table tr td.range.today.disabled:hover[disabled]:focus,
fieldset[disabled] .datepicker table tr td.range.today:focus,
fieldset[disabled] .datepicker table tr td.range.today:hover:focus,
fieldset[disabled] .datepicker table tr td.range.today.disabled:focus,
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:focus,
.datepicker table tr td.range.today.disabled:active,
.datepicker table tr td.range.today:hover.disabled:active,
.datepicker table tr td.range.today.disabled.disabled:active,
.datepicker table tr td.range.today.disabled:hover.disabled:active,
.datepicker table tr td.range.today[disabled]:active,
.datepicker table tr td.range.today:hover[disabled]:active,
.datepicker table tr td.range.today.disabled[disabled]:active,
.datepicker table tr td.range.today.disabled:hover[disabled]:active,
fieldset[disabled] .datepicker table tr td.range.today:active,
fieldset[disabled] .datepicker table tr td.range.today:hover:active,
fieldset[disabled] .datepicker table tr td.range.today.disabled:active,
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:active,
.datepicker table tr td.range.today.disabled.active,
.datepicker table tr td.range.today:hover.disabled.active,
.datepicker table tr td.range.today.disabled.disabled.active,
.datepicker table tr td.range.today.disabled:hover.disabled.active,
.datepicker table tr td.range.today[disabled].active,
.datepicker table tr td.range.today:hover[disabled].active,
.datepicker table tr td.range.today.disabled[disabled].active,
.datepicker table tr td.range.today.disabled:hover[disabled].active,
fieldset[disabled] .datepicker table tr td.range.today.active,
fieldset[disabled] .datepicker table tr td.range.today:hover.active,
fieldset[disabled] .datepicker table tr td.range.today.disabled.active,
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover.active {
background-color: #f7ca77;
border-color: #f1a417;
}
.datepicker table tr td.selected,
.datepicker table tr td.selected:hover,
.datepicker table tr td.selected.disabled,
.datepicker table tr td.selected.disabled:hover {
color: #ffffff;
background-color: #999999;
border-color: #555555;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datepicker table tr td.selected:hover,
.datepicker table tr td.selected:hover:hover,
.datepicker table tr td.selected.disabled:hover,
.datepicker table tr td.selected.disabled:hover:hover,
.datepicker table tr td.selected:focus,
.datepicker table tr td.selected:hover:focus,
.datepicker table tr td.selected.disabled:focus,
.datepicker table tr td.selected.disabled:hover:focus,
.datepicker table tr td.selected:active,
.datepicker table tr td.selected:hover:active,
.datepicker table tr td.selected.disabled:active,
.datepicker table tr td.selected.disabled:hover:active,
.datepicker table tr td.selected.active,
.datepicker table tr td.selected:hover.active,
.datepicker table tr td.selected.disabled.active,
.datepicker table tr td.selected.disabled:hover.active,
.open .dropdown-toggle.datepicker table tr td.selected,
.open .dropdown-toggle.datepicker table tr td.selected:hover,
.open .dropdown-toggle.datepicker table tr td.selected.disabled,
.open .dropdown-toggle.datepicker table tr td.selected.disabled:hover {
color: #ffffff;
background-color: #858585;
border-color: #373737;
}
.datepicker table tr td.selected:active,
.datepicker table tr td.selected:hover:active,
.datepicker table tr td.selected.disabled:active,
.datepicker table tr td.selected.disabled:hover:active,
.datepicker table tr td.selected.active,
.datepicker table tr td.selected:hover.active,
.datepicker table tr td.selected.disabled.active,
.datepicker table tr td.selected.disabled:hover.active,
.open .dropdown-toggle.datepicker table tr td.selected,
.open .dropdown-toggle.datepicker table tr td.selected:hover,
.open .dropdown-toggle.datepicker table tr td.selected.disabled,
.open .dropdown-toggle.datepicker table tr td.selected.disabled:hover {
background-image: none;
}
.datepicker table tr td.selected.disabled,
.datepicker table tr td.selected:hover.disabled,
.datepicker table tr td.selected.disabled.disabled,
.datepicker table tr td.selected.disabled:hover.disabled,
.datepicker table tr td.selected[disabled],
.datepicker table tr td.selected:hover[disabled],
.datepicker table tr td.selected.disabled[disabled],
.datepicker table tr td.selected.disabled:hover[disabled],
fieldset[disabled] .datepicker table tr td.selected,
fieldset[disabled] .datepicker table tr td.selected:hover,
fieldset[disabled] .datepicker table tr td.selected.disabled,
fieldset[disabled] .datepicker table tr td.selected.disabled:hover,
.datepicker table tr td.selected.disabled:hover,
.datepicker table tr td.selected:hover.disabled:hover,
.datepicker table tr td.selected.disabled.disabled:hover,
.datepicker table tr td.selected.disabled:hover.disabled:hover,
.datepicker table tr td.selected[disabled]:hover,
.datepicker table tr td.selected:hover[disabled]:hover,
.datepicker table tr td.selected.disabled[disabled]:hover,
.datepicker table tr td.selected.disabled:hover[disabled]:hover,
fieldset[disabled] .datepicker table tr td.selected:hover,
fieldset[disabled] .datepicker table tr td.selected:hover:hover,
fieldset[disabled] .datepicker table tr td.selected.disabled:hover,
fieldset[disabled] .datepicker table tr td.selected.disabled:hover:hover,
.datepicker table tr td.selected.disabled:focus,
.datepicker table tr td.selected:hover.disabled:focus,
.datepicker table tr td.selected.disabled.disabled:focus,
.datepicker table tr td.selected.disabled:hover.disabled:focus,
.datepicker table tr td.selected[disabled]:focus,
.datepicker table tr td.selected:hover[disabled]:focus,
.datepicker table tr td.selected.disabled[disabled]:focus,
.datepicker table tr td.selected.disabled:hover[disabled]:focus,
fieldset[disabled] .datepicker table tr td.selected:focus,
fieldset[disabled] .datepicker table tr td.selected:hover:focus,
fieldset[disabled] .datepicker table tr td.selected.disabled:focus,
fieldset[disabled] .datepicker table tr td.selected.disabled:hover:focus,
.datepicker table tr td.selected.disabled:active,
.datepicker table tr td.selected:hover.disabled:active,
.datepicker table tr td.selected.disabled.disabled:active,
.datepicker table tr td.selected.disabled:hover.disabled:active,
.datepicker table tr td.selected[disabled]:active,
.datepicker table tr td.selected:hover[disabled]:active,
.datepicker table tr td.selected.disabled[disabled]:active,
.datepicker table tr td.selected.disabled:hover[disabled]:active,
fieldset[disabled] .datepicker table tr td.selected:active,
fieldset[disabled] .datepicker table tr td.selected:hover:active,
fieldset[disabled] .datepicker table tr td.selected.disabled:active,
fieldset[disabled] .datepicker table tr td.selected.disabled:hover:active,
.datepicker table tr td.selected.disabled.active,
.datepicker table tr td.selected:hover.disabled.active,
.datepicker table tr td.selected.disabled.disabled.active,
.datepicker table tr td.selected.disabled:hover.disabled.active,
.datepicker table tr td.selected[disabled].active,
.datepicker table tr td.selected:hover[disabled].active,
.datepicker table tr td.selected.disabled[disabled].active,
.datepicker table tr td.selected.disabled:hover[disabled].active,
fieldset[disabled] .datepicker table tr td.selected.active,
fieldset[disabled] .datepicker table tr td.selected:hover.active,
fieldset[disabled] .datepicker table tr td.selected.disabled.active,
fieldset[disabled] .datepicker table tr td.selected.disabled:hover.active {
background-color: #999999;
border-color: #555555;
}
.datepicker table tr td.active,
.datepicker table tr td.active:hover,
.datepicker table tr td.active.disabled,
.datepicker table tr td.active.disabled:hover {
color: #ffffff;
background-color: #428bca;
border-color: #357ebd;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datepicker table tr td.active:hover,
.datepicker table tr td.active:hover:hover,
.datepicker table tr td.active.disabled:hover,
.datepicker table tr td.active.disabled:hover:hover,
.datepicker table tr td.active:focus,
.datepicker table tr td.active:hover:focus,
.datepicker table tr td.active.disabled:focus,
.datepicker table tr td.active.disabled:hover:focus,
.datepicker table tr td.active:active,
.datepicker table tr td.active:hover:active,
.datepicker table tr td.active.disabled:active,
.datepicker table tr td.active.disabled:hover:active,
.datepicker table tr td.active.active,
.datepicker table tr td.active:hover.active,
.datepicker table tr td.active.disabled.active,
.datepicker table tr td.active.disabled:hover.active,
.open .dropdown-toggle.datepicker table tr td.active,
.open .dropdown-toggle.datepicker table tr td.active:hover,
.open .dropdown-toggle.datepicker table tr td.active.disabled,
.open .dropdown-toggle.datepicker table tr td.active.disabled:hover {
color: #ffffff;
background-color: #3276b1;
border-color: #285e8e;
}
.datepicker table tr td.active:active,
.datepicker table tr td.active:hover:active,
.datepicker table tr td.active.disabled:active,
.datepicker table tr td.active.disabled:hover:active,
.datepicker table tr td.active.active,
.datepicker table tr td.active:hover.active,
.datepicker table tr td.active.disabled.active,
.datepicker table tr td.active.disabled:hover.active,
.open .dropdown-toggle.datepicker table tr td.active,
.open .dropdown-toggle.datepicker table tr td.active:hover,
.open .dropdown-toggle.datepicker table tr td.active.disabled,
.open .dropdown-toggle.datepicker table tr td.active.disabled:hover {
background-image: none;
}
.datepicker table tr td.active.disabled,
.datepicker table tr td.active:hover.disabled,
.datepicker table tr td.active.disabled.disabled,
.datepicker table tr td.active.disabled:hover.disabled,
.datepicker table tr td.active[disabled],
.datepicker table tr td.active:hover[disabled],
.datepicker table tr td.active.disabled[disabled],
.datepicker table tr td.active.disabled:hover[disabled],
fieldset[disabled] .datepicker table tr td.active,
fieldset[disabled] .datepicker table tr td.active:hover,
fieldset[disabled] .datepicker table tr td.active.disabled,
fieldset[disabled] .datepicker table tr td.active.disabled:hover,
.datepicker table tr td.active.disabled:hover,
.datepicker table tr td.active:hover.disabled:hover,
.datepicker table tr td.active.disabled.disabled:hover,
.datepicker table tr td.active.disabled:hover.disabled:hover,
.datepicker table tr td.active[disabled]:hover,
.datepicker table tr td.active:hover[disabled]:hover,
.datepicker table tr td.active.disabled[disabled]:hover,
.datepicker table tr td.active.disabled:hover[disabled]:hover,
fieldset[disabled] .datepicker table tr td.active:hover,
fieldset[disabled] .datepicker table tr td.active:hover:hover,
fieldset[disabled] .datepicker table tr td.active.disabled:hover,
fieldset[disabled] .datepicker table tr td.active.disabled:hover:hover,
.datepicker table tr td.active.disabled:focus,
.datepicker table tr td.active:hover.disabled:focus,
.datepicker table tr td.active.disabled.disabled:focus,
.datepicker table tr td.active.disabled:hover.disabled:focus,
.datepicker table tr td.active[disabled]:focus,
.datepicker table tr td.active:hover[disabled]:focus,
.datepicker table tr td.active.disabled[disabled]:focus,
.datepicker table tr td.active.disabled:hover[disabled]:focus,
fieldset[disabled] .datepicker table tr td.active:focus,
fieldset[disabled] .datepicker table tr td.active:hover:focus,
fieldset[disabled] .datepicker table tr td.active.disabled:focus,
fieldset[disabled] .datepicker table tr td.active.disabled:hover:focus,
.datepicker table tr td.active.disabled:active,
.datepicker table tr td.active:hover.disabled:active,
.datepicker table tr td.active.disabled.disabled:active,
.datepicker table tr td.active.disabled:hover.disabled:active,
.datepicker table tr td.active[disabled]:active,
.datepicker table tr td.active:hover[disabled]:active,
.datepicker table tr td.active.disabled[disabled]:active,
.datepicker table tr td.active.disabled:hover[disabled]:active,
fieldset[disabled] .datepicker table tr td.active:active,
fieldset[disabled] .datepicker table tr td.active:hover:active,
fieldset[disabled] .datepicker table tr td.active.disabled:active,
fieldset[disabled] .datepicker table tr td.active.disabled:hover:active,
.datepicker table tr td.active.disabled.active,
.datepicker table tr td.active:hover.disabled.active,
.datepicker table tr td.active.disabled.disabled.active,
.datepicker table tr td.active.disabled:hover.disabled.active,
.datepicker table tr td.active[disabled].active,
.datepicker table tr td.active:hover[disabled].active,
.datepicker table tr td.active.disabled[disabled].active,
.datepicker table tr td.active.disabled:hover[disabled].active,
fieldset[disabled] .datepicker table tr td.active.active,
fieldset[disabled] .datepicker table tr td.active:hover.active,
fieldset[disabled] .datepicker table tr td.active.disabled.active,
fieldset[disabled] .datepicker table tr td.active.disabled:hover.active {
background-color: #428bca;
border-color: #357ebd;
}
.datepicker table tr td span {
display: block;
width: 23%;
height: 54px;
line-height: 54px;
float: left;
margin: 1%;
cursor: pointer;
border-radius: 4px;
}
.datepicker table tr td span:hover {
background: #eeeeee;
}
.datepicker table tr td span.disabled,
.datepicker table tr td span.disabled:hover {
background: none;
color: #999999;
cursor: default;
}
.datepicker table tr td span.active,
.datepicker table tr td span.active:hover,
.datepicker table tr td span.active.disabled,
.datepicker table tr td span.active.disabled:hover {
color: #ffffff;
background-color: #428bca;
border-color: #357ebd;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datepicker table tr td span.active:hover,
.datepicker table tr td span.active:hover:hover,
.datepicker table tr td span.active.disabled:hover,
.datepicker table tr td span.active.disabled:hover:hover,
.datepicker table tr td span.active:focus,
.datepicker table tr td span.active:hover:focus,
.datepicker table tr td span.active.disabled:focus,
.datepicker table tr td span.active.disabled:hover:focus,
.datepicker table tr td span.active:active,
.datepicker table tr td span.active:hover:active,
.datepicker table tr td span.active.disabled:active,
.datepicker table tr td span.active.disabled:hover:active,
.datepicker table tr td span.active.active,
.datepicker table tr td span.active:hover.active,
.datepicker table tr td span.active.disabled.active,
.datepicker table tr td span.active.disabled:hover.active,
.open .dropdown-toggle.datepicker table tr td span.active,
.open .dropdown-toggle.datepicker table tr td span.active:hover,
.open .dropdown-toggle.datepicker table tr td span.active.disabled,
.open .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
color: #ffffff;
background-color: #3276b1;
border-color: #285e8e;
}
.datepicker table tr td span.active:active,
.datepicker table tr td span.active:hover:active,
.datepicker table tr td span.active.disabled:active,
.datepicker table tr td span.active.disabled:hover:active,
.datepicker table tr td span.active.active,
.datepicker table tr td span.active:hover.active,
.datepicker table tr td span.active.disabled.active,
.datepicker table tr td span.active.disabled:hover.active,
.open .dropdown-toggle.datepicker table tr td span.active,
.open .dropdown-toggle.datepicker table tr td span.active:hover,
.open .dropdown-toggle.datepicker table tr td span.active.disabled,
.open .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
background-image: none;
}
.datepicker table tr td span.active.disabled,
.datepicker table tr td span.active:hover.disabled,
.datepicker table tr td span.active.disabled.disabled,
.datepicker table tr td span.active.disabled:hover.disabled,
.datepicker table tr td span.active[disabled],
.datepicker table tr td span.active:hover[disabled],
.datepicker table tr td span.active.disabled[disabled],
.datepicker table tr td span.active.disabled:hover[disabled],
fieldset[disabled] .datepicker table tr td span.active,
fieldset[disabled] .datepicker table tr td span.active:hover,
fieldset[disabled] .datepicker table tr td span.active.disabled,
fieldset[disabled] .datepicker table tr td span.active.disabled:hover,
.datepicker table tr td span.active.disabled:hover,
.datepicker table tr td span.active:hover.disabled:hover,
.datepicker table tr td span.active.disabled.disabled:hover,
.datepicker table tr td span.active.disabled:hover.disabled:hover,
.datepicker table tr td span.active[disabled]:hover,
.datepicker table tr td span.active:hover[disabled]:hover,
.datepicker table tr td span.active.disabled[disabled]:hover,
.datepicker table tr td span.active.disabled:hover[disabled]:hover,
fieldset[disabled] .datepicker table tr td span.active:hover,
fieldset[disabled] .datepicker table tr td span.active:hover:hover,
fieldset[disabled] .datepicker table tr td span.active.disabled:hover,
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover,
.datepicker table tr td span.active.disabled:focus,
.datepicker table tr td span.active:hover.disabled:focus,
.datepicker table tr td span.active.disabled.disabled:focus,
.datepicker table tr td span.active.disabled:hover.disabled:focus,
.datepicker table tr td span.active[disabled]:focus,
.datepicker table tr td span.active:hover[disabled]:focus,
.datepicker table tr td span.active.disabled[disabled]:focus,
.datepicker table tr td span.active.disabled:hover[disabled]:focus,
fieldset[disabled] .datepicker table tr td span.active:focus,
fieldset[disabled] .datepicker table tr td span.active:hover:focus,
fieldset[disabled] .datepicker table tr td span.active.disabled:focus,
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus,
.datepicker table tr td span.active.disabled:active,
.datepicker table tr td span.active:hover.disabled:active,
.datepicker table tr td span.active.disabled.disabled:active,
.datepicker table tr td span.active.disabled:hover.disabled:active,
.datepicker table tr td span.active[disabled]:active,
.datepicker table tr td span.active:hover[disabled]:active,
.datepicker table tr td span.active.disabled[disabled]:active,
.datepicker table tr td span.active.disabled:hover[disabled]:active,
fieldset[disabled] .datepicker table tr td span.active:active,
fieldset[disabled] .datepicker table tr td span.active:hover:active,
fieldset[disabled] .datepicker table tr td span.active.disabled:active,
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:active,
.datepicker table tr td span.active.disabled.active,
.datepicker table tr td span.active:hover.disabled.active,
.datepicker table tr td span.active.disabled.disabled.active,
.datepicker table tr td span.active.disabled:hover.disabled.active,
.datepicker table tr td span.active[disabled].active,
.datepicker table tr td span.active:hover[disabled].active,
.datepicker table tr td span.active.disabled[disabled].active,
.datepicker table tr td span.active.disabled:hover[disabled].active,
fieldset[disabled] .datepicker table tr td span.active.active,
fieldset[disabled] .datepicker table tr td span.active:hover.active,
fieldset[disabled] .datepicker table tr td span.active.disabled.active,
fieldset[disabled] .datepicker table tr td span.active.disabled:hover.active {
background-color: #428bca;
border-color: #357ebd;
}
.datepicker table tr td span.old,
.datepicker table tr td span.new {
color: #999999;
}
.datepicker th.datepicker-switch {
width: 145px;
}
.datepicker thead tr:first-child th,
.datepicker tfoot tr th {
cursor: pointer;
}
.datepicker thead tr:first-child th:hover,
.datepicker tfoot tr th:hover {
background: #eeeeee;
}
.datepicker .cw {
font-size: 10px;
width: 12px;
padding: 0 2px 0 5px;
vertical-align: middle;
}
.datepicker thead tr:first-child th.cw {
cursor: default;
background-color: transparent;
}
.input-group.date .input-group-addon i {
cursor: pointer;
width: 16px;
height: 16px;
}
.input-daterange input {
text-align: center;
}
.input-daterange input:first-child {
border-radius: 3px 0 0 3px;
}
.input-daterange input:last-child {
border-radius: 0 3px 3px 0;
}
.input-daterange .input-group-addon {
width: auto;
min-width: 16px;
padding: 4px 5px;
font-weight: normal;
line-height: 1.428571429;
text-align: center;
text-shadow: 0 1px 0 #fff;
vertical-align: middle;
background-color: #eeeeee;
border-width: 1px 0;
margin-left: -5px;
margin-right: -5px;
}
.datepicker.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
float: left;
display: none;
min-width: 160px;
list-style: none;
background-color: #ffffff;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 5px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
*border-right-width: 2px;
*border-bottom-width: 2px;
color: #333333;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
line-height: 1.428571429;
}
.datepicker.dropdown-menu th,
.datepicker.dropdown-menu td {
padding: 4px 5px;
}

9
static/js/cropper/cropper.min.js vendored Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
define("echarts/chart/heatmap",["require","./base","../layer/heatmap","../config","../util/ecData","zrender/tool/util","zrender/tool/color","zrender/shape/Image","../chart"],function(e){function t(e,t,n,a,o){i.call(this,e,t,n,a,o),this.refresh(a)}var i=e("./base"),n=e("../layer/heatmap"),a=e("../config"),o=(e("../util/ecData"),e("zrender/tool/util")),r=(e("zrender/tool/color"),e("zrender/shape/Image"));return a.heatmap={zlevel:0,z:2,clickable:!0},t.prototype={type:a.CHART_TYPE_HEATMAP,refresh:function(e){this.clear(),e&&(this.option=e,this.series=e.series),this._init()},_init:function(){var e=this.series;this.backupShapeList();for(var t=e.length,i=0;t>i;++i)if(e[i].type===a.CHART_TYPE_HEATMAP){e[i]=this.reformOption(e[i]);var o=new n(e[i]),s=o.getCanvas(e[i].data,this.zr.getWidth(),this.zr.getHeight()),l=new r({position:[0,0],scale:[1,1],hoverable:this.option.hoverable,style:{x:0,y:0,image:s,width:s.width,height:s.height}});this.shapeList.push(l)}this.addShapeList()}},o.inherits(t,i),e("../chart").define("heatmap",t),t}),define("echarts/layer/heatmap",["require"],function(){function e(e){if(this.option=e,e)for(var i in t)this.option[i]=void 0!==e[i]?e[i]:t[i];else this.option=t}var t={blurSize:30,gradientColors:["blue","cyan","lime","yellow","red"],minAlpha:.05,valueScale:1,opacity:1},i=20,n=256;return e.prototype={getCanvas:function(e,t,a){var o=this._getBrush(),r=this._getGradient(),s=i+this.option.blurSize,l=document.createElement("canvas");l.width=t,l.height=a;for(var h=l.getContext("2d"),m=e.length,V=0;m>V;++V){var d=e[V],U=d[0],p=d[1],c=d[2],u=Math.min(1,Math.max(c*this.option.valueScale||this.option.minAlpha,this.option.minAlpha));h.globalAlpha=u,h.drawImage(o,U-s,p-s)}for(var g=h.getImageData(0,0,l.width,l.height),y=g.data,m=y.length/4;m--;){var b=4*m+3,u=y[b]/256,f=Math.floor(u*(n-1));y[b-3]=r[4*f],y[b-2]=r[4*f+1],y[b-1]=r[4*f+2],y[b]*=this.option.opacity}return h.putImageData(g,0,0),l},_getBrush:function(){if(!this._brushCanvas){this._brushCanvas=document.createElement("canvas");var e=i+this.option.blurSize,t=2*e;this._brushCanvas.width=t,this._brushCanvas.height=t;var n=this._brushCanvas.getContext("2d");n.shadowOffsetX=t,n.shadowBlur=this.option.blurSize,n.shadowColor="black",n.beginPath(),n.arc(-e,e,i,0,2*Math.PI,!0),n.closePath(),n.fill()}return this._brushCanvas},_getGradient:function(){if(!this._gradientPixels){var e=n,t=document.createElement("canvas");t.width=1,t.height=e;for(var i=t.getContext("2d"),a=i.createLinearGradient(0,0,0,e),o=this.option.gradientColors.length,r=0;o>r;++r)"string"==typeof this.option.gradientColors[r]?a.addColorStop((r+1)/o,this.option.gradientColors[r]):a.addColorStop(this.option.gradientColors[r].offset,this.option.gradientColors[r].color);i.fillStyle=a,i.fillRect(0,0,1,e),this._gradientPixels=i.getImageData(0,0,1,e).data}return this._gradientPixels}},e}),define("echarts/layer/heatmap",["require"],function(){function e(e){if(this.option=e,e)for(var i in t)this.option[i]=void 0!==e[i]?e[i]:t[i];else this.option=t}var t={blurSize:30,gradientColors:["blue","cyan","lime","yellow","red"],minAlpha:.05,valueScale:1,opacity:1},i=20,n=256;return e.prototype={getCanvas:function(e,t,a){var o=this._getBrush(),r=this._getGradient(),s=i+this.option.blurSize,l=document.createElement("canvas");l.width=t,l.height=a;for(var h=l.getContext("2d"),m=e.length,V=0;m>V;++V){var d=e[V],U=d[0],p=d[1],c=d[2],u=Math.min(1,Math.max(c*this.option.valueScale||this.option.minAlpha,this.option.minAlpha));h.globalAlpha=u,h.drawImage(o,U-s,p-s)}for(var g=h.getImageData(0,0,l.width,l.height),y=g.data,m=y.length/4;m--;){var b=4*m+3,u=y[b]/256,f=Math.floor(u*(n-1));y[b-3]=r[4*f],y[b-2]=r[4*f+1],y[b-1]=r[4*f+2],y[b]*=this.option.opacity}return h.putImageData(g,0,0),l},_getBrush:function(){if(!this._brushCanvas){this._brushCanvas=document.createElement("canvas");var e=i+this.option.blurSize,t=2*e;this._brushCanvas.width=t,this._brushCanvas.height=t;var n=this._brushCanvas.getContext("2d");n.shadowOffsetX=t,n.shadowBlur=this.option.blurSize,n.shadowColor="black",n.beginPath(),n.arc(-e,e,i,0,2*Math.PI,!0),n.closePath(),n.fill()}return this._brushCanvas},_getGradient:function(){if(!this._gradientPixels){var e=n,t=document.createElement("canvas");t.width=1,t.height=e;for(var i=t.getContext("2d"),a=i.createLinearGradient(0,0,0,e),o=this.option.gradientColors.length,r=0;o>r;++r)"string"==typeof this.option.gradientColors[r]?a.addColorStop((r+1)/o,this.option.gradientColors[r]):a.addColorStop(this.option.gradientColors[r].offset,this.option.gradientColors[r].color);i.fillStyle=a,i.fillRect(0,0,1,e),this._gradientPixels=i.getImageData(0,0,1,e).data}return this._gradientPixels}},e});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

4182
static/js/term.js Normal file

File diff suppressed because it is too large Load Diff

89
static/js/wssh.js Normal file
View File

@ -0,0 +1,89 @@
/*
WSSH Javascript Client
Usage:
var client = new WSSHClient();
client.connect({
// Connection and authentication parameters
username: 'root',
hostname: 'localhost',
authentication_method: 'password', // can either be password or private_key
password: 'secretpassword', // do not provide when using private_key
key_passphrase: 'secretpassphrase', // *may* be provided if the private_key is encrypted
// Callbacks
onError: function(error) {
// Called upon an error
console.error(error);
},
onConnect: function() {
// Called after a successful connection to the server
console.debug('Connected!');
client.send('ls\n'); // You can send data back to the server by using WSSHClient.send()
},
onClose: function() {
// Called when the remote closes the connection
console.debug('Connection Reset By Peer');
},
onData: function(data) {
// Called when data is received from the server
console.debug('Received: ' + data);
}
});
*/
function WSSHClient() {
}
WSSHClient.prototype._generateEndpoint = function(options) {
console.log(options);
if (window.location.protocol == 'https:') {
var protocol = 'wss://';
} else {
var protocol = 'ws://';
}
var endpoint = protocol + window.location.host + ':8080' + '/terminal';
return endpoint;
};
WSSHClient.prototype.connect = function(options) {
var endpoint = this._generateEndpoint(options);
if (window.WebSocket) {
this._connection = new WebSocket(endpoint);
}
else if (window.MozWebSocket) {
this._connection = MozWebSocket(endpoint);
}
else {
options.onError('WebSocket Not Supported');
return ;
}
this._connection.onopen = function() {
options.onConnect();
};
this._connection.onmessage = function (evt) {
var data = JSON.parse(evt.data.toString());
if (data.error !== undefined) {
options.onError(data.error);
}
else {
options.onData(data.data);
}
};
this._connection.onclose = function(evt) {
options.onClose();
};
};
WSSHClient.prototype.send = function(data) {
this._connection.send(JSON.stringify({'data': data}));
};

View File

@ -24,6 +24,7 @@
<!-- highcharts -->
<script src="/static/js/highcharts/highcharts.js"></script>
<script src="/static/js/dropzone/dropzone.js"></script>
<!-- active menu -->
<script>

View File

@ -8,4 +8,5 @@
<!-- validator js -->
<script src="/static/js/validator/jquery.validator.js"></script>
<script src="/static/js/validator/zh_CN.js"></script>
<script src="/static/js/datapicker/bootstrap-datepicker.js"></script>

View File

@ -14,7 +14,7 @@
</div>
<div class="ibox-content">
<h1 class="no-margins"><a href="/juser/user_list/">{{ users.count}}</a></h1>
<div class="stat-percent font-bold text-success">{{ percent_user }} <i class="fa fa-bolt"></i></div>
{# <div class="stat-percent font-bold text-success">{{ percent_user }} <i class="fa fa-bolt"></i></div>#}
<small>All user</small>
</div>
</div>
@ -27,7 +27,7 @@
</div>
<div class="ibox-content">
<h1 class="no-margins"><a href="/jasset/host_list/">{{ hosts.count }}</a></h1>
<div class="stat-percent font-bold text-info">{{ percent_host }} <i class="fa fa-level-up"></i></div>
{# <div class="stat-percent font-bold text-info">{{ percent_host }} <i class="fa fa-level-up"></i></div>#}
<small>All host</small>
</div>
</div>
@ -40,8 +40,8 @@
<h5>实时在线用户</h5>
</div>
<div class="ibox-content">
<h1 class="no-margins"><a href="/jlog/log_list/online/"> <span id="online_users"></span></a></h1>
<div class="stat-percent font-bold text-navy">{{ percent_online_user }} <i class="fa fa-level-up"></i></div>
<h1 class="no-margins"><a href="/jlog/log_list/online/"> <span id="online_users">{{ online_user | length }}</span></a></h1>
{# <div class="stat-percent font-bold text-navy">{{ percent_online_user }} <i class="fa fa-level-up"></i></div>#}
<small>Online user</small>
</div>
</div>
@ -54,15 +54,15 @@
<h5>已连接服务器</h5>
</div>
<div class="ibox-content">
<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">{{ percent_online_host }} <i class="fa fa-level-down"></i></div>
<h1 class="no-margins"><a href="/jlog/log_list/online/"> <span id="online_hosts">{{ online_host | length }}</span></a></h1>
{# <div class="stat-percent font-bold text-danger">{{ percent_online_host }} <i class="fa fa-level-down"></i></div>#}
<small>Connected host</small>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-3 border-bottom white-bg dashboard-header" style="margin-left:15px;height: 346px">
<div class="col-sm-2 border-bottom white-bg dashboard-header" style="margin-left:15px;height: 346px">
<h2>活跃用户TOP5</h2>
<small>过去一周共有<span class="text-info">{{ week_users }}</span>位用户登录<span class="text-success">{{ week_hosts }}</span>次服务器.</small>
<ul class="list-group clear-list m-t">
@ -76,7 +76,32 @@
{% endfor %}
</ul>
</div>
<div class="col-lg-9" id="top10" style="margin-left: -15px;height: 345px"></div>
<div class="col-sm-7" id="top10" style="margin-left: -15px;height: 346px;padding: 15px 0 15px 0;"></div>
<div class="col-lg-3 white-bg" id="top1" style="margin-left: -15px;height: 346px">
<div class="statistic-box">
<h4>
活跃用户资产占比
</h4>
<p>
以下图形分别描述一个月活跃用户和资产占所有用户主机的百分比
</p>
<div class="row text-center">
<div class="col-lg-6">
<div id="activeUser" style="width: 140px; height: 140px;">
</div>
<h5>用户</h5>
</div>
<div class="col-lg-6">
<div id="activeAsset" style="width: 140px; height: 140px;"></div>
<h5>主机</h5>
</div>
</div>
<div class="m-t">
<small></small>
</div>
</div>
</div>
</div>
<br/>
@ -126,6 +151,51 @@
</div>
</div>
</div>
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>一周Top10资产</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user"></ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content ibox-heading">
<h3><i class="fa fa-user"></i> 一周Top10资产 </h3>
<small><i class="fa fa-map-marker"></i> 登录次数及最近一次登录记录. </small>
</div>
<div class="ibox-content inspinia-timeline">
{% if host_top_ten %}
{% for data in host_top_ten %}
<div class="timeline-item">
<div class="row">
<div class="col-xs-5 date">
<i class="fa fa-info-circle"></i>
<strong>{{ data.host }}</strong>
<br/>
<small class="text-navy">{{ data.times }}次</small>
</div>
<div class="col-xs-7 content no-top-border">
<p class="m-b-xs">最近一次登录用户</p>
<p>{{ data.last.user }}</p>
<p>于{{ data.last.start_time |date:"Y-m-d H:i:s" }}</p>
</div>
</div>
</div>
{% endfor %}
{% else %}
<p class="text-center">(暂无)</p>
{% endif %}
</div>
</div>
</div>
<div class="col-lg-4">
<div class="ibox float-e-margins">
@ -137,7 +207,7 @@
</div>
<div class="ibox-content ibox-heading">
<h3><i class="fa fa-paper-plane-o"></i> 登录记录 </h3>
<small<i class="fa fa-map-marker"></i> 最近十次登录记录. </small>
<small><i class="fa fa-map-marker"></i> 最近十次登录记录. </small>
</div>
<div class="ibox-content">
<div>
@ -212,7 +282,7 @@
</div>
<div class="ibox-content ibox-heading">
<h3><i class="fa fa-user"></i> 一周Top10用户 </h3>
<small><i class="fa fa-map-marker"></i> 一周Top10用户登录次数及最近一次登录记录. </small>
<small><i class="fa fa-map-marker"></i> 用户登录次数及最近一次登录记录. </small>
</div>
<div class="ibox-content inspinia-timeline">
{% if user_top_ten %}
@ -226,7 +296,7 @@
<small class="text-navy">{{ data.times }}次</small>
</div>
<div class="col-xs-7 content no-top-border">
<p class="m-b-xs">最近一次登录</p>
<p class="m-b-xs">最近一次登录主机</p>
<p>{{ data.last.host }}</p>
<p>于{{ data.last.start_time |date:"Y-m-d H:i:s" }}</p>
</div>
@ -247,162 +317,257 @@
<!--<div class="col-lg-6" id="hosttop10" style="width:50%;height:400px; margin-top: 20px"></div>-->
<!--</div>-->
</div>
</div>
{% endblock %}
{% block self_footer_js %}
<script src="/static/js/echarts/echarts.js"></script>
<script>
$(document).ready(function(){
$('#show').click(function(){
$('#show').css('display', 'none');
$('#more').css('display', 'block');
})
})
var cate = {{ li_str|safe }};
$(function () {
$('#top10').highcharts({
// chart: {
// type: 'column'
// },
title: {
text: '一周数据总览',
x: -20 //center
},
subtitle: {
text: 'Source: JumpServer',
x: -20
},
rangeSelector: {
allButtonsEnabled: true,
selected: 2
},
xAxis: {
type: 'datetime',
categories: cate
},
yAxis:{
min: 0,
title: {
text: ''
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}]
},
tooltip: {
valueSuffix: '次'
},
navigation: {
buttonOptions: {
align: 'right'
}
},
series: [
{% for k,v in top_dic.items %}
{
name: '{{ k }}',
data: {{ v }}
},
{% endfor %}
]
});
$('#usertop10').highcharts({
title: {
text: '一周用户登录TOP10',
x: -20 //center
},
subtitle: {
text: 'Source: JumpServer',
x: -20
},
xAxis: {
type: 'datetime',
categories: cate
},
yAxis:{
min: 0,
title: {
text: '登录次数'
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}]
},
tooltip: {
valueSuffix: '次'
},
series: [
{% for k,v in user_dic.items %}
{
name: '{{ k }}',
data: {{ v }}
},
{% endfor %}
]
});
$('#hosttop10').highcharts({
title: {
text: '一周主机登录TOP10',
x: -20 //center
},
subtitle: {
text: 'Source: JumpServer',
x: -20
},
xAxis: {
type: 'datetime',
categories: cate
},
yAxis:{
min: 0,
title: {
text: '登录次数'
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}]
},
tooltip: {
valueSuffix: '次'
},
series: [
{% for k,v in host_dic.items %}
{
name: '{{ k }}',
data: {{ v }}
},
{% endfor %}
]
});
function magic_number(value, id) {
var num = $("#"+id);
num.animate({count: value}, {
duration: 500,
step: function() {
num.text(String(parseInt(this.count)));
}
});
};
function update() {
$.getJSON('api/user/', function(data) {
var users = data.users;
var hosts = data.hosts;
magic_number(users, 'online_users');
magic_number(hosts, 'online_hosts')
});
};
setInterval(update, 5000); //5秒钟执行一次
update();
});
require.config({
paths: {
'echarts': '/static/js/echarts/chart',
'echarts/chart/line': '/static/js/echarts/chart/line',
'echarts/chart/pie': '/static/js/echarts/chart/pie'
}
});
require(
[
'echarts',
'echarts/chart/line'
],
function (ec) {
var top10Chart = ec.init(document.getElementById('top10'));
var option = {
title : {
text: '月数据总览',
subtext: '一个月内历史汇总',
x: 'center'
},
tooltip : {
trigger: 'axis'
},
backgroundColor: '#fff',
legend: {
data:['登陆次数', '活跃用户','活跃资产'],
y: 'bottom'
},
toolbox: {
show : false,
feature : {
{# mark : {show: true},#}
{# dataView : {show: true, readOnly: false},#}
magicType : {show: true, type: ['line', 'bar']}
}
},
calculable : true,
xAxis : [
{
type : 'category',
boundaryGap : false,
data : {{ date_month | safe}}
}
],
yAxis : [
{
type : 'value'
}
],
series : [
{
name:'登陆次数',
type:'line',
smooth:true,
itemStyle: {normal: {areaStyle: {type: 'default'}}},
data: {{ active_login_per_month | safe }}
},
{
name:'活跃用户',
type:'line',
smooth:true,
itemStyle: {normal: {areaStyle: {type: 'default'}}},
data: {{ active_user_per_month | safe }}
},
{
name:'活跃资产',
type:'line',
smooth:true,
itemStyle: {normal: {areaStyle: {type: 'default'}}},
data: {{ active_asset_per_month | safe }}
}
]
};
top10Chart.setOption(option);
}
);
require(
[
'echarts',
'echarts/chart/pie'
],
function (ec) {
var auChart = ec.init(document.getElementById('activeUser'));
var option = {
tooltip : {
trigger: 'item',
formatter: "{b} <br> {c} ({d}%)"
},
legend: {
show: false,
orient : 'vertical',
x : 'left',
data:['月活跃用户','禁用用户','月未登陆用户']
},
toolbox: {
show : false,
feature : {
mark : {show: true},
dataView : {show: true, readOnly: false},
magicType : {
show: true,
type: ['pie', 'funnel'],
option: {
funnel: {
x: '25%',
width: '50%',
funnelAlign: 'center',
max: 1548
}
}
},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable : true,
series : [
{
name:'访问来源',
type:'pie',
radius : ['50%', '70%'],
itemStyle : {
normal : {
label : {
show : false
},
labelLine : {
show : false
}
},
emphasis : {
label : {
show : true,
position : 'center',
textStyle : {
fontSize : '5',
fontWeight : 'bold'
}
}
}
},
data:[
{value:{{ active_user_month }}, name:'月活跃用户'},
{value:{{ disabled_user_count }}, name:'禁用用户'},
{value:{{ inactive_user_month }}, name:'月未登陆用户'}
]
}
]
};
auChart.setOption(option);
}
);
require(
[
'echarts',
'echarts/chart/pie'
],
function (ec) {
var aaChart = ec.init(document.getElementById('activeAsset'));
var option = {
tooltip : {
trigger: 'item',
formatter: "{b} <br> {c} ({d}%)"
},
legend: {
show: false,
orient : 'vertical',
x : 'left',
data:['月被登陆主机','禁用主机','月未登陆主机']
},
toolbox: {
show : false,
feature : {
mark : {show: true},
dataView : {show: true, readOnly: false},
magicType : {
show: true,
type: ['pie', 'funnel'],
option: {
funnel: {
x: '25%',
width: '50%',
funnelAlign: 'center',
max: 1548
}
}
},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable : true,
series : [
{
name:'访问来源',
type:'pie',
radius : ['50%', '70%'],
itemStyle : {
normal : {
label : {
show : false
},
labelLine : {
show : false
}
},
emphasis : {
label : {
show : true,
position : 'center',
textStyle : {
fontSize : '5',
fontWeight : 'bold'
}
}
}
},
data:[
{value:{{ active_asset_month }}, name:'月被登陆主机'},
{value:{{ disabled_asset_count }}, name:'禁用主机'},
{value:{{ inactive_asset_month }}, name:'月未登陆主机'}
]
}
]
};
aaChart.setOption(option);
}
);
</script>
{% endblock %}

View File

@ -72,7 +72,7 @@
<div class="col-lg-4">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label label-primary"><b>{{ user.name }}</b></span>
<span class="label label-primary"><b>{{ user.username }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
@ -109,7 +109,7 @@
</tr>
<tr>
<td class="text-navy">角色</td>
<td>{{ user.id | get_role }}</td>
<td>{{ user.role }}</td>
</tr>
<tr>
<td class="text-navy">Email</td>

View File

@ -24,7 +24,9 @@
<div class="ibox-content">
<div>
{% if session_role_id > 0 %}
<a target="_blank" href="/jasset/asset_add/" class="btn btn-sm btn-primary "> 添加 </a>
{% endif %}
<form id="search_form" method="get" action="" class="pull-right mail-search">
<div class="input-group">
<input type="text" class="form-control input-sm" id="search_input" name="keyword" placeholder="Search">

111
templates/jlog/base.jinja2 Normal file
View File

@ -0,0 +1,111 @@
<html>
<head>{% block head %}{% endblock %}
</head>
<body>
<input type="button" value="Play/Pause" onclick="pause(false);" />
<input type="button" value="Restart" onclick="restart(1);" />
<span id="beforeScrubberText"></span>
<input id="scrubber" type="range" value="0" min=0 max=100
onmousedown="pause(true);" onmouseup="scrub();" />
<span id="afterScrubberText"></span>
-5x <input id="speed" type="range" value="0" min=-5 max=5
onmouseup="setSpeed();" /> +5x
<script>
var data = {{ json }};
var toggle = true;
var totalTime = 0;
var TICK = 33;
var TIMESTEP = 33;
var time = 33;
var pos = 0;
var timer;
// Thanks http://stackoverflow.com/a/2998822
function zeroPad(num, size) {
var s = "0" + num;
return s.substr(s.length-size);
}
function scrub() {
setPercent = document.getElementById('scrubber').value;
time = (setPercent / 100) * totalTime;
restart(time);
}
function buildTimeString(millis) {
hours = zeroPad(Math.floor(millis / (1000 * 60 * 60)), 2);
millis -= hours * (1000 * 60 * 60)
minutes = zeroPad(Math.floor(millis / (1000 * 60)), 2);
millis -= minutes * (1000 * 60);
seconds = zeroPad(Math.floor(millis / 1000), 2);
return hours + ':' + minutes + ':' + seconds;
}
function advance() {
document.getElementById('scrubber').value =
Math.ceil((time / totalTime) * 100);
timestr = buildTimeString(time);
document.getElementById("beforeScrubberText").innerHTML =
timestr;
for (; pos < data.length; pos++) {
if (data[pos][1] <= time) {
term.write(eval(data[pos][0]));
} else {
break;
}
}
if (pos >= data.length) {
clearInterval(timer);
}
time += TIMESTEP;
}
function pause(test) {
if (!toggle && test) {
return;
}
if (toggle) {
clearInterval(timer);
toggle = !toggle;
} else {
timer = setInterval(advance, TICK);
toggle = !toggle;
}
}
function setSpeed() {
speed = document.getElementById('speed').value;
if (speed == 0) {
TIMESTEP = TICK;
} else if (speed < 0) {
TIMESTEP = TICK / -speed;
} else {
TIMESTEP = TICK * speed;
}
}
function restart(millis) {
clearInterval(timer);
term.reset();
time = millis;
pos = 0;
toggle = true;
timer = setInterval(advance, TICK);
}
var term = new Terminal({
cols: {{ dimensions[1] }},
rows: {{ dimensions[0] }},
screenKeys: true
});
totalTime = data[data.length - 1][1];
timestr = buildTimeString(totalTime);
document.getElementById("afterScrubberText").innerHTML = timestr;
term.open(document.body);
timer = setInterval(advance, TICK);
</script>
</body>
</html>

View File

@ -0,0 +1,11 @@
{% extends "base.jinja2" %}
{% block head %}
<script src='term.js'></script>
<link href='http://fonts.googleapis.com/css?family=Ubuntu+Mono' rel='stylesheet' type='text/css'>
<style>
body {
font-family: 'Ubuntu Mono', Courier, monospace;
font-size: 14pt;
}
</style>
{% endblock %}

View File

@ -0,0 +1,139 @@
{% extends 'base.html' %}
{% block self_head_css_js %}
<link href="/static/css/plugins/datapicker/datepicker3.css" rel="stylesheet">
<link href="/static/css/plugins/chosen/chosen.css" rel="stylesheet">
<script src="/static/js/plugins/chosen/chosen.jquery.js"></script>
{% endblock %}
{% block content %}
{% include 'nav_cat_bar.html' %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div id="ibox-content" class="ibox-title">
<h5> 用户日志详细信息列表 </h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div class="panel-options">
<ul class="nav nav-tabs">
<li><a href="/jlog/log_list/online/" class="text-center"><i class="fa fa-laptop"></i> 在线 </a></li>
<li><a href="/jlog/log_list/offline/" class="text-center"><i class="fa fa-bar-chart-o"></i> 历史记录 </a></li>
<li class="active"><a href="/jlog/search/" class="text-center"><i class="fa fa-bar-chart-o"></i> 详细搜索 </a></li>
</ul>
</div>
<br/>
<div class="tab-content">
<form method="get" action="" role="form" class="form-inline">
<p>
选择相应条件进行搜索
</p>
<div class="form-group" id="data_5">
<div class="input-daterange input-group" id="datepicker">
<input type="text" class="input-sm form-control" style="width: 100px;" name="start" value="{{ date_seven_day }}">
<span class="input-group-addon">to</span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="end" value="{{ date_now_str }}">
</div>
</div>
<div class="form-group">
<div class="input-group">
<select name="single" data-placeholder="用户名" class="chosen-select" multiple style="width:300px;" tabindex="2">
<option value="用户">用户名</option>
<option value="Bolivia, Plurinational State of">hongweiguang</option>
<option value="Bonaire, Sint Eustatius and Saba">wangyong</option>
<option value="Bosnia and Herzegovina">hehe</option>
<option value="Botswana">wangyong</option>
<option value="Bouvet Island">wangyongd</option>
<option value="Romania">Romania</option>
<option value="Zambia">Zambia</option>
<option value="Zimbabwe">Zimbabwe</option>
</select>
</div>
</div>
<div class="form-group">
<div class="input-group">
<select name="multi" data-placeholder="主机" class="chosen-select" multiple style="width:200px;" tabindex="4">
<option value="主机">主机</option>
<option value="United States">172.16.1.1</option>
<option value="Afghanistan">172.16.1.1</option>
<option value="Aland Islands">172.16.1.1</option>
<option value="Albania">172.16.1.1</option>
<option value="Algeria">172.16.1.1</option>
<option value="American Samoa">172.16.1.1</option>
<option value="Andorra">172.16.1.1</option>
<option value="Angola">172.16.1.1</option>
<option value="Anguilla">172.16.1.1</option>
<option value="Antarctica">172.16.1.1</option>
</select>
</div>
</div>
<div class="form-group">
<input id="cmd" name="cmd" placeholder="命令" type="text" class="form-control" style="width: 200px;">
</div>
<div class="form-group">
<button id="submit_button" class="btn btn-primary" type="submit">搜索</button>
</div>
</form>
<div class="row">
<div class="col-sm-6">
</div>
{% include 'paginator.html' %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function log_search(){
$.ajax({
type: "GET",
url: "/jlog/search/?env=offline",
data: $("#search_form").serialize(),
success: function (data) {
$(".tab-content").html(data);
}
});
}
$(document).ready(function(){
$('#data_5 .input-daterange').datepicker({
dateFormat: 'yy-mm-dd',
keyboardNavigation: false,
forceParse: false,
autoclose: true
});
});
var config = {
'.chosen-select' : {},
'.chosen-select-deselect' : {allow_single_deselect:true},
'.chosen-select-no-single' : {disable_search_threshold:10},
'.chosen-select-no-results': {no_results_text:'Oops, nothing found!'},
'.chosen-select-width' : {width:"95%"}
};
for (var selector in config) {
$(selector).chosen(config[selector]);
}
</script>
{% endblock %}
{% block self_footer_js %}
<script src="/static/js/cropper/cropper.min.js"></script>
<script src="/static/js/datapicker/bootstrap-datepicker.js"></script>
<script src="/static/js/plugins/chosen/chosen.jquery.js"></script>
{% endblock %}

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jumpserver | 开源跳板机系统</title>
<link rel="shortcut icon" href="/static/img/facio.ico" type="image/x-icon">
{% include 'link_css.html' %}
{% include 'head_script.html' %}
</head>
<body>
<div id="wrapper">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5> 实时监控 </h5>
</div>
<div class="ibox-content blank-panel" id="content" style="background-color: #0b0b0b; color: #006621; height: 500px; padding: 20px;">
你好<br>
</div>
</div>
</div>
</div>
</body>
{% block self_footer_js %}
<script>
function monitor(){
var wsUri = 'ws://j:8080/send';
var ws = new WebSocket(wsUri);
ws.onopen = function(evt){
$('#content').append('Connect websocket success' + '<br />');
ws.send('Start')
};
ws.onmessage = function(evt){
console.log(evt.data);
$('#content').append(evt.data.replace(/\n|\r|(\r\n)|(\u0085)|(\u2028)|(\u2029)/g, '<br>'));
};
ws.onclose = function(evt){
$('#content').append('Disconnect with websocket')
}
}
monitor();
</script>
{% endblock %}
</html>

View File

@ -1,12 +1,18 @@
{% extends 'base.html' %}
{% block self_head_css_js %}
<link href="/static/css/plugins/datapicker/datepicker3.css" rel="stylesheet">
<link href="/static/css/plugins/chosen/chosen.css" rel="stylesheet">
<script src="/static/js/plugins/chosen/chosen.jquery.js"></script>
{% endblock %}
{% block content %}
{% include 'nav_cat_bar.html' %}
<style>
.bootstrap-dialog-body {
background-color: rgba(0, 0, 0, 0);
}
.bootstrap-dialog-message {
background-color: rgba(0, 0, 0, 0);
color: #00FF00;
}
.modal-content {
background-color: rgba(0, 0, 0, 0.6);
@ -21,9 +27,7 @@
.modal-header {
background-color: #FFFFFF;
}
.bootstrap-dialog-message {
color: #00FF00;
}
</style>
<div class="wrapper wrapper-content animated fadeInRight">
@ -39,12 +43,6 @@
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
<li><a href="#">未启用 1</a>
</li>
<li><a href="#">未启用 2</a>
</li>
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
@ -56,48 +54,73 @@
<ul class="nav nav-tabs">
<li><a href="/jlog/log_list/online/" class="text-center"><i class="fa fa-laptop"></i> 在线 </a></li>
<li class="active"><a href="/jlog/log_list/offline/" class="text-center"><i class="fa fa-bar-chart-o"></i> 历史记录 </a></li>
<li style="float: right">
<form id="search_form" method="get" action="" class="pull-right mail-search">
<div class="input-group">
<input type="text" class="form-control input-sm" id="search_input" name="keyword" placeholder="Search">
<input type="text" style="display: none">
<div class="input-group-btn">
<button id='search_btn' type="button" class="btn btn-sm btn-primary" onclick="log_search()">
Search
</button>
</div>
</div>
</form>
</li>
{# <li><a href="/jlog/search/" class="text-center"><i class="fa fa-bar-chart-o"></i> 详细搜索 </a></li>#}
</ul>
</div>
<br/>
<form class="form-inline" action="" method="get">
<div class="form-group" id="data_5">
<div class="form-group" id="data_5">
<div class="input-daterange input-group" id="datepicker">
<input type="text" class="input-sm form-control" style="width: 100px;" name="start" value="{{ date_seven_day }}">
<span class="input-group-addon">to</span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="end" value="{{ date_now_str }}">
</div>
</div>
<div class="form-group">
<div class="input-group">
<select name="username" data-placeholder="用户名" class="chosen-select" multiple style="width:200px;" tabindex="2">
{% for username in username_all %}
<option value="{{ username }}"{% if username in username_list %}selected{% endif %}>{{ username }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<div class="input-group">
<select name="host" data-placeholder="主机" class="chosen-select" multiple style="width:200px;" tabindex="4">
{% for ip in ip_all %}
<option value="{{ ip }}" {% if ip in host_list %}selected{% endif %}>{{ ip }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<input id="cmd" name="cmd" placeholder="命令" type="text" class="form-control" value="{{ cmd }}" style="width: 200px;">
</div>
</div>
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
Search
</button>
</form>
<div class="tab-content">
<table class="table table-striped table-bordered table-hover ">
<thead>
<tr>
<th class="text-center"> 用户名 </th>
<th class="text-center"> 所属部门 </th>
<th class="text-center"> 登录主机 </th>
<th class="text-center"> 来源IP </th>
{% ifnotequal session_role_id 0 %}
<th class="text-center"> 命令统计 </th>
{% endifnotequal %}
<th class="text-center"> 回放录像 </th>
<th class="text-center"> 登录时间 </th>
<th class="text-center"> 结束时间 </th>
</tr>
</thead>
<tbody>
{% for post in contacts.object_list %}
<tr class="gradeX">
<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="remote_ip"> {{ post.remote_ip }} </td>
{% ifnotequal session_role_id 0 %}
<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"> 命令统计 </a></td>
{% endifnotequal %}
<td class="text-center"><a value="/jlog/record/?id={{ post.id }}" class="log_record"> 回放 </a></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>
</tr>
@ -116,10 +139,14 @@
</div>
</div>
{#<script src="http://{{ web_socket_host }}/socket.io/socket.io.js"></script>#}
<script>
$('.log_command').on('click',function(){
$('.log_record').click(function(){
var url = $(this).attr('value');
window.open(url, '播放', 'height=500, width=910, top=89px, left=99px,toolbar=no,menubar=no,scrollbars=auto,resizeable=no,location=no,status=no');
return false;
});
$('.log_command').on('click',function(){
var url = $(this).attr('href');
var username = $('#username')[0].innerText;
var ip = $('#ip')[0].innerText;
@ -127,17 +154,15 @@
var end_time = $('#end_time')[0].innerText;
var div_username = ' 用户名: '+'<span class="text-info">'+username+'' + '</span>';
var div_ip = ' 主机: '+'<span class="text-info">' + ip + '</span>';
var div_time = ' 开始时间: ' + '<span class="text-info">'+start_time +'</span>' + ' 结束时间: ' +'<span class="text-info">' + end_time + '</span'
var div_time = ' 开始时间: ' + '<span class="text-info">'+start_time +'</span>' + ' 结束时间: ' +'<span class="text-info">' + end_time + '</span>';
var title = 'JumpServer命令统计 '+ div_username + div_ip + div_time;
$.ajax({url:url,success:function(data){
var tag = $('<div style="height: 500px;overflow: auto;background-color: rgba(0, 0, 0, 0);"></div>').html(data.replace(/\n/g,"<br />"));
BootstrapDialog.show({title: title, message:tag[0]});
$.ajax({url:url,
success:function(data){
var tag = $('<div style="height: 500px;overflow: auto;background-color: rgba(0, 0, 0, 0);"></div>').html(data.replace(/\n/g,"<br />"));
BootstrapDialog.show({title: title, message:tag[0]});
}});
return false;
});
globalConfig = {
SOCKET_HOST: "{{ web_socket_host }}"
};
function log_search(){
$.ajax({
@ -150,11 +175,33 @@
});
}
$("#search_input").keydown(function(e){
if(e.keyCode==13){
log_search()
}
})
{# $("#search_input").keydown(function(e){#}
{# if(e.keyCode==13){#}
{# log_search()#}
{# }#}
{# });#}
$('#data_5 .input-daterange').datepicker({
dateFormat: 'yy-mm-dd',
keyboardNavigation: false,
forceParse: false,
autoclose: true
});
var config = {
'.chosen-select' : {},
'.chosen-select-deselect' : {allow_single_deselect:true},
'.chosen-select-no-single' : {disable_search_threshold:10},
'.chosen-select-no-results': {no_results_text:'Oops, nothing found!'},
'.chosen-select-width' : {width:"95%"}
};
for (var selector in config) {
$(selector).chosen(config[selector]);
}
</script>
{% endblock %}
{% block self_footer_js %}
<script src="/static/js/cropper/cropper.min.js"></script>
<script src="/static/js/datapicker/bootstrap-datepicker.js"></script>
{% endblock %}

View File

@ -1,18 +1,32 @@
{% extends 'base.html' %}
{% block content %}
{% include 'nav_cat_bar.html' %}
<style>
{% block self_head_css_js %}
<style>
.terminal {
border: #000 solid 5px;
font-family: "Monaco", "DejaVu Sans Mono", "Liberation Mono", monospace;
font-size: 11px;
color: #f0f0f0;
background: rgba(0, 0, 0, 0.6);
width: 600px;
box-shadow: rgba(0, 0, 0, 0.6) 2px 2px 20px;
}
.reverse-video {
color: #000;
background: #f0f0f0;
}
.bootstrap-dialog-body {
background-color: rgba(0, 0, 0, 0);
}
.bootstrap-dialog-message {
background-color: rgba(0, 0, 0, 0);
color: #00FF00;
}
.pre-class {
background-color: rgba(0, 0, 0, 1);
}
.modal-content {
background-color: rgba(0, 0, 0, 0.6);
background-color: #000;
}
.modal-dialog {
background-color: rgba(0, 0, 0, 0);
@ -24,14 +38,19 @@
.modal-header {
background-color: #FFFFFF;
}
</style>
</style>
<script src="/static/js/term.js"></script>
{% endblock %}
{% block content %}
{% include 'nav_cat_bar.html' %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div id="ibox-content" class="ibox-title">
<h5> 用户日志详细信息列表 </h5>
<h5> 用户日志详细信息列表 <input type="button" id="test_connect" class="btn btn-primary" value="测试连接 web terminal" /> </h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
@ -39,12 +58,6 @@
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
<li><a href="#">未启用 1</a>
</li>
<li><a href="#">未启用 2</a>
</li>
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
@ -56,19 +69,6 @@
<ul class="nav nav-tabs">
<li class="active"><a href="/jlog/log_list/online/" class="text-center"><i class="fa fa-laptop"></i> 在线 </a></li>
<li><a href="/jlog/log_list/offline/" class="text-center"><i class="fa fa-bar-chart-o"></i> 历史记录 </a></li>
<li style="float: right">
<form id="search_form" method="get" action="" class="pull-right mail-search">
<div class="input-group">
<input type="text" class="form-control input-sm" id="search_input" name="keyword" placeholder="Search">
<input type="text" style="display: none">
<div class="input-group-btn">
<button id='search_btn' type="button" class="btn btn-sm btn-primary" onclick="log_search()">
Search
</button>
</div>
</div>
</form>
</li>
</ul>
</div>
<br/>
@ -77,10 +77,10 @@
<thead>
<tr>
<th class="text-center"> 用户名 </th>
<th class="text-center"> 所属部门 </th>
<th class="text-center"> 登录主机 </th>
<th class="text-center"> 来源IP </th>
{% ifnotequal session_role_id 0 %}
<th class="text-center"> 统计命令 </th>
<th class="text-center"> 实时监控 </th>
<th class="text-center"> 阻断 </th>
{% endifnotequal %}
@ -92,14 +92,14 @@
{% for post in contacts.object_list %}
<tr class="gradeX">
<td id="username" class="text-center"> {{ post.user }} </td>
<td id="deptname" class="text-center"> {{ post.dept_name }} </td>
<td id="ip" class="text-center"> {{ post.host }} </td>
<td id="remote_ip" class="text-center"> {{ post.remote_ip }} </td>
{% ifnotequal session_role_id 0 %}
<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"><a href="/jlog/history/?id={{ post.id }}" class="log_command"> 命令统计 </a></td>
<td class="text-center"><a class="monitor" file_path="{{ 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 }}", "{{ post.remote_ip }}")' /></td>
{% endifnotequal %}
<td class="text-center"> {{ 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>
</tr>
{% endfor %}
</tbody>
@ -116,115 +116,108 @@
</div>
<script src="http://{{ web_socket_host }}/socket.io/socket.io.js"></script>
{#<script src="http://{{ web_socket_host }}/socket.io/socket.io.js"></script>#}
<script>
$.fn.webSocket = function(opt){
var st = {};
st = $.extend(st,opt);
var message = {};
var $this = $(this);
var genUid = function(){
return new Date().getTime()+""+Math.floor(Math.random()*899+100);
};
var init = function(e){
var socket = io.connect('ws://'+globalConfig.SOCKET_HOST);
var node = $(e.target);
message.id = genUid();
message.filename = node.attr('filename');
var username = $('#username')[0].innerText;
var ip = $('#ip')[0].innerText;
BootstrapDialog.show({message:function(){
var option, exsit_message;
var escapeString = function (html){
var elem = document.createElement('div');
var txt = document.createTextNode(html);
elem.appendChild(txt);
return elem.innerHTML;
};
var tag = $('<div id="log" style="height: 500px;overflow: auto;background-color: rgba(0, 0, 0, 0);"></div>');
{# $(document).ready(function(){#}
{# $('.monitor').click(function(){#}
{# window.open('/jlog/monitor/', '监控', 'height=500, width=910, top=89px, left=99px,toolbar=no,menubar=no,scrollbars=auto,resizeable=no,location=no,status=no');#}
{# })#}
{# });#}
function init(obj){
var file_path = obj.attr('file_path');
var wsUri = '{{ web_monitor_uri }}';
var socket = new WebSocket(wsUri + '?file_path=' + file_path);
var username = "";
var seed = "";
document.cookie.split('; ').forEach(function(obj){
var info = obj.split('=');
if(info.length == 2 ){
if(info[0] == 'username'){
username = info[1];
}else if(info[0] == 'seed'){
seed = info[1];
}
}
})
var term = new Terminal({
cols: 80,
rows: 24,
screenKeys: false
});
var tag = $('<div id="term" style="height:500px; overflow: auto;background-color: rgba(0, 0, 0, 0);border: none"></div>');
term.open();
term.resize(80, 24);
socket.onopen = function(evt){
socket.send('hello');
term.write('~.~ Connect WebSocket Success.~.~ \r\n');
};
window.onbeforeunload = function(){
socket.close()
};
var username = obj.closest('tr').find('#username').text();
var ip = obj.closest('tr').find('#ip').text();
BootstrapDialog.show({message: function(){
//服务器端认证
{# socket.send('login', {userid:message.id, filename:message.filename,username:username,seed:seed});#}
window.setTimeout(function(){
$('.terminal').detach().appendTo('#term');
socket.onmessage = function(evt){
term.write(evt.data);
}}, 1000);
//告诉服务器端有用户登录
socket.emit('login', {userid:message.id, filename:message.filename,username:username,seed:seed});
socket.on('message',function(obj){
option = obj.option;
console.log(option+'so')
exsit_message = obj.content;
console.log(obj.content)
//去除log中的颜色控制字符
var regx = /\x1B\[([0-9]{1,3}((;[0-9]{1,3})*)?)?[m|K]/g;
// tag.append('<p>'+escapeString(obj.content.replace(regx,''))+'</p>');
if (option == 'new') {
// tag.append('<p style="margin: 2px">' + escapeString(obj.content) + '</p>');
tag.append('<p style="margin: 2px">'+escapeString(obj.content.replace(regx,' '))+'</p>');
} else if (option == 'exist') {
tag.append('<pre>' + exsit_message + '</pre>');
}
tag.animate({ scrollTop: tag[0].scrollHeight}, 1);
});
tag[0].style.color = "#00FF00";
return tag[0];
} ,
title:'Jumpserver实时监控 '+' 登录用户名: '+'<span class="text-info">'+username+'</span>'+' 登录主机: '+'<span class="text-info">'+ip,
onhide:function(){
socket.emit('disconnect');
socket.close();
}});
}
$this.on("click",function(e){
init(e);
return false;
});
}
$('.log_command').on('click',function(){
var url = $(this).attr('href');
$.ajax({url:url,success:function(data){
BootstrapDialog.show({title:'命令统计',message:data});
}});
return false;
})
globalConfig = {
SOCKET_HOST: "{{ web_socket_host }}"
}
$(".monitor").webSocket()
function log_search(){
$.ajax({
type: "GET",
url: "/jlog/search/?env=online",
data: $("#search_form").serialize(),
success: function (data) {
$(".tab-content").html(data);
}
$(document).ready(function(){
$('.monitor').click(function(){
init($(this));
});
}
$("#search_input").keydown(function(e){
if(e.keyCode==13){
log_search()
}
})
$('.log_command').on('click',function(){
var url = $(this).attr('href');
var username = $(this).closest('tr').find('#username').text();
var ip = $(this).closest('tr').find('#ip').text();
var start_time = $(this).closest('tr').find('#start_time').text();
var div_username = ' 用户名: '+'<span class="text-info">'+username+'' + '</span>';
var div_ip = ' 主机: '+'<span class="text-info">' + ip + '</span>';
var div_time = ' 开始时间: ' + '<span class="text-info">'+start_time +'</span>' + '</span>';
var title = 'JumpServer命令统计 '+ div_username + div_ip + div_time;
$.ajax({url:url,
success:function(data){
var tag = $('<div style="height: 500px;overflow: auto;background-color: rgba(0, 0, 0, 0);"></div>').html(data.replace(/\n/g,"<br />"));
BootstrapDialog.show({title: title, message:tag[0]});
}});
return false;
});
$('#test_connect').click(function(){
window.open('/jlog/web_terminal/?asset_name="hello', '播放', 'height=400, width=600, top=89px, left=99px,toolbar=no,menubar=no,scrollbars=auto,resizeable=no,location=no,status=no');
});
});
{# function log_search(){#}
{# $.ajax({#}
{# type: "GET",#}
{# url: "/jlog/search/?env=online",#}
{# data: $("#search_form").serialize(),#}
{# success: function (data) {#}
{# $(".tab-content").html(data);#}
{# }#}
{# });#}
{# }#}
function cut(num, host){
console.log(host);
if (host=='Web'){
var g_url = '{{ web_kill_uri }}' + '?id=' + num;
} else {
g_url = "/jlog/log_kill/?id=" + num;
}
function cut(num){
var g_url = "/jlog/log_kill/?id="+num;
$.ajax({
type: "GET",
url: g_url,
success: window.open("/jlog/log_list/online/", "_self")
// error: window.open(g_url, "_self")
});
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,117 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Jumpserver web terminal</title>
<style>
body {
padding-bottom: 40px;
}
.terminal {
border: #000 solid 5px;
font-family: "Monaco", "DejaVu Sans Mono", "Liberation Mono", monospace;
font-size: 11px;
color: #f0f0f0;
background: #000;
width: 600px;
box-shadow: rgba(0, 0, 0, 0.8) 2px 2px 20px;
}
.reverse-video {
color: #000;
background: #f0f0f0;
}
</style>
</head>
<body>
<div class="container">
<div id="term">
</div>
</div>
<script type="application/javascript" src="/static/js/jquery-2.1.1.js">
</script>
<script type="application/javascript" src="/static/js/term.js">
</script>
<script type="application/javascript">
function WSSHClient() {
}
WSSHClient.prototype.connect = function(options) {
var endpoint = '{{ web_terminal_uri }}';
if (window.WebSocket) {
this._connection = new WebSocket(endpoint);
}
else if (window.MozWebSocket) {
this._connection = MozWebSocket(endpoint);
}
else {
options.onError('WebSocket Not Supported');
return ;
}
this._connection.onopen = function() {
options.onConnect();
};
this._connection.onmessage = function (evt) {
var data = JSON.parse(evt.data.toString());
if (data.error !== undefined) {
options.onError(data.error);
}
else {
options.onData(data.data);
}
};
this._connection.onclose = function(evt) {
options.onClose();
};
};
WSSHClient.prototype.send = function(data) {
this._connection.send(JSON.stringify({'data': data}));
};
function openTerminal(options) {
var client = new WSSHClient();
var term = new Terminal(80, 24, function(key) {
client.send(key);
});
term.open();
$('.terminal').detach().appendTo('#term');
term.resize(80, 24);
term.write('Connecting...');
client.connect($.extend(options, {
onError: function(error) {
term.write('Error: ' + error + '\r\n');
},
onConnect: function() {
// Erase our connecting message
term.write('\r');
},
onClose: function() {
term.write('Connection Reset By Peer');
},
onData: function(data) {
term.write(data);
}
}));
}
</script>
<script type='application/javascript'>
$(document).ready(function() {
var options = {
};
$('#ssh').show();
openTerminal(options);
});
</script>
</body>
</html>

138
templates/log_watch.html Normal file
View File

@ -0,0 +1,138 @@
<html>
<head>
<link href="http://cdn.bootcss.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<link href="http://cdn.bootcss.com/bootstrap/3.2.0/css/bootstrap-theme.min.css" rel="stylesheet">
<script src="http://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
<script src="http://cdn.bootcss.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script>
var wsUri = "ws://j:8080/send";
var ws;
function connect(){
ws = new WebSocket(wsUri);
ws.onopen = function(evt){ console.log(evt.data) };
ws.onclose = function(evt){ console.log(evt.data) };
ws.onmessage = function(evt){ console.log(evt.data) };
ws.onerror = function(evt){ console.log(evt.data) };
return ws;
}
function send() {
var data = {
input:$("#input_data").val()
};
console.log('hello');
ws.send(JSON.stringify(data));
$("#message").empty();
ws.onopen = function() {
ws.send(JSON.stringify(data));
};
$("#message").append('<div class="panel-body"><p>');
ws.onmessage = function(event) {
$("#message").append(JSON.parse(event.data).input + "<br>");
};
ws.onclose = function(event) {
$("#message").append('</p></div>');
};
}
{# function send() {#}
{# var ws = new WebSocket("ws://j:8080/send");#}
{##}
{# var data = {#}
{# input:$("#input_data").val(),#}
{# };#}
{##}
{# $("#message").empty();#}
{# ws.onopen = function() {#}
{# ws.send(JSON.stringify(data));#}
{# };#}
{##}
{# $("#message").append('<div class="panel-body"><p>');#}
{# ws.onmessage = function(event) {#}
{# $("#message").append(JSON.parse(event.data).input + "<br>");#}
{# };#}
{##}
{# ws.onclose = function(event) {#}
{# $("#message").append('</p></div>');#}
{# };#}
{# }#}
function disconnect(){
ws.close();
}
</script>
</head>
<body>
{# <div id="test">#}
{# <form class="form-horizontal" role="form">#}
{# <div class="panel panel-default">#}
{# <div class="panel-heading">#}
{# <h5 class="panel-title">>>输入</h5>#}
{# </div>#}
{# <div class="panel-body">#}
{# <div class="form-group">#}
{# <div class="col-md-8">#}
{# <input type="text" class="form-control" id="input_data" value="">#}
{# </div>#}
{# </div>#}
{# <div class="form-group">#}
{# <div class="col-md-8">#}
{# <input type="button" class="btn btn-success" id="input_1_btn" onclick="connect();" value="连接" />#}
{# <input type="button" class="btn btn-success" id="input_btn" onclick="send();" value="查看" />#}
{# <input type="button" class="btn btn-success" id="input_2_btn" onclick="close();" value="关闭" />#}
{# </div>#}
{# </div>#}
{# </div>#}
{# </div>#}
{# </form>#}
{# <div class="panel panel-default">#}
{# <div class="panel-heading">#}
{# <h5 class="panel-title">>> 输出</h5>#}
{# </div>#}
{# <div class="panel-body">#}
{# <div id="message"></div>#}
{# </div>#}
{# </div>#}
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title" style="border: solid">
<h5> 实时监控 </h5>
<div class="ibox-tools">
<a class="collapise-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content blank-panel" style="background-color: #0b0b0b; color: #006621; height: 500px; padding: 20px;">
你好<br>
你好<br>
你好<br>
你好你好你好<br>
你好<br>
你好<br>
你好<br>
你好<br>
你好<br>
你好<br>
</div>
</div>
</div>
</body>
</html>
</html>

View File

@ -1,4 +1,4 @@
{% ifequal session_role_id 2 %}
{% if request.session.role_id == 2 %}
<nav class="navbar-default navbar-static-side" role="navigation">
<div class="sidebar-collapse">
<ul class="nav" id="side-menu">
@ -51,8 +51,8 @@
</div>
</nav>
{% endifequal %}
{% ifequal session_role_id 1 %}
{% endif %}
{% if request.session.role_id == 1 %}
<nav class="navbar-default navbar-static-side" role="navigation">
<div class="sidebar-collapse">
<ul class="nav" id="side-menu">
@ -73,8 +73,8 @@
<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_add host_add_multi"><a href="/jasset/host_add/">添加资产</a></li>
<li class="host_list host_detail host_edit"><a href="/jasset/host_list/">查看资产<span class="label label-info pull-right">{{ host_active_num }}/{{ host_total_num}}</span></a></li>
{# <li class="host_add host_add_multi"><a href="/jasset/host_add/">添加资产</a></li>#}
<li class="host_list host_detail host_edit"><a href="/jasset/asset_list/">查看资产<span class="label label-info pull-right">{{ host_active_num }}/{{ host_total_num}}</span></a></li>
<li class="idc_list idc_detail idc_edit"><a href="/jasset/idc_list/">查看IDC</a></li>
<li class="group_add"><a href="/jasset/group_add/">添加主机组</a></li>
<li class="group_list group_detail group_edit"><a href="/jasset/group_list/">查看主机组</a></li>
@ -103,9 +103,9 @@
</ul>
</div>
</nav>
{% endifequal %}
{% endif %}
{% ifequal session_role_id 0 %}
{% if request.session.role_id == 0 %}
<nav class="navbar-default navbar-static-side" role="navigation">
<div class="sidebar-collapse">
<ul class="nav" id="side-menu">
@ -117,7 +117,7 @@
<a href="/juser/user_detail/?id={{ session_user_id }}"><i class="fa fa-rebel"></i> <span class="nav-label">个人信息</span><span class="label label-info pull-right"></span></a>
</li>
<li id="jasset">
<a href="/jasset/host_list/"><i class="fa fa-cube"></i> <span class="nav-label">查看主机</span><span class="label label-info pull-right"></span></a>
<a href="/jasset/asset_list/"><i class="fa fa-cube"></i> <span class="nav-label">查看主机</span><span class="label label-info pull-right"></span></a>
</li>
<li id="jperm">
<a><i class="fa fa-cube"></i> <span class="nav-label">权限申请</span><span class="fa arrow"></span></a>
@ -144,4 +144,4 @@
</ul>
</div>
</nav>
{% endifequal %}
{% endif %}

View File

@ -17,8 +17,13 @@
<ul class="dropdown-menu animated fadeInRight m-t-xs">
<li><a value="/juser/profile/?id={{ session_user_id }}" class="iframe_user">个人信息</a></li>
<li><a href="/juser/change_info/">修改信息</a></li>
<li><a href="/juser/change_role/">切换角色</a></li>
{% if not user.role == 'CU' %}
{% if request.session.role_id == 0 %}
<li><a href="/juser/change_role/">系统后台</a></li>
{% else %}
<li><a href="/juser/change_role/">主机控制台</a></li>
{% endif %}
{% endif %}
<li class="divider"></li>
<li><a href="/logout/">注销</a></li>
</ul>

View File

@ -1,72 +1,56 @@
<div class="col-sm-6">
<div class="dataTables_paginate paging_simple_numbers" id="editable_paginate">
<ul class="pagination" style="margin-top: 0; float: right">
{% if keyword %}
{% if contacts.has_previous %}
<li class="paginate_button previous" aria-controls="editable" tabindex="0" id="editable_previous">
<a href="?keyword={{ keyword }}&page={{ contacts.previous_page_number }}">Previous</a>
<a class="page" href="?page={{ contacts.previous_page_number }}">Previous</a>
</li>
{% else %}
<li class="paginate_button previous disabled" aria-controls="editable" tabindex="0" id="editable_previous">
<a href="#">Previous</a>
<a class="page" href="#">Previous</a>
</li>
{% endif %}
{% ifequal show_first 1 %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?keyword={{ keyword }}&page=1" title="第1页">1...</a></li>
<li class="paginate_button" aria-controls="editable" tabindex="0"><a class="page" href="?page=1" title="第1页">1...</a></li>
{% endifequal %}
{% for page in page_range %}
{% ifequal current_page page %}
<li class="paginate_button active" aria-controls="editable" tabindex="0"><a href="?keyword={{ keyword }}&page={{ page }}" title="第{{ page }}页">{{ page }}</a></li>
<li class="paginate_button active" aria-controls="editable" tabindex="0"><a class="page" href="?page={{ page }}" title="第{{ page }}页">{{ page }}</a></li>
{% else %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?keyword={{ keyword }}&page={{ page }}" title="第{{ page }}页">{{ page }}</a></li>
<li class="paginate_button" aria-controls="editable" tabindex="0"><a class="page" href="?page={{ page }}" title="第{{ page }}页">{{ page }}</a></li>
{% endifequal %}
{% endfor %}
{% ifequal show_end 1 %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?keyword={{ keyword }}&page={{ p.num_pages }}" title="第{{ page }}页">...{{ p.num_pages }}</a></li>
<li class="paginate_button" aria-controls="editable" tabindex="0"><a class="page" href="?page={{ p.num_pages }}" title="第{{ page }}页">...{{ p.num_pages }}</a></li>
{% endifequal %}
{% if contacts.has_next %}
<li class="paginate_button next" aria-controls="editable" tabindex="0" id="editable_next">
<a href="?keyword={{ keyword }}&page={{ contacts.next_page_number }}">Next</a>
<a class="page" href="?page={{ contacts.next_page_number }}">Next</a>
</li>
{% else %}
<li class="paginate_button next disabled" aria-controls="editable" tabindex="0" id="editable_next">
<a href="#">Next</a>
<a class="page" href="#">Next</a>
</li>
{% endif %}
{% else %}
{% if contacts.has_previous %}
<li class="paginate_button previous" aria-controls="editable" tabindex="0" id="editable_previous">
<a href="?page={{ contacts.previous_page_number }}">Previous</a>
</li>
{% else %}
<li class="paginate_button previous disabled" aria-controls="editable" tabindex="0" id="editable_previous">
<a href="#">Previous</a>
</li>
{% endif %}
{% ifequal show_first 1 %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?page=1" title="第1页">1...</a></li>
{% endifequal %}
{% for page in page_range %}
{% ifequal current_page page %}
<li class="paginate_button active" aria-controls="editable" tabindex="0"><a href="?page={{ page }}" title="第{{ page }}页">{{ page }}</a></li>
{% else %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?page={{ page }}" title="第{{ page }}页">{{ page }}</a></li>
{% endifequal %}
{% endfor %}
{% ifequal show_end 1 %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?page={{ p.num_pages }}" title="第{{ page }}页">...{{ p.num_pages }}</a></li>
{% endifequal %}
{% if contacts.has_next %}
<li class="paginate_button next" aria-controls="editable" tabindex="0" id="editable_next">
<a href="?page={{ contacts.next_page_number }}">Next</a>
</li>
{% else %}
<li class="paginate_button next disabled" aria-controls="editable" tabindex="0" id="editable_next">
<a href="#">Next</a>
</li>
{% endif %}
{% endif %}
</ul>
</div>
</div>
<script>
$(document).ready(function(){
$('.page').click(function(){
var searchStr = location.search;
var old_href = $(this).attr('href').replace('?', '');
var searchArray = searchStr.split('&');
if (searchStr.indexOf('page')){
searchArray.pop();
}
searchArray.push(old_href);
if (searchArray.length > 2){
$(this).attr('href', searchArray.join('&'));
}
})
});
</script>

View File

@ -46,20 +46,28 @@
{% endif %}
<div class="form-group">
<label for="username" class="col-sm-2 control-label">默认用户名<span class="red-fonts">*</span></label>
<input name="setting" value="default" style="display: none">
<div class="col-sm-8">
<input id="username" name="username" placeholder="Username" type="text" value="{{ setting_r.default_user }}" class="form-control">
<input id="username" name="username" placeholder="Username" type="text" value="{{ setting_default.default_user }}" class="form-control">
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<label for="port" class="col-sm-2 control-label">默认ssh端口<span class="red-fonts">*</span></label>
<div class="col-sm-8">
<input id="port" name="port" placeholder="Port" type="text" value="{{ setting_r.default_port }}" class="form-control">
<input id="port" name="port" placeholder="Port" type="text" value="{{ setting_default.default_port }}" class="form-control">
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<label for="key" class="col-sm-2 control-label">默认密钥<span class="red-fonts">*</span></label>
<label for="key" class="col-sm-2 control-label">默认密码</label>
<div class="col-sm-8">
<input id="password" name="password" placeholder="Password" type="password" value="{{ setting_default.default_password }}" class="form-control">
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<label for="key" class="col-sm-2 control-label">默认密钥</label>
<div class="col-sm-8">
<textarea class="form-control" name="key" placeholder="请复制粘贴私钥(原来的因为安全原因不被显示)" rows="10" style="font-size: 9px;"></textarea>
</div>

View File

@ -1,74 +0,0 @@
<html>
<head>
<meta charset='utf8'>
<script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script>
<style type="text/css">
.blue
{
color: blue;
}
</style>
{#<script>#}
{#$('html').ajaxSend(function(event, xhr, settings) {#}
{# function getCookie(name) {#}
{# var cookieValue = null;#}
{# if (document.cookie && document.cookie != '') {#}
{# var cookies = document.cookie.split(';');#}
{# for (var i = 0; i < cookies.length; i++) {#}
{# var cookie = jQuery.trim(cookies[i]);#}
{# // Does this cookie string begin with the name we want?#}
{# if (cookie.substring(0, name.length + 1) == (name + '=')) {#}
{# cookieValue = decodeURIComponent(cookie.substring(name.length + 1));#}
{# break;#}
{# }#}
{# }#}
{# }#}
{# return cookieValue;#}
{# }#}
{# if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {#}
{# // Only send the token to relative URLs i.e. locally.#}
{# xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));#}
{# }#}
{#});#}
{#</script>#}
<script>
$(document).ready(function(){
$("#btn1").click(function(){
$("#test").text(function(i,origText){
return "Old text: " + origText + "New text: Hllo" + i
})
});
$("#btn2").click(function(){
$.post('/test_ajax/',
{'name': 'join', 'age': 10},
function(data, status){
$('#btn1').text(data)
})
});
$("#btn3").click(function(){
$("p").toggleClass('blue')
// $("p").addClass('blue')
// $("p").before("Some thine")
});
})
</script>
</head>
<body>
<p id="test" class="blue">
这是段落中的<b>粗体</b>文本。
<p><span>hello</span></p>
</p>
<!-- <input type="text" id="test2" name="nameaaaaaaaaaa" value="米老鼠"> -->
<button id="btn1">显示文本</button>
<button id="btn2">显示 HTML</button>
<button id="btn3">显示 value</button>
</body>
</html>

85
templates/test2.html Normal file
View File

@ -0,0 +1,85 @@
<html>
<body>
<div id="main" style="height:400px;"></div>
...
<script src="/static/js/echarts/echarts.js"></script>
<script type="text/javascript">
require.config({
paths: {
'echarts': '/static/js/echarts/chart',
'echarts/chart/line': '/static/js/echarts/chart/line'
}
});
require(
[
'echarts',
'echarts/chart/line'
],
function (ec) {
var myChart = ec.init(document.getElementById('main'));
var option = {
title : {
text: '某楼盘销售情况',
subtext: '纯属虚构'
},
tooltip : {
trigger: 'axis'
},
legend: {
data:['意向','预购','成交']
},
toolbox: {
show : true,
feature : {
mark : {show: true},
dataView : {show: true, readOnly: false},
magicType : {show: true, type: ['line', 'bar', 'stack', 'tiled']},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable : true,
xAxis : [
{
type : 'category',
boundaryGap : false,
data : ['周一','周二','周三','周四','周五','周六','周日']
}
],
yAxis : [
{
type : 'value'
}
],
series : [
{
name:'成交',
type:'line',
smooth:true,
itemStyle: {normal: {areaStyle: {type: 'default'}}},
data:[10, 12, 21, 54, 260, 830, 710]
},
{
name:'预购',
type:'line',
smooth:true,
itemStyle: {normal: {areaStyle: {type: 'default'}}},
data:[30, 182, 434, 791, 390, 30, 10]
},
{
name:'意向',
type:'line',
smooth:true,
itemStyle: {normal: {areaStyle: {type: 'default'}}},
data:[1320, 1132, 601, 234, 120, 90, 20]
}
]
};
myChart.setOption(option);
}
);
</script>
</body>
</html>