diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 228971d07..000000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 48fa35e3d..c6a0a14a9 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,5 @@ logs keys jumpserver.conf nohup.out +tmp/* +db.sqlite3 diff --git a/connect.py b/connect.py index 7994205ec..063708ad3 100644 --- a/connect.py +++ b/connect.py @@ -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,358 @@ 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 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 newline_code_in(strings): + for i in ['\r', '\r\n', '\n']: + if i in strings: + #print "new line" + return True + return False + + +class Jtty(object): + """ + A virtual tty class + 一个虚拟终端类,实现连接ssh和记录日志 + """ + def __init__(self, username, ip): + self.chan = None + self.username = username + self.ip = 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)) + 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.ip, time_start)) + 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_f = open('/opt/jumpserver/logs/tty/20151102/a_b_191034.log', 'a') + 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) + + log = Log(user=self.username, host=self.ip, remote_ip=ip_list, + log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid) + log_file_f.write('Start time is %s\n' % datetime.datetime.now()) + log.save() + return log_file_f, log_time_f, ip_list, log + + def posix_shell(self,ssh): + """ + Use paramiko channel connect server interactive. + 使用paramiko模块的channel,连接后端,进入交互式 + """ + log_file_f, log_time_f, ip_list, log = self.log_record() + 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.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_f.write(x) + now_timestamp = time.time() + log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(x))) + pre_timestamp = now_timestamp + log_file_f.flush() + log_time_f.flush() + + if input_mode and not newline_code_in(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) + TtyLog(log=log, datetime=datetime.datetime.now(), cmd=input_r).save() + input_r = '' + input_mode = False + + if len(x) == 0: + break + self.chan.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 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) + + # password = CRYPTOR.decrypt(self.]) + # return self.username, password, self.ip, int(self.asset.port) + return 'root', 'redhat', '127.0.0.1', 22 + + 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) + 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.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) + 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(ssh) + + # Shutdown channel socket + channel.close() + ssh.close() + + def execute(self, cmd): + """ + execute cmd on the asset + 执行命令 + """ + pass + + def print_prompt(): """ Print prompt @@ -98,76 +464,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 diff --git a/jasset/models.py b/jasset/models.py index e108d507c..413387281 100644 --- a/jasset/models.py +++ b/jasset/models.py @@ -1,6 +1,6 @@ import datetime from django.db import models -from juser.models import User, UserGroup +# from juser.models import User, UserGroup class AssetGroup(models.Model): @@ -14,48 +14,48 @@ class AssetGroup(models.Model): def __unicode__(self): return self.name - def get_asset(self): - return self.asset_set.all() - - def get_asset_info(self, printable=False): - assets = self.get_asset() - ip_comment = {} - for asset in assets: - ip_comment[asset.ip] = asset.comment - - for ip in sorted(ip_comment): - if ip_comment[ip]: - print '%-15s -- %s' % (ip, ip_comment[ip]) - else: - print '%-15s' % ip - print '' - - def get_asset_num(self): - return len(self.get_asset()) - - def get_user_group(self): - perm_list = self.perm_set.all() - user_group_list = [] - for perm in perm_list: - user_group_list.append(perm.user_group) - return user_group_list - - def get_user(self): - user_list = [] - user_group_list = self.get_user_group() - for user_group in user_group_list: - user_list.extend(user_group.user_set.all()) - return user_list - - def is_permed(self, user=None, user_group=None): - if user: - if user in self.get_user(): - return True - - if user_group: - if user_group in self.get_user_group(): - return True - return False + # def get_asset(self): + # return self.asset_set.all() + # + # def get_asset_info(self, printable=False): + # assets = self.get_asset() + # ip_comment = {} + # for asset in assets: + # ip_comment[asset.ip] = asset.comment + # + # for ip in sorted(ip_comment): + # if ip_comment[ip]: + # print '%-15s -- %s' % (ip, ip_comment[ip]) + # else: + # print '%-15s' % ip + # print '' + # + # def get_asset_num(self): + # return len(self.get_asset()) + # + # def get_user_group(self): + # perm_list = self.perm_set.all() + # user_group_list = [] + # for perm in perm_list: + # user_group_list.append(perm.user_group) + # return user_group_list + # + # def get_user(self): + # user_list = [] + # user_group_list = self.get_user_group() + # for user_group in user_group_list: + # user_list.extend(user_group.user_set.all()) + # return user_list + # + # def is_permed(self, user=None, user_group=None): + # if user: + # if user in self.get_user(): + # return True + # + # if user_group: + # if user_group in self.get_user_group(): + # return True + # return False class Asset(models.Model): @@ -72,27 +72,28 @@ class Asset(models.Model): def __unicode__(self): return self.ip - def get_user(self): - perm_list = [] - asset_group_all = self.bis_group.all() - for asset_group in asset_group_all: - perm_list.extend(asset_group.perm_set.all()) - - user_group_list = [] - for perm in perm_list: - user_group_list.append(perm.user_group) - - user_permed_list = [] - for user_group in user_group_list: - user_permed_list.extend(user_group.user_set.all()) - user_permed_list = list(set(user_permed_list)) - return user_permed_list + # def get_user(self): + # perm_list = [] + # asset_group_all = self.bis_group.all() + # for asset_group in asset_group_all: + # perm_list.extend(asset_group.perm_set.all()) + # + # user_group_list = [] + # for perm in perm_list: + # user_group_list.append(perm.user_group) + # + # user_permed_list = [] + # for user_group in user_group_list: + # user_permed_list.extend(user_group.user_set.all()) + # user_permed_list = list(set(user_permed_list)) + # return user_permed_list class AssetAlias(models.Model): - user = models.ForeignKey(User) - asset = models.ForeignKey(Asset) - alias = models.CharField(max_length=100, blank=True, null=True) - - def __unicode__(self): - return self.alias + pass +# user = models.ForeignKey(User) +# asset = models.ForeignKey(Asset) +# alias = models.CharField(max_length=100, blank=True, null=True) +# +# def __unicode__(self): +# return self.alias diff --git a/jasset/views.py b/jasset/views.py index 259a7357d..8e639c3b3 100644 --- a/jasset/views.py +++ b/jasset/views.py @@ -87,18 +87,19 @@ def asset_add(request): asset_group_all = AssetGroup.objects.all() if request.method == 'POST': ip = request.POST.get('ip') - port = request.POST.get('port') groups = request.POST.getlist('groups') - use_default_auth = True if request.POST.getlist('use_default_auth', []) else False + use_default = True if request.POST.getlist('use_default', []) else False is_active = True if request.POST.get('is_active') else False comment = request.POST.get('comment') - if not use_default_auth: + if not use_default: username = request.POST.get('username') password = request.POST.get('password') + port = request.POST.get('port') password_encode = CRYPTOR.encrypt(password) else: username = None + port = None password_encode = None try: @@ -110,126 +111,13 @@ def asset_add(request): pass else: db_asset_add( - ip=ip, port=port, use_default_auth=use_default_auth, is_active=is_active, comment=comment, + ip=ip, port=port, use_default=use_default, is_active=is_active, comment=comment, groups=groups, username=username, password=password_encode ) msg = u'主机 %s 添加成功' % ip return my_render('jasset/asset_add.html', locals(), request) -# -# -# @require_admin -# def host_add_batch(request): -# """ 批量添加主机 """ -# header_title, path1, path2 = u'批量添加主机', u'资产管理', u'批量添加主机' -# login_types = {'LDAP': 'L', 'MAP': 'M'} -# active_types = {'激活': 1, '禁用': 0} -# dept_id = get_user_dept(request) -# if request.method == 'POST': -# multi_hosts = request.POST.get('j_multi').split('\n') -# for host in multi_hosts: -# if host == '': -# break -# j_ip, j_port, j_type, j_idc, j_groups, j_depts, j_active, j_comment = host.split() -# j_active = active_types[str(j_active)] -# j_group = ast.literal_eval(j_groups) -# j_dept = ast.literal_eval(j_depts) -# -# if j_type not in ['LDAP', 'MAP']: -# return httperror(request, u'没有%s这种登录方式!' %j_type) -# -# j_type = login_types[j_type] -# idc = IDC.objects.filter(name=j_idc) -# if idc: -# j_idc = idc[0].id -# else: -# return httperror(request, '添加失败, 没有%s这个IDC' % j_idc) -# -# group_ids, dept_ids = [], [] -# for group_name in j_group: -# group = BisGroup.objects.filter(name=group_name) -# if group: -# group_id = group[0].id -# else: -# return httperror(request, '添加失败, 没有%s这个主机组' % group_name) -# group_ids.append(group_id) -# -# for dept_name in j_dept: -# dept = DEPT.objects.filter(name=dept_name) -# if dept: -# dept_id = dept[0].id -# else: -# return httperror(request, '添加失败, 没有%s这个部门' % dept_name) -# dept_ids.append(dept_id) -# -# if is_group_admin(request) and not validate(request, asset_group=group_ids, edept=dept_ids): -# return httperror(request, '添加失败, 没有%s这个主机组' % group_name) -# -# if Asset.objects.filter(ip=str(j_ip)): -# return httperror(request, '添加失败, 改IP%s已存在' % j_ip) -# -# host_info = [j_ip, j_port, j_idc, j_type, group_ids, dept_ids, j_active, j_comment] -# db_host_insert(host_info) -# -# smg = u'批量添加添加成功' -# return my_render('jasset/host_add_multi.html', locals(), request) -# -# return my_render('jasset/host_add_multi.html', locals(), request) -# -# -# @require_admin -# def host_edit_batch(request): -# """ 批量修改主机 """ -# if request.method == 'POST': -# len_table = request.POST.get('len_table') -# for i in range(int(len_table)): -# j_id = "editable[" + str(i) + "][j_id]" -# j_ip = "editable[" + str(i) + "][j_ip]" -# j_port = "editable[" + str(i) + "][j_port]" -# j_dept = "editable[" + str(i) + "][j_dept]" -# j_idc = "editable[" + str(i) + "][j_idc]" -# j_type = "editable[" + str(i) + "][j_type]" -# j_group = "editable[" + str(i) + "][j_group]" -# j_active = "editable[" + str(i) + "][j_active]" -# j_comment = "editable[" + str(i) + "][j_comment]" -# -# j_id = request.POST.get(j_id).strip() -# j_ip = request.POST.get(j_ip).strip() -# j_port = request.POST.get(j_port).strip() -# j_dept = request.POST.getlist(j_dept) -# j_idc = request.POST.get(j_idc).strip() -# j_type = request.POST.get(j_type).strip() -# j_group = request.POST.getlist(j_group) -# j_active = request.POST.get(j_active).strip() -# j_comment = request.POST.get(j_comment).strip() -# -# host_info = [j_id, j_ip, j_idc, j_port, j_type, j_group, j_dept, j_active, j_comment] -# batch_host_edit(host_info) -# -# return HttpResponseRedirect('/jasset/host_list/') -# -# -# @require_role(role='user') -# def host_edit_common_batch(request): -# """ 普通用户批量修改主机别名 """ -# u = get_session_user_info(request)[2] -# if request.method == 'POST': -# len_table = request.POST.get('len_table') -# for i in range(int(len_table)): -# j_id = "editable[" + str(i) + "][j_id]" -# j_alias = "editable[" + str(i) + "][j_alias]" -# j_id = request.POST.get(j_id, '').strip() -# j_alias = request.POST.get(j_alias, '').strip() -# a = Asset.objects.get(id=j_id) -# asset_alias = AssetAlias.objects.filter(user=u, host=a) -# if asset_alias: -# asset_alias = asset_alias[0] -# asset_alias.alias = j_alias -# asset_alias.save() -# else: -# AssetAlias.objects.create(user=u, host=a, alias=j_alias) -# return my_render('jasset/host_list_common.html', locals(), request) @require_role(role='user') @@ -277,15 +165,15 @@ def asset_edit(request): if request.method == 'POST': ip = request.POST.get('ip') - port = request.POST.get('port') groups = request.POST.getlist('groups') - use_default_auth = True if request.POST.getlist('use_default_auth', []) else False + use_default = True if request.POST.getlist('use_default', []) else False is_active = True if request.POST.get('is_active') else False comment = request.POST.get('comment') - if not use_default_auth: + if not use_default: username = request.POST.get('username') password = request.POST.get('password') + port = request.POST.get('port') if password == asset.password: password_encode = password else: @@ -293,6 +181,7 @@ def asset_edit(request): else: username = None password_encode = None + port = 22 try: asset_test = get_object(Asset, ip=ip) @@ -302,7 +191,7 @@ def asset_edit(request): except ServerError: pass else: - db_asset_update(id=asset_id, ip=ip, port=port, use_default_auth=use_default_auth, + db_asset_update(id=asset_id, ip=ip, port=port, use_default=use_default, username=username, password=password_encode, is_active=is_active, comment=comment) msg = u'主机 %s 修改成功' % ip @@ -311,53 +200,6 @@ def asset_edit(request): return my_render('jasset/asset_edit.html', locals(), request) -# @require_role(role='admin') -# def host_edit_adm(request): -# """ 部门管理员修改主机 """ -# header_title, path1, path2 = u'修改主机', u'资产管理', u'修改主机' -# actives = {1: u'激活', 0: u'禁用'} -# login_types = {'L': 'LDAP', 'M': 'MAP'} -# eidc = IDC.objects.all() -# dept = get_session_user_info(request)[5] -# egroup = BisGroup.objects.exclude(name='ALL').filter(dept=dept) -# host_id = request.GET.get('id', '') -# post = Asset.objects.filter(id=int(host_id)) -# if post: -# post = post[0] -# else: -# return httperror(request, '没有此主机!') -# -# e_group = post.bis_group.all() -# -# if request.method == 'POST': -# j_ip = request.POST.get('j_ip') -# j_idc = request.POST.get('j_idc') -# j_port = request.POST.get('j_port') -# j_type = request.POST.get('j_type') -# j_dept = request.POST.getlist('j_dept') -# j_group = request.POST.getlist('j_group') -# j_active = request.POST.get('j_active') -# j_comment = request.POST.get('j_comment') -# -# host_info = [j_ip, j_port, j_idc, j_type, j_group, j_dept, j_active, j_comment] -# -# if not validate(request, asset_group=j_group, edept=j_dept): -# emg = u'修改失败,您无权操作!' -# return my_render('jasset/asset_edit.html', locals(), request) -# -# if j_type == 'M': -# j_user = request.POST.get('j_user') -# j_password = request.POST.get('j_password') -# db_host_update(host_info, j_user, j_password, post) -# else: -# db_host_update(host_info, post) -# -# smg = u'主机 %s 修改成功' % j_ip -# return HttpResponseRedirect('/jasset/host_detail/?id=%s' % host_id) -# -# return my_render('jasset/asset_edit.html', locals(), request) - - @require_role('admin') def asset_detail(request): """ 主机详情 """ @@ -368,146 +210,3 @@ def asset_detail(request): return my_render('jasset/asset_detail.html', locals(), request) - - -# -# -# @require_admin -# def group_edit(request): -# """ 修改主机组 """ -# header_title, path1, path2 = u'编辑主机组', u'资产管理', u'编辑主机组' -# group_id = request.GET.get('id', '') -# group = BisGroup.objects.filter(id=group_id) -# if group: -# group = group[0] -# else: -# httperror(request, u'没有这个主机组!') -# -# host_all = Asset.objects.all() -# dept_id = get_session_user_info(request)[3] -# eposts = Asset.objects.filter(bis_group=group) -# -# if is_group_admin(request) and not validate(request, asset_group=[group_id]): -# return httperror(request, '编辑失败, 您无权操作!') -# dept = DEPT.objects.filter(id=group.dept.id) -# if dept: -# dept = dept[0] -# else: -# return httperror(request, u'没有这个部门!') -# -# all_dept = dept.asset_set.all() -# posts = [g for g in all_dept if g not in eposts] -# -# if request.method == 'POST': -# j_group = request.POST.get('j_group', '') -# j_hosts = request.POST.getlist('j_hosts', '') -# j_dept = request.POST.get('j_dept', '') -# j_comment = request.POST.get('j_comment', '') -# -# j_dept = DEPT.objects.filter(id=int(j_dept)) -# j_dept = j_dept[0] -# -# group.asset_set.clear() -# for host in j_hosts: -# g = Asset.objects.get(id=host) -# group.asset_set.add(g) -# BisGroup.objects.filter(id=group_id).update(name=j_group, dept=j_dept, comment=j_comment) -# smg = u'主机组%s修改成功' % j_group -# return HttpResponseRedirect('/jasset/group_list') -# -# return my_render('jasset/group_edit.html', locals(), request) -# -# -# @require_admin -# def group_detail(request): -# """ 主机组详情 """ -# header_title, path1, path2 = u'主机组详情', u'资产管理', u'主机组详情' -# login_types = {'L': 'LDAP', 'M': 'MAP'} -# dept = get_session_user_info(request)[5] -# group_id = request.GET.get('id', '') -# group = BisGroup.objects.get(id=group_id) -# if is_super_user(request): -# posts = Asset.objects.filter(bis_group=group).order_by('ip') -# -# elif is_group_admin(request): -# if not validate(request, asset_group=[group_id]): -# return httperror(request, u'您无权查看!') -# posts = Asset.objects.filter(bis_group=group).filter(dept=dept).order_by('ip') -# -# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) -# return my_render('jasset/group_detail.html', locals(), request) -# -# -# @require_admin -# def group_del_host(request): -# """ 主机组中剔除主机, 并不删除真实主机 """ -# if request.method == 'POST': -# group_id = request.POST.get('group_id') -# offset = request.GET.get('id', '') -# group = BisGroup.objects.get(id=group_id) -# if offset == 'group': -# len_list = request.POST.get("len_list") -# for i in range(int(len_list)): -# key = "id_list[" + str(i) + "]" -# jid = request.POST.get(key) -# g = Asset.objects.get(id=jid) -# group.asset_set.remove(g) -# -# else: -# offset = request.GET.get('id', '') -# group_id = request.GET.get('gid', '') -# group = BisGroup.objects.get(id=group_id) -# g = Asset.objects.get(id=offset) -# group.asset_set.remove(g) -# -# return HttpResponseRedirect('/jasset/group_detail/?id=%s' % group.id) -# - -# @require_admin -# def dept_host_ajax(request): -# """ 添加主机组时, 部门联动主机异步 """ -# dept_id = request.GET.get('id', '') -# if dept_id not in ['1', '2']: -# dept = DEPT.objects.filter(id=dept_id) -# if dept: -# dept = dept[0] -# hosts = dept.asset_set.all() -# else: -# hosts = Asset.objects.all() -# -# return my_render('jasset/dept_host_ajax.html', locals(), request) -# -# -# def show_all_ajax(request): -# """ 批量修改主机时, 部门和组全部显示 """ -# env = request.GET.get('env', '') -# get_id = request.GET.get('id', '') -# host = Asset.objects.filter(id=get_id) -# if host: -# host = host[0] -# return my_render('jasset/show_all_ajax.html', locals(), request) -# -# -# @require_login -# def host_search(request): -# """ 搜索主机 """ -# keyword = request.GET.get('keyword') -# login_types = {'L': 'LDAP', 'M': 'MAP'} -# dept = get_session_user_info(request)[5] -# post_all = Asset.objects.filter(Q(ip__contains=keyword) | -# Q(idc__name__contains=keyword) | -# Q(bis_group__name__contains=keyword) | -# Q(comment__contains=keyword)).distinct().order_by('ip') -# if is_super_user(request): -# posts = post_all -# -# elif is_group_admin(request): -# posts = post_all.filter(dept=dept) -# -# elif is_common_user(request): -# user_id, username = get_session_user_info(request)[0:2] -# post_perm = user_perm_asset_api(username) -# posts = list(set(post_all) & set(post_perm)) -# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) -# -# return my_render('jasset/host_search.html', locals(), request) \ No newline at end of file diff --git a/jlog/log_api.py b/jlog/log_api.py new file mode 100644 index 000000000..5ef484777 --- /dev/null +++ b/jlog/log_api.py @@ -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 + + diff --git a/jlog/models.py b/jlog/models.py index cf13a5354..a75b7bccd 100644 --- a/jlog/models.py +++ b/jlog/models.py @@ -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) \ No newline at end of file + is_finished = models.BigIntegerField(default=False) + + +class TtyLog(models.Model): + log = models.ForeignKey(Log) + datetime = models.DateTimeField() + cmd = models.CharField(max_length=200) + diff --git a/jlog/urls.py b/jlog/urls.py index 0b6810d3c..0058bcfe6 100644 --- a/jlog/urls.py +++ b/jlog/urls.py @@ -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), -) \ No newline at end of file + 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), + ) \ No newline at end of file diff --git a/jlog/views.py b/jlog/views.py index 88d325eea..02c3678ff 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -4,75 +4,61 @@ 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 + 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', '') - 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 = '%s/monitor' % 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: @@ -83,35 +69,40 @@ 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): + web_terminal_uri = '%s/terminal' % web_socket_host + return render_to_response('jlog/web_terminal.html', locals()) + diff --git a/jperm/models.py b/jperm/models.py index 167ae0485..b86ddd433 100644 --- a/jperm/models.py +++ b/jperm/models.py @@ -5,56 +5,17 @@ from juser.models import User, UserGroup from jasset.models import Asset, AssetGroup -class UserPerm(models.Model): - user = models.ForeignKey(User) - asset = models.ForeignKey(Asset, null=True) - asset_group = models.ForeignKey(AssetGroup, null=True) - - def __unicode__(self): - return self.user.name +class PermLog(models.Model): + datetime = models.DateTimeField(auto_now_add=True) + action = models.CharField(max_length=100, null=True, blank=True, default='') + results = models.CharField(max_length=1000, null=True, blank=True, default='') + is_success = models.BooleanField(default=False) + is_finish = models.BooleanField(default=False) -class GroupPerm(models.Model): - user_group = models.ForeignKey(UserGroup) - asset = models.ForeignKey(Asset, null=True) - asset_group = models.ForeignKey(AssetGroup, null=True) - - def __unicode__(self): - return self.user.name +class SysUser(models.Model): + username = models.CharField(max_length=100) + password = models.CharField(max_length=100) + comment = models.CharField(max_length=100, null=True, blank=True, default='') -# class CmdGroup(models.Model): -# name = models.CharField(max_length=50, unique=True) -# cmd = models.CharField(max_length=999) -# comment = models.CharField(blank=True, null=True, max_length=50) -# -# def __unicode__(self): -# return self.name -# -# -# class SudoPerm(models.Model): -# user_group = models.ForeignKey(UserGroup) -# user_runas = models.CharField(max_length=100) -# asset_group = models.ManyToManyField(AssetGroup) -# cmd_group = models.ManyToManyField(CmdGroup) -# comment = models.CharField(max_length=30, null=True, blank=True) -# -# def __unicode__(self): -# return self.user_group.name -# -# -# class Apply(models.Model): -# uuid = UUIDField(auto=True) -# applyer = models.CharField(max_length=20) -# admin = models.CharField(max_length=20) -# approver = models.CharField(max_length=20) -# bisgroup = models.CharField(max_length=500) -# asset = models.CharField(max_length=500) -# comment = models.TextField(blank=True, null=True) -# status = models.IntegerField(max_length=2) -# date_add = models.DateTimeField(null=True) -# date_end = models.DateTimeField(null=True) -# read = models.IntegerField(max_length=2) -# -# def __unicode__(self): -# return self.applyer diff --git a/jperm/perm_api.py b/jperm/perm_api.py new file mode 100644 index 000000000..ccfcbb6b4 --- /dev/null +++ b/jperm/perm_api.py @@ -0,0 +1,290 @@ +# coding: utf-8 + +from jasset.models import * +from jumpserver.api import * +import uuid +import re +from jumpserver.tasks import playbook_run + +from jumpserver.models import Setting +from jperm.models import PermLog + + +def get_object_list(model, id_list): + """根据id列表获取对象列表""" + object_list = [] + for object_id in id_list: + if object_id: + object_list.extend(model.objects.filter(id=int(object_id))) + + return object_list + + +def get_rand_file_path(base_dir=os.path.join(BASE_DIR, 'tmp')): + """获取随机文件路径""" + filename = uuid.uuid1().hex + return os.path.join(base_dir, filename) + + +def get_inventory(host_group): + """生成资产表库存清单""" + path = get_rand_file_path() + f = open(path, 'w') + for group, host_list in host_group.items(): + f.write('[%s]\n' % group) + for ip in host_list: + asset = get_object(Asset, ip=ip) + if asset.use_default: + f.write('%s\n' % ip) + else: + f.write('%s ansible_ssh_port=%s ansible_ssh_user=%s ansible_ssh_pass=%s\n' % + (ip, asset.port, asset.username, CRYPTOR.decrypt(asset.password))) + f.close() + return path + + +def get_playbook(template, var): + """根据playbook模板,生成playbook""" + str_playbook = open(template).read() + for k, v in var.items(): + str_playbook = re.sub(r'%s' % k, v, str_playbook) # 正则来替换传入的字符 + path = get_rand_file_path() + f = open(path, 'w') + f.write(str_playbook) + return path + + +def perm_user_api(perm_info): + """ + 用户授权api,通过调用ansible API完成用户新建等,传入参数必须如下,列表中可以是对象,也可以是用户名和ip + perm_info = {'del': {'users': [], + 'assets': [], + }, + 'new': {'users': [], + 'assets': []}} + """ + log = PermLog(action=perm_info.get('action', '')) + try: + new_users = perm_info.get('new', {}).get('users', []) + new_assets = perm_info.get('new', {}).get('assets', []) + del_users = perm_info.get('del', {}).get('users', []) + del_assets = perm_info.get('del', {}).get('assets', []) + print new_users, new_assets + except IndexError: + raise ServerError("Error: function perm_user_api传入参数错误") + + try: + new_ip = [asset.ip for asset in new_assets if isinstance(asset, Asset)] + del_ip = [asset.ip for asset in del_assets if isinstance(asset, Asset)] + new_username = [user.username for user in new_users] + del_username = [user.username for user in del_users] + except IndexError: + raise ServerError("Error: function perm_user_api传入参数类型错误") + + host_group = {'new': new_ip, 'del': del_ip} + inventory = get_inventory(host_group) + + the_new_users = ','.join(new_username) + the_del_users = ','.join(del_username) + + playbook = get_playbook(os.path.join(BASE_DIR, 'playbook', 'user_perm.yaml'), + {'the_new_group': 'new', 'the_del_group': 'del', + 'the_new_users': the_new_users, 'the_del_users': the_del_users, + 'KEY_DIR': os.path.join(SSH_KEY_DIR, 'sysuser')}) + + print playbook, inventory + + settings = get_object(Setting, name='default') + results = playbook_run(inventory, playbook, settings) + if not results.get('failures', 1) and not results.get('unreachable', ''): + is_success = True + else: + is_success = False + + log.results = results + log.is_finish = True + log.is_success = is_success + log.save() + return results + + +def user_group_permed(user_group): + assets = user_group.asset.all() + asset_groups = user_group.asset_group.all() + + for asset_group in asset_groups: + assets.extend(asset_group.asset.all()) + + return {'assets': assets, 'asset_groups': asset_groups} + + +def user_permed(user): + asset_groups = [] + assets = [] + user_groups = user.group.all() + asset_groups.extend(user.asset_group.all()) + assets.extend(user.asset.all()) + + for user_group in user_groups: + asset_groups.extend(user_group_permed(user_group).get('assets', [])) + assets.extend((user_group_permed(user_group).get('asset_groups', []))) + + return {'assets': assets, 'asset_groups': asset_groups} + + +def _public_perm_api(info): + """ + 公用的用户,用户组,主机,主机组编辑修改新建调用的api,用来完成授权 + info like that: + { + 'type': 'new_user', + 'user': 'a', + 'group': ['A', 'B'] + } + + { + 'type': 'edit_user', + 'user': 'a', + 'group': {'new': ['A'], 'del': []} + } + + { + 'type': 'del_user', + 'user': ['a', 'b'] + } + + { + 'type': 'edit_user_group', + 'group': 'A', + 'user': {'del': ['a', 'b'], 'new': ['c', 'd']} + } + + { + 'type': 'del_user_group', + 'group': ['A'] + } + + { + 'type': 'new_asset', + 'asset': 'a', + 'group': ['A', 'B'] + } + + { + 'type': 'edit_asset', + 'asset': 'a', + 'group': { + 'del': ['A', ['B'], + 'new': ['C', ['D']] + } + } + + { + 'type': 'del_asset', + 'asset': ['a', 'b'] + } + + { + 'type': 'edit_asset_group', + 'group': 'A', + 'asset': {'new': ['a', 'b'], 'del': ['c', 'd']} + } + + { + 'type': 'del_asset_group', + 'group': ['A', 'B'] + } + """ + + if info.get('type') == 'new_user': + new_assets = [] + user = info.get('user') + user_groups = info.get('group') + for user_group in user_groups: + new_assets.extend(user_group_permed(user_group).get('assets', [])) + + perm_info = { + 'action': 'new user: ' + user.name, + 'new': {'users': [user], 'assets': new_assets} + } + elif info.get('type') == 'edit_user': + new_assets = [] + del_assets = [] + user = info.get('user') + new_group = info.get('group').get('new') + del_group = info.get('group').get('del') + + for user_group in new_group: + new_assets.extend(user_group_permed(user_group).get('assets', [])) + + for user_group in del_group: + del_assets.extend((user_group_permed(user_group).get('assets', []))) + + perm_info = { + 'action': 'edit user: ' + user.name, + 'del': {'users': [user], 'assets': del_assets}, + 'new': {'users': [user], 'assets': new_assets} + } + + elif info.get('type') == 'del_user': + user = info.get('user') + del_assets = user_permed(user).get('assets', []) + perm_info = { + 'action': 'del user: ' + user.name, 'del': {'users': [user], 'assets': del_assets}, + } + + elif info.get('type') == 'edit_user_group': + user_group = info.get('group') + new_users = info.get('user').get('new') + del_users = info.get('user').get('del') + assets = user_group_permed(user_group).get('assets', []) + + perm_info = { + 'action': 'edit user group: ' + user_group.name, + 'new': {'users': new_users, 'assets': assets}, + 'del': {'users': del_users, 'assets': assets} + } + + elif info.get('type') == 'del_user_group': + user_group = info.get('group', []) + del_users = user_group.user_set.all() + assets = user_group_permed(user_group).get('assets', []) + + perm_info = { + 'action': "del user group: " + user_group.name, 'del': {'users': del_users, 'assets': assets} + } + else: + return + + try: + results = perm_user_api(perm_info) # 通过API授权或回收 + except ServerError, e: + return e + else: + return results + + +def push_user(user, asset_groups_id): + assets = [] + if not user: + return {'error': '没有该用户'} + for group_id in asset_groups_id: + asset_group = get_object(AssetGroup, id=group_id) + if asset_group: + assets.extend(asset_group.asset_set.all()) + perm_info = { + 'action': 'Push user:' + user.username, + 'new': {'users': [user], 'assets': assets} + } + + results = perm_user_api(perm_info) + return results + + + + + + + + + diff --git a/jperm/urls.py b/jperm/urls.py index c4f398c66..3892407c1 100644 --- a/jperm/urls.py +++ b/jperm/urls.py @@ -2,30 +2,13 @@ from django.conf.urls import patterns, include, url from jperm.views import * urlpatterns = patterns('jperm.views', - # Examples: - (r'^user/$', user_perm), - # (r'^dept_perm_edit/$', 'dept_perm_edit'), - # (r'^perm_list/$', view_splitter, {'su': perm_list, 'adm': perm_list_adm}), - # (r'^dept_perm_list/$', 'dept_perm_list'), - # (r'^perm_user_detail/$', 'perm_user_detail'), - # (r'^perm_detail/$', 'perm_detail'), - # (r'^perm_del/$', 'perm_del'), - # (r'^perm_asset_detail/$', 'perm_asset_detail'), - # (r'^sudo_list/$', view_splitter, {'su': sudo_list, 'adm': sudo_list_adm}), - # (r'^sudo_del/$', 'sudo_del'), - # (r'^sudo_edit/$', view_splitter, {'su': sudo_edit, 'adm': sudo_edit_adm}), - # (r'^sudo_refresh/$', 'sudo_refresh'), - # (r'^sudo_detail/$', 'sudo_detail'), - # (r'^cmd_add/$', view_splitter, {'su': cmd_add, 'adm': cmd_add_adm}), - # (r'^cmd_list/$', 'cmd_list'), - # (r'^cmd_del/$', 'cmd_del'), - # (r'^cmd_edit/$', 'cmd_edit'), - # (r'^cmd_detail/$', 'cmd_detail'), - # (r'^apply/$', 'perm_apply'), - # (r'^apply_show/(\w+)/$', 'perm_apply_log'), - # (r'^apply_exec/$', 'perm_apply_exec'), - # (r'^apply_info/$', 'perm_apply_info'), - # (r'^apply_del/$', 'perm_apply_del'), - # (r'^apply_search/$', 'perm_apply_search'), - -) + (r'^user/$', perm_user_list), + (r'^perm_user_edit/$', perm_user_edit), + (r'^group/$', perm_group_list), + (r'^perm_group_edit/$', perm_group_edit), + (r'^log/$', log), + (r'^sys_user_add/$', sys_user_add), + (r'^sys_user_list/$', sys_user_list), + (r'^sys_user_del/$', sys_user_del), + (r'^sys_user_edit/$', sys_user_edit), + ) diff --git a/jperm/views.py b/jperm/views.py index c098f6b37..2e910e025 100644 --- a/jperm/views.py +++ b/jperm/views.py @@ -1,818 +1 @@ -# # coding: utf-8 -# import sys -# -# reload(sys) -# sys.setdefaultencoding('utf8') -# -# from django.shortcuts import render_to_response -# from django.template import RequestContext -# from jperm.models import Perm, SudoPerm, CmdGroup, Apply -from django.db.models import Q -from jumpserver.api import * - - -def user_perm(request): - header_title, path1, path2 = '用户授权', '授权管理', '用户授权' - return my_render('jperm/user_perm.html', locals(), request) - - -# def asset_cmd_groups_get(asset_groups_select='', cmd_groups_select=''): -# asset_groups_select_list = [] -# cmd_groups_select_list = [] -# -# for asset_group_id in asset_groups_select: -# asset_groups_select_list.extend(BisGroup.objects.filter(id=asset_group_id)) -# -# for cmd_group_id in cmd_groups_select: -# cmd_groups_select_list.extend(CmdGroup.objects.filter(id=cmd_group_id)) -# -# return asset_groups_select_list, cmd_groups_select_list -# -# -# @require_admin -# def perm_add(request): -# header_title, path1, path2 = u'主机授权添加', u'授权管理', u'授权添加' -# -# if request.method == 'GET': -# user_groups = UserGroup.objects.filter(id__gt=2) -# asset_groups = BisGroup.objects.all() -# -# else: -# name = request.POST.get('name', '') -# user_groups_select = request.POST.getlist('user_groups_select') -# asset_groups_select = request.POST.getlist('asset_groups_select') -# comment = request.POST.get('comment', '') -# -# user_groups, asset_groups = user_asset_cmd_groups_get(user_groups_select, asset_groups_select, '')[0:2] -# -# perm = Perm(name=name, comment=comment) -# perm.save() -# -# perm.user_group = user_groups -# perm.asset_group = asset_groups -# msg = '添加成功' -# return render_to_response('jperm/user_perm.html', locals(), context_instance=RequestContext(request)) -# -# -# def dept_add_asset(dept_id, asset_list): -# dept = DEPT.objects.filter(id=dept_id) -# if dept: -# dept = dept[0] -# new_perm_asset = [] -# for asset_id in asset_list: -# asset = Asset.objects.filter(id=asset_id) -# new_perm_asset.extend(asset) -# -# dept.asset_set.clear() -# dept.asset_set = new_perm_asset -# -# -# @require_super_user -# def dept_perm_edit(request): -# header_title, path1, path2 = u'部门授权添加', u'授权管理', u'部门授权添加' -# if request.method == 'GET': -# dept_id = request.GET.get('id', '') -# dept = DEPT.objects.filter(id=dept_id) -# if dept: -# dept = dept[0] -# asset_all = Asset.objects.all() -# asset_select = dept.asset_set.all() -# assets = [asset for asset in asset_all if asset not in asset_select] -# else: -# dept_id = request.POST.get('dept_id') -# asset_select = request.POST.getlist('asset_select') -# dept_add_asset(dept_id, asset_select) -# return HttpResponseRedirect('/jperm/dept_perm_list/') -# return render_to_response('jperm/dept_perm_edit.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_super_user -# def perm_list(request): -# header_title, path1, path2 = u'小组授权', u'授权管理', u'授权详情' -# keyword = request.GET.get('search', '') -# uid = request.GET.get('uid', '') -# agid = request.GET.get('agid', '') -# if keyword: -# contact_list = UserGroup.objects.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) -# else: -# contact_list = UserGroup.objects.all().order_by('name') -# -# if uid: -# user = User.objects.filter(id=uid) -# print user -# if user: -# user = user[0] -# contact_list = contact_list.filter(user=user) -# -# if agid: -# contact_list_confirm = [] -# asset_group = BisGroup.objects.filter(id=agid) -# if asset_group: -# asset_group = asset_group[0] -# for user_group in contact_list: -# if asset_group in user_group_perm_asset_group_api(user_group): -# contact_list_confirm.append(user_group) -# contact_list = contact_list_confirm -# -# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) -# return render_to_response('jperm/perm_list.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def perm_list_adm(request): -# header_title, path1, path2 = u'小组授权', u'授权管理', u'授权详情' -# keyword = request.GET.get('search', '') -# uid = request.GET.get('uid', '') -# agid = request.GET.get('agid', '') -# user, dept = get_session_user_dept(request) -# contact_list = dept.usergroup_set.all().order_by('name') -# if keyword: -# contact_list = contact_list.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) -# -# if uid: -# user = User.objects.filter(id=uid) -# print user -# if user: -# user = user[0] -# contact_list = contact_list.filter(user=user) -# -# if agid: -# contact_list_confirm = [] -# asset_group = BisGroup.objects.filter(id=agid) -# if asset_group: -# asset_group = asset_group[0] -# for user_group in contact_list: -# if asset_group in user_group_perm_asset_group_api(user_group): -# contact_list_confirm.append(user_group) -# contact_list = contact_list_confirm -# -# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) -# return render_to_response('jperm/perm_list.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_super_user -# def dept_perm_list(request): -# header_title, path1, path2 = '查看部门', '授权管理', '部门授权' -# keyword = request.GET.get('search') -# if keyword: -# contact_list = DEPT.objects.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)).order_by('name') -# else: -# contact_list = DEPT.objects.filter(id__gt=2) -# -# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) -# -# return render_to_response('jperm/dept_perm_list.html', locals(), context_instance=RequestContext(request)) -# -# -# def perm_group_update(user_group_id, asset_groups_id_list): -# user_group = UserGroup.objects.filter(id=user_group_id) -# if user_group: -# user_group = user_group[0] -# old_asset_group = [perm.asset_group for perm in user_group.perm_set.all()] -# new_asset_group = [] -# -# for asset_group_id in asset_groups_id_list: -# new_asset_group.extend(BisGroup.objects.filter(id=asset_group_id)) -# -# del_asset_group = [asset_group for asset_group in old_asset_group if asset_group not in new_asset_group] -# add_asset_group = [asset_group for asset_group in new_asset_group if asset_group not in old_asset_group] -# -# for asset_group in del_asset_group: -# Perm.objects.filter(user_group=user_group, asset_group=asset_group).delete() -# -# for asset_group in add_asset_group: -# Perm(user_group=user_group, asset_group=asset_group).save() -# -# -# @require_super_user -# def perm_edit(request): -# if request.method == 'GET': -# header_title, path1, path2 = u'编辑授权', u'授权管理', u'授权编辑' -# user_group_id = request.GET.get('id', '') -# user_group = UserGroup.objects.filter(id=user_group_id) -# if user_group: -# user_group = user_group[0] -# asset_groups_all = BisGroup.objects.all() -# asset_groups_select = [perm.asset_group for perm in user_group.perm_set.all()] -# asset_groups = [asset_group for asset_group in asset_groups_all if asset_group not in asset_groups_select] -# else: -# user_group_id = request.POST.get('user_group_id') -# asset_group_id_list = request.POST.getlist('asset_groups_select') -# perm_group_update(user_group_id, asset_group_id_list) -# -# return HttpResponseRedirect('/jperm/perm_list/') -# return render_to_response('jperm/perm_edit.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def perm_edit_adm(request): -# if request.method == 'GET': -# header_title, path1, path2 = u'编辑授权', u'授权管理', u'授权编辑' -# user_group_id = request.GET.get('id', '') -# user_group = UserGroup.objects.filter(id=user_group_id) -# user, dept = get_session_user_dept(request) -# if user_group: -# user_group = user_group[0] -# asset_groups_all = dept.bisgroup_set.all() -# asset_groups_select = [perm.asset_group for perm in user_group.perm_set.all()] -# asset_groups = [asset_group for asset_group in asset_groups_all if asset_group not in asset_groups_select] -# else: -# user_group_id = request.POST.get('user_group_id') -# asset_group_id_list = request.POST.getlist('asset_groups_select') -# print user_group_id, asset_group_id_list -# if not validate(request, user_group=[user_group_id], asset_group=asset_group_id_list): -# return HttpResponseRedirect('/') -# perm_group_update(user_group_id, asset_group_id_list) -# -# return HttpResponseRedirect('/jperm/perm_list/') -# return render_to_response('jperm/perm_edit.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def perm_detail(request): -# header_title, path1, path2 = u'授权管理', u'小组管理', u'授权详情' -# group_id = request.GET.get('id') -# user_group = UserGroup.objects.filter(id=group_id) -# if user_group: -# user_group = user_group[0] -# users = user_group.user_set.all() -# group_user_num = len(users) -# perms = user_group.perm_set.all() -# asset_groups = [perm.asset_group for perm in perms] -# return render_to_response('jperm/perm_detail.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def perm_del(request): -# perm_id = request.GET.get('id') -# perm = Perm.objects.filter(id=perm_id) -# if perm: -# perm = perm[0] -# perm.delete() -# return HttpResponseRedirect('/jperm/perm_list/') -# -# -# @require_admin -# def perm_asset_detail(request): -# header_title, path1, path2 = u'用户授权主机', u'权限管理', u'用户主机详情' -# user_id = request.GET.get('id') -# user = User.objects.filter(id=user_id) -# if user: -# user = user[0] -# assets_list = user_perm_asset_api(user.username) -# return render_to_response('jperm/perm_asset_detail.html', locals(), context_instance=RequestContext(request)) -# -# -# def unicode2str(unicode_list): -# return [str(i) for i in unicode_list] -# -# -# # def sudo_ldap_add(user_group, user_runas, asset_groups_select, -# # cmd_groups_select): -# # if not LDAP_ENABLE: -# # return True -# # -# # assets = [] -# # cmds = [] -# # user_runas = user_runas.split(',') -# # if len(asset_groups_select) == 1 and asset_groups_select[0].name == 'ALL': -# # asset_all = True -# # else: -# # asset_all = False -# # for asset_group in asset_groups_select: -# # assets.extend(asset_group.asset_set.all()) -# # -# # if user_group.name == 'ALL': -# # user_all = True -# # users = [] -# # else: -# # user_all = False -# # users = user_group.user_set.all() -# # -# # for cmd_group in cmd_groups_select: -# # cmds.extend(cmd_group.cmd.split(',')) -# # -# # if user_all: -# # users_name = ['ALL'] -# # else: -# # users_name = list(set([user.username for user in users])) -# # -# # if asset_all: -# # assets_ip = ['ALL'] -# # else: -# # assets_ip = list(set([asset.ip for asset in assets])) -# # -# # name = 'sudo%s' % user_group.id -# # sudo_dn = 'cn=%s,ou=Sudoers,%s' % (name, LDAP_BASE_DN) -# # sudo_attr = {'objectClass': ['top', 'sudoRole'], -# # 'cn': ['%s' % name], -# # 'sudoCommand': unicode2str(cmds), -# # 'sudoHost': unicode2str(assets_ip), -# # 'sudoOption': ['!authenticate'], -# # 'sudoRunAsUser': unicode2str(user_runas), -# # 'sudoUser': unicode2str(users_name)} -# # ldap_conn.delete(sudo_dn) -# # ldap_conn.add(sudo_dn, sudo_attr) -# -# # -# # def sudo_update(user_group, user_runas, asset_groups_select, cmd_groups_select, comment): -# # asset_groups_select_list, cmd_groups_select_list = \ -# # asset_cmd_groups_get(asset_groups_select, cmd_groups_select) -# # sudo_perm = user_group.sudoperm_set.all() -# # if sudo_perm: -# # sudo_perm.update(user_runas=user_runas, comment=comment) -# # sudo_perm = sudo_perm[0] -# # sudo_perm.asset_group = asset_groups_select_list -# # sudo_perm.cmd_group = cmd_groups_select_list -# # else: -# # sudo_perm = SudoPerm(user_group=user_group, user_runas=user_runas, comment=comment) -# # sudo_perm.save() -# # sudo_perm.asset_group = asset_groups_select_list -# # sudo_perm.cmd_group = cmd_groups_select_list -# # -# # sudo_ldap_add(user_group, user_runas, asset_groups_select_list, cmd_groups_select_list) -# -# -# @require_super_user -# def sudo_list(request): -# header_title, path1, path2 = u'Sudo授权', u'权限管理', u'Sudo权限详情' -# keyword = request.GET.get('search', '') -# contact_list = UserGroup.objects.all().order_by('name') -# if keyword: -# contact_list = contact_list.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) -# -# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) -# return render_to_response('jperm/sudo_list.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def sudo_list_adm(request): -# header_title, path1, path2 = u'Sudo授权', u'权限管理', u'Sudo权限详情' -# keyword = request.GET.get('search', '') -# user, dept = get_session_user_dept(request) -# contact_list = dept.usergroup_set.all().order_by('name') -# if keyword: -# contact_list = contact_list.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) -# -# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) -# return render_to_response('jperm/sudo_list.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_super_user -# def sudo_edit(request): -# header_title, path1, path2 = u'Sudo授权', u'授权管理', u'Sudo授权' -# -# if request.method == 'GET': -# user_group_id = request.GET.get('id', '0') -# user_group = UserGroup.objects.filter(id=user_group_id) -# asset_group_all = BisGroup.objects.filter() -# cmd_group_all = CmdGroup.objects.all() -# if user_group: -# user_group = user_group[0] -# sudo_perm = user_group.sudoperm_set.all() -# if sudo_perm: -# sudo_perm = sudo_perm[0] -# asset_group_permed = sudo_perm.asset_group.all() -# cmd_group_permed = sudo_perm.cmd_group.all() -# user_runas = sudo_perm.user_runas -# comment = sudo_perm.comment -# else: -# asset_group_permed = [] -# cmd_group_permed = [] -# -# asset_groups = [asset_group for asset_group in asset_group_all if asset_group not in asset_group_permed] -# cmd_groups = [cmd_group for cmd_group in cmd_group_all if cmd_group not in cmd_group_permed] -# -# else: -# user_group_id = request.POST.get('user_group_id', '') -# users_runas = request.POST.get('runas') if request.POST.get('runas') else 'root' -# asset_groups_select = request.POST.getlist('asset_groups_select') -# cmd_groups_select = request.POST.getlist('cmd_groups_select') -# comment = request.POST.get('comment', '') -# user_group = UserGroup.objects.filter(id=user_group_id) -# if user_group: -# user_group = user_group[0] -# if LDAP_ENABLE: -# sudo_update(user_group, users_runas, asset_groups_select, cmd_groups_select, comment) -# msg = '修改成功' -# -# return HttpResponseRedirect('/jperm/sudo_list/') -# -# return render_to_response('jperm/sudo_edit.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def sudo_edit_adm(request): -# header_title, path1, path2 = u'Sudo授权', u'授权管理', u'Sudo授权' -# user, dept = get_session_user_dept(request) -# if request.method == 'GET': -# user_group_id = request.GET.get('id', '0') -# if not validate(request, user_group=[user_group_id]): -# return render_to_response('/jperm/sudo_list/') -# user_group = UserGroup.objects.filter(id=user_group_id) -# asset_group_all = dept.bisgroup_set.all() -# cmd_group_all = dept.cmdgroup_set.all() -# if user_group: -# user_group = user_group[0] -# sudo_perm = user_group.sudoperm_set.all() -# if sudo_perm: -# sudo_perm = sudo_perm[0] -# asset_group_permed = sudo_perm.asset_group.all() -# cmd_group_permed = sudo_perm.cmd_group.all() -# user_runas = sudo_perm.user_runas -# comment = sudo_perm.comment -# else: -# asset_group_permed = [] -# cmd_group_permed = [] -# -# asset_groups = [asset_group for asset_group in asset_group_all if asset_group not in asset_group_permed] -# cmd_groups = [cmd_group for cmd_group in cmd_group_all if cmd_group not in cmd_group_permed] -# -# else: -# user_group_id = request.POST.get('user_group_id', '') -# users_runas = request.POST.get('runas', 'root') -# asset_groups_select = request.POST.getlist('asset_groups_select') -# cmd_groups_select = request.POST.getlist('cmd_groups_select') -# comment = request.POST.get('comment', '') -# user_group = UserGroup.objects.filter(id=user_group_id) -# if not validate(request, user_group=[user_group_id], asset_group=asset_groups_select): -# return render_to_response('/jperm/sudo_list/') -# if user_group: -# user_group = user_group[0] -# if LDAP_ENABLE: -# sudo_update(user_group, users_runas, asset_groups_select, cmd_groups_select, comment) -# msg = '修改成功' -# -# return HttpResponseRedirect('/jperm/sudo_list/') -# return render_to_response('jperm/sudo_edit.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def sudo_detail(request): -# header_title, path1, path2 = u'Sudo授权详情', u'授权管理', u'授权详情' -# user_group_id = request.GET.get('id') -# user_group = UserGroup.objects.filter(id=user_group_id) -# if user_group: -# asset_groups = [] -# cmd_groups = [] -# user_group = user_group[0] -# users = user_group.user_set.all() -# group_user_num = len(users) -# -# for perm in user_group.sudoperm_set.all(): -# asset_groups.extend(perm.asset_group.all()) -# cmd_groups.extend(perm.cmd_group.all()) -# -# print asset_groups -# return render_to_response('jperm/sudo_detail.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def sudo_refresh(request): -# sudo_perm_all = SudoPerm.objects.all() -# for sudo_perm in sudo_perm_all: -# user_group = sudo_perm.user_group -# user_runas = sudo_perm.user_runas -# asset_groups_select = sudo_perm.asset_group.all() -# cmd_groups_select = sudo_perm.cmd_group.all() -# sudo_ldap_add(user_group, user_runas, asset_groups_select, cmd_groups_select) -# return HttpResponse('刷新sudo授权成功') -# -# -# @require_super_user -# def cmd_add(request): -# header_title, path1, path2 = u'sudo命令添加', u'授权管理', u'命令组添加' -# dept_all = DEPT.objects.all() -# -# if request.method == 'POST': -# name = request.POST.get('name') -# dept_id = request.POST.get('dept_id') -# cmd = ','.join(request.POST.get('cmd').split('\n')) -# comment = request.POST.get('comment') -# dept = DEPT.objects.filter(id=dept_id) -# -# try: -# if CmdGroup.objects.filter(name=name): -# error = '%s 命令组已存在' -# raise ServerError(error) -# -# if not dept: -# error = u"部门不能为空" -# raise ServerError(error) -# except ServerError, e: -# pass -# else: -# dept = dept[0] -# CmdGroup.objects.create(name=name, dept=dept, cmd=cmd, comment=comment) -# msg = u'命令组添加成功' -# return HttpResponseRedirect('/jperm/cmd_list/') -# -# return render_to_response('jperm/sudo_cmd_add.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def cmd_add_adm(request): -# header_title, path1, path2 = u'sudo命令添加', u'授权管理', u'命令组添加' -# user, dept = get_session_user_dept(request) -# -# if request.method == 'POST': -# name = request.POST.get('name') -# cmd = ','.join(request.POST.get('cmd').split('\n')) -# comment = request.POST.get('comment') -# -# try: -# if CmdGroup.objects.filter(name=name): -# error = '%s 命令组已存在' -# raise ServerError(error) -# except ServerError, e: -# pass -# else: -# CmdGroup.objects.create(name=name, dept=dept, cmd=cmd, comment=comment) -# return HttpResponseRedirect('/jperm/cmd_list/') -# -# return HttpResponseRedirect('/jperm/cmd_list/') -# -# return render_to_response('jperm/sudo_cmd_add.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def cmd_edit(request): -# header_title, path1, path2 = u'sudo命令修改', u'授权管理管理', u'命令组修改' -# -# cmd_group_id = request.GET.get('id') -# cmd_group = CmdGroup.objects.filter(id=cmd_group_id) -# dept_all = DEPT.objects.all() -# -# if cmd_group: -# cmd_group = cmd_group[0] -# cmd_group_id = cmd_group.id -# dept_id = cmd_group.dept.id -# name = cmd_group.name -# cmd = '\n'.join(cmd_group.cmd.split(',')) -# comment = cmd_group.comment -# -# if request.method == 'POST': -# cmd_group_id = request.POST.get('cmd_group_id') -# name = request.POST.get('name') -# dept_id = request.POST.get('dept_id') -# cmd = ','.join(request.POST.get('cmd').split()) -# comment = request.POST.get('comment') -# cmd_group = CmdGroup.objects.filter(id=cmd_group_id) -# -# dept = DEPT.objects.filter(id=dept_id) -# try: -# if not dept: -# error = '没有该部门' -# raise ServerError(error) -# -# if not cmd_group: -# error = '没有该命令组' -# except ServerError, e: -# pass -# else: -# cmd_group.update(name=name, cmd=cmd, dept=dept[0], comment=comment) -# return HttpResponseRedirect('/jperm/cmd_list/') -# return render_to_response('jperm/sudo_cmd_add.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def cmd_list(request): -# header_title, path1, path2 = u'sudo命令查看', u'权限管理', u'Sudo命令添加' -# -# if is_super_user(request): -# cmd_groups = contact_list = CmdGroup.objects.all() -# else: -# user, dept = get_session_user_dept(request) -# cmd_groups = contact_list = dept.cmdgroup_set.all() -# p = paginator = Paginator(contact_list, 10) -# -# try: -# page = int(request.GET.get('page', '1')) -# except ValueError: -# page = 1 -# -# try: -# contacts = paginator.page(page) -# except (EmptyPage, InvalidPage): -# contacts = paginator.page(paginator.num_pages) -# return render_to_response('jperm/sudo_cmd_list.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def cmd_del(request): -# cmd_group_id = request.GET.get('id') -# cmd_group = CmdGroup.objects.filter(id=cmd_group_id) -# -# if cmd_group: -# cmd_group[0].delete() -# return HttpResponseRedirect('/jperm/cmd_list/') -# -# -# @require_admin -# def cmd_detail(request): -# cmd_ids = request.GET.get('id').split(',') -# cmds = [] -# if len(cmd_ids) == 1: -# if cmd_ids[0]: -# cmd_id = cmd_ids[0] -# else: -# cmd_id = 1 -# cmd_group = CmdGroup.objects.filter(id=cmd_id) -# if cmd_group: -# cmd_group = cmd_group[0] -# cmds.extend(cmd_group.cmd.split(',')) -# cmd_group_name = cmd_group.name -# else: -# cmd_groups = [] -# for cmd_id in cmd_ids: -# cmd_groups.extend(CmdGroup.objects.filter(id=cmd_id)) -# for cmd_group in cmd_groups: -# cmds.extend(cmd_group.cmd.split(',')) -# -# cmds_str = ', '.join(cmds) -# -# return render_to_response('jperm/sudo_cmd_detail.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_login -# def perm_apply(request): -# """ 权限申请 """ -# header_title, path1, path2 = u'主机权限申请', u'权限管理', u'申请主机' -# user_id, username = get_session_user_info(request)[0:2] -# name = User.objects.get(id=user_id).username -# dept_id, deptname, dept = get_session_user_info(request)[3:6] -# perm_host = user_perm_asset_api(username) -# all_host = Asset.objects.filter(dept=dept) -# -# perm_group = user_perm_group_api(username) -# all_group = dept.bisgroup_set.all() -# -# posts = [g for g in all_host if g not in perm_host] -# egroup = [d for d in all_group if d not in perm_group] -# -# dept_da = User.objects.filter(dept_id=dept_id, role='DA') -# admin = User.objects.get(name='admin') -# -# if request.method == 'POST': -# applyer = request.POST.get('applyer') -# dept = request.POST.get('dept') -# da = request.POST.get('da') -# group = request.POST.getlist('group') -# hosts = request.POST.getlist('hosts') -# comment = request.POST.get('comment') -# if not da: -# return httperror(request, u'请选择管理员!') -# da = User.objects.get(id=da) -# mail_address = da.email -# mail_title = '%s - 权限申请' % username -# group_lis = ', '.join(group) -# hosts_lis = ', '.join(hosts) -# time_now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') -# a = Apply.objects.create(applyer=applyer, admin=da, dept=dept, bisgroup=group, date_add=datetime.datetime.now(), -# asset=hosts, status=0, comment=comment, read=0) -# uuid = a.uuid -# url = "http://%s:%s/jperm/apply_exec/?uuid=%s" % (SEND_IP, SEND_PORT, uuid) -# mail_msg = """ -# Hi,%s: -# 有新的权限申请, 详情如下: -# 申请人: %s -# 申请主机组: %s -# 申请的主机: %s -# 申请时间: %s -# 申请说明: %s -# 请及时审批, 审批完成后, 点击以下链接或登录授权管理-权限审批页面点击确认键,告知申请人。 -# -# %s -# """ % (da.username, applyer, group_lis, hosts_lis, time_now, comment, url) -# -# send_mail(mail_title, mail_msg, MAIL_FROM, [mail_address], fail_silently=False) -# smg = "提交成功,已发邮件至 %s 通知部门管理员。" % mail_address -# return render_to_response('jperm/perm_apply.html', locals(), context_instance=RequestContext(request)) -# return render_to_response('jperm/perm_apply.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def perm_apply_exec(request): -# """ 确认权限 """ -# header_title, path1, path2 = u'主机权限申请', u'权限管理', u'审批完成' -# uuid = request.GET.get('uuid') -# user_id = request.session.get('user_id') -# approver = User.objects.get(id=user_id).name -# if uuid: -# p_apply = Apply.objects.filter(uuid=str(uuid)) -# q_apply = Apply.objects.get(uuid=str(uuid)) -# if q_apply.status == 1: -# smg = '此权限已经审批完成, 请勿重复审批, 十秒钟后返回首页' -# return render_to_response('jperm/perm_apply_exec.html', locals(), context_instance=RequestContext(request)) -# else: -# user = User.objects.get(username=q_apply.applyer) -# mail_address = user.email -# time_now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') -# p_apply.update(status=1, approver=approver, date_end=time_now) -# mail_title = '%s - 权限审批完成' % q_apply.applyer -# mail_msg = """ -# Hi,%s: -# 您所申请的权限已由 %s 在 %s 审批完成, 请登录验证。 -# """ % (q_apply.applyer, q_apply.approver, time_now) -# send_mail(mail_title, mail_msg, MAIL_FROM, [mail_address], fail_silently=False) -# smg = '授权完成, 已邮件通知申请人, 十秒钟后返回首页' -# return render_to_response('jperm/perm_apply_exec.html', locals(), context_instance=RequestContext(request)) -# else: -# smg = '没有此授权记录, 十秒钟后返回首页' -# return render_to_response('jperm/perm_apply_exec.html', locals(), context_instance=RequestContext(request)) -# -# -# def get_apply_posts(request, status, username, dept_name, keyword=None): -# """ 获取申请记录 """ -# post_all = Apply.objects.filter(status=status).order_by('-date_add') -# post_keyword_all = Apply.objects.filter(Q(applyer__contains=keyword) | -# Q(approver__contains=keyword)) \ -# .filter(status=status).order_by('-date_add') -# -# 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=dept_name) -# else: -# posts = post_all.filter(dept=dept_name) -# elif is_common_user(request): -# if keyword: -# posts = post_keyword_all.filter(applyer=username) -# else: -# posts = post_all.filter(applyer=username) -# -# return posts -# -# -# @require_login -# def perm_apply_log(request, offset): -# """ 申请记录 """ -# header_title, path1, path2 = u'权限申请记录', u'权限管理', u'申请记录' -# keyword = request.GET.get('keyword', '') -# user_id = get_session_user_info(request)[0] -# username = User.objects.get(id=user_id).name -# dept_name = get_session_user_info(request)[4] -# status_dic = {'online': 0, 'offline': 1} -# status = status_dic[offset] -# posts = get_apply_posts(request, status, username, dept_name, keyword) -# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) -# return render_to_response('jperm/perm_log_%s.html' % offset, locals(), context_instance=RequestContext(request)) -# -# -# @require_login -# def perm_apply_info(request): -# """ 申请信息详情 """ -# uuid = request.GET.get('uuid', '') -# post = Apply.objects.filter(uuid=uuid) -# username = get_session_user_info(request)[1] -# if post: -# post = post[0] -# if post.read == 0 and post.applyer != username: -# post.read = 1 -# post.save() -# else: -# return httperror(request, u'没有这个申请记录!') -# -# return render_to_response('jperm/perm_apply_info.html', locals(), context_instance=RequestContext(request)) -# -# -# @require_admin -# def perm_apply_del(request): -# """ 删除日志记录 """ -# uuid = request.GET.get('uuid') -# u_apply = Apply.objects.filter(uuid=uuid) -# if u_apply: -# u_apply.delete() -# return HttpResponseRedirect('/jperm/apply_show/online/') -# -# -# @require_login -# def perm_apply_search(request): -# """ 申请搜索 """ -# keyword = request.GET.get('keyword') -# offset = request.GET.get('env') -# username = get_session_user_info(request)[1] -# dept_name = get_session_user_info(request)[3] -# status_dic = {'online': 0, 'offline': 1} -# status = status_dic[offset] -# posts = get_apply_posts(request, status, username, dept_name, keyword) -# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) -# return render_to_response('jperm/perm_apply_search.html', locals(), context_instance=RequestContext(request)) -# -# -# -# -# -# -# -# -# -# -# -# -# -# +# # coding: utf-8 # import sysuser # # reload(sysuser) # sysuser.setdefaultencoding('utf8') # # from django.shortcuts import render_to_response # from django.template import RequestContext # from jperm.models import Perm, SudoPerm, CmdGroup, Apply from django.db.models import Q from jumpserver.api import * from jperm.perm_api import * from jperm.models import PermLog as Log from jperm.models import SysUser from juser.user_api import gen_ssh_key @require_role('admin') def perm_user_list(request): header_title, path1, path2 = '用户授权', '授权管理', '用户授权' keyword = request.GET.get('search', '') users_list = User.objects.all() # 获取所有用户 if keyword: users_list = users_list.filter(Q(name=keyword) | Q(username=keyword)) # 搜索 users_list, p, users, page_range, current_page, show_first, show_end = pages(users_list, request) # 分页 return my_render('jperm/perm_user_list.html', locals(), request) @require_role('admin') def perm_user_edit(request): header_title, path1, path2 = '用户授权', '授权管理', '授权更改' user_id = request.GET.get('id', '') user = get_object(User, id=user_id) asset_all = Asset.objects.all() # 获取所有资产 asset_group_all = AssetGroup.objects.all() # 获取所有资产组 asset_permed = user.asset.all() # 获取授权的资产对象列表 asset_group_permed = user.asset_group.all() # 获取授权的资产组对象列表 if request.method == 'GET' and user: assets = [asset for asset in asset_all if asset not in asset_permed] # 获取没有授权的资产对象列表 asset_groups = [asset_group for asset_group in asset_group_all if asset_group not in asset_group_permed] # 同理 return my_render('jperm/perm_user_edit.html', locals(), request) elif request.method == 'POST' and user: asset_id_select = request.POST.getlist('asset_select', []) # 获取选择的资产id列表 asset_group_id_select = request.POST.getlist('asset_groups_select', []) # 获取选择的资产组id列表 asset_select = get_object_list(Asset, asset_id_select) asset_group_select = get_object_list(AssetGroup, asset_group_id_select) asset_new = list(set(asset_select) - set(asset_permed)) # 计算的得到新授权的资产对象列表 asset_del = list(set(asset_permed) - set(asset_select)) # 计算得到回收权限的资产对象列表 asset_group_new = list(set(asset_group_select) - set(asset_group_permed)) # 新授权的资产组对象列表 asset_group_del = list(set(asset_group_permed) - set(asset_group_select)) # 回收的资产组对象列表 for asset_group in asset_group_new: asset_new.extend(asset_group.asset_set.all()) for asset_group in asset_group_del: asset_del.extend(asset_group.asset_set.all()) perm_info = { 'action': 'perm user edit: ' + user.name, 'del': {'users': [user], 'assets': asset_del}, 'new': {'users': [user], 'assets': asset_new} } print perm_info try: results = perm_user_api(perm_info) # 通过API授权或回收 except ServerError, e: return HttpResponse(e) unreachable_asset = [] failures_asset = [] for ip in results.get('unreachable'): unreachable_asset.extend(filter(lambda x: x, Asset.objects.filter(ip=ip))) for ip in results.get('failures'): failures_asset.extend(filter(lambda x: x, Asset.objects.filter(ip=ip))) failures_asset.extend(unreachable_asset) # 失败的授权要统计 for asset in failures_asset: if asset in asset_select: asset_select.remove(asset) else: asset_select.append(asset) user.asset = asset_select user.asset_group = asset_group_select user.save() # 保存到数据库 return HttpResponse(json.dumps(results, sort_keys=True, indent=4), content_type="application/json") else: return HttpResponse('输入错误') @require_role('admin') def perm_group_list(request): header_title, path1, path2 = '用户组授权', '授权管理', '用户组授权' keyword = request.GET.get('search', '') user_groups_list = UserGroup.objects.all() if keyword: request = user_groups_list.filter(Q(name=keyword) | Q(comment=keyword)) user_groups_list, p, user_groups, page_range, current_page, show_first, show_end = pages(user_groups_list, request) return my_render('jperm/perm_group_list.html', locals(), request) @require_role('admin') def perm_group_edit(request): header_title, path1, path2 = '用户组授权', '授权管理', '授权更改' user_group_id = request.GET.get('id', '') user_group = get_object(UserGroup, id=user_group_id) asset_all = Asset.objects.all() asset_group_all = AssetGroup.objects.all() asset_permed = user_group.asset.all() # 获取授权的资产对象列表 asset_group_permed = user_group.asset_group.all() # 获取授权的资产组对象列表 if request.method == 'GET' and user_group: assets = [asset for asset in asset_all if asset not in asset_permed] asset_groups = [asset_group for asset_group in asset_group_all if asset_group not in asset_group_permed] return my_render('jperm/perm_group_edit.html', locals(), request) elif request.method == 'POST' and user_group: asset_id_select = request.POST.getlist('asset_select', []) asset_group_id_select = request.POST.getlist('asset_groups_select', []) asset_select = get_object_list(Asset, asset_id_select) asset_group_select = get_object_list(AssetGroup, asset_group_id_select) asset_new = list(set(asset_select) - set(asset_permed)) # 计算的得到新授权的资产对象列表 asset_del = list(set(asset_permed) - set(asset_select)) # 计算得到回收权限的资产对象列表 asset_group_new = list(set(asset_group_select) - set(asset_group_permed)) # 新授权的资产组对象列表 asset_group_del = list(set(asset_group_permed) - set(asset_group_select)) # 回收的资产组对象列表 users = user_group.user_set.all() perm_info = { 'action': 'perm group edit: ' + user_group.name, 'del': {'users': users, 'assets': asset_del}, 'new': {'users': users, 'assets': asset_new} } results = perm_user_api(perm_info) unreachable_asset = [] failures_asset = [] for ip in results.get('unreachable'): unreachable_asset.extend(filter(lambda x: x, Asset.objects.filter(ip=ip))) for ip in results.get('failures'): failures_asset.extend(filter(lambda x: x, Asset.objects.filter(ip=ip))) failures_asset.extend(unreachable_asset) # 失败的授权要统计 for asset in failures_asset: if asset in asset_select: asset_select.remove(asset) else: asset_select.append(asset) user_group.asset = asset_select user_group.asset_group = asset_group_select user_group.save() # 保存到数据库 return HttpResponse(json.dumps(results, sort_keys=True, indent=4), content_type="application/json") else: return HttpResponse('输入错误') def log(request): header_title, path1, path2 = '授权记录', '授权管理', '授权记录' log_all = Log.objects.all().order_by('-datetime') log_all, p, logs, page_range, current_page, show_first, show_end = pages(log_all, request) return my_render('jperm/perm_log.html', locals(), request) def sys_user_add(request): asset_group_all = AssetGroup.objects.all() if request.method == 'POST': username = request.POST.get('username', '') password = request.POST.get('password', '') asset_groups_id = request.POST.getlist('asset_groups_select', []) comment = request.POST.get('comment') sys_user = SysUser(username=username, password=password, comment=comment) sys_user.save() gen_ssh_key(username, key_dir=os.path.join(SSH_KEY_DIR, 'sysuser'), authorized_keys=False) results = push_user(sys_user, asset_groups_id) return HttpResponse(json.dumps(results, sort_keys=True, indent=4), content_type="application/json") return my_render('jperm/sys_user_add.html', locals(), request) def sys_user_list(request): users_list = SysUser.objects.all() users_list, p, users, page_range, current_page, show_first, show_end = pages(users_list, request) return my_render('jperm/sys_user_list.html', locals(), request) def sys_user_edit(request): pass def sys_user_del(request): pass \ No newline at end of file diff --git a/jumpserver.conf b/jumpserver.conf index 94ee1653f..cb0eba98d 100644 --- a/jumpserver.conf +++ b/jumpserver.conf @@ -23,7 +23,7 @@ root_pw = secret234 [websocket] -web_socket_host = 192.168.40.140:3000 +web_socket_host = ws://192.168.244.129:3000 [mail] diff --git a/jumpserver/api.py b/jumpserver/api.py index 06b8f5460..8f0073c15 100644 --- a/jumpserver/api.py +++ b/jumpserver/api.py @@ -1,6 +1,6 @@ # coding: utf-8 -import os, sys, time +import os, sys, time, re from Crypto.Cipher import AES import crypt from binascii import b2a_hex, a2b_hex @@ -8,9 +8,7 @@ import hashlib import datetime import random import subprocess -import paramiko -import struct, fcntl, signal, socket, select, fnmatch -from settings import JLOG_FILE, KEY, URL, log_dir, log_level +from settings import * from django.core.paginator import Paginator, EmptyPage, InvalidPage from django.http import HttpResponse, Http404 @@ -18,7 +16,7 @@ from django.template import RequestContext from juser.models import User, UserGroup from jasset.models import Asset, AssetGroup # from jlog.models import Log -from jasset.models import AssetAlias +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 @@ -26,13 +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() def set_log(level): @@ -52,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 @@ -164,209 +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): """ @@ -460,6 +179,10 @@ def get_object(model, **kwargs): use this function for query 使用改封装函数查询数据库 """ + for value in kwargs.values(): + if not value: + return None + the_object = model.objects.filter(**kwargs) if len(the_object) == 1: the_object = the_object[0] @@ -476,10 +199,10 @@ def require_role(role='user'): def _deco(func): def __deco(request, *args, **kwargs): - if role == 'user': - if not request.user.is_authenticated(): - return HttpResponseRedirect('/login/') - elif role == 'admin': + 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('/') @@ -691,13 +414,5 @@ def my_render(template, data, request): CRYPTOR = PyCrypt(KEY) - -# 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 logger = set_log(log_level) + diff --git a/jumpserver/models.py b/jumpserver/models.py new file mode 100644 index 000000000..5958c5e1a --- /dev/null +++ b/jumpserver/models.py @@ -0,0 +1,13 @@ +# coding: utf-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(max_length=10, null=True, blank=True) + default_pri_key_path = models.CharField(max_length=100, null=True, blank=True) + + class Meta: + db_table = u'setting' diff --git a/jumpserver/settings.py b/jumpserver/settings.py index 6eb10c252..23908986d 100644 --- a/jumpserver/settings.py +++ b/jumpserver/settings.py @@ -44,9 +44,7 @@ URL = config.get('base', 'url') MAIL_ENABLE = config.get('mail', 'mail_enable') MAIL_FROM = config.get('mail', 'email_host_user') 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 @@ -62,7 +60,6 @@ TEMPLATE_DEBUG = True ALLOWED_HOSTS = ['0.0.0.0/8'] - # Application definition INSTALLED_APPS = ( @@ -98,23 +95,23 @@ WSGI_APPLICATION = 'jumpserver.wsgi.application' # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases -# DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.mysql', -# 'NAME': DB_DATABASE, -# 'USER': DB_USER, -# 'PASSWORD': DB_PASSWORD, -# 'HOST': DB_HOST, -# 'PORT': DB_PORT, -# } -# } - DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + 'ENGINE': 'django.db.backends.mysql', + 'NAME': DB_DATABASE, + 'USER': DB_USER, + 'PASSWORD': DB_PASSWORD, + 'HOST': DB_HOST, + 'PORT': DB_PORT, } } + +# 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', diff --git a/jumpserver/tasks.py b/jumpserver/tasks.py new file mode 100644 index 000000000..22fd514ef --- /dev/null +++ b/jumpserver/tasks.py @@ -0,0 +1,47 @@ +# coding: utf-8 + +from ansible.playbook import PlayBook +from ansible import callbacks, utils + + +def playbook_run(inventory, playbook, default_user=None, default_port=None, default_pri_key_path=None): + stats = callbacks.AggregateStats() + playbook_cb = callbacks.PlaybookCallbacks(verbose=utils.VERBOSITY) + runner_cb = callbacks.PlaybookRunnerCallbacks(stats, verbose=utils.VERBOSITY) + # run the playbook + print default_user, default_port, default_pri_key_path, inventory, playbook + if default_user and default_port and default_pri_key_path: + playbook = PlayBook(host_list=inventory, + playbook=playbook, + forks=5, + remote_user=default_user, + remote_port=default_port, + private_key_file=default_pri_key_path, + callbacks=playbook_cb, + runner_callbacks=runner_cb, + stats=stats, + become=True, + become_user='root') + else: + playbook = PlayBook(host_list=inventory, + playbook=playbook, + forks=5, + callbacks=playbook_cb, + runner_callbacks=runner_cb, + stats=stats, + become=True, + become_user='root') + results = playbook.run() + print results + results_r = {'unreachable': [], 'failures': [], 'success': []} + for hostname, result in results.items(): + if result.get('unreachable', 2): + results_r['unreachable'].append(hostname) + print "%s >>> unreachable" % hostname + elif result.get('failures', 2): + results_r['failures'].append(hostname) + print "%s >>> Failed" % hostname + else: + results_r['success'].append(hostname) + print "%s >>> Success" % hostname + return results_r \ No newline at end of file diff --git a/jumpserver/templatetags/mytags.py b/jumpserver/templatetags/mytags.py index da5ba9850..2bd1df20b 100644 --- a/jumpserver/templatetags/mytags.py +++ b/jumpserver/templatetags/mytags.py @@ -12,14 +12,6 @@ from jasset.models import AssetAlias register = template.Library() -# @register.filter(name='stamp2str') -# def stamp2str(value): -# try: -# return time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(value)) -# except AttributeError: -# return '0000/00/00 00:00:00' - - @register.filter(name='int2str') def int2str(value): """ @@ -42,20 +34,8 @@ def get_role(user_id): return u"普通用户" -# @register.filter(name='groups_str') -# def groups_str(user_id): -# groups = [] -# user = User.objects.get(id=user_id) -# for group in user.group.all(): -# groups.append(group.name) -# if len(groups) < 3: -# return ' '.join(groups) -# else: -# return "%s ..." % ' '.join(groups[0:2]) -# - -@register.filter(name='group_str2') -def groups_str2(group_list): +@register.filter(name='groups2str') +def groups2str(group_list): """ 将用户组列表转换为str """ @@ -64,47 +44,28 @@ def groups_str2(group_list): else: return '%s ...' % ' '.join([group.name for group in group_list[0:2]]) -# -# @register.filter(name='group_str2_all') -# def group_str2_all(group_list): -# group_lis = [] -# for i in group_list: -# if str(i) != 'ALL': -# group_lis.append(i) -# if len(group_lis) < 3: -# return ' '.join([group.name for group in group_lis]) -# else: -# return '%s ...' % ' '.join([group.name for group in group_lis[0:2]]) -# -# -# @register.filter(name='group_dept_all') -# def group_dept_all(group_list): -# group_lis = [] -# for i in group_list: -# if str(i) != 'ALL': -# group_lis.append(i) -# return ' '.join([group.name for group in group_lis]) -# -# -# @register.filter(name='group_manage_str') -# def group_manage_str(username): -# user = User.objects.get(username=username) -# group = user.user_group.filter(type='M') -# if group: -# return group[0].name -# else: -# return '' -# -# -# @register.filter(name='get_item') -# def get_item(dictionary, key): -# return dictionary.get(key) -# -# -# @register.filter(name='get_login_type') -# def get_login_type(login): -# login_types = {'L': 'LDAP', 'M': 'MAP'} -# return login_types[login] + +@register.filter(name='user_asset_count') +def user_asset_count(user): + """ + 返回用户权限主机的数量 + """ + assets = user.asset.all() + asset_groups = user.asset_group.all() + + for asset_group in asset_groups: + if asset_group: + assets.extend(asset_group.asset_set.all()) + + return len(assets) + + +@register.filter(name='user_asset_group_count') +def user_asset_group_count(user): + """ + 返回用户权限主机组的数量 + """ + return len(user.asset_group.all()) @register.filter(name='bool2str') @@ -115,16 +76,6 @@ def bool2str(value): return u'否' -# # @register.filter(name='user_readonly') -# # def user_readonly(user_id): -# # user = User.objects.filter(id=user_id) -# # if user: -# # user = user[0] -# # if user.role == 'CU': -# # return False -# # return True - - @register.filter(name='members_count') def members_count(group_id): """统计用户组下成员数量""" @@ -134,148 +85,6 @@ def members_count(group_id): else: return 0 -# -# @register.filter(name='group_user_count') -# def group_user_count(group_id): -# group = UserGroup.objects.get(id=group_id) -# return group.user_set.count() -# -# -# @register.filter(name='dept_user_num') -# def dept_user_num(dept_id): -# dept = DEPT.objects.filter(id=dept_id) -# if dept: -# dept = dept[0] -# return dept.user_set.count() -# else: -# return 0 -# -# -# @register.filter(name='dept_group_num') -# def dept_group_num(dept_id): -# dept = DEPT.objects.filter(id=dept_id) -# if dept: -# dept = dept[0] -# return dept.usergroup_set.all().count() -# else: -# return 0 -# -# -# @register.filter(name='perm_count') -# def perm_count(group_id): -# group = UserGroup.objects.get(id=group_id) -# return group.perm_set.count() -# -# -# @register.filter(name='dept_asset_num') -# def dept_asset_num(dept_id): -# dept = DEPT.objects.filter(id=dept_id) -# if dept: -# dept = dept[0] -# return dept.asset_set.all().count() -# return 0 -# -# -# @register.filter(name='ugrp_perm_agrp_count') -# def ugrp_perm_agrp_count(user_group_id): -# user_group = UserGroup.objects.filter(id=user_group_id) -# if user_group: -# user_group = user_group[0] -# return user_group.perm_set.all().count() -# return 0 -# -# -# @register.filter(name='ugrp_sudo_agrp_count') -# def ugrp_sudo_agrp_count(user_group_id): -# user_group = UserGroup.objects.filter(id=user_group_id) -# asset_groups = [] -# if user_group: -# user_group = user_group[0] -# for perm in user_group.sudoperm_set.all(): -# asset_groups.extend(perm.asset_group.all()) -# return len(set(asset_groups)) -# return 0 -# -# -# @register.filter(name='ugrp_perm_asset_count') -# def ugrp_perm_asset_count(user_group_id): -# user_group = UserGroup.objects.filter(id=user_group_id) -# assets = [] -# if user_group: -# user_group = user_group[0] -# asset_groups = [perm.asset_group for perm in user_group.perm_set.all()] -# for asset_group in asset_groups: -# assets.extend(asset_group.asset_set.all()) -# return len(set(assets)) -# -# -# @register.filter(name='ugrp_sudo_asset_count') -# def ugrp_sudo_asset_count(user_group_id): -# user_group = UserGroup.objects.filter(id=user_group_id) -# asset_groups = [] -# assets = [] -# if user_group: -# user_group = user_group[0] -# for perm in user_group.sudoperm_set.all(): -# asset_groups.extend(perm.asset_group.all()) -# -# for asset_group in asset_groups: -# assets.extend(asset_group.asset_set.all()) -# return len(set(assets)) -# -# -# @register.filter(name='get_user_alias') -# def get_user_alias(post, user_id): -# user = User.objects.get(id=user_id) -# host = Asset.objects.get(id=post.id) -# alias = AssetAlias.objects.filter(user=user, host=host) -# if alias: -# return alias[0].alias -# else: -# return '' -# -# -# @register.filter(name='group_type_to_str') -# def group_type_to_str(type_name): -# group_types = { -# 'P': '用户', -# 'M': '部门', -# 'A': '用户组', -# } -# return group_types.get(type_name) -# -# -# @register.filter(name='ast_to_list') -# def ast_to_list(lis): -# ast_lis = ast.literal_eval(lis) -# if len(ast_lis) <= 2: -# return ','.join([i for i in ast_lis]) -# else: -# restr = ','.join([i for i in ast_lis[0:2]]) + '...' -# return restr -# -# -# @register.filter(name='get_group_count') -# def get_group_count(post, dept): -# count = post.asset_set.filter(dept=dept).count() -# return count -# -# -# @register.filter(name='get_idc_count') -# def get_idc_count(post, dept): -# count = post.asset_set.filter(dept=dept).count() -# return count -# -# -# @register.filter(name='ast_to_list_1') -# def ast_to_list_1(lis): -# return ast.literal_eval(lis) -# -# -# @register.filter(name='string_length') -# def string_length(string, length): -# return '%s ...' % string[0:length] - @register.filter(name='to_name') def to_name(user_id): @@ -289,17 +98,6 @@ def to_name(user_id): return '非法用户' -# @register.filter(name='to_dept_name') -# def to_dept_name(user_id): -# try: -# user = User.objects.filter(id=int(user_id)) -# if user: -# user = user[0] -# return user.dept.name -# except: -# return '非法部门' - - @register.filter(name='to_role_name') def to_role_name(role_id): """role_id 转变为角色名称""" @@ -312,99 +110,16 @@ def to_avatar(role_id='0'): """不同角色不同头像""" role_dict = {'0': 'user', '1': 'admin', '2': 'root'} return role_dict.get(str(role_id), 'user') -# -# -# @register.filter(name='get_user_asset_group') -# def get_user_asset_group(user): -# return user.get_asset_group() -# -# -# @register.filter(name='group_asset_list') -# def group_asset_list(group): -# return group.asset_set.all() -# -# -# @register.filter(name='group_asset_list_count') -# def group_asset_list_count(group): -# return group.asset_set.all().count() -# -# -# @register.filter(name='time_delta') -# def time_delta(time_before): -# delta = datetime.datetime.now() - time_before -# days = delta.days -# if days: -# return "%s 天前" % days -# else: -# hours = delta.seconds/3600 -# if hours: -# return "%s 小时前" % hours -# else: -# mins = delta.seconds/60 -# if mins: -# return '%s 分钟前' % mins -# else: -# return '%s 秒前' % delta.seconds -# -# -# @register.filter(name='sudo_cmd_list') -# def sudo_cmd_list(cmd_group_id): -# cmd_group = CmdGroup.objects.filter(id=cmd_group_id) -# if cmd_group: -# cmd_group = cmd_group[0] -# return cmd_group.cmd.split(',') -# -# -# @register.filter(name='sudo_cmd_count') -# def sudo_cmd_count(user_group_id): -# user_group = UserGroup.objects.filter(id=user_group_id) -# cmds = [] -# if user_group: -# user_group = user_group[0] -# cmd_groups = [] -# -# for perm in user_group.sudoperm_set.all(): -# cmd_groups.extend(perm.cmd_group.all()) -# -# for cmd_group in cmd_groups: -# cmds.extend(cmd_group.cmd.split(',')) -# return len(set(cmds)) -# -# else: -# return 0 -# -# -# @register.filter(name='sudo_cmd_count') -# def sudo_cmd_count(user_group_id): -# user_group = UserGroup.objects.filter(id=user_group_id) -# cmds = [] -# if user_group: -# user_group = user_group[0] -# cmd_groups = [] -# for perm in user_group.sudoperm_set.all(): -# cmd_groups.extend(perm.cmd_group.all()) -# -# for cmd_group in cmd_groups: -# cmds.extend(cmd_group.cmd.split(',')) -# return len(set(cmds)) -# else: -# return 0 -# -# -# @register.filter(name='sudo_cmd_ids') -# def sudo_cmd_ids(user_group_id): -# user_group = UserGroup.objects.filter(id=user_group_id) -# if user_group: -# user_group = user_group[0] -# cmd_groups = [] -# for perm in user_group.sudoperm_set.all(): -# cmd_groups.extend(perm.cmd_group.all()) -# cmd_ids = [str(cmd_group.id) for cmd_group in cmd_groups] -# return ','.join(cmd_ids) -# else: -# return '0' -# -# -# @register.filter(name='cmd_group_split') -# def cmd_group_split(cmd_group): -# return cmd_group.cmd.split(',') + + +@register.filter(name='result2bool') +def result2bool(result=''): + """将结果定向为结果""" + result = eval(result) + unreachable = result.get('unreachable', []) + failures = result.get('failures', []) + + if unreachable or failures: + return '失败' + else: + return '成功' diff --git a/jumpserver/urls.py b/jumpserver/urls.py index c9a23b5ad..2c930635d 100644 --- a/jumpserver/urls.py +++ b/jumpserver/urls.py @@ -12,10 +12,11 @@ urlpatterns = patterns('', (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'download/(\d{4}/\d\d/\d\d/.*)', 'jumpserver.views.download_file') diff --git a/jumpserver/views.py b/jumpserver/views.py index 1266d576b..25aca82d6 100644 --- a/jumpserver/views.py +++ b/jumpserver/views.py @@ -12,11 +12,13 @@ from django.http import HttpResponse # from jperm.models import Apply 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): today = datetime.date.today() oneday = datetime.timedelta(days=1) @@ -196,6 +198,7 @@ def is_latest(): def Login(request): """登录界面""" + error = '' if request.user.is_authenticated(): return HttpResponseRedirect('/') if request.method == 'GET': @@ -240,6 +243,33 @@ def Logout(request): logout(request) return HttpResponseRedirect('/login/') + +def setting(request): + header_title, path1 = '项目设置', '设置' + setting_r = 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', '') + + 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) + else: + setting_r = Setting(name='default', default_user=username, default_port=port, + default_pri_key_path=private_key_path).save() + + msg = "设置成功" + return my_render('setting.html', locals(), request) # # def filter_ajax_api(request): # attr = request.GET.get('attr', 'user') diff --git a/juser/models.py b/juser/models.py index c0aceade9..c8d600c30 100644 --- a/juser/models.py +++ b/juser/models.py @@ -3,22 +3,18 @@ from django.db import models from django.contrib.auth.models import AbstractUser import time +from jasset.models import Asset, AssetGroup + class UserGroup(models.Model): name = models.CharField(max_length=80, unique=True) comment = models.CharField(max_length=160, blank=True, null=True) + asset = models.ManyToManyField(Asset) + asset_group = models.ManyToManyField(AssetGroup) 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(AbstractUser): USER_ROLE_CHOICES = ( @@ -35,83 +31,6 @@ class User(AbstractUser): 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): """ diff --git a/juser/user_api.py b/juser/user_api.py index 5ff4f07e3..5d77f5b28 100644 --- a/juser/user_api.py +++ b/juser/user_api.py @@ -1,10 +1,12 @@ # coding: utf-8 from Crypto.PublicKey import RSA +from subprocess import call from juser.models import AdminGroup from jumpserver.api import * -from jumpserver.settings import BASE_DIR +from jumpserver.settings import BASE_DIR + def group_add_user(group, user_id=None, username=None): """ @@ -118,30 +120,27 @@ def db_del_user(username): user.delete() -def gen_ssh_key(username, password=None, length=2048): +def gen_ssh_key(username, password='', + key_dir=os.path.join(BASE_DIR, 'keys/user/'), + authorized_keys=True, home="/home", length=2048): """ generate a user ssh key in a property dir 生成一个用户ssh密钥对 """ - print "gen_ssh_key" + str(time.time()) - private_key_dir = os.path.join(BASE_DIR, 'keys/jumpserver/') - private_key_file = os.path.join(private_key_dir, username+".pem") - public_key_dir = '/home/%s/.ssh/' % username - public_key_file = os.path.join(public_key_dir, 'authorized_keys') - is_dir(private_key_dir) - is_dir(public_key_dir, username, mode=0700) + private_key_file = os.path.join(key_dir, username) + if os.path.isfile(private_key_file): + os.unlink(private_key_file) + ret = bash('ssh-keygen -t rsa -f %s -b %s -P "%s"' % (private_key_file, length, password)) - key = RSA.generate(length) - with open(private_key_file, 'w') as pri_f: - pri_f.write(key.exportKey('PEM', password)) - os.chmod(private_key_file, 0600) - print "gen_ssh_pub_key" + str(time.time()) - pub_key = key.publickey() - with open(public_key_file, 'w') as pub_f: - pub_f.write(pub_key.exportKey('OpenSSH')) - os.chmod(public_key_file, 0600) - bash('chown %s:%s %s' % (username, username, public_key_file)) - print "gen_ssh_key_end" + str(time.time()) + if authorized_keys: + auth_key_dir = os.path.join(home, username, '.ssh') + is_dir(auth_key_dir, username, mode=0700) + authorized_key_file = os.path.join(auth_key_dir, 'authorized_keys') + with open(private_key_file+'.pub') as pub_f: + with open(authorized_key_file, 'w') as auth_f: + auth_f.write(pub_f.read()) + os.chmod(authorized_key_file, 0600) + bash('chown %s:%s %s' % (username, username, authorized_key_file)) def server_add_user(username, password, ssh_key_pwd, ssh_key_login_need): diff --git a/juser/views.py b/juser/views.py index 47dc48443..505b95baf 100644 --- a/juser/views.py +++ b/juser/views.py @@ -12,6 +12,7 @@ from django.template import RequestContext from django.db.models import ObjectDoesNotExist from jumpserver.settings import MAIL_FROM, MAIL_ENABLE from juser.user_api import * +from jperm.perm_api import _public_perm_api, perm_user_api, user_permed def chg_role(request): @@ -89,31 +90,6 @@ def group_del(request): return HttpResponse('删除成功') -# @require_role(role='admin') -# def group_list_adm(request): -# header_title, path1, path2 = '查看部门小组', '用户管理', '查看小组' -# keyword = request.GET.get('search', '') -# did = request.GET.get('did', '') -# user, dept = get_session_user_dept(request) -# contact_list = dept.usergroup_set.all().order_by('name') -# -# if keyword: -# contact_list = contact_list.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) -# -# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) -# return render_to_response('juser/group_list.html', locals(), context_instance=RequestContext(request)) - -# -# @require_role(role='admin') -# def group_detail(request): -# group_id = request.GET.get('id', None) -# if not group_id: -# return HttpResponseRedirect('/') -# group = UserGroup.objects.get(id=group_id) -# users = group.user_set.all() -# return render_to_response('juser/group_detail.html', locals(), context_instance=RequestContext(request)) - - @require_role(role='super') def group_edit(request): error = '' @@ -165,6 +141,7 @@ def group_edit(request): return my_render('juser/group_edit.html', locals(), request) +<<<<<<< HEAD # @require_role(role='admin') # def group_edit_adm(request): # error = '' @@ -258,11 +235,20 @@ def user_add(request): is_active=is_active, date_joined=datetime.datetime.now()) server_add_user(username, password, ssh_key_pwd, ssh_key_login_need) - except Exception, e: + user = get_object(User, username=username) + if groups: + user_groups = [] + for user_group_id in groups: + user_groups.extend(UserGroup.objects.filter(id=user_group_id)) + print user_groups + results = _public_perm_api({'type': 'new_user', 'user': user, 'group': user_groups}) + print results + except IndexError, e: error = u'添加用户 %s 失败 %s ' % (username, e) try: db_del_user(username) server_del_user(username) + _public_perm_api({'type': 'del_user', 'user': user, 'group': user_groups}) except Exception: pass else: @@ -272,78 +258,6 @@ def user_add(request): return my_render('juser/user_add.html', locals(), request) -# @require_role(role='admin') -# def user_add_adm(request): -# error = '' -# msg = '' -# header_title, path1, path2 = '添加用户', '用户管理', '添加用户' -# user, dept = get_session_user_dept(request) -# group_all = dept.usergroup_set.all() -# -# if request.method == 'POST': -# username = request.POST.get('username', '') -# password = PyCrypt.gen_rand_pwd(16) -# name = request.POST.get('name', '') -# email = request.POST.get('email', '') -# groups = request.POST.getlist('groups', []) -# ssh_key_pwd = PyCrypt.gen_rand_pwd(16) -# is_active = True if request.POST.get('is_active', '1') == '1' else False -# ldap_pwd = PyCrypt.gen_rand_pwd(16) -# -# try: -# if '' in [username, password, ssh_key_pwd, name, groups, is_active]: -# error = u'带*内容不能为空' -# raise ServerError -# user = User.objects.filter(username=username) -# if user: -# error = u'用户 %s 已存在' % username -# raise ServerError -# -# except ServerError: -# pass -# else: -# try: -# user = db_add_user(username=username, -# password=CRYPTOR.md5_crypt(password), -# name=name, email=email, dept=dept, -# groups=groups, role='CU', -# ssh_key_pwd=CRYPTOR.md5_crypt(ssh_key_pwd), -# ldap_pwd=CRYPTOR.encrypt(ldap_pwd), -# is_active=is_active, -# date_joined=datetime.datetime.now()) -# -# server_add_user(username, password, ssh_key_pwd) -# if LDAP_ENABLE: -# ldap_add_user(username, ldap_pwd) -# -# except Exception, e: -# error = u'添加用户 %s 失败 %s ' % (username, e) -# try: -# db_del_user(username) -# server_del_user(username) -# if LDAP_ENABLE: -# ldap_del_user(username) -# except Exception: -# pass -# else: -# mail_title = u'恭喜你的跳板机用户添加成功 Jumpserver' -# mail_msg = """ -# Hi, %s -# 您的用户名: %s -# 您的部门: %s -# 您的角色: %s -# 您的web登录密码: %s -# 您的ssh密钥文件密码: %s -# 密钥下载地址: http://%s:%s/juser/down_key/?id=%s -# 说明: 请登陆后再下载密钥! -# """ % (name, username, dept.name, '普通用户', -# password, ssh_key_pwd, SEND_IP, SEND_PORT, user.id) -# send_mail(mail_title, mail_msg, MAIL_FROM, [email], fail_silently=False) -# msg = u'添加用户 %s 成功! 用户密码已发送到 %s 邮箱!' % (username, email) -# -# return render_to_response('juser/user_add.html', locals(), context_instance=RequestContext(request)) - - @require_role(role='super') def user_list(request): user_role = {'SU': u'超级管理员', 'GA': u'组管理员', 'CU': u'普通用户'} @@ -366,31 +280,6 @@ def user_list(request): return my_render('juser/user_list.html', locals(), request) -# @require_role(role='admin') -# def user_list_adm(request): -# user_role = {'SU': u'超级管理员', 'GA': u'组管理员', 'CU': u'普通用户'} -# header_title, path1, path2 = '查看用户', '用户管理', '用户列表' -# keyword = request.GET.get('keyword', '') -# user, dept = get_session_user_dept(request) -# gid = request.GET.get('gid', '') -# contact_list = dept.user_set.all().order_by('name') -# -# if gid: -# if not validate(request, user_group=[gid]): -# return HttpResponseRedirect('/juser/user_list/') -# user_group = UserGroup.objects.filter(id=gid) -# if user_group: -# user_group = user_group[0] -# contact_list = user_group.user_set.all() -# -# if keyword: -# contact_list = contact_list.filter(Q(username__icontains=keyword) | Q(name__icontains=keyword)).order_by('name') -# -# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) -# -# return render_to_response('juser/user_list.html', locals(), context_instance=RequestContext(request)) - - @require_role(role='user') def user_detail(request): header_title, path1, path2 = '用户详情', '用户管理', '用户详情' @@ -427,8 +316,12 @@ def user_del(request): else: return HttpResponse('错误请求') for user_id in user_id_list: - User.objects.filter(id=user_id).delete() - + user = get_object(User, id=user_id) + if user: + assets = user_permed(user) + result = _public_perm_api({'type': 'del_user', 'user': user, 'asset': assets}) + print result + user.delete() return HttpResponse('删除成功') @@ -547,6 +440,7 @@ def user_edit(request): admin_groups=admin_groups, role=role_post, is_active=is_active) + _public_perm_api({'type': 'del_user', 'user': user, 'asset': user_permed(user)}) if email_need: msg = u""" @@ -568,59 +462,6 @@ def user_edit(request): # @require_role(role='admin') def user_edit_adm(request): pass -# header_title, path1, path2 = '编辑用户', '用户管理', '用户编辑' -# user, dept = get_session_user_dept(request) -# if request.method == 'GET': -# user_id = request.GET.get('id', '') -# if not user_id: -# return HttpResponseRedirect('/juser/user_list/') -# -# if not validate(request, user=[user_id]): -# return HttpResponseRedirect('/juser/user_list/') -# -# user = User.objects.filter(id=user_id) -# dept_all = DEPT.objects.all() -# group_all = dept.usergroup_set.all() -# if user: -# user = user[0] -# groups_str = ' '.join([str(group.id) for group in user.group.all()]) -# -# else: -# user_id = request.POST.get('user_id', '') -# password = request.POST.get('password', '') -# name = request.POST.get('name', '') -# email = request.POST.get('email', '') -# groups = request.POST.getlist('groups', []) -# ssh_key_pwd = request.POST.get('ssh_key_pwd', '') -# is_active = True if request.POST.get('is_active', '1') == '1' else False -# -# if not validate(request, user=[user_id], user_group=groups): -# return HttpResponseRedirect('/juser/user_edit/') -# if user_id: -# user = User.objects.filter(id=user_id) -# if user: -# user = user[0] -# else: -# return HttpResponseRedirect('/juser/user_list/') -# -# if password != user.password: -# password = CRYPTOR.md5_crypt(password) -# -# if ssh_key_pwd != user.ssh_key_pwd: -# ssh_key_pwd = CRYPTOR.encrypt(ssh_key_pwd) -# -# db_update_user(user_id=user_id, -# password=password, -# name=name, -# email=email, -# groups=groups, -# is_active=is_active, -# ssh_key_pwd=ssh_key_pwd) -# -# return HttpResponseRedirect('/juser/user_list/') -# -# return render_to_response('juser/user_edit.html', locals(), context_instance=RequestContext(request)) -# def profile(request): diff --git a/playbook/user_perm.yaml b/playbook/user_perm.yaml new file mode 100644 index 000000000..4bcfd72e6 --- /dev/null +++ b/playbook/user_perm.yaml @@ -0,0 +1,17 @@ +- hosts: the_del_group + tasks: + - name: del user + user: name={{ item }} state=absent remove=yes + with_items: [ the_del_users ] + +- hosts: the_new_group + tasks: + - name: add user + user: name={{ item }} state=present + with_items: [ the_new_users ] + - name: .ssh direcotory + file: name=/home/{{ item }}/.ssh mode=700 owner={{ item }} group={{ item }} state=directory + with_items: [ the_new_users ] + - name: set authorizied_file + copy: src=KEY_DIR/{{ item }}.pub dest=/home/{{ item }}/.ssh/authorizied_keys owner={{ item }} group={{ item }} mode=600 + with_items: [ the_new_users ] diff --git a/run_websocket.py b/run_websocket.py new file mode 100644 index 000000000..7283ed9b0 --- /dev/null +++ b/run_websocket.py @@ -0,0 +1,224 @@ +# coding: utf-8 + +import time +import json +import os +import sys +import os.path +import threading + +import tornado.ioloop +import tornado.options +import tornado.web +import tornado.websocket +import tornado.httpserver +import tornado.gen +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 paramiko + +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) + + +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), + ] + + 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 + + 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) + + print len(MonitorHandler.threads), len(MonitorHandler.clients) + + def on_message(self, message): + self.write_message('Connect WebSocket Success.
') + # 监控日志,发生变动发向客户端 + + 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]) + + 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 WebTerminalHandler(tornado.websocket.WebSocketHandler): + tasks = [] + + def __init__(self, *args, **kwargs): + self.chan = None + self.ssh = None + super(WebTerminalHandler, self).__init__(*args, **kwargs) + + def check_origin(self, origin): + return True + + def open(self): + self.ssh = paramiko.SSHClient() + self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + try: + self.ssh.connect('127.0.0.1', 22, 'root', 'redhat') + except: + self.write_message(json.loads({'data': 'Connect server Error'})) + self.close() + + self.chan = self.ssh.invoke_shell(term='xterm') + WebTerminalHandler.tasks.append(threading.Thread(target=self._forward_outbound)) + + 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 'resize' in data: + self.chan.resize_pty( + data['resize'].get('width', 80), + data['resize'].get('height', 24)) + if 'data' in data: + self.chan.send(data['data']) + + def on_close(self): + self.write_message(json.dumps({'data': 'close websocket'})) + + def _forward_outbound(self): + """ Forward outbound traffic (ssh -> websockets) """ + try: + data = '' + while True: + wait_read(self.chan.fileno()) + recv = self.chan.recv(1024) + if not len(recv): + return + data += recv + try: + self.write_message(json.dumps({'data': 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() diff --git a/static/css/plugins/bootstrap.min.css b/static/css/plugins/bootstrap.min.css new file mode 100644 index 000000000..c95146773 --- /dev/null +++ b/static/css/plugins/bootstrap.min.css @@ -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;} diff --git a/static/css/plugins/chosen/chosen-sprite.png b/static/css/plugins/chosen/chosen-sprite.png new file mode 100755 index 000000000..3611ae4ac Binary files /dev/null and b/static/css/plugins/chosen/chosen-sprite.png differ diff --git a/static/css/plugins/chosen/chosen-sprite@2x.png b/static/css/plugins/chosen/chosen-sprite@2x.png new file mode 100755 index 000000000..ffe4d7d11 Binary files /dev/null and b/static/css/plugins/chosen/chosen-sprite@2x.png differ diff --git a/static/css/plugins/chosen/chosen.css b/static/css/plugins/chosen/chosen.css new file mode 100755 index 000000000..e7a5495cd --- /dev/null +++ b/static/css/plugins/chosen/chosen.css @@ -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 */ diff --git a/static/css/plugins/datapicker/datepicker3.css b/static/css/plugins/datapicker/datepicker3.css new file mode 100755 index 000000000..d5203af6d --- /dev/null +++ b/static/css/plugins/datapicker/datepicker3.css @@ -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; +} diff --git a/static/js/base.js b/static/js/base.js index 0c5612a0d..be5b3cade 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -16,6 +16,13 @@ function check_all(form) { } } +function checkAll(){ + // 选择该页面所有checkbox + $('input[type=checkbox]').each(function(){ + $(this).attr('checked', true) + }) +} + //提取指定行的数据,JSON格式 function GetRowData(row){ var rowData = {}; @@ -89,22 +96,31 @@ function move(from, to, from_o, to_o) { //} // -function selectAll(){ - var checklist = document.getElementsByName ("selected"); - if(document.getElementById("select_all").checked) - { - for(var i=0;i")[0].getContext)},init:function(){var b=this.defaults;a.each(b,function(a,c){switch(a){case"aspectRatio":b[a]=M(P(c))||h;break;case"autoCropArea":b[a]=M(P(c))||.8;break;case"minWidth":case"minHeight":b[a]=M(P(c))||0;break;case"maxWidth":case"maxHeight":b[a]=M(P(c))||i}}),this.image={rotate:0},this.load()},load:function(){var b,c,d=this,f=this.$element,g=this.element,h=this.image,i="";f.is("img")?c=f.prop("src"):f.is("canvas")&&this.support.canvas&&(c=g.toDataURL()),c&&(this.replaced&&(h.rotate=0),this.defaults.checkImageOrigin&&(f.prop("crossOrigin")||this.isCrossOriginURL(c))&&(i=" crossOrigin"),this.$clone=b=a("'),b.one("load",function(){h.naturalWidth=this.naturalWidth||b.width(),h.naturalHeight=this.naturalHeight||b.height(),h.aspectRatio=h.naturalWidth/h.naturalHeight,d.url=c,d.ready=e,d.build()}),b.addClass(r).prependTo("body"))},isCrossOriginURL:function(a){var b=a.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);return!b||b[1]===d.protocol&&b[2]===d.hostname&&b[3]===d.port?f:e},build:function(){var b,d,f=this.$element,g=this.defaults;this.ready&&(this.built&&this.unbuild(),f.one(B,g.build),b=a.Event(B),f.trigger(b),b.isDefaultPrevented()||(this.$cropper=d=a(H.TEMPLATE),f.addClass(q),this.$clone.removeClass(r).prependTo(d),this.rotated||(this.$original=this.$clone.clone(),this.$original.addClass(q).prependTo(this.$cropper),this.originalImage=a.extend({},this.image)),this.$container=f.parent(),this.$container.append(d),this.$canvas=d.find(".cropper-canvas"),this.$dragger=d.find(".cropper-dragger"),this.$viewer=d.find(".cropper-viewer"),g.autoCrop?this.cropped=e:this.$dragger.addClass(q),g.dragCrop&&this.setDragMode("crop"),g.modal&&this.$canvas.addClass(p),!g.dashed&&this.$dragger.find(".cropper-dashed").addClass(q),!g.movable&&this.$dragger.find(".cropper-face").data(k,"move"),!g.resizable&&this.$dragger.find(".cropper-line, .cropper-point").addClass(q),this.$scope=g.multiple?this.$cropper:c,this.addListeners(),this.initPreview(),this.built=e,this.update(),f.one(C,g.built),f.trigger(C)))},unbuild:function(){this.built&&(this.built=f,this.removeListeners(),this.$preview.empty(),this.$preview=g,this.$dragger=g,this.$canvas=g,this.$container=g,this.$cropper.remove(),this.$cropper=g)},update:function(a){this.initContainer(),this.initCropper(),this.initImage(),this.initDragger(),a?(this.setData(a,e),this.setDragMode("crop")):this.setData(this.defaults.data)},resize:function(){clearTimeout(this.resizing),this.resizing=setTimeout(a.proxy(this.update,this,this.getData()),200)},preview:function(){var b=this.image,c=this.dragger,d=b.width,e=b.height,f=c.left-b.left,g=c.top-b.top;this.$viewer.find("img").css({width:I(d),height:I(e),marginLeft:-I(f),marginTop:-I(g)}),this.$preview.each(function(){var b=a(this),h=b.width()/c.width;b.find("img").css({width:I(d*h),height:I(e*h),marginLeft:-I(f*h),marginTop:-I(g*h)})})},addListeners:function(){var c=this.defaults;this.$element.on(D,c.dragstart).on(E,c.dragmove).on(F,c.dragend),this.$cropper.on(v,this._dragstart=a.proxy(this.dragstart,this)).on(A,this._dblclick=a.proxy(this.dblclick,this)),c.zoomable&&this.$cropper.on(y,this._wheel=a.proxy(this.wheel,this)),this.$scope.on(w,this._dragmove=a.proxy(this.dragmove,this)).on(x,this._dragend=a.proxy(this.dragend,this)),b.on(z,this._resize=a.proxy(this.resize,this))},removeListeners:function(){var a=this.defaults;this.$element.off(D,a.dragstart).off(E,a.dragmove).off(F,a.dragend),this.$cropper.off(v,this._dragstart).off(A,this._dblclick),a.zoomable&&this.$cropper.off(y,this._wheel),this.$scope.off(w,this._dragmove).off(x,this._dragend),b.off(z,this._resize)},initPreview:function(){var b='';this.$preview=a(this.defaults.preview),this.$viewer.html(b),this.$preview.html(b).find("img").css("cssText","min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important;")},initContainer:function(){var a=this.$container;this.container={width:L(a.width(),300),height:L(a.height(),150)}},initCropper:function(){var a,b=this.container,c=this.image;c.naturalWidth*b.height/c.naturalHeight-b.width>=0?(a={width:b.width,height:b.width/c.aspectRatio,left:0},a.top=(b.height-a.height)/2):(a={width:b.height*c.aspectRatio,height:b.height,top:0},a.left=(b.width-a.width)/2),this.$cropper.css({width:I(a.width),height:I(a.height),left:I(a.left),top:I(a.top)}),this.cropper=a},initImage:function(){var b=this.image,c=this.cropper,d={_width:c.width,_height:c.height,width:c.width,height:c.height,left:0,top:0,ratio:c.width/b.naturalWidth};this.defaultImage=a.extend({},b,d),b._width!==c.width||b._height!==c.height?a.extend(b,d):(b=a.extend({},d,b),this.replaced&&(this.replaced=f,b.ratio=d.ratio)),this.image=b,this.renderImage()},renderImage:function(a){var b=this.image;"zoom"===a&&(b.left-=(b.width-b.oldWidth)/2,b.top-=(b.height-b.oldHeight)/2),b.left=K(L(b.left,b._width-b.width),0),b.top=K(L(b.top,b._height-b.height),0),this.$clone.css({width:I(b.width),height:I(b.height),marginLeft:I(b.left),marginTop:I(b.top)}),a&&(this.defaults.done(this.getData()),this.preview())},initDragger:function(){var b,c=this.defaults,d=this.cropper,e=c.aspectRatio||this.image.aspectRatio,f=this.image.ratio;b=d.height*e-d.width>=0?{height:d.width/e,width:d.width,left:0,top:(d.height-d.width/e)/2,maxWidth:d.width,maxHeight:d.width/e}:{height:d.height,width:d.height*e,left:(d.width-d.height*e)/2,top:0,maxWidth:d.height*e,maxHeight:d.height},b.minWidth=0,b.minHeight=0,c.aspectRatio?(isFinite(c.maxWidth)?(b.maxWidth=K(b.maxWidth,c.maxWidth*f),b.maxHeight=b.maxWidth/e):isFinite(c.maxHeight)&&(b.maxHeight=K(b.maxHeight,c.maxHeight*f),b.maxWidth=b.maxHeight*e),c.minWidth>0?(b.minWidth=L(0,c.minWidth*f),b.minHeight=b.minWidth/e):c.minHeight>0&&(b.minHeight=L(0,c.minHeight*f),b.minWidth=b.minHeight*e)):(b.maxWidth=K(b.maxWidth,c.maxWidth*f),b.maxHeight=K(b.maxHeight,c.maxHeight*f),b.minWidth=L(0,c.minWidth*f),b.minHeight=L(0,c.minHeight*f)),b.minWidth=K(b.maxWidth,b.minWidth),b.minHeight=K(b.maxHeight,b.minHeight),b.height*=c.autoCropArea,b.width*=c.autoCropArea,b.left=(d.width-b.width)/2,b.top=(d.height-b.height)/2,b.oldLeft=b.left,b.oldTop=b.top,this.defaultDragger=b,this.dragger=a.extend({},b)},renderDragger:function(){var a=this.dragger,b=this.cropper;a.width>a.maxWidth?(a.width=a.maxWidth,a.left=a.oldLeft):a.widtha.maxHeight?(a.height=a.maxHeight,a.top=a.oldTop):a.height').one("load",function(){i.width=this.width,i.height=this.height,d.clearRect(0,0,i.width,i.height),d.drawImage(this,0,0),g.load()})))},setData:function(b,c){var d=this.cropper,e=this.dragger,f=this.image,h=this.defaults.aspectRatio;this.built&&typeof b!==j&&((b===g||a.isEmptyObject(b))&&(e=a.extend({},this.defaultDragger)),a.isPlainObject(b)&&!a.isEmptyObject(b)&&(c||(this.defaults.data=b),b=this.transformData(b),G(b.x)&&b.x<=d.width-f.left&&(e.left=b.x+f.left),G(b.y)&&b.y<=d.height-f.top&&(e.top=b.y+f.top),h?G(b.width)&&b.width<=e.maxWidth&&b.width>=e.minWidth?(e.width=b.width,e.height=e.width/h):G(b.height)&&b.height<=e.maxHeight&&b.height>=e.minHeight&&(e.height=b.height,e.width=e.height*h):(G(b.width)&&b.width<=e.maxWidth&&b.width>=e.minWidth&&(e.width=b.width),G(b.height)&&b.height<=e.maxHeight&&b.height>=e.minHeight&&(e.height=b.height))),this.dragger=e,this.renderDragger())},getData:function(a){var b=this.dragger,c=this.image,d={};return this.built&&(d={x:b.left-c.left,y:b.top-c.top,width:b.width,height:b.height},d=this.transformData(d,e,a)),d},transformData:function(b,c,d){var e=this.image.ratio,f={};return a.each(b,function(a,b){b=P(b),n.test(a)&&!isNaN(b)&&(f[a]=c?d?I(b/e):b/e:b*e)}),f},setAspectRatio:function(a){var b="auto"===a;a=P(a),(b||!isNaN(a)&&a>0)&&(this.defaults.aspectRatio=b?h:a,this.built&&(this.initDragger(),this.renderDragger()))},getImageData:function(){var b={};return this.ready&&a.each(this.image,function(a,c){o.test(a)&&(b[a]=c)}),b},getDataURL:function(b,c,d){var e,f=a("")[0],g=this.getData(),h="";return a.isPlainObject(b)||(d=c,c=b,b={}),b=a.extend({width:g.width,height:g.height},b),this.cropped&&this.support.canvas&&(f.width=b.width,f.height=b.height,e=f.getContext("2d"),"image/jpeg"===c&&(e.fillStyle="#fff",e.fillRect(0,0,b.width,b.height)),e.drawImage(this.$clone[0],g.x,g.y,g.width,g.height,0,0,b.width,b.height),h=f.toDataURL(c,d)),h},setDragMode:function(a){var b=this.$canvas,c=this.defaults,d=f,g=f;if(this.built&&!this.disabled){switch(a){case"crop":c.dragCrop&&(d=e,b.data(k,a));break;case"move":g=e,b.data(k,a);break;default:b.removeData(k)}b.toggleClass(t,d).toggleClass(s,g)}},enable:function(){this.built&&(this.disabled=f,this.$cropper.removeClass(u))},disable:function(){this.built&&(this.disabled=e,this.$cropper.addClass(u))},rotate:function(a){var b=this.image;a=P(a)||0,this.built&&0!==a&&!this.disabled&&this.defaults.rotatable&&this.support.canvas&&(this.rotated=e,a=b.rotate=(b.rotate+a)%360,this.replace(this.getRotatedDataURL(a),!0))},getRotatedDataURL:function(b){var c=a("")[0],d=c.getContext("2d"),e=b*Math.PI/180,f=M(b)%180,g=f>90?180-f:f,h=g*Math.PI/180,i=this.originalImage,j=i.naturalWidth,k=i.naturalHeight,l=M(j*O(h)+k*N(h)),m=M(j*N(h)+k*O(h));return c.width=l,c.height=m,d.save(),d.translate(l/2,m/2),d.rotate(e),d.drawImage(this.$original[0],-j/2,-k/2,j,k),d.restore(),c.toDataURL()},zoom:function(a){var b,c,d,e=this.image;a=P(a),this.built&&a&&!this.disabled&&this.defaults.zoomable&&(b=e.width*(1+a),c=e.height*(1+a),d=b/e._width,d>10||(1>d&&(b=e._width,c=e._height),this.setDragMode(1>=d?"crop":"move"),e.oldWidth=e.width,e.oldHeight=e.height,e.width=b,e.height=c,e.ratio=e.width/e.naturalWidth,this.renderImage("zoom")))},dblclick:function(){this.disabled||this.setDragMode(this.$canvas.hasClass(t)?"move":"crop")},wheel:function(a){var b,c=a.originalEvent,d=117.25,e=5,f=166.66665649414062,g=.1;this.disabled||(a.preventDefault(),c.deltaY?(b=c.deltaY,b=b%e===0?b/e:b%d===0?b/d:b/f):b=c.wheelDelta?-c.wheelDelta/120:c.detail?c.detail/3:0,this.zoom(b*g))},dragstart:function(b){var c,d,g,h=b.originalEvent.touches,i=b;if(!this.disabled){if(h){if(g=h.length,g>1){if(!this.defaults.zoomable||2!==g)return;i=h[1],this.startX2=i.pageX,this.startY2=i.pageY,c="zoom"}i=h[0]}if(c=c||a(i.target).data(k),m.test(c)){if(b.preventDefault(),d=a.Event(D),this.$element.trigger(d),d.isDefaultPrevented())return;this.directive=c,this.cropping=f,this.startX=i.pageX,this.startY=i.pageY,"crop"===c&&(this.cropping=e,this.$canvas.addClass(p))}}},dragmove:function(b){var c,d,e=b.originalEvent.touches,f=b;if(!this.disabled){if(e){if(d=e.length,d>1){if(!this.defaults.zoomable||2!==d)return;f=e[1],this.endX2=f.pageX,this.endY2=f.pageY}f=e[0]}if(this.directive){if(b.preventDefault(),c=a.Event(E),this.$element.trigger(c),c.isDefaultPrevented())return;this.endX=f.pageX,this.endY=f.pageY,this.dragging()}}},dragend:function(b){var c;if(!this.disabled&&this.directive){if(b.preventDefault(),c=a.Event(F),this.$element.trigger(c),c.isDefaultPrevented())return;this.cropping&&(this.cropping=f,this.$canvas.toggleClass(p,this.cropped&&this.defaults.modal)),this.directive=""}},dragging:function(){var a,b=this.directive,c=this.image,d=this.cropper,g=d.width,h=d.height,i=this.dragger,j=i.width,k=i.height,l=i.left,m=i.top,n=l+j,o=m+k,p=e,r=this.defaults,s=r.aspectRatio,t={x:this.endX-this.startX,y:this.endY-this.startY};switch(s&&(t.X=t.y*s,t.Y=t.x/s),b){case"all":l+=t.x,m+=t.y;break;case"e":if(t.x>=0&&(n>=g||s&&(0>=m||o>=h))){p=f;break}j+=t.x,s&&(k=j/s,m-=t.Y/2),0>j&&(b="w",j=0);break;case"n":if(t.y<=0&&(0>=m||s&&(0>=l||n>=g))){p=f;break}k-=t.y,m+=t.y,s&&(j=k*s,l+=t.X/2),0>k&&(b="s",k=0);break;case"w":if(t.x<=0&&(0>=l||s&&(0>=m||o>=h))){p=f;break}j-=t.x,l+=t.x,s&&(k=j/s,m+=t.Y/2),0>j&&(b="e",j=0);break;case"s":if(t.y>=0&&(o>=h||s&&(0>=l||n>=g))){p=f;break}k+=t.y,s&&(j=k*s,l-=t.X/2),0>k&&(b="n",k=0);break;case"ne":if(s){if(t.y<=0&&(0>=m||n>=g)){p=f;break}k-=t.y,m+=t.y,j=k*s}else t.x>=0?g>n?j+=t.x:t.y<=0&&0>=m&&(p=f):j+=t.x,t.y<=0?m>0&&(k-=t.y,m+=t.y):(k-=t.y,m+=t.y);0>k&&(b="sw",k=0,j=0);break;case"nw":if(s){if(t.y<=0&&(0>=m||0>=l)){p=f;break}k-=t.y,m+=t.y,j=k*s,l+=t.X}else t.x<=0?l>0?(j-=t.x,l+=t.x):t.y<=0&&0>=m&&(p=f):(j-=t.x,l+=t.x),t.y<=0?m>0&&(k-=t.y,m+=t.y):(k-=t.y,m+=t.y);0>k&&(b="se",k=0,j=0);break;case"sw":if(s){if(t.x<=0&&(0>=l||o>=h)){p=f;break}j-=t.x,l+=t.x,k=j/s}else t.x<=0?l>0?(j-=t.x,l+=t.x):t.y>=0&&o>=h&&(p=f):(j-=t.x,l+=t.x),t.y>=0?h>o&&(k+=t.y):k+=t.y;0>j&&(b="ne",k=0,j=0);break;case"se":if(s){if(t.x>=0&&(n>=g||o>=h)){p=f;break}j+=t.x,k=j/s}else t.x>=0?g>n?j+=t.x:t.y>=0&&o>=h&&(p=f):j+=t.x,t.y>=0?h>o&&(k+=t.y):k+=t.y;0>j&&(b="nw",k=0,j=0);break;case"move":c.left+=t.x,c.top+=t.y,this.renderImage("move"),p=f;break;case"zoom":r.zoomable&&(this.zoom(function(a,b,c,d,e,f){return(J(e*e+f*f)-J(c*c+d*d))/J(a*a+b*b)}(c.width,c.height,M(this.startX-this.startX2),M(this.startY-this.startY2),M(this.endX-this.endX2),M(this.endY-this.endY2))),this.endX2=this.startX2,this.endY2=this.startY2);break;case"crop":t.x&&t.y&&(a=this.$cropper.offset(),l=this.startX-a.left,m=this.startY-a.top,j=i.minWidth,k=i.minHeight,t.x>0?t.y>0?b="se":(b="ne",m-=k):t.y>0?(b="sw",l-=j):(b="nw",l-=j,m-=k),this.cropped||(this.cropped=e,this.$dragger.removeClass(q)))}p&&(i.width=j,i.height=k,i.left=l,i.top=m,this.directive=b,this.renderDragger()),this.startX=this.endX,this.startY=this.endY}},H.TEMPLATE=function(a,b){return b=b.split(","),a.replace(/\d+/g,function(a){return b[a]})}('<0 6="5-container"><0 6="5-canvas"><0 6="5-dragger"><1 6="5-viewer"><1 6="5-8 8-h"><1 6="5-8 8-v"><1 6="5-face" 3-2="all"><1 6="5-7 7-e" 3-2="e"><1 6="5-7 7-n" 3-2="n"><1 6="5-7 7-w" 3-2="w"><1 6="5-7 7-s" 3-2="s"><1 6="5-4 4-e" 3-2="e"><1 6="5-4 4-n" 3-2="n"><1 6="5-4 4-w" 3-2="w"><1 6="5-4 4-s" 3-2="s"><1 6="5-4 4-ne" 3-2="ne"><1 6="5-4 4-nw" 3-2="nw"><1 6="5-4 4-sw" 3-2="sw"><1 6="5-4 4-se" 3-2="se">',"div,span,directive,data,point,cropper,class,line,dashed"),H.DEFAULTS={aspectRatio:"auto",autoCropArea:.8,data:{},done:a.noop,preview:"",multiple:f,autoCrop:e,dragCrop:e,dashed:e,modal:e,movable:e,resizable:e,zoomable:e,rotatable:e,checkImageOrigin:e,minWidth:0,minHeight:0,maxWidth:i,maxHeight:i,build:g,built:g,dragstart:g,dragmove:g,dragend:g},H.setDefaults=function(b){a.extend(H.DEFAULTS,b)},H.other=a.fn.cropper,a.fn.cropper=function(b){var c,d=[].slice.call(arguments,1);return this.each(function(){var e,f=a(this),g=f.data("cropper");g||f.data("cropper",g=new H(this,b)),"string"==typeof b&&a.isFunction(e=g[b])&&(c=e.apply(g,d))}),typeof c!==j?c:this},a.fn.cropper.Constructor=H,a.fn.cropper.setDefaults=H.setDefaults,a.fn.cropper.noConflict=function(){return a.fn.cropper=H.other,this}}); \ No newline at end of file diff --git a/static/js/datapicker/bootstrap-datepicker.js b/static/js/datapicker/bootstrap-datepicker.js new file mode 100755 index 000000000..f17de6d1c --- /dev/null +++ b/static/js/datapicker/bootstrap-datepicker.js @@ -0,0 +1,1671 @@ +/* ========================================================= + * bootstrap-datepicker.js + * Repo: https://github.com/eternicode/bootstrap-datepicker/ + * Demo: http://eternicode.github.io/bootstrap-datepicker/ + * Docs: http://bootstrap-datepicker.readthedocs.org/ + * Forked from http://www.eyecon.ro/bootstrap-datepicker + * ========================================================= + * Started by Stefan Petre; improvements by Andrew Rowls + contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + +(function($, undefined){ + + var $window = $(window); + + function UTCDate(){ + return new Date(Date.UTC.apply(Date, arguments)); + } + function UTCToday(){ + var today = new Date(); + return UTCDate(today.getFullYear(), today.getMonth(), today.getDate()); + } + function alias(method){ + return function(){ + return this[method].apply(this, arguments); + }; + } + + var DateArray = (function(){ + var extras = { + get: function(i){ + return this.slice(i)[0]; + }, + contains: function(d){ + // Array.indexOf is not cross-browser; + // $.inArray doesn't work with Dates + var val = d && d.valueOf(); + for (var i=0, l=this.length; i < l; i++) + if (this[i].valueOf() === val) + return i; + return -1; + }, + remove: function(i){ + this.splice(i,1); + }, + replace: function(new_array){ + if (!new_array) + return; + if (!$.isArray(new_array)) + new_array = [new_array]; + this.clear(); + this.push.apply(this, new_array); + }, + clear: function(){ + this.splice(0); + }, + copy: function(){ + var a = new DateArray(); + a.replace(this); + return a; + } + }; + + return function(){ + var a = []; + a.push.apply(a, arguments); + $.extend(a, extras); + return a; + }; + })(); + + + // Picker object + + var Datepicker = function(element, options){ + this.dates = new DateArray(); + this.viewDate = UTCToday(); + this.focusDate = null; + + this._process_options(options); + + this.element = $(element); + this.isInline = false; + this.isInput = this.element.is('input'); + this.component = this.element.is('.date') ? this.element.find('.add-on, .input-group-addon, .btn') : false; + this.hasInput = this.component && this.element.find('input').length; + if (this.component && this.component.length === 0) + this.component = false; + + this.picker = $(DPGlobal.template); + this._buildEvents(); + this._attachEvents(); + + if (this.isInline){ + this.picker.addClass('datepicker-inline').appendTo(this.element); + } + else { + this.picker.addClass('datepicker-dropdown dropdown-menu'); + } + + if (this.o.rtl){ + this.picker.addClass('datepicker-rtl'); + } + + this.viewMode = this.o.startView; + + if (this.o.calendarWeeks) + this.picker.find('tfoot th.today') + .attr('colspan', function(i, val){ + return parseInt(val) + 1; + }); + + this._allow_update = false; + + this.setStartDate(this._o.startDate); + this.setEndDate(this._o.endDate); + this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled); + + this.fillDow(); + this.fillMonths(); + + this._allow_update = true; + + this.update(); + this.showMode(); + + if (this.isInline){ + this.show(); + } + }; + + Datepicker.prototype = { + constructor: Datepicker, + + _process_options: function(opts){ + // Store raw options for reference + this._o = $.extend({}, this._o, opts); + // Processed options + var o = this.o = $.extend({}, this._o); + + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + var lang = o.language; + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + lang = defaults.language; + } + o.language = lang; + + switch (o.startView){ + case 2: + case 'decade': + o.startView = 2; + break; + case 1: + case 'year': + o.startView = 1; + break; + default: + o.startView = 0; + } + + switch (o.minViewMode){ + case 1: + case 'months': + o.minViewMode = 1; + break; + case 2: + case 'years': + o.minViewMode = 2; + break; + default: + o.minViewMode = 0; + } + + o.startView = Math.max(o.startView, o.minViewMode); + + // true, false, or Number > 0 + if (o.multidate !== true){ + o.multidate = Number(o.multidate) || false; + if (o.multidate !== false) + o.multidate = Math.max(0, o.multidate); + else + o.multidate = 1; + } + o.multidateSeparator = String(o.multidateSeparator); + + o.weekStart %= 7; + o.weekEnd = ((o.weekStart + 6) % 7); + + var format = DPGlobal.parseFormat(o.format); + if (o.startDate !== -Infinity){ + if (!!o.startDate){ + if (o.startDate instanceof Date) + o.startDate = this._local_to_utc(this._zero_time(o.startDate)); + else + o.startDate = DPGlobal.parseDate(o.startDate, format, o.language); + } + else { + o.startDate = -Infinity; + } + } + if (o.endDate !== Infinity){ + if (!!o.endDate){ + if (o.endDate instanceof Date) + o.endDate = this._local_to_utc(this._zero_time(o.endDate)); + else + o.endDate = DPGlobal.parseDate(o.endDate, format, o.language); + } + else { + o.endDate = Infinity; + } + } + + o.daysOfWeekDisabled = o.daysOfWeekDisabled||[]; + if (!$.isArray(o.daysOfWeekDisabled)) + o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/); + o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function(d){ + return parseInt(d, 10); + }); + + var plc = String(o.orientation).toLowerCase().split(/\s+/g), + _plc = o.orientation.toLowerCase(); + plc = $.grep(plc, function(word){ + return (/^auto|left|right|top|bottom$/).test(word); + }); + o.orientation = {x: 'auto', y: 'auto'}; + if (!_plc || _plc === 'auto') + ; // no action + else if (plc.length === 1){ + switch (plc[0]){ + case 'top': + case 'bottom': + o.orientation.y = plc[0]; + break; + case 'left': + case 'right': + o.orientation.x = plc[0]; + break; + } + } + else { + _plc = $.grep(plc, function(word){ + return (/^left|right$/).test(word); + }); + o.orientation.x = _plc[0] || 'auto'; + + _plc = $.grep(plc, function(word){ + return (/^top|bottom$/).test(word); + }); + o.orientation.y = _plc[0] || 'auto'; + } + }, + _events: [], + _secondaryEvents: [], + _applyEvents: function(evs){ + for (var i=0, el, ch, ev; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.on(ev, ch); + } + }, + _unapplyEvents: function(evs){ + for (var i=0, el, ev, ch; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.off(ev, ch); + } + }, + _buildEvents: function(){ + if (this.isInput){ // single input + this._events = [ + [this.element, { + focus: $.proxy(this.show, this), + keyup: $.proxy(function(e){ + if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1) + this.update(); + }, this), + keydown: $.proxy(this.keydown, this) + }] + ]; + } + else if (this.component && this.hasInput){ // component: input + button + this._events = [ + // For components that are not readonly, allow keyboard nav + [this.element.find('input'), { + focus: $.proxy(this.show, this), + keyup: $.proxy(function(e){ + if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1) + this.update(); + }, this), + keydown: $.proxy(this.keydown, this) + }], + [this.component, { + click: $.proxy(this.show, this) + }] + ]; + } + else if (this.element.is('div')){ // inline datepicker + this.isInline = true; + } + else { + this._events = [ + [this.element, { + click: $.proxy(this.show, this) + }] + ]; + } + this._events.push( + // Component: listen for blur on element descendants + [this.element, '*', { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }], + // Input: listen for blur on element + [this.element, { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }] + ); + + this._secondaryEvents = [ + [this.picker, { + click: $.proxy(this.click, this) + }], + [$(window), { + resize: $.proxy(this.place, this) + }], + [$(document), { + 'mousedown touchstart': $.proxy(function(e){ + // Clicked outside the datepicker, hide it + if (!( + this.element.is(e.target) || + this.element.find(e.target).length || + this.picker.is(e.target) || + this.picker.find(e.target).length + )){ + this.hide(); + } + }, this) + }] + ]; + }, + _attachEvents: function(){ + this._detachEvents(); + this._applyEvents(this._events); + }, + _detachEvents: function(){ + this._unapplyEvents(this._events); + }, + _attachSecondaryEvents: function(){ + this._detachSecondaryEvents(); + this._applyEvents(this._secondaryEvents); + }, + _detachSecondaryEvents: function(){ + this._unapplyEvents(this._secondaryEvents); + }, + _trigger: function(event, altdate){ + var date = altdate || this.dates.get(-1), + local_date = this._utc_to_local(date); + + this.element.trigger({ + type: event, + date: local_date, + dates: $.map(this.dates, this._utc_to_local), + format: $.proxy(function(ix, format){ + if (arguments.length === 0){ + ix = this.dates.length - 1; + format = this.o.format; + } + else if (typeof ix === 'string'){ + format = ix; + ix = this.dates.length - 1; + } + format = format || this.o.format; + var date = this.dates.get(ix); + return DPGlobal.formatDate(date, format, this.o.language); + }, this) + }); + }, + + show: function(){ + if (!this.isInline) + this.picker.appendTo('body'); + this.picker.show(); + this.place(); + this._attachSecondaryEvents(); + this._trigger('show'); + }, + + hide: function(){ + if (this.isInline) + return; + if (!this.picker.is(':visible')) + return; + this.focusDate = null; + this.picker.hide().detach(); + this._detachSecondaryEvents(); + this.viewMode = this.o.startView; + this.showMode(); + + if ( + this.o.forceParse && + ( + this.isInput && this.element.val() || + this.hasInput && this.element.find('input').val() + ) + ) + this.setValue(); + this._trigger('hide'); + }, + + remove: function(){ + this.hide(); + this._detachEvents(); + this._detachSecondaryEvents(); + this.picker.remove(); + delete this.element.data().datepicker; + if (!this.isInput){ + delete this.element.data().date; + } + }, + + _utc_to_local: function(utc){ + return utc && new Date(utc.getTime() + (utc.getTimezoneOffset()*60000)); + }, + _local_to_utc: function(local){ + return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000)); + }, + _zero_time: function(local){ + return local && new Date(local.getFullYear(), local.getMonth(), local.getDate()); + }, + _zero_utc_time: function(utc){ + return utc && new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate())); + }, + + getDates: function(){ + return $.map(this.dates, this._utc_to_local); + }, + + getUTCDates: function(){ + return $.map(this.dates, function(d){ + return new Date(d); + }); + }, + + getDate: function(){ + return this._utc_to_local(this.getUTCDate()); + }, + + getUTCDate: function(){ + return new Date(this.dates.get(-1)); + }, + + setDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, args); + this._trigger('changeDate'); + this.setValue(); + }, + + setUTCDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, $.map(args, this._utc_to_local)); + this._trigger('changeDate'); + this.setValue(); + }, + + setDate: alias('setDates'), + setUTCDate: alias('setUTCDates'), + + setValue: function(){ + var formatted = this.getFormattedDate(); + if (!this.isInput){ + if (this.component){ + this.element.find('input').val(formatted).change(); + } + } + else { + this.element.val(formatted).change(); + } + }, + + getFormattedDate: function(format){ + if (format === undefined) + format = this.o.format; + + var lang = this.o.language; + return $.map(this.dates, function(d){ + return DPGlobal.formatDate(d, format, lang); + }).join(this.o.multidateSeparator); + }, + + setStartDate: function(startDate){ + this._process_options({startDate: startDate}); + this.update(); + this.updateNavArrows(); + }, + + setEndDate: function(endDate){ + this._process_options({endDate: endDate}); + this.update(); + this.updateNavArrows(); + }, + + setDaysOfWeekDisabled: function(daysOfWeekDisabled){ + this._process_options({daysOfWeekDisabled: daysOfWeekDisabled}); + this.update(); + this.updateNavArrows(); + }, + + place: function(){ + if (this.isInline) + return; + var calendarWidth = this.picker.outerWidth(), + calendarHeight = this.picker.outerHeight(), + visualPadding = 10, + windowWidth = $window.width(), + windowHeight = $window.height(), + scrollTop = $window.scrollTop(); + + var zIndex = parseInt(this.element.parents().filter(function(){ + return $(this).css('z-index') !== 'auto'; + }).first().css('z-index'))+10; + var offset = this.component ? this.component.parent().offset() : this.element.offset(); + var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false); + var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false); + var left = offset.left, + top = offset.top; + + this.picker.removeClass( + 'datepicker-orient-top datepicker-orient-bottom '+ + 'datepicker-orient-right datepicker-orient-left' + ); + + if (this.o.orientation.x !== 'auto'){ + this.picker.addClass('datepicker-orient-' + this.o.orientation.x); + if (this.o.orientation.x === 'right') + left -= calendarWidth - width; + } + // auto x orientation is best-placement: if it crosses a window + // edge, fudge it sideways + else { + // Default to left + this.picker.addClass('datepicker-orient-left'); + if (offset.left < 0) + left -= offset.left - visualPadding; + else if (offset.left + calendarWidth > windowWidth) + left = windowWidth - calendarWidth - visualPadding; + } + + // auto y orientation is best-situation: top or bottom, no fudging, + // decision based on which shows more of the calendar + var yorient = this.o.orientation.y, + top_overflow, bottom_overflow; + if (yorient === 'auto'){ + top_overflow = -scrollTop + offset.top - calendarHeight; + bottom_overflow = scrollTop + windowHeight - (offset.top + height + calendarHeight); + if (Math.max(top_overflow, bottom_overflow) === bottom_overflow) + yorient = 'top'; + else + yorient = 'bottom'; + } + this.picker.addClass('datepicker-orient-' + yorient); + if (yorient === 'top') + top += height; + else + top -= calendarHeight + parseInt(this.picker.css('padding-top')); + + this.picker.css({ + top: top, + left: left, + zIndex: zIndex + }); + }, + + _allow_update: true, + update: function(){ + if (!this._allow_update) + return; + + var oldDates = this.dates.copy(), + dates = [], + fromArgs = false; + if (arguments.length){ + $.each(arguments, $.proxy(function(i, date){ + if (date instanceof Date) + date = this._local_to_utc(date); + dates.push(date); + }, this)); + fromArgs = true; + } + else { + dates = this.isInput + ? this.element.val() + : this.element.data('date') || this.element.find('input').val(); + if (dates && this.o.multidate) + dates = dates.split(this.o.multidateSeparator); + else + dates = [dates]; + delete this.element.data().date; + } + + dates = $.map(dates, $.proxy(function(date){ + return DPGlobal.parseDate(date, this.o.format, this.o.language); + }, this)); + dates = $.grep(dates, $.proxy(function(date){ + return ( + date < this.o.startDate || + date > this.o.endDate || + !date + ); + }, this), true); + this.dates.replace(dates); + + if (this.dates.length) + this.viewDate = new Date(this.dates.get(-1)); + else if (this.viewDate < this.o.startDate) + this.viewDate = new Date(this.o.startDate); + else if (this.viewDate > this.o.endDate) + this.viewDate = new Date(this.o.endDate); + + if (fromArgs){ + // setting date by clicking + this.setValue(); + } + else if (dates.length){ + // setting date by typing + if (String(oldDates) !== String(this.dates)) + this._trigger('changeDate'); + } + if (!this.dates.length && oldDates.length) + this._trigger('clearDate'); + + this.fill(); + }, + + fillDow: function(){ + var dowCnt = this.o.weekStart, + html = ''; + if (this.o.calendarWeeks){ + var cell = ' '; + html += cell; + this.picker.find('.datepicker-days thead tr:first-child').prepend(cell); + } + while (dowCnt < this.o.weekStart + 7){ + html += ''+dates[this.o.language].daysMin[(dowCnt++)%7]+''; + } + html += ''; + this.picker.find('.datepicker-days thead').append(html); + }, + + fillMonths: function(){ + var html = '', + i = 0; + while (i < 12){ + html += ''+dates[this.o.language].monthsShort[i++]+''; + } + this.picker.find('.datepicker-months td').html(html); + }, + + setRange: function(range){ + if (!range || !range.length) + delete this.range; + else + this.range = $.map(range, function(d){ + return d.valueOf(); + }); + this.fill(); + }, + + getClassNames: function(date){ + var cls = [], + year = this.viewDate.getUTCFullYear(), + month = this.viewDate.getUTCMonth(), + today = new Date(); + if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){ + cls.push('old'); + } + else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){ + cls.push('new'); + } + if (this.focusDate && date.valueOf() === this.focusDate.valueOf()) + cls.push('focused'); + // Compare internal UTC date with local today, not UTC today + if (this.o.todayHighlight && + date.getUTCFullYear() === today.getFullYear() && + date.getUTCMonth() === today.getMonth() && + date.getUTCDate() === today.getDate()){ + cls.push('today'); + } + if (this.dates.contains(date) !== -1) + cls.push('active'); + if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate || + $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1){ + cls.push('disabled'); + } + if (this.range){ + if (date > this.range[0] && date < this.range[this.range.length-1]){ + cls.push('range'); + } + if ($.inArray(date.valueOf(), this.range) !== -1){ + cls.push('selected'); + } + } + return cls; + }, + + fill: function(){ + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(), + startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity, + startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity, + endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity, + endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity, + todaytxt = dates[this.o.language].today || dates['en'].today || '', + cleartxt = dates[this.o.language].clear || dates['en'].clear || '', + tooltip; + this.picker.find('.datepicker-days thead th.datepicker-switch') + .text(dates[this.o.language].months[month]+' '+year); + this.picker.find('tfoot th.today') + .text(todaytxt) + .toggle(this.o.todayBtn !== false); + this.picker.find('tfoot th.clear') + .text(cleartxt) + .toggle(this.o.clearBtn !== false); + this.updateNavArrows(); + this.fillMonths(); + var prevMonth = UTCDate(year, month-1, 28), + day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); + prevMonth.setUTCDate(day); + prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7); + var nextMonth = new Date(prevMonth); + nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); + nextMonth = nextMonth.valueOf(); + var html = []; + var clsName; + while (prevMonth.valueOf() < nextMonth){ + if (prevMonth.getUTCDay() === this.o.weekStart){ + html.push(''); + if (this.o.calendarWeeks){ + // ISO 8601: First week contains first thursday. + // ISO also states week starts on Monday, but we can be more abstract here. + var + // Start of current week: based on weekstart/current date + ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5), + // Thursday of this week + th = new Date(Number(ws) + (7 + 4 - ws.getUTCDay()) % 7 * 864e5), + // First Thursday of year, year from thursday + yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5), + // Calendar week: ms between thursdays, div ms per day, div 7 days + calWeek = (th - yth) / 864e5 / 7 + 1; + html.push(''+ calWeek +''); + + } + } + clsName = this.getClassNames(prevMonth); + clsName.push('day'); + + if (this.o.beforeShowDay !== $.noop){ + var before = this.o.beforeShowDay(this._utc_to_local(prevMonth)); + if (before === undefined) + before = {}; + else if (typeof(before) === 'boolean') + before = {enabled: before}; + else if (typeof(before) === 'string') + before = {classes: before}; + if (before.enabled === false) + clsName.push('disabled'); + if (before.classes) + clsName = clsName.concat(before.classes.split(/\s+/)); + if (before.tooltip) + tooltip = before.tooltip; + } + + clsName = $.unique(clsName); + html.push(''+prevMonth.getUTCDate() + ''); + if (prevMonth.getUTCDay() === this.o.weekEnd){ + html.push(''); + } + prevMonth.setUTCDate(prevMonth.getUTCDate()+1); + } + this.picker.find('.datepicker-days tbody').empty().append(html.join('')); + + var months = this.picker.find('.datepicker-months') + .find('th:eq(1)') + .text(year) + .end() + .find('span').removeClass('active'); + + $.each(this.dates, function(i, d){ + if (d.getUTCFullYear() === year) + months.eq(d.getUTCMonth()).addClass('active'); + }); + + if (year < startYear || year > endYear){ + months.addClass('disabled'); + } + if (year === startYear){ + months.slice(0, startMonth).addClass('disabled'); + } + if (year === endYear){ + months.slice(endMonth+1).addClass('disabled'); + } + + html = ''; + year = parseInt(year/10, 10) * 10; + var yearCont = this.picker.find('.datepicker-years') + .find('th:eq(1)') + .text(year + '-' + (year + 9)) + .end() + .find('td'); + year -= 1; + var years = $.map(this.dates, function(d){ + return d.getUTCFullYear(); + }), + classes; + for (var i = -1; i < 11; i++){ + classes = ['year']; + if (i === -1) + classes.push('old'); + else if (i === 10) + classes.push('new'); + if ($.inArray(year, years) !== -1) + classes.push('active'); + if (year < startYear || year > endYear) + classes.push('disabled'); + html += ''+year+''; + year += 1; + } + yearCont.html(html); + }, + + updateNavArrows: function(){ + if (!this._allow_update) + return; + + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(); + switch (this.viewMode){ + case 0: + if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()){ + this.picker.find('.prev').css({visibility: 'hidden'}); + } + else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()){ + this.picker.find('.next').css({visibility: 'hidden'}); + } + else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + case 1: + case 2: + if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()){ + this.picker.find('.prev').css({visibility: 'hidden'}); + } + else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()){ + this.picker.find('.next').css({visibility: 'hidden'}); + } + else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + } + }, + + click: function(e){ + e.preventDefault(); + var target = $(e.target).closest('span, td, th'), + year, month, day; + if (target.length === 1){ + switch (target[0].nodeName.toLowerCase()){ + case 'th': + switch (target[0].className){ + case 'datepicker-switch': + this.showMode(1); + break; + case 'prev': + case 'next': + var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1); + switch (this.viewMode){ + case 0: + this.viewDate = this.moveMonth(this.viewDate, dir); + this._trigger('changeMonth', this.viewDate); + break; + case 1: + case 2: + this.viewDate = this.moveYear(this.viewDate, dir); + if (this.viewMode === 1) + this._trigger('changeYear', this.viewDate); + break; + } + this.fill(); + break; + case 'today': + var date = new Date(); + date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); + + this.showMode(-2); + var which = this.o.todayBtn === 'linked' ? null : 'view'; + this._setDate(date, which); + break; + case 'clear': + var element; + if (this.isInput) + element = this.element; + else if (this.component) + element = this.element.find('input'); + if (element) + element.val("").change(); + this.update(); + this._trigger('changeDate'); + if (this.o.autoclose) + this.hide(); + break; + } + break; + case 'span': + if (!target.is('.disabled')){ + this.viewDate.setUTCDate(1); + if (target.is('.month')){ + day = 1; + month = target.parent().find('span').index(target); + year = this.viewDate.getUTCFullYear(); + this.viewDate.setUTCMonth(month); + this._trigger('changeMonth', this.viewDate); + if (this.o.minViewMode === 1){ + this._setDate(UTCDate(year, month, day)); + } + } + else { + day = 1; + month = 0; + year = parseInt(target.text(), 10)||0; + this.viewDate.setUTCFullYear(year); + this._trigger('changeYear', this.viewDate); + if (this.o.minViewMode === 2){ + this._setDate(UTCDate(year, month, day)); + } + } + this.showMode(-1); + this.fill(); + } + break; + case 'td': + if (target.is('.day') && !target.is('.disabled')){ + day = parseInt(target.text(), 10)||1; + year = this.viewDate.getUTCFullYear(); + month = this.viewDate.getUTCMonth(); + if (target.is('.old')){ + if (month === 0){ + month = 11; + year -= 1; + } + else { + month -= 1; + } + } + else if (target.is('.new')){ + if (month === 11){ + month = 0; + year += 1; + } + else { + month += 1; + } + } + this._setDate(UTCDate(year, month, day)); + } + break; + } + } + if (this.picker.is(':visible') && this._focused_from){ + $(this._focused_from).focus(); + } + delete this._focused_from; + }, + + _toggle_multidate: function(date){ + var ix = this.dates.contains(date); + if (!date){ + this.dates.clear(); + } + else if (ix !== -1){ + this.dates.remove(ix); + } + else { + this.dates.push(date); + } + if (typeof this.o.multidate === 'number') + while (this.dates.length > this.o.multidate) + this.dates.remove(0); + }, + + _setDate: function(date, which){ + if (!which || which === 'date') + this._toggle_multidate(date && new Date(date)); + if (!which || which === 'view') + this.viewDate = date && new Date(date); + + this.fill(); + this.setValue(); + this._trigger('changeDate'); + var element; + if (this.isInput){ + element = this.element; + } + else if (this.component){ + element = this.element.find('input'); + } + if (element){ + element.change(); + } + if (this.o.autoclose && (!which || which === 'date')){ + this.hide(); + } + }, + + moveMonth: function(date, dir){ + if (!date) + return undefined; + if (!dir) + return date; + var new_date = new Date(date.valueOf()), + day = new_date.getUTCDate(), + month = new_date.getUTCMonth(), + mag = Math.abs(dir), + new_month, test; + dir = dir > 0 ? 1 : -1; + if (mag === 1){ + test = dir === -1 + // If going back one month, make sure month is not current month + // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) + ? function(){ + return new_date.getUTCMonth() === month; + } + // If going forward one month, make sure month is as expected + // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) + : function(){ + return new_date.getUTCMonth() !== new_month; + }; + new_month = month + dir; + new_date.setUTCMonth(new_month); + // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 + if (new_month < 0 || new_month > 11) + new_month = (new_month + 12) % 12; + } + else { + // For magnitudes >1, move one month at a time... + for (var i=0; i < mag; i++) + // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... + new_date = this.moveMonth(new_date, dir); + // ...then reset the day, keeping it in the new month + new_month = new_date.getUTCMonth(); + new_date.setUTCDate(day); + test = function(){ + return new_month !== new_date.getUTCMonth(); + }; + } + // Common date-resetting loop -- if date is beyond end of month, make it + // end of month + while (test()){ + new_date.setUTCDate(--day); + new_date.setUTCMonth(new_month); + } + return new_date; + }, + + moveYear: function(date, dir){ + return this.moveMonth(date, dir*12); + }, + + dateWithinRange: function(date){ + return date >= this.o.startDate && date <= this.o.endDate; + }, + + keydown: function(e){ + if (this.picker.is(':not(:visible)')){ + if (e.keyCode === 27) // allow escape to hide and re-show picker + this.show(); + return; + } + var dateChanged = false, + dir, newDate, newViewDate, + focusDate = this.focusDate || this.viewDate; + switch (e.keyCode){ + case 27: // escape + if (this.focusDate){ + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); + } + else + this.hide(); + e.preventDefault(); + break; + case 37: // left + case 39: // right + if (!this.o.keyboardNavigation) + break; + dir = e.keyCode === 37 ? -1 : 1; + if (e.ctrlKey){ + newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveYear(focusDate, dir); + this._trigger('changeYear', this.viewDate); + } + else if (e.shiftKey){ + newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveMonth(focusDate, dir); + this._trigger('changeMonth', this.viewDate); + } + else { + newDate = new Date(this.dates.get(-1) || UTCToday()); + newDate.setUTCDate(newDate.getUTCDate() + dir); + newViewDate = new Date(focusDate); + newViewDate.setUTCDate(focusDate.getUTCDate() + dir); + } + if (this.dateWithinRange(newDate)){ + this.focusDate = this.viewDate = newViewDate; + this.setValue(); + this.fill(); + e.preventDefault(); + } + break; + case 38: // up + case 40: // down + if (!this.o.keyboardNavigation) + break; + dir = e.keyCode === 38 ? -1 : 1; + if (e.ctrlKey){ + newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveYear(focusDate, dir); + this._trigger('changeYear', this.viewDate); + } + else if (e.shiftKey){ + newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveMonth(focusDate, dir); + this._trigger('changeMonth', this.viewDate); + } + else { + newDate = new Date(this.dates.get(-1) || UTCToday()); + newDate.setUTCDate(newDate.getUTCDate() + dir * 7); + newViewDate = new Date(focusDate); + newViewDate.setUTCDate(focusDate.getUTCDate() + dir * 7); + } + if (this.dateWithinRange(newDate)){ + this.focusDate = this.viewDate = newViewDate; + this.setValue(); + this.fill(); + e.preventDefault(); + } + break; + case 32: // spacebar + // Spacebar is used in manually typing dates in some formats. + // As such, its behavior should not be hijacked. + break; + case 13: // enter + focusDate = this.focusDate || this.dates.get(-1) || this.viewDate; + this._toggle_multidate(focusDate); + dateChanged = true; + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.setValue(); + this.fill(); + if (this.picker.is(':visible')){ + e.preventDefault(); + if (this.o.autoclose) + this.hide(); + } + break; + case 9: // tab + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); + this.hide(); + break; + } + if (dateChanged){ + if (this.dates.length) + this._trigger('changeDate'); + else + this._trigger('clearDate'); + var element; + if (this.isInput){ + element = this.element; + } + else if (this.component){ + element = this.element.find('input'); + } + if (element){ + element.change(); + } + } + }, + + showMode: function(dir){ + if (dir){ + this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir)); + } + this.picker + .find('>div') + .hide() + .filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName) + .css('display', 'block'); + this.updateNavArrows(); + } + }; + + var DateRangePicker = function(element, options){ + this.element = $(element); + this.inputs = $.map(options.inputs, function(i){ + return i.jquery ? i[0] : i; + }); + delete options.inputs; + + $(this.inputs) + .datepicker(options) + .bind('changeDate', $.proxy(this.dateUpdated, this)); + + this.pickers = $.map(this.inputs, function(i){ + return $(i).data('datepicker'); + }); + this.updateDates(); + }; + DateRangePicker.prototype = { + updateDates: function(){ + this.dates = $.map(this.pickers, function(i){ + return i.getUTCDate(); + }); + this.updateRanges(); + }, + updateRanges: function(){ + var range = $.map(this.dates, function(d){ + return d.valueOf(); + }); + $.each(this.pickers, function(i, p){ + p.setRange(range); + }); + }, + dateUpdated: function(e){ + // `this.updating` is a workaround for preventing infinite recursion + // between `changeDate` triggering and `setUTCDate` calling. Until + // there is a better mechanism. + if (this.updating) + return; + this.updating = true; + + var dp = $(e.target).data('datepicker'), + new_date = dp.getUTCDate(), + i = $.inArray(e.target, this.inputs), + l = this.inputs.length; + if (i === -1) + return; + + $.each(this.pickers, function(i, p){ + if (!p.getUTCDate()) + p.setUTCDate(new_date); + }); + + if (new_date < this.dates[i]){ + // Date being moved earlier/left + while (i >= 0 && new_date < this.dates[i]){ + this.pickers[i--].setUTCDate(new_date); + } + } + else if (new_date > this.dates[i]){ + // Date being moved later/right + while (i < l && new_date > this.dates[i]){ + this.pickers[i++].setUTCDate(new_date); + } + } + this.updateDates(); + + delete this.updating; + }, + remove: function(){ + $.map(this.pickers, function(p){ p.remove(); }); + delete this.element.data().datepicker; + } + }; + + function opts_from_el(el, prefix){ + // Derive options from element data-attrs + var data = $(el).data(), + out = {}, inkey, + replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'); + prefix = new RegExp('^' + prefix.toLowerCase()); + function re_lower(_,a){ + return a.toLowerCase(); + } + for (var key in data) + if (prefix.test(key)){ + inkey = key.replace(replace, re_lower); + out[inkey] = data[key]; + } + return out; + } + + function opts_from_locale(lang){ + // Derive options from locale plugins + var out = {}; + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + return; + } + var d = dates[lang]; + $.each(locale_opts, function(i,k){ + if (k in d) + out[k] = d[k]; + }); + return out; + } + + var old = $.fn.datepicker; + $.fn.datepicker = function(option){ + var args = Array.apply(null, arguments); + args.shift(); + var internal_return; + this.each(function(){ + var $this = $(this), + data = $this.data('datepicker'), + options = typeof option === 'object' && option; + if (!data){ + var elopts = opts_from_el(this, 'date'), + // Preliminary otions + xopts = $.extend({}, defaults, elopts, options), + locopts = opts_from_locale(xopts.language), + // Options priority: js args, data-attrs, locales, defaults + opts = $.extend({}, defaults, locopts, elopts, options); + if ($this.is('.input-daterange') || opts.inputs){ + var ropts = { + inputs: opts.inputs || $this.find('input').toArray() + }; + $this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts)))); + } + else { + $this.data('datepicker', (data = new Datepicker(this, opts))); + } + } + if (typeof option === 'string' && typeof data[option] === 'function'){ + internal_return = data[option].apply(data, args); + if (internal_return !== undefined) + return false; + } + }); + if (internal_return !== undefined) + return internal_return; + else + return this; + }; + + var defaults = $.fn.datepicker.defaults = { + autoclose: false, + beforeShowDay: $.noop, + calendarWeeks: false, + clearBtn: false, + daysOfWeekDisabled: [], + endDate: Infinity, + forceParse: true, + format: 'mm/dd/yyyy', + keyboardNavigation: true, + language: 'en', + minViewMode: 0, + multidate: false, + multidateSeparator: ',', + orientation: "auto", + rtl: false, + startDate: -Infinity, + startView: 0, + todayBtn: false, + todayHighlight: false, + weekStart: 0 + }; + var locale_opts = $.fn.datepicker.locale_opts = [ + 'format', + 'rtl', + 'weekStart' + ]; + $.fn.datepicker.Constructor = Datepicker; + var dates = $.fn.datepicker.dates = { + en: { + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], + daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], + daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + today: "Today", + clear: "Clear" + } + }; + + var DPGlobal = { + modes: [ + { + clsName: 'days', + navFnc: 'Month', + navStep: 1 + }, + { + clsName: 'months', + navFnc: 'FullYear', + navStep: 1 + }, + { + clsName: 'years', + navFnc: 'FullYear', + navStep: 10 + }], + isLeapYear: function(year){ + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); + }, + getDaysInMonth: function(year, month){ + return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; + }, + validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, + nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, + parseFormat: function(format){ + // IE treats \0 as a string end in inputs (truncating the value), + // so it's a bad format delimiter, anyway + var separators = format.replace(this.validParts, '\0').split('\0'), + parts = format.match(this.validParts); + if (!separators || !separators.length || !parts || parts.length === 0){ + throw new Error("Invalid date format."); + } + return {separators: separators, parts: parts}; + }, + parseDate: function(date, format, language){ + if (!date) + return undefined; + if (date instanceof Date) + return date; + if (typeof format === 'string') + format = DPGlobal.parseFormat(format); + var part_re = /([\-+]\d+)([dmwy])/, + parts = date.match(/([\-+]\d+)([dmwy])/g), + part, dir, i; + if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)){ + date = new Date(); + for (i=0; i < parts.length; i++){ + part = part_re.exec(parts[i]); + dir = parseInt(part[1]); + switch (part[2]){ + case 'd': + date.setUTCDate(date.getUTCDate() + dir); + break; + case 'm': + date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir); + break; + case 'w': + date.setUTCDate(date.getUTCDate() + dir * 7); + break; + case 'y': + date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir); + break; + } + } + return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0); + } + parts = date && date.match(this.nonpunctuation) || []; + date = new Date(); + var parsed = {}, + setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'], + setters_map = { + yyyy: function(d,v){ + return d.setUTCFullYear(v); + }, + yy: function(d,v){ + return d.setUTCFullYear(2000+v); + }, + m: function(d,v){ + if (isNaN(d)) + return d; + v -= 1; + while (v < 0) v += 12; + v %= 12; + d.setUTCMonth(v); + while (d.getUTCMonth() !== v) + d.setUTCDate(d.getUTCDate()-1); + return d; + }, + d: function(d,v){ + return d.setUTCDate(v); + } + }, + val, filtered; + setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; + setters_map['dd'] = setters_map['d']; + date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); + var fparts = format.parts.slice(); + // Remove noop parts + if (parts.length !== fparts.length){ + fparts = $(fparts).filter(function(i,p){ + return $.inArray(p, setters_order) !== -1; + }).toArray(); + } + // Process remainder + function match_part(){ + var m = this.slice(0, parts[i].length), + p = parts[i].slice(0, m.length); + return m === p; + } + if (parts.length === fparts.length){ + var cnt; + for (i=0, cnt = fparts.length; i < cnt; i++){ + val = parseInt(parts[i], 10); + part = fparts[i]; + if (isNaN(val)){ + switch (part){ + case 'MM': + filtered = $(dates[language].months).filter(match_part); + val = $.inArray(filtered[0], dates[language].months) + 1; + break; + case 'M': + filtered = $(dates[language].monthsShort).filter(match_part); + val = $.inArray(filtered[0], dates[language].monthsShort) + 1; + break; + } + } + parsed[part] = val; + } + var _date, s; + for (i=0; i < setters_order.length; i++){ + s = setters_order[i]; + if (s in parsed && !isNaN(parsed[s])){ + _date = new Date(date); + setters_map[s](_date, parsed[s]); + if (!isNaN(_date)) + date = _date; + } + } + } + return date; + }, + formatDate: function(date, format, language){ + if (!date) + return ''; + if (typeof format === 'string') + format = DPGlobal.parseFormat(format); + var val = { + d: date.getUTCDate(), + D: dates[language].daysShort[date.getUTCDay()], + DD: dates[language].days[date.getUTCDay()], + m: date.getUTCMonth() + 1, + M: dates[language].monthsShort[date.getUTCMonth()], + MM: dates[language].months[date.getUTCMonth()], + yy: date.getUTCFullYear().toString().substring(2), + yyyy: date.getUTCFullYear() + }; + val.dd = (val.d < 10 ? '0' : '') + val.d; + val.mm = (val.m < 10 ? '0' : '') + val.m; + date = []; + var seps = $.extend([], format.separators); + for (var i=0, cnt = format.parts.length; i <= cnt; i++){ + if (seps.length) + date.push(seps.shift()); + date.push(val[format.parts[i]]); + } + return date.join(''); + }, + headTemplate: ''+ + ''+ + '«'+ + ''+ + '»'+ + ''+ + '', + contTemplate: '', + footTemplate: ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '' + }; + DPGlobal.template = '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + ''+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'; + + $.fn.datepicker.DPGlobal = DPGlobal; + + + /* DATEPICKER NO CONFLICT + * =================== */ + + $.fn.datepicker.noConflict = function(){ + $.fn.datepicker = old; + return this; + }; + + + /* DATEPICKER DATA-API + * ================== */ + + $(document).on( + 'focus.datepicker.data-api click.datepicker.data-api', + '[data-provide="datepicker"]', + function(e){ + var $this = $(this); + if ($this.data('datepicker')) + return; + e.preventDefault(); + // component click requires us to explicitly show it + $this.datepicker('show'); + } + ); + $(function(){ + $('[data-provide="datepicker-inline"]').datepicker(); + }); + +}(window.jQuery)); diff --git a/static/js/plugins/chosen/chosen.jquery.js b/static/js/plugins/chosen/chosen.jquery.js new file mode 100755 index 000000000..fc77de83f --- /dev/null +++ b/static/js/plugins/chosen/chosen.jquery.js @@ -0,0 +1,1211 @@ +/*! + 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. + */ + +(function() { + var $, AbstractChosen, Chosen, SelectParser, _ref, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + SelectParser = (function() { + function SelectParser() { + this.options_index = 0; + this.parsed = []; + } + + SelectParser.prototype.add_node = function(child) { + if (child.nodeName.toUpperCase() === "OPTGROUP") { + return this.add_group(child); + } else { + return this.add_option(child); + } + }; + + SelectParser.prototype.add_group = function(group) { + var group_position, option, _i, _len, _ref, _results; + group_position = this.parsed.length; + this.parsed.push({ + array_index: group_position, + group: true, + label: this.escapeExpression(group.label), + children: 0, + disabled: group.disabled + }); + _ref = group.childNodes; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + option = _ref[_i]; + _results.push(this.add_option(option, group_position, group.disabled)); + } + return _results; + }; + + SelectParser.prototype.add_option = function(option, group_position, group_disabled) { + if (option.nodeName.toUpperCase() === "OPTION") { + if (option.text !== "") { + if (group_position != null) { + this.parsed[group_position].children += 1; + } + this.parsed.push({ + array_index: this.parsed.length, + options_index: this.options_index, + value: option.value, + text: option.text, + html: option.innerHTML, + selected: option.selected, + disabled: group_disabled === true ? group_disabled : option.disabled, + group_array_index: group_position, + classes: option.className, + style: option.style.cssText + }); + } else { + this.parsed.push({ + array_index: this.parsed.length, + options_index: this.options_index, + empty: true + }); + } + return this.options_index += 1; + } + }; + + SelectParser.prototype.escapeExpression = function(text) { + var map, unsafe_chars; + if ((text == null) || text === false) { + return ""; + } + if (!/[\&\<\>\"\'\`]/.test(text)) { + return text; + } + map = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }; + unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g; + return text.replace(unsafe_chars, function(chr) { + return map[chr] || "&"; + }); + }; + + return SelectParser; + + })(); + + SelectParser.select_to_array = function(select) { + var child, parser, _i, _len, _ref; + parser = new SelectParser(); + _ref = select.childNodes; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + child = _ref[_i]; + parser.add_node(child); + } + return parser.parsed; + }; + + AbstractChosen = (function() { + function AbstractChosen(form_field, options) { + this.form_field = form_field; + this.options = options != null ? options : {}; + if (!AbstractChosen.browser_is_supported()) { + return; + } + this.is_multiple = this.form_field.multiple; + this.set_default_text(); + this.set_default_values(); + this.setup(); + this.set_up_html(); + this.register_observers(); + } + + AbstractChosen.prototype.set_default_values = function() { + var _this = this; + this.click_test_action = function(evt) { + return _this.test_active_click(evt); + }; + this.activate_action = function(evt) { + return _this.activate_field(evt); + }; + this.active_field = false; + this.mouse_on_container = false; + this.results_showing = false; + this.result_highlighted = null; + this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false; + this.disable_search_threshold = this.options.disable_search_threshold || 0; + this.disable_search = this.options.disable_search || false; + this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true; + this.group_search = this.options.group_search != null ? this.options.group_search : true; + this.search_contains = this.options.search_contains || false; + this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true; + this.max_selected_options = this.options.max_selected_options || Infinity; + this.inherit_select_classes = this.options.inherit_select_classes || false; + this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true; + return this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true; + }; + + AbstractChosen.prototype.set_default_text = function() { + if (this.form_field.getAttribute("data-placeholder")) { + this.default_text = this.form_field.getAttribute("data-placeholder"); + } else if (this.is_multiple) { + this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text; + } else { + this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text; + } + return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text; + }; + + AbstractChosen.prototype.mouse_enter = function() { + return this.mouse_on_container = true; + }; + + AbstractChosen.prototype.mouse_leave = function() { + return this.mouse_on_container = false; + }; + + AbstractChosen.prototype.input_focus = function(evt) { + var _this = this; + if (this.is_multiple) { + if (!this.active_field) { + return setTimeout((function() { + return _this.container_mousedown(); + }), 50); + } + } else { + if (!this.active_field) { + return this.activate_field(); + } + } + }; + + AbstractChosen.prototype.input_blur = function(evt) { + var _this = this; + if (!this.mouse_on_container) { + this.active_field = false; + return setTimeout((function() { + return _this.blur_test(); + }), 100); + } + }; + + AbstractChosen.prototype.results_option_build = function(options) { + var content, data, _i, _len, _ref; + content = ''; + _ref = this.results_data; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + data = _ref[_i]; + if (data.group) { + content += this.result_add_group(data); + } else { + content += this.result_add_option(data); + } + if (options != null ? options.first : void 0) { + if (data.selected && this.is_multiple) { + this.choice_build(data); + } else if (data.selected && !this.is_multiple) { + this.single_set_selected_text(data.text); + } + } + } + return content; + }; + + AbstractChosen.prototype.result_add_option = function(option) { + var classes, option_el; + if (!option.search_match) { + return ''; + } + if (!this.include_option_in_results(option)) { + return ''; + } + classes = []; + if (!option.disabled && !(option.selected && this.is_multiple)) { + classes.push("active-result"); + } + if (option.disabled && !(option.selected && this.is_multiple)) { + classes.push("disabled-result"); + } + if (option.selected) { + classes.push("result-selected"); + } + if (option.group_array_index != null) { + classes.push("group-option"); + } + if (option.classes !== "") { + classes.push(option.classes); + } + option_el = document.createElement("li"); + option_el.className = classes.join(" "); + option_el.style.cssText = option.style; + option_el.setAttribute("data-option-array-index", option.array_index); + option_el.innerHTML = option.search_text; + return this.outerHTML(option_el); + }; + + AbstractChosen.prototype.result_add_group = function(group) { + var group_el; + if (!(group.search_match || group.group_match)) { + return ''; + } + if (!(group.active_options > 0)) { + return ''; + } + group_el = document.createElement("li"); + group_el.className = "group-result"; + group_el.innerHTML = group.search_text; + return this.outerHTML(group_el); + }; + + AbstractChosen.prototype.results_update_field = function() { + this.set_default_text(); + if (!this.is_multiple) { + this.results_reset_cleanup(); + } + this.result_clear_highlight(); + this.results_build(); + if (this.results_showing) { + return this.winnow_results(); + } + }; + + AbstractChosen.prototype.reset_single_select_options = function() { + var result, _i, _len, _ref, _results; + _ref = this.results_data; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + result = _ref[_i]; + if (result.selected) { + _results.push(result.selected = false); + } else { + _results.push(void 0); + } + } + return _results; + }; + + AbstractChosen.prototype.results_toggle = function() { + if (this.results_showing) { + return this.results_hide(); + } else { + return this.results_show(); + } + }; + + AbstractChosen.prototype.results_search = function(evt) { + if (this.results_showing) { + return this.winnow_results(); + } else { + return this.results_show(); + } + }; + + AbstractChosen.prototype.winnow_results = function() { + var escapedSearchText, option, regex, regexAnchor, results, results_group, searchText, startpos, text, zregex, _i, _len, _ref; + this.no_results_clear(); + results = 0; + searchText = this.get_search_text(); + escapedSearchText = searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + regexAnchor = this.search_contains ? "" : "^"; + regex = new RegExp(regexAnchor + escapedSearchText, 'i'); + zregex = new RegExp(escapedSearchText, 'i'); + _ref = this.results_data; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + option = _ref[_i]; + option.search_match = false; + results_group = null; + if (this.include_option_in_results(option)) { + if (option.group) { + option.group_match = false; + option.active_options = 0; + } + if ((option.group_array_index != null) && this.results_data[option.group_array_index]) { + results_group = this.results_data[option.group_array_index]; + if (results_group.active_options === 0 && results_group.search_match) { + results += 1; + } + results_group.active_options += 1; + } + if (!(option.group && !this.group_search)) { + option.search_text = option.group ? option.label : option.html; + option.search_match = this.search_string_match(option.search_text, regex); + if (option.search_match && !option.group) { + results += 1; + } + if (option.search_match) { + if (searchText.length) { + startpos = option.search_text.search(zregex); + text = option.search_text.substr(0, startpos + searchText.length) + '' + option.search_text.substr(startpos + searchText.length); + option.search_text = text.substr(0, startpos) + '' + text.substr(startpos); + } + if (results_group != null) { + results_group.group_match = true; + } + } else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) { + option.search_match = true; + } + } + } + } + this.result_clear_highlight(); + if (results < 1 && searchText.length) { + this.update_results_content(""); + return this.no_results(searchText); + } else { + this.update_results_content(this.results_option_build()); + return this.winnow_results_set_highlight(); + } + }; + + AbstractChosen.prototype.search_string_match = function(search_string, regex) { + var part, parts, _i, _len; + if (regex.test(search_string)) { + return true; + } else if (this.enable_split_word_search && (search_string.indexOf(" ") >= 0 || search_string.indexOf("[") === 0)) { + parts = search_string.replace(/\[|\]/g, "").split(" "); + if (parts.length) { + for (_i = 0, _len = parts.length; _i < _len; _i++) { + part = parts[_i]; + if (regex.test(part)) { + return true; + } + } + } + } + }; + + AbstractChosen.prototype.choices_count = function() { + var option, _i, _len, _ref; + if (this.selected_option_count != null) { + return this.selected_option_count; + } + this.selected_option_count = 0; + _ref = this.form_field.options; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + option = _ref[_i]; + if (option.selected) { + this.selected_option_count += 1; + } + } + return this.selected_option_count; + }; + + AbstractChosen.prototype.choices_click = function(evt) { + evt.preventDefault(); + if (!(this.results_showing || this.is_disabled)) { + return this.results_show(); + } + }; + + AbstractChosen.prototype.keyup_checker = function(evt) { + var stroke, _ref; + stroke = (_ref = evt.which) != null ? _ref : evt.keyCode; + this.search_field_scale(); + switch (stroke) { + case 8: + if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) { + return this.keydown_backstroke(); + } else if (!this.pending_backstroke) { + this.result_clear_highlight(); + return this.results_search(); + } + break; + case 13: + evt.preventDefault(); + if (this.results_showing) { + return this.result_select(evt); + } + break; + case 27: + if (this.results_showing) { + this.results_hide(); + } + return true; + case 9: + case 38: + case 40: + case 16: + case 91: + case 17: + break; + default: + return this.results_search(); + } + }; + + AbstractChosen.prototype.clipboard_event_checker = function(evt) { + var _this = this; + return setTimeout((function() { + return _this.results_search(); + }), 50); + }; + + AbstractChosen.prototype.container_width = function() { + if (this.options.width != null) { + return this.options.width; + } else { + return "" + this.form_field.offsetWidth + "px"; + } + }; + + AbstractChosen.prototype.include_option_in_results = function(option) { + if (this.is_multiple && (!this.display_selected_options && option.selected)) { + return false; + } + if (!this.display_disabled_options && option.disabled) { + return false; + } + if (option.empty) { + return false; + } + return true; + }; + + AbstractChosen.prototype.search_results_touchstart = function(evt) { + this.touch_started = true; + return this.search_results_mouseover(evt); + }; + + AbstractChosen.prototype.search_results_touchmove = function(evt) { + this.touch_started = false; + return this.search_results_mouseout(evt); + }; + + AbstractChosen.prototype.search_results_touchend = function(evt) { + if (this.touch_started) { + return this.search_results_mouseup(evt); + } + }; + + AbstractChosen.prototype.outerHTML = function(element) { + var tmp; + if (element.outerHTML) { + return element.outerHTML; + } + tmp = document.createElement("div"); + tmp.appendChild(element); + return tmp.innerHTML; + }; + + AbstractChosen.browser_is_supported = function() { + if (window.navigator.appName === "Microsoft Internet Explorer") { + return document.documentMode >= 8; + } + if (/iP(od|hone)/i.test(window.navigator.userAgent)) { + return false; + } + if (/Android/i.test(window.navigator.userAgent)) { + if (/Mobile/i.test(window.navigator.userAgent)) { + return false; + } + } + return true; + }; + + AbstractChosen.default_multiple_text = "Select Some Options"; + + AbstractChosen.default_single_text = "Select an Option"; + + AbstractChosen.default_no_result_text = "No results match"; + + return AbstractChosen; + + })(); + + $ = jQuery; + + $.fn.extend({ + chosen: function(options) { + if (!AbstractChosen.browser_is_supported()) { + return this; + } + return this.each(function(input_field) { + var $this, chosen; + $this = $(this); + chosen = $this.data('chosen'); + if (options === 'destroy' && chosen) { + chosen.destroy(); + } else if (!chosen) { + $this.data('chosen', new Chosen(this, options)); + } + }); + } + }); + + Chosen = (function(_super) { + __extends(Chosen, _super); + + function Chosen() { + _ref = Chosen.__super__.constructor.apply(this, arguments); + return _ref; + } + + Chosen.prototype.setup = function() { + this.form_field_jq = $(this.form_field); + this.current_selectedIndex = this.form_field.selectedIndex; + return this.is_rtl = this.form_field_jq.hasClass("chosen-rtl"); + }; + + Chosen.prototype.set_up_html = function() { + var container_classes, container_props; + container_classes = ["chosen-container"]; + container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single")); + if (this.inherit_select_classes && this.form_field.className) { + container_classes.push(this.form_field.className); + } + if (this.is_rtl) { + container_classes.push("chosen-rtl"); + } + container_props = { + 'class': container_classes.join(' '), + 'style': "width: " + (this.container_width()) + ";", + 'title': this.form_field.title + }; + if (this.form_field.id.length) { + container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen"; + } + this.container = $("
", container_props); + if (this.is_multiple) { + this.container.html('
    '); + } else { + this.container.html('' + this.default_text + '
      '); + } + this.form_field_jq.hide().after(this.container); + this.dropdown = this.container.find('div.chosen-drop').first(); + this.search_field = this.container.find('input').first(); + this.search_results = this.container.find('ul.chosen-results').first(); + this.search_field_scale(); + this.search_no_results = this.container.find('li.no-results').first(); + if (this.is_multiple) { + this.search_choices = this.container.find('ul.chosen-choices').first(); + this.search_container = this.container.find('li.search-field').first(); + } else { + this.search_container = this.container.find('div.chosen-search').first(); + this.selected_item = this.container.find('.chosen-single').first(); + } + this.results_build(); + this.set_tab_index(); + this.set_label_behavior(); + return this.form_field_jq.trigger("chosen:ready", { + chosen: this + }); + }; + + Chosen.prototype.register_observers = function() { + var _this = this; + this.container.bind('mousedown.chosen', function(evt) { + _this.container_mousedown(evt); + }); + this.container.bind('mouseup.chosen', function(evt) { + _this.container_mouseup(evt); + }); + this.container.bind('mouseenter.chosen', function(evt) { + _this.mouse_enter(evt); + }); + this.container.bind('mouseleave.chosen', function(evt) { + _this.mouse_leave(evt); + }); + this.search_results.bind('mouseup.chosen', function(evt) { + _this.search_results_mouseup(evt); + }); + this.search_results.bind('mouseover.chosen', function(evt) { + _this.search_results_mouseover(evt); + }); + this.search_results.bind('mouseout.chosen', function(evt) { + _this.search_results_mouseout(evt); + }); + this.search_results.bind('mousewheel.chosen DOMMouseScroll.chosen', function(evt) { + _this.search_results_mousewheel(evt); + }); + this.search_results.bind('touchstart.chosen', function(evt) { + _this.search_results_touchstart(evt); + }); + this.search_results.bind('touchmove.chosen', function(evt) { + _this.search_results_touchmove(evt); + }); + this.search_results.bind('touchend.chosen', function(evt) { + _this.search_results_touchend(evt); + }); + this.form_field_jq.bind("chosen:updated.chosen", function(evt) { + _this.results_update_field(evt); + }); + this.form_field_jq.bind("chosen:activate.chosen", function(evt) { + _this.activate_field(evt); + }); + this.form_field_jq.bind("chosen:open.chosen", function(evt) { + _this.container_mousedown(evt); + }); + this.form_field_jq.bind("chosen:close.chosen", function(evt) { + _this.input_blur(evt); + }); + this.search_field.bind('blur.chosen', function(evt) { + _this.input_blur(evt); + }); + this.search_field.bind('keyup.chosen', function(evt) { + _this.keyup_checker(evt); + }); + this.search_field.bind('keydown.chosen', function(evt) { + _this.keydown_checker(evt); + }); + this.search_field.bind('focus.chosen', function(evt) { + _this.input_focus(evt); + }); + this.search_field.bind('cut.chosen', function(evt) { + _this.clipboard_event_checker(evt); + }); + this.search_field.bind('paste.chosen', function(evt) { + _this.clipboard_event_checker(evt); + }); + if (this.is_multiple) { + return this.search_choices.bind('click.chosen', function(evt) { + _this.choices_click(evt); + }); + } else { + return this.container.bind('click.chosen', function(evt) { + evt.preventDefault(); + }); + } + }; + + Chosen.prototype.destroy = function() { + $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action); + if (this.search_field[0].tabIndex) { + this.form_field_jq[0].tabIndex = this.search_field[0].tabIndex; + } + this.container.remove(); + this.form_field_jq.removeData('chosen'); + return this.form_field_jq.show(); + }; + + Chosen.prototype.search_field_disabled = function() { + this.is_disabled = this.form_field_jq[0].disabled; + if (this.is_disabled) { + this.container.addClass('chosen-disabled'); + this.search_field[0].disabled = true; + if (!this.is_multiple) { + this.selected_item.unbind("focus.chosen", this.activate_action); + } + return this.close_field(); + } else { + this.container.removeClass('chosen-disabled'); + this.search_field[0].disabled = false; + if (!this.is_multiple) { + return this.selected_item.bind("focus.chosen", this.activate_action); + } + } + }; + + Chosen.prototype.container_mousedown = function(evt) { + if (!this.is_disabled) { + if (evt && evt.type === "mousedown" && !this.results_showing) { + evt.preventDefault(); + } + if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) { + if (!this.active_field) { + if (this.is_multiple) { + this.search_field.val(""); + } + $(this.container[0].ownerDocument).bind('click.chosen', this.click_test_action); + this.results_show(); + } else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length)) { + evt.preventDefault(); + this.results_toggle(); + } + return this.activate_field(); + } + } + }; + + Chosen.prototype.container_mouseup = function(evt) { + if (evt.target.nodeName === "ABBR" && !this.is_disabled) { + return this.results_reset(evt); + } + }; + + Chosen.prototype.search_results_mousewheel = function(evt) { + var delta; + if (evt.originalEvent) { + delta = -evt.originalEvent.wheelDelta || evt.originalEvent.detail; + } + if (delta != null) { + evt.preventDefault(); + if (evt.type === 'DOMMouseScroll') { + delta = delta * 40; + } + return this.search_results.scrollTop(delta + this.search_results.scrollTop()); + } + }; + + Chosen.prototype.blur_test = function(evt) { + if (!this.active_field && this.container.hasClass("chosen-container-active")) { + return this.close_field(); + } + }; + + Chosen.prototype.close_field = function() { + $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action); + this.active_field = false; + this.results_hide(); + this.container.removeClass("chosen-container-active"); + this.clear_backstroke(); + this.show_search_field_default(); + return this.search_field_scale(); + }; + + Chosen.prototype.activate_field = function() { + this.container.addClass("chosen-container-active"); + this.active_field = true; + this.search_field.val(this.search_field.val()); + return this.search_field.focus(); + }; + + Chosen.prototype.test_active_click = function(evt) { + var active_container; + active_container = $(evt.target).closest('.chosen-container'); + if (active_container.length && this.container[0] === active_container[0]) { + return this.active_field = true; + } else { + return this.close_field(); + } + }; + + Chosen.prototype.results_build = function() { + this.parsing = true; + this.selected_option_count = null; + this.results_data = SelectParser.select_to_array(this.form_field); + if (this.is_multiple) { + this.search_choices.find("li.search-choice").remove(); + } else if (!this.is_multiple) { + this.single_set_selected_text(); + if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) { + this.search_field[0].readOnly = true; + this.container.addClass("chosen-container-single-nosearch"); + } else { + this.search_field[0].readOnly = false; + this.container.removeClass("chosen-container-single-nosearch"); + } + } + this.update_results_content(this.results_option_build({ + first: true + })); + this.search_field_disabled(); + this.show_search_field_default(); + this.search_field_scale(); + return this.parsing = false; + }; + + Chosen.prototype.result_do_highlight = function(el) { + var high_bottom, high_top, maxHeight, visible_bottom, visible_top; + if (el.length) { + this.result_clear_highlight(); + this.result_highlight = el; + this.result_highlight.addClass("highlighted"); + maxHeight = parseInt(this.search_results.css("maxHeight"), 10); + visible_top = this.search_results.scrollTop(); + visible_bottom = maxHeight + visible_top; + high_top = this.result_highlight.position().top + this.search_results.scrollTop(); + high_bottom = high_top + this.result_highlight.outerHeight(); + if (high_bottom >= visible_bottom) { + return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0); + } else if (high_top < visible_top) { + return this.search_results.scrollTop(high_top); + } + } + }; + + Chosen.prototype.result_clear_highlight = function() { + if (this.result_highlight) { + this.result_highlight.removeClass("highlighted"); + } + return this.result_highlight = null; + }; + + Chosen.prototype.results_show = function() { + if (this.is_multiple && this.max_selected_options <= this.choices_count()) { + this.form_field_jq.trigger("chosen:maxselected", { + chosen: this + }); + return false; + } + this.container.addClass("chosen-with-drop"); + this.results_showing = true; + this.search_field.focus(); + this.search_field.val(this.search_field.val()); + this.winnow_results(); + return this.form_field_jq.trigger("chosen:showing_dropdown", { + chosen: this + }); + }; + + Chosen.prototype.update_results_content = function(content) { + return this.search_results.html(content); + }; + + Chosen.prototype.results_hide = function() { + if (this.results_showing) { + this.result_clear_highlight(); + this.container.removeClass("chosen-with-drop"); + this.form_field_jq.trigger("chosen:hiding_dropdown", { + chosen: this + }); + } + return this.results_showing = false; + }; + + Chosen.prototype.set_tab_index = function(el) { + var ti; + if (this.form_field.tabIndex) { + ti = this.form_field.tabIndex; + this.form_field.tabIndex = -1; + return this.search_field[0].tabIndex = ti; + } + }; + + Chosen.prototype.set_label_behavior = function() { + var _this = this; + this.form_field_label = this.form_field_jq.parents("label"); + if (!this.form_field_label.length && this.form_field.id.length) { + this.form_field_label = $("label[for='" + this.form_field.id + "']"); + } + if (this.form_field_label.length > 0) { + return this.form_field_label.bind('click.chosen', function(evt) { + if (_this.is_multiple) { + return _this.container_mousedown(evt); + } else { + return _this.activate_field(); + } + }); + } + }; + + Chosen.prototype.show_search_field_default = function() { + if (this.is_multiple && this.choices_count() < 1 && !this.active_field) { + this.search_field.val(this.default_text); + return this.search_field.addClass("default"); + } else { + this.search_field.val(""); + return this.search_field.removeClass("default"); + } + }; + + Chosen.prototype.search_results_mouseup = function(evt) { + var target; + target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first(); + if (target.length) { + this.result_highlight = target; + this.result_select(evt); + return this.search_field.focus(); + } + }; + + Chosen.prototype.search_results_mouseover = function(evt) { + var target; + target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first(); + if (target) { + return this.result_do_highlight(target); + } + }; + + Chosen.prototype.search_results_mouseout = function(evt) { + if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) { + return this.result_clear_highlight(); + } + }; + + Chosen.prototype.choice_build = function(item) { + var choice, close_link, + _this = this; + choice = $('
    • ', { + "class": "search-choice" + }).html("" + item.html + ""); + if (item.disabled) { + choice.addClass('search-choice-disabled'); + } else { + close_link = $('', { + "class": 'search-choice-close', + 'data-option-array-index': item.array_index + }); + close_link.bind('click.chosen', function(evt) { + return _this.choice_destroy_link_click(evt); + }); + choice.append(close_link); + } + return this.search_container.before(choice); + }; + + Chosen.prototype.choice_destroy_link_click = function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + if (!this.is_disabled) { + return this.choice_destroy($(evt.target)); + } + }; + + Chosen.prototype.choice_destroy = function(link) { + if (this.result_deselect(link[0].getAttribute("data-option-array-index"))) { + this.show_search_field_default(); + if (this.is_multiple && this.choices_count() > 0 && this.search_field.val().length < 1) { + this.results_hide(); + } + link.parents('li').first().remove(); + return this.search_field_scale(); + } + }; + + Chosen.prototype.results_reset = function() { + this.reset_single_select_options(); + this.form_field.options[0].selected = true; + this.single_set_selected_text(); + this.show_search_field_default(); + this.results_reset_cleanup(); + this.form_field_jq.trigger("change"); + if (this.active_field) { + return this.results_hide(); + } + }; + + Chosen.prototype.results_reset_cleanup = function() { + this.current_selectedIndex = this.form_field.selectedIndex; + return this.selected_item.find("abbr").remove(); + }; + + Chosen.prototype.result_select = function(evt) { + var high, item; + if (this.result_highlight) { + high = this.result_highlight; + this.result_clear_highlight(); + if (this.is_multiple && this.max_selected_options <= this.choices_count()) { + this.form_field_jq.trigger("chosen:maxselected", { + chosen: this + }); + return false; + } + if (this.is_multiple) { + high.removeClass("active-result"); + } else { + this.reset_single_select_options(); + } + item = this.results_data[high[0].getAttribute("data-option-array-index")]; + item.selected = true; + this.form_field.options[item.options_index].selected = true; + this.selected_option_count = null; + if (this.is_multiple) { + this.choice_build(item); + } else { + this.single_set_selected_text(item.text); + } + if (!((evt.metaKey || evt.ctrlKey) && this.is_multiple)) { + this.results_hide(); + } + this.search_field.val(""); + if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) { + this.form_field_jq.trigger("change", { + 'selected': this.form_field.options[item.options_index].value + }); + } + this.current_selectedIndex = this.form_field.selectedIndex; + return this.search_field_scale(); + } + }; + + Chosen.prototype.single_set_selected_text = function(text) { + if (text == null) { + text = this.default_text; + } + if (text === this.default_text) { + this.selected_item.addClass("chosen-default"); + } else { + this.single_deselect_control_build(); + this.selected_item.removeClass("chosen-default"); + } + return this.selected_item.find("span").text(text); + }; + + Chosen.prototype.result_deselect = function(pos) { + var result_data; + result_data = this.results_data[pos]; + if (!this.form_field.options[result_data.options_index].disabled) { + result_data.selected = false; + this.form_field.options[result_data.options_index].selected = false; + this.selected_option_count = null; + this.result_clear_highlight(); + if (this.results_showing) { + this.winnow_results(); + } + this.form_field_jq.trigger("change", { + deselected: this.form_field.options[result_data.options_index].value + }); + this.search_field_scale(); + return true; + } else { + return false; + } + }; + + Chosen.prototype.single_deselect_control_build = function() { + if (!this.allow_single_deselect) { + return; + } + if (!this.selected_item.find("abbr").length) { + this.selected_item.find("span").first().after(""); + } + return this.selected_item.addClass("chosen-single-with-deselect"); + }; + + Chosen.prototype.get_search_text = function() { + if (this.search_field.val() === this.default_text) { + return ""; + } else { + return $('
      ').text($.trim(this.search_field.val())).html(); + } + }; + + Chosen.prototype.winnow_results_set_highlight = function() { + var do_high, selected_results; + selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : []; + do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first(); + if (do_high != null) { + return this.result_do_highlight(do_high); + } + }; + + Chosen.prototype.no_results = function(terms) { + var no_results_html; + no_results_html = $('
    • ' + this.results_none_found + ' ""
    • '); + no_results_html.find("span").first().html(terms); + this.search_results.append(no_results_html); + return this.form_field_jq.trigger("chosen:no_results", { + chosen: this + }); + }; + + Chosen.prototype.no_results_clear = function() { + return this.search_results.find(".no-results").remove(); + }; + + Chosen.prototype.keydown_arrow = function() { + var next_sib; + if (this.results_showing && this.result_highlight) { + next_sib = this.result_highlight.nextAll("li.active-result").first(); + if (next_sib) { + return this.result_do_highlight(next_sib); + } + } else { + return this.results_show(); + } + }; + + Chosen.prototype.keyup_arrow = function() { + var prev_sibs; + if (!this.results_showing && !this.is_multiple) { + return this.results_show(); + } else if (this.result_highlight) { + prev_sibs = this.result_highlight.prevAll("li.active-result"); + if (prev_sibs.length) { + return this.result_do_highlight(prev_sibs.first()); + } else { + if (this.choices_count() > 0) { + this.results_hide(); + } + return this.result_clear_highlight(); + } + } + }; + + Chosen.prototype.keydown_backstroke = function() { + var next_available_destroy; + if (this.pending_backstroke) { + this.choice_destroy(this.pending_backstroke.find("a").first()); + return this.clear_backstroke(); + } else { + next_available_destroy = this.search_container.siblings("li.search-choice").last(); + if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) { + this.pending_backstroke = next_available_destroy; + if (this.single_backstroke_delete) { + return this.keydown_backstroke(); + } else { + return this.pending_backstroke.addClass("search-choice-focus"); + } + } + } + }; + + Chosen.prototype.clear_backstroke = function() { + if (this.pending_backstroke) { + this.pending_backstroke.removeClass("search-choice-focus"); + } + return this.pending_backstroke = null; + }; + + Chosen.prototype.keydown_checker = function(evt) { + var stroke, _ref1; + stroke = (_ref1 = evt.which) != null ? _ref1 : evt.keyCode; + this.search_field_scale(); + if (stroke !== 8 && this.pending_backstroke) { + this.clear_backstroke(); + } + switch (stroke) { + case 8: + this.backstroke_length = this.search_field.val().length; + break; + case 9: + if (this.results_showing && !this.is_multiple) { + this.result_select(evt); + } + this.mouse_on_container = false; + break; + case 13: + evt.preventDefault(); + break; + case 38: + evt.preventDefault(); + this.keyup_arrow(); + break; + case 40: + evt.preventDefault(); + this.keydown_arrow(); + break; + } + }; + + Chosen.prototype.search_field_scale = function() { + var div, f_width, h, style, style_block, styles, w, _i, _len; + if (this.is_multiple) { + h = 0; + w = 0; + style_block = "position:absolute; left: -1000px; top: -1000px; display:none;"; + styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing']; + for (_i = 0, _len = styles.length; _i < _len; _i++) { + style = styles[_i]; + style_block += style + ":" + this.search_field.css(style) + ";"; + } + div = $('
      ', { + 'style': style_block + }); + div.text(this.search_field.val()); + $('body').append(div); + w = div.width() + 25; + div.remove(); + f_width = this.container.outerWidth(); + if (w > f_width - 10) { + w = f_width - 10; + } + return this.search_field.css({ + 'width': w + 'px' + }); + } + }; + + return Chosen; + + })(AbstractChosen); + +}).call(this); diff --git a/static/js/term.js b/static/js/term.js new file mode 100644 index 000000000..dc8f9fc5c --- /dev/null +++ b/static/js/term.js @@ -0,0 +1,4182 @@ +/** + * tty.js - an xterm emulator + * Christopher Jeffrey (https://github.com/chjj/tty.js) + * + * Originally forked from (with the author's permission): + * + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + * (Redistribution or commercial use is prohibited + * without the author's permission.) + * + * The original design remains. The terminal itself + * has been extended to include xterm CSI codes, among + * other features. +*/ + +;(function() { + +/** + * Terminal Emulation References: + * http://vt100.net/ + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * http://invisible-island.net/vttest/ + * http://www.inwap.com/pdp10/ansicode.txt + * http://linux.die.net/man/4/console_codes + * http://linux.die.net/man/7/urxvt + */ + +'use strict'; + +/** + * Shared + */ + +var window = this + , document = this.document; + +/** + * EventEmitter + */ + +function EventEmitter() { + this._events = {}; +} + +EventEmitter.prototype.addListener = function(type, listener) { + this._events[type] = this._events[type] || []; + this._events[type].push(listener); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.removeListener = function(type, listener) { + if (!this._events[type]) return; + + var obj = this._events[type] + , i = obj.length; + + while (i--) { + if (obj[i] === listener || obj[i].listener === listener) { + obj.splice(i, 1); + return; + } + } +}; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +EventEmitter.prototype.removeAllListeners = function(type) { + if (this._events[type]) delete this._events[type]; +}; + +EventEmitter.prototype.once = function(type, listener) { + function on() { + var args = Array.prototype.slice.call(arguments); + this.removeListener(type, on); + return listener.apply(this, args); + } + on.listener = listener; + return this.on(type, on); +}; + +EventEmitter.prototype.emit = function(type) { + if (!this._events[type]) return; + + var args = Array.prototype.slice.call(arguments, 1) + , obj = this._events[type] + , l = obj.length + , i = 0; + + for (; i < l; i++) { + obj[i].apply(this, args); + } +}; + +EventEmitter.prototype.listeners = function(type) { + return this._events[type] = this._events[type] || []; +}; + +/** + * States + */ + +var normal = 0 + , escaped = 1 + , csi = 2 + , osc = 3 + , charset = 4 + , dcs = 5 + , ignore = 6; + +/** + * Terminal + */ + +function Terminal(cols, rows, handler) { + EventEmitter.call(this); + + var options; + if (typeof cols === 'object') { + options = cols; + cols = options.cols; + rows = options.rows; + handler = options.handler; + } + this._options = options || {}; + + this.cols = cols || Terminal.geometry[0]; + this.rows = rows || Terminal.geometry[1]; + + //if (handler) { + // this.on('data', handler); + //} + this.handler = handler; + if (this.handler){ + this.on('data', this.handler); + } + + this.ybase = 0; + this.ydisp = 0; + this.x = 0; + this.y = 0; + this.cursorState = 0; + this.cursorHidden = false; + this.convertEol = false; + this.state = 0; + this.queue = ''; + this.scrollTop = 0; + this.scrollBottom = this.rows - 1; + + // modes + this.applicationKeypad = false; + this.originMode = false; + this.insertMode = false; + this.wraparoundMode = false; + this.normal = null; + + // charset + this.charset = null; + this.gcharset = null; + this.glevel = 0; + this.charsets = [null]; + + // mouse properties + this.decLocator; + this.x10Mouse; + this.vt200Mouse; + this.vt300Mouse; + this.normalMouse; + this.mouseEvents; + this.sendFocus; + this.utfMouse; + this.sgrMouse; + this.urxvtMouse; + + // misc + this.element; + this.children; + this.refreshStart; + this.refreshEnd; + this.savedX; + this.savedY; + this.savedCols; + + // stream + this.readable = true; + this.writable = true; + + this.defAttr = (257 << 9) | 256; + this.curAttr = this.defAttr; + + this.params = []; + this.currentParam = 0; + this.prefix = ''; + this.postfix = ''; + + this.lines = []; + var i = this.rows; + while (i--) { + this.lines.push(this.blankLine()); + } + + this.tabs; + this.setupStops(); +} + +inherits(Terminal, EventEmitter); + +/** + * Colors + */ + +// Colors 0-15 +Terminal.colors = [ + // dark: + '#2e3436', + '#cc0000', + '#4e9a06', + '#c4a000', + '#3465a4', + '#75507b', + '#06989a', + '#d3d7cf', + // bright: + '#555753', + '#ef2929', + '#8ae234', + '#fce94f', + '#729fcf', + '#ad7fa8', + '#34e2e2', + '#eeeeec' +]; + +// Colors 16-255 +// Much thanks to TooTallNate for writing this. +Terminal.colors = (function() { + var colors = Terminal.colors + , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff] + , i; + + // 16-231 + i = 0; + for (; i < 216; i++) { + out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]); + } + + // 232-255 (grey) + i = 0; + for (; i < 24; i++) { + r = 8 + i * 10; + out(r, r, r); + } + + function out(r, g, b) { + colors.push('#' + hex(r) + hex(g) + hex(b)); + } + + function hex(c) { + c = c.toString(16); + return c.length < 2 ? '0' + c : c; + } + + return colors; +})(); + +// Default BG/FG +Terminal.defaultColors = { + bg: '#000000', + fg: '#f0f0f0' +}; + +Terminal.colors[256] = Terminal.defaultColors.bg; +Terminal.colors[257] = Terminal.defaultColors.fg; + +/** + * Options + */ + +Terminal.termName = 'xterm'; +Terminal.geometry = [80, 30]; +Terminal.cursorBlink = true; +Terminal.visualBell = false; +Terminal.popOnBell = false; +Terminal.scrollback = 1000; +Terminal.screenKeys = false; +Terminal.programFeatures = false; +Terminal.debug = false; + +/** + * Focused Terminal + */ + +Terminal.focus = null; + +Terminal.prototype.focus = function() { + if (Terminal.focus === this) return; + if (Terminal.focus) { + Terminal.focus.cursorState = 0; + Terminal.focus.refresh(Terminal.focus.y, Terminal.focus.y); + if (Terminal.focus.sendFocus) Terminal.focus.send('\x1b[O'); + } + Terminal.focus = this; + if (this.sendFocus) this.send('\x1b[I'); + this.showCursor(); +}; + +/** + * Global Events for key handling + */ + +Terminal.bindKeys = function() { + if (Terminal.focus) return; + + // We could put an "if (Terminal.focus)" check + // here, but it shouldn't be necessary. + on(document, 'keydown', function(ev) { + return Terminal.focus.keyDown(ev); + }, true); + + on(document, 'keypress', function(ev) { + return Terminal.focus.keyPress(ev); + }, true); +}; + +/** + * Open Terminal + */ + +Terminal.prototype.open = function() { + var self = this + , i = 0 + , div; + + this.element = document.createElement('div'); + this.element.className = 'terminal'; + this.children = []; + + for (; i < this.rows; i++) { + div = document.createElement('div'); + this.element.appendChild(div); + this.children.push(div); + } + + document.body.appendChild(this.element); + + this.refresh(0, this.rows - 1); + + Terminal.bindKeys(); + this.focus(); + + this.startBlink(); + + on(this.element, 'mousedown', function() { + self.focus(); + }); + + // This probably shouldn't work, + // ... but it does. Firefox's paste + // event seems to only work for textareas? + on(this.element, 'mousedown', function(ev) { + var button = ev.button != null + ? +ev.button + : ev.which != null + ? ev.which - 1 + : null; + + // Does IE9 do this? + if (~navigator.userAgent.indexOf('MSIE')) { + button = button === 1 ? 0 : button === 4 ? 1 : button; + } + + if (button !== 2) return; + + self.element.contentEditable = 'true'; + setTimeout(function() { + self.element.contentEditable = 'inherit'; // 'false'; + }, 1); + }, true); + + on(this.element, 'paste', function(ev) { + if (ev.clipboardData) { + self.send(ev.clipboardData.getData('text/plain')); + } else if (window.clipboardData) { + self.send(window.clipboardData.getData('Text')); + } + // Not necessary. Do it anyway for good measure. + self.element.contentEditable = 'inherit'; + return cancel(ev); + }); + + this.bindMouse(); + + // XXX - hack, move this somewhere else. + if (Terminal.brokenBold == null) { + Terminal.brokenBold = isBoldBroken(); + } + + // sync default bg/fg colors + this.element.style.backgroundColor = Terminal.defaultColors.bg; + this.element.style.color = Terminal.defaultColors.fg; + + //this.emit('open'); +}; + +// XTerm mouse events +// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking +// To better understand these +// the xterm code is very helpful: +// Relevant files: +// button.c, charproc.c, misc.c +// Relevant functions in xterm/button.c: +// BtnCode, EmitButtonCode, EditorButton, SendMousePosition +Terminal.prototype.bindMouse = function() { + var el = this.element + , self = this + , pressed = 32; + + var wheelEvent = 'onmousewheel' in window + ? 'mousewheel' + : 'DOMMouseScroll'; + + // mouseup, mousedown, mousewheel + // left click: ^[[M 3<^[[M#3< + // mousewheel up: ^[[M`3> + function sendButton(ev) { + var button + , pos; + + // get the xterm-style button + button = getButton(ev); + + // get mouse coordinates + pos = getCoords(ev); + if (!pos) return; + + sendEvent(button, pos); + + switch (ev.type) { + case 'mousedown': + pressed = button; + break; + case 'mouseup': + // keep it at the left + // button, just in case. + pressed = 32; + break; + case wheelEvent: + // nothing. don't + // interfere with + // `pressed`. + break; + } + } + + // motion example of a left click: + // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< + function sendMove(ev) { + var button = pressed + , pos; + + pos = getCoords(ev); + if (!pos) return; + + // buttons marked as motions + // are incremented by 32 + button += 32; + + sendEvent(button, pos); + } + + // encode button and + // position to characters + function encode(data, ch) { + if (!self.utfMouse) { + if (ch === 255) return data.push(0); + if (ch > 127) ch = 127; + data.push(ch); + } else { + if (ch === 2047) return data.push(0); + if (ch < 127) { + data.push(ch); + } else { + if (ch > 2047) ch = 2047; + data.push(0xC0 | (ch >> 6)); + data.push(0x80 | (ch & 0x3F)); + } + } + } + + // send a mouse event: + // regular/utf8: ^[[M Cb Cx Cy + // urxvt: ^[[ Cb ; Cx ; Cy M + // sgr: ^[[ Cb ; Cx ; Cy M/m + // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r + // locator: CSI P e ; P b ; P r ; P c ; P p & w + function sendEvent(button, pos) { + // self.emit('mouse', { + // x: pos.x - 32, + // y: pos.x - 32, + // button: button + // }); + + if (self.vt300Mouse) { + // NOTE: Unstable. + // http://www.vt100.net/docs/vt3xx-gp/chapter15.html + button &= 3; + pos.x -= 32; + pos.y -= 32; + var data = '\x1b[24'; + if (button === 0) data += '1'; + else if (button === 1) data += '3'; + else if (button === 2) data += '5'; + else if (button === 3) return; + else data += '0'; + data += '~[' + pos.x + ',' + pos.y + ']\r'; + self.send(data); + return; + } + + if (self.decLocator) { + // NOTE: Unstable. + button &= 3; + pos.x -= 32; + pos.y -= 32; + if (button === 0) button = 2; + else if (button === 1) button = 4; + else if (button === 2) button = 6; + else if (button === 3) button = 3; + self.send('\x1b[' + + button + + ';' + + (button === 3 ? 4 : 0) + + ';' + + pos.y + + ';' + + pos.x + + ';' + + (pos.page || 0) + + '&w'); + return; + } + + if (self.urxvtMouse) { + pos.x -= 32; + pos.y -= 32; + pos.x++; + pos.y++; + self.send('\x1b[' + button + ';' + pos.x + ';' + pos.y + 'M'); + return; + } + + if (self.sgrMouse) { + pos.x -= 32; + pos.y -= 32; + self.send('\x1b[<' + + ((button & 3) === 3 ? button & ~3 : button) + + ';' + + pos.x + + ';' + + pos.y + + ((button & 3) === 3 ? 'm' : 'M')); + return; + } + + var data = []; + + encode(data, button); + encode(data, pos.x); + encode(data, pos.y); + + self.send('\x1b[M' + String.fromCharCode.apply(String, data)); + } + + function getButton(ev) { + var button + , shift + , meta + , ctrl + , mod; + + // two low bits: + // 0 = left + // 1 = middle + // 2 = right + // 3 = release + // wheel up/down: + // 1, and 2 - with 64 added + switch (ev.type) { + case 'mousedown': + button = ev.button != null + ? +ev.button + : ev.which != null + ? ev.which - 1 + : null; + + if (~navigator.userAgent.indexOf('MSIE')) { + button = button === 1 ? 0 : button === 4 ? 1 : button; + } + break; + case 'mouseup': + button = 3; + break; + case 'DOMMouseScroll': + button = ev.detail < 0 + ? 64 + : 65; + break; + case 'mousewheel': + button = ev.wheelDeltaY > 0 + ? 64 + : 65; + break; + } + + // next three bits are the modifiers: + // 4 = shift, 8 = meta, 16 = control + shift = ev.shiftKey ? 4 : 0; + meta = ev.metaKey ? 8 : 0; + ctrl = ev.ctrlKey ? 16 : 0; + mod = shift | meta | ctrl; + + // no mods + if (self.vt200Mouse) { + // ctrl only + mod &= ctrl; + } else if (!self.normalMouse) { + mod = 0; + } + + // increment to SP + button = (32 + (mod << 2)) + button; + + return button; + } + + // mouse coordinates measured in cols/rows + function getCoords(ev) { + var x, y, w, h, el; + + // ignore browsers without pageX for now + if (ev.pageX == null) return; + + x = ev.pageX; + y = ev.pageY; + el = self.element; + + // should probably check offsetParent + // but this is more portable + while (el !== document.documentElement) { + x -= el.offsetLeft; + y -= el.offsetTop; + el = el.parentNode; + } + + // convert to cols/rows + w = self.element.clientWidth; + h = self.element.clientHeight; + x = ((x / w) * self.cols) | 0; + y = ((y / h) * self.rows) | 0; + + // be sure to avoid sending + // bad positions to the program + if (x < 0) x = 0; + if (x > self.cols) x = self.cols; + if (y < 0) y = 0; + if (y > self.rows) y = self.rows; + + // xterm sends raw bytes and + // starts at 32 (SP) for each. + x += 32; + y += 32; + + return { + x: x, + y: y, + down: ev.type === 'mousedown', + up: ev.type === 'mouseup', + wheel: ev.type === wheelEvent, + move: ev.type === 'mousemove' + }; + } + + on(el, 'mousedown', function(ev) { + if (!self.mouseEvents) return; + + // send the button + sendButton(ev); + + // ensure focus + self.focus(); + + // fix for odd bug + if (self.vt200Mouse) { + sendButton({ __proto__: ev, type: 'mouseup' }); + return cancel(ev); + } + + // bind events + if (self.normalMouse) on(document, 'mousemove', sendMove); + + // x10 compatibility mode can't send button releases + if (!self.x10Mouse) { + on(document, 'mouseup', function up(ev) { + sendButton(ev); + if (self.normalMouse) off(document, 'mousemove', sendMove); + off(document, 'mouseup', up); + return cancel(ev); + }); + } + + return cancel(ev); + }); + + on(el, wheelEvent, function(ev) { + if (!self.mouseEvents) return; + if (self.x10Mouse + || self.vt300Mouse + || self.decLocator) return; + sendButton(ev); + return cancel(ev); + }); + + // allow mousewheel scrolling in + // the shell for example + on(el, wheelEvent, function(ev) { + if (self.mouseEvents) return; + if (self.applicationKeypad) return; + if (ev.type === 'DOMMouseScroll') { + self.scrollDisp(ev.detail < 0 ? -5 : 5); + } else { + self.scrollDisp(ev.wheelDeltaY > 0 ? -5 : 5); + } + return cancel(ev); + }); +}; + +/** + * Destroy Terminal + */ + +Terminal.prototype.destroy = function() { + this.readable = false; + this.writable = false; + this._events = {}; + this.handler = function() {}; + this.write = function() {}; + //this.emit('close'); +}; + +/** + * Rendering Engine + */ + +// In the screen buffer, each character +// is stored as a an array with a character +// and a 32-bit integer. +// First value: a utf-16 character. +// Second value: +// Next 9 bits: background color (0-511). +// Next 9 bits: foreground color (0-511). +// Next 14 bits: a mask for misc. flags: +// 1=bold, 2=underline, 4=inverse + +Terminal.prototype.refresh = function(start, end) { + var x + , y + , i + , line + , out + , ch + , width + , data + , attr + , fgColor + , bgColor + , flags + , row + , parent; + + if (end - start >= this.rows / 2) { + parent = this.element.parentNode; + if (parent) parent.removeChild(this.element); + } + + width = this.cols; + y = start; + + for (; y <= end; y++) { + row = y + this.ydisp; + + line = this.lines[row]; + out = ''; + + if (y === this.y + && this.cursorState + && this.ydisp === this.ybase + && !this.cursorHidden) { + x = this.x; + } else { + x = -1; + } + + attr = this.defAttr; + i = 0; + + for (; i < width; i++) { + data = line[i][0]; + ch = line[i][1]; + + if (i === x) data = -1; + + if (data !== attr) { + if (attr !== this.defAttr) { + out += ''; + } + if (data !== this.defAttr) { + if (data === -1) { + out += ''; + } else { + out += ''; + } + } + } + + switch (ch) { + case '&': + out += '&'; + break; + case '<': + out += '<'; + break; + case '>': + out += '>'; + break; + default: + if (ch <= ' ') { + out += ' '; + } else { + out += ch; + } + break; + } + + attr = data; + } + + if (attr !== this.defAttr) { + out += ''; + } + + this.children[y].innerHTML = out; + } + + if (parent) parent.appendChild(this.element); +}; + +Terminal.prototype.cursorBlink = function() { + if (Terminal.focus !== this) return; + this.cursorState ^= 1; + this.refresh(this.y, this.y); +}; + +Terminal.prototype.showCursor = function() { + if (!this.cursorState) { + this.cursorState = 1; + this.refresh(this.y, this.y); + } else { + // Temporarily disabled: + // this.refreshBlink(); + } +}; + +Terminal.prototype.startBlink = function() { + if (!Terminal.cursorBlink) return; + var self = this; + this._blinker = function() { + self.cursorBlink(); + }; + this._blink = setInterval(this._blinker, 500); +}; + +Terminal.prototype.refreshBlink = function() { + if (!Terminal.cursorBlink) return; + clearInterval(this._blink); + this._blink = setInterval(this._blinker, 500); +}; + +Terminal.prototype.scroll = function() { + var row; + + if (++this.ybase === Terminal.scrollback) { + this.ybase = this.ybase / 2 | 0; + this.lines = this.lines.slice(-(this.ybase + this.rows) + 1); + } + + this.ydisp = this.ybase; + + // last line + row = this.ybase + this.rows - 1; + + // subtract the bottom scroll region + row -= this.rows - 1 - this.scrollBottom; + + if (row === this.lines.length) { + // potential optimization: + // pushing is faster than splicing + // when they amount to the same + // behavior. + this.lines.push(this.blankLine()); + } else { + // add our new line + this.lines.splice(row, 0, this.blankLine()); + } + + if (this.scrollTop !== 0) { + if (this.ybase !== 0) { + this.ybase--; + this.ydisp = this.ybase; + } + this.lines.splice(this.ybase + this.scrollTop, 1); + } + + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); +}; + +Terminal.prototype.scrollDisp = function(disp) { + this.ydisp += disp; + + if (this.ydisp > this.ybase) { + this.ydisp = this.ybase; + } else if (this.ydisp < 0) { + this.ydisp = 0; + } + + this.refresh(0, this.rows - 1); +}; + +Terminal.prototype.write = function(data) { + var l = data.length + , i = 0 + , cs + , ch; + + this.refreshStart = this.y; + this.refreshEnd = this.y; + + if (this.ybase !== this.ydisp) { + this.ydisp = this.ybase; + this.maxRange(); + } + + // this.log(JSON.stringify(data.replace(/\x1b/g, '^['))); + + for (; i < l; i++) { + ch = data[i]; + switch (this.state) { + case normal: + switch (ch) { + // '\0' + // case '\0': + // break; + + // '\a' + case '\x07': + this.bell(); + break; + + // '\n', '\v', '\f' + case '\n': + case '\x0b': + case '\x0c': + if (this.convertEol) { + this.x = 0; + } + this.y++; + if (this.y > this.scrollBottom) { + this.y--; + this.scroll(); + } + break; + + // '\r' + case '\r': + this.x = 0; + break; + + // '\b' + case '\x08': + if (this.x > 0) { + this.x--; + } + break; + + // '\t' + case '\t': + this.x = this.nextStop(); + break; + + // shift out + case '\x0e': + this.setgLevel(1); + break; + + // shift in + case '\x0f': + this.setgLevel(0); + break; + + // '\e' + case '\x1b': + this.state = escaped; + break; + + default: + // ' ' + if (ch >= ' ') { + if (this.charset && this.charset[ch]) { + ch = this.charset[ch]; + } + if (this.x >= this.cols) { + this.x = 0; + this.y++; + if (this.y > this.scrollBottom) { + this.y--; + this.scroll(); + } + } + this.lines[this.y + this.ybase][this.x] = [this.curAttr, ch]; + this.x++; + this.updateRange(this.y); + } + break; + } + break; + case escaped: + switch (ch) { + // ESC [ Control Sequence Introducer ( CSI is 0x9b). + case '[': + this.params = []; + this.currentParam = 0; + this.state = csi; + break; + + // ESC ] Operating System Command ( OSC is 0x9d). + case ']': + this.params = []; + this.currentParam = 0; + this.state = osc; + break; + + // ESC P Device Control String ( DCS is 0x90). + case 'P': + this.params = []; + this.currentParam = 0; + this.state = dcs; + break; + + // ESC _ Application Program Command ( APC is 0x9f). + case '_': + this.state = ignore; + break; + + // ESC ^ Privacy Message ( PM is 0x9e). + case '^': + this.state = ignore; + break; + + // ESC c Full Reset (RIS). + case 'c': + this.reset(); + break; + + // ESC E Next Line ( NEL is 0x85). + // ESC D Index ( IND is 0x84). + case 'E': + this.x = 0; + ; + case 'D': + this.index(); + break; + + // ESC M Reverse Index ( RI is 0x8d). + case 'M': + this.reverseIndex(); + break; + + // ESC % Select default/utf-8 character set. + // @ = default, G = utf-8 + case '%': + //this.charset = null; + this.setgLevel(0); + this.setgCharset(0, Terminal.charsets.US); + this.state = normal; + i++; + break; + + // ESC (,),*,+,-,. Designate G0-G2 Character Set. + case '(': // <-- this seems to get all the attention + case ')': + case '*': + case '+': + case '-': + case '.': + switch (ch) { + case '(': + this.gcharset = 0; + break; + case ')': + this.gcharset = 1; + break; + case '*': + this.gcharset = 2; + break; + case '+': + this.gcharset = 3; + break; + case '-': + this.gcharset = 1; + break; + case '.': + this.gcharset = 2; + break; + } + this.state = charset; + break; + + // Designate G3 Character Set (VT300). + // A = ISO Latin-1 Supplemental. + // Not implemented. + case '/': + this.gcharset = 3; + this.state = charset; + i--; + break; + + // ESC N + // Single Shift Select of G2 Character Set + // ( SS2 is 0x8e). This affects next character only. + case 'N': + break; + // ESC O + // Single Shift Select of G3 Character Set + // ( SS3 is 0x8f). This affects next character only. + case 'O': + break; + // ESC n + // Invoke the G2 Character Set as GL (LS2). + case 'n': + this.setgLevel(2); + break; + // ESC o + // Invoke the G3 Character Set as GL (LS3). + case 'o': + this.setgLevel(3); + break; + // ESC | + // Invoke the G3 Character Set as GR (LS3R). + case '|': + this.setgLevel(3); + break; + // ESC } + // Invoke the G2 Character Set as GR (LS2R). + case '}': + this.setgLevel(2); + break; + // ESC ~ + // Invoke the G1 Character Set as GR (LS1R). + case '~': + this.setgLevel(1); + break; + + // ESC 7 Save Cursor (DECSC). + case '7': + this.saveCursor(); + this.state = normal; + break; + + // ESC 8 Restore Cursor (DECRC). + case '8': + this.restoreCursor(); + this.state = normal; + break; + + // ESC # 3 DEC line height/width + case '#': + this.state = normal; + i++; + break; + + // ESC H Tab Set (HTS is 0x88). + case 'H': + this.tabSet(); + break; + + // ESC = Application Keypad (DECPAM). + case '=': + this.log('Serial port requested application keypad.'); + this.applicationKeypad = true; + this.state = normal; + break; + + // ESC > Normal Keypad (DECPNM). + case '>': + this.log('Switching back to normal keypad.'); + this.applicationKeypad = false; + this.state = normal; + break; + + default: + this.state = normal; + this.error('Unknown ESC control: %s.', ch); + break; + } + break; + + case charset: + switch (ch) { + case '0': // DEC Special Character and Line Drawing Set. + cs = Terminal.charsets.SCLD; + break; + case 'A': // UK + cs = Terminal.charsets.UK; + break; + case 'B': // United States (USASCII). + cs = Terminal.charsets.US; + break; + case '4': // Dutch + cs = Terminal.charsets.Dutch; + break; + case 'C': // Finnish + case '5': + cs = Terminal.charsets.Finnish; + break; + case 'R': // French + cs = Terminal.charsets.French; + break; + case 'Q': // FrenchCanadian + cs = Terminal.charsets.FrenchCanadian; + break; + case 'K': // German + cs = Terminal.charsets.German; + break; + case 'Y': // Italian + cs = Terminal.charsets.Italian; + break; + case 'E': // NorwegianDanish + case '6': + cs = Terminal.charsets.NorwegianDanish; + break; + case 'Z': // Spanish + cs = Terminal.charsets.Spanish; + break; + case 'H': // Swedish + case '7': + cs = Terminal.charsets.Swedish; + break; + case '=': // Swiss + cs = Terminal.charsets.Swiss; + break; + case '/': // ISOLatin (actually /A) + cs = Terminal.charsets.ISOLatin; + i++; + break; + default: // Default + cs = Terminal.charsets.US; + break; + } + this.setgCharset(this.gcharset, cs); + this.gcharset = null; + this.state = normal; + break; + + case osc: + // OSC Ps ; Pt ST + // OSC Ps ; Pt BEL + // Set Text Parameters. + if (ch === '\x1b' || ch === '\x07') { + if (ch === '\x1b') i++; + + this.params.push(this.currentParam); + + switch (this.params[0]) { + case 0: + case 1: + case 2: + if (this.params[1]) { + this.title = this.params[1]; + this.handleTitle(this.title); + } + break; + case 3: + // set X property + break; + case 4: + case 5: + // change dynamic colors + break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + // change dynamic ui colors + break; + case 46: + // change log file + break; + case 50: + // dynamic font + break; + case 51: + // emacs shell + break; + case 52: + // manipulate selection data + break; + case 104: + case 105: + case 110: + case 111: + case 112: + case 113: + case 114: + case 115: + case 116: + case 117: + case 118: + // reset colors + break; + } + + this.params = []; + this.currentParam = 0; + this.state = normal; + } else { + if (!this.params.length) { + if (ch >= '0' && ch <= '9') { + this.currentParam = + this.currentParam * 10 + ch.charCodeAt(0) - 48; + } else if (ch === ';') { + this.params.push(this.currentParam); + this.currentParam = ''; + } + } else { + this.currentParam += ch; + } + } + break; + + case csi: + // '?', '>', '!' + if (ch === '?' || ch === '>' || ch === '!') { + this.prefix = ch; + break; + } + + // 0 - 9 + if (ch >= '0' && ch <= '9') { + this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48; + break; + } + + // '$', '"', ' ', '\'' + if (ch === '$' || ch === '"' || ch === ' ' || ch === '\'') { + this.postfix = ch; + break; + } + + this.params.push(this.currentParam); + this.currentParam = 0; + + // ';' + if (ch === ';') break; + + this.state = normal; + + switch (ch) { + // CSI Ps A + // Cursor Up Ps Times (default = 1) (CUU). + case 'A': + this.cursorUp(this.params); + break; + + // CSI Ps B + // Cursor Down Ps Times (default = 1) (CUD). + case 'B': + this.cursorDown(this.params); + break; + + // CSI Ps C + // Cursor Forward Ps Times (default = 1) (CUF). + case 'C': + this.cursorForward(this.params); + break; + + // CSI Ps D + // Cursor Backward Ps Times (default = 1) (CUB). + case 'D': + this.cursorBackward(this.params); + break; + + // CSI Ps ; Ps H + // Cursor Position [row;column] (default = [1,1]) (CUP). + case 'H': + this.cursorPos(this.params); + break; + + // CSI Ps J Erase in Display (ED). + case 'J': + this.eraseInDisplay(this.params); + break; + + // CSI Ps K Erase in Line (EL). + case 'K': + this.eraseInLine(this.params); + break; + + // CSI Pm m Character Attributes (SGR). + case 'm': + this.charAttributes(this.params); + break; + + // CSI Ps n Device Status Report (DSR). + case 'n': + this.deviceStatus(this.params); + break; + + /** + * Additions + */ + + // CSI Ps @ + // Insert Ps (Blank) Character(s) (default = 1) (ICH). + case '@': + this.insertChars(this.params); + break; + + // CSI Ps E + // Cursor Next Line Ps Times (default = 1) (CNL). + case 'E': + this.cursorNextLine(this.params); + break; + + // CSI Ps F + // Cursor Preceding Line Ps Times (default = 1) (CNL). + case 'F': + this.cursorPrecedingLine(this.params); + break; + + // CSI Ps G + // Cursor Character Absolute [column] (default = [row,1]) (CHA). + case 'G': + this.cursorCharAbsolute(this.params); + break; + + // CSI Ps L + // Insert Ps Line(s) (default = 1) (IL). + case 'L': + this.insertLines(this.params); + break; + + // CSI Ps M + // Delete Ps Line(s) (default = 1) (DL). + case 'M': + this.deleteLines(this.params); + break; + + // CSI Ps P + // Delete Ps Character(s) (default = 1) (DCH). + case 'P': + this.deleteChars(this.params); + break; + + // CSI Ps X + // Erase Ps Character(s) (default = 1) (ECH). + case 'X': + this.eraseChars(this.params); + break; + + // CSI Pm ` Character Position Absolute + // [column] (default = [row,1]) (HPA). + case '`': + this.charPosAbsolute(this.params); + break; + + // 141 61 a * HPR - + // Horizontal Position Relative + case 'a': + this.HPositionRelative(this.params); + break; + + // CSI P s c + // Send Device Attributes (Primary DA). + // CSI > P s c + // Send Device Attributes (Secondary DA) + case 'c': + this.sendDeviceAttributes(this.params); + break; + + // CSI Pm d + // Line Position Absolute [row] (default = [1,column]) (VPA). + case 'd': + this.linePosAbsolute(this.params); + break; + + // 145 65 e * VPR - Vertical Position Relative + case 'e': + this.VPositionRelative(this.params); + break; + + // CSI Ps ; Ps f + // Horizontal and Vertical Position [row;column] (default = + // [1,1]) (HVP). + case 'f': + this.HVPosition(this.params); + break; + + // CSI Pm h Set Mode (SM). + // CSI ? Pm h - mouse escape codes, cursor escape codes + case 'h': + this.setMode(this.params); + break; + + // CSI Pm l Reset Mode (RM). + // CSI ? Pm l + case 'l': + this.resetMode(this.params); + break; + + // CSI Ps ; Ps r + // Set Scrolling Region [top;bottom] (default = full size of win- + // dow) (DECSTBM). + // CSI ? Pm r + case 'r': + this.setScrollRegion(this.params); + break; + + // CSI s + // Save cursor (ANSI.SYS). + case 's': + this.saveCursor(this.params); + break; + + // CSI u + // Restore cursor (ANSI.SYS). + case 'u': + this.restoreCursor(this.params); + break; + + /** + * Lesser Used + */ + + // CSI Ps I + // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). + case 'I': + this.cursorForwardTab(this.params); + break; + + // CSI Ps S Scroll up Ps lines (default = 1) (SU). + case 'S': + this.scrollUp(this.params); + break; + + // CSI Ps T Scroll down Ps lines (default = 1) (SD). + // CSI Ps ; Ps ; Ps ; Ps ; Ps T + // CSI > Ps; Ps T + case 'T': + // if (this.prefix === '>') { + // this.resetTitleModes(this.params); + // break; + // } + // if (this.params.length > 2) { + // this.initMouseTracking(this.params); + // break; + // } + if (this.params.length < 2 && !this.prefix) { + this.scrollDown(this.params); + } + break; + + // CSI Ps Z + // Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). + case 'Z': + this.cursorBackwardTab(this.params); + break; + + // CSI Ps b Repeat the preceding graphic character Ps times (REP). + case 'b': + this.repeatPrecedingCharacter(this.params); + break; + + // CSI Ps g Tab Clear (TBC). + case 'g': + this.tabClear(this.params); + break; + + // CSI Pm i Media Copy (MC). + // CSI ? Pm i + // case 'i': + // this.mediaCopy(this.params); + // break; + + // CSI Pm m Character Attributes (SGR). + // CSI > Ps; Ps m + // case 'm': // duplicate + // if (this.prefix === '>') { + // this.setResources(this.params); + // } else { + // this.charAttributes(this.params); + // } + // break; + + // CSI Ps n Device Status Report (DSR). + // CSI > Ps n + // case 'n': // duplicate + // if (this.prefix === '>') { + // this.disableModifiers(this.params); + // } else { + // this.deviceStatus(this.params); + // } + // break; + + // CSI > Ps p Set pointer mode. + // CSI ! p Soft terminal reset (DECSTR). + // CSI Ps$ p + // Request ANSI mode (DECRQM). + // CSI ? Ps$ p + // Request DEC private mode (DECRQM). + // CSI Ps ; Ps " p + case 'p': + switch (this.prefix) { + // case '>': + // this.setPointerMode(this.params); + // break; + case '!': + this.softReset(this.params); + break; + // case '?': + // if (this.postfix === '$') { + // this.requestPrivateMode(this.params); + // } + // break; + // default: + // if (this.postfix === '"') { + // this.setConformanceLevel(this.params); + // } else if (this.postfix === '$') { + // this.requestAnsiMode(this.params); + // } + // break; + } + break; + + // CSI Ps q Load LEDs (DECLL). + // CSI Ps SP q + // CSI Ps " q + // case 'q': + // if (this.postfix === ' ') { + // this.setCursorStyle(this.params); + // break; + // } + // if (this.postfix === '"') { + // this.setCharProtectionAttr(this.params); + // break; + // } + // this.loadLEDs(this.params); + // break; + + // CSI Ps ; Ps r + // Set Scrolling Region [top;bottom] (default = full size of win- + // dow) (DECSTBM). + // CSI ? Pm r + // CSI Pt; Pl; Pb; Pr; Ps$ r + // case 'r': // duplicate + // if (this.prefix === '?') { + // this.restorePrivateValues(this.params); + // } else if (this.postfix === '$') { + // this.setAttrInRectangle(this.params); + // } else { + // this.setScrollRegion(this.params); + // } + // break; + + // CSI s Save cursor (ANSI.SYS). + // CSI ? Pm s + // case 's': // duplicate + // if (this.prefix === '?') { + // this.savePrivateValues(this.params); + // } else { + // this.saveCursor(this.params); + // } + // break; + + // CSI Ps ; Ps ; Ps t + // CSI Pt; Pl; Pb; Pr; Ps$ t + // CSI > Ps; Ps t + // CSI Ps SP t + // case 't': + // if (this.postfix === '$') { + // this.reverseAttrInRectangle(this.params); + // } else if (this.postfix === ' ') { + // this.setWarningBellVolume(this.params); + // } else { + // if (this.prefix === '>') { + // this.setTitleModeFeature(this.params); + // } else { + // this.manipulateWindow(this.params); + // } + // } + // break; + + // CSI u Restore cursor (ANSI.SYS). + // CSI Ps SP u + // case 'u': // duplicate + // if (this.postfix === ' ') { + // this.setMarginBellVolume(this.params); + // } else { + // this.restoreCursor(this.params); + // } + // break; + + // CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v + // case 'v': + // if (this.postfix === '$') { + // this.copyRectagle(this.params); + // } + // break; + + // CSI Pt ; Pl ; Pb ; Pr ' w + // case 'w': + // if (this.postfix === '\'') { + // this.enableFilterRectangle(this.params); + // } + // break; + + // CSI Ps x Request Terminal Parameters (DECREQTPARM). + // CSI Ps x Select Attribute Change Extent (DECSACE). + // CSI Pc; Pt; Pl; Pb; Pr$ x + // case 'x': + // if (this.postfix === '$') { + // this.fillRectangle(this.params); + // } else { + // this.requestParameters(this.params); + // //this.__(this.params); + // } + // break; + + // CSI Ps ; Pu ' z + // CSI Pt; Pl; Pb; Pr$ z + // case 'z': + // if (this.postfix === '\'') { + // this.enableLocatorReporting(this.params); + // } else if (this.postfix === '$') { + // this.eraseRectangle(this.params); + // } + // break; + + // CSI Pm ' { + // CSI Pt; Pl; Pb; Pr$ { + // case '{': + // if (this.postfix === '\'') { + // this.setLocatorEvents(this.params); + // } else if (this.postfix === '$') { + // this.selectiveEraseRectangle(this.params); + // } + // break; + + // CSI Ps ' | + // case '|': + // if (this.postfix === '\'') { + // this.requestLocatorPosition(this.params); + // } + // break; + + // CSI P m SP } + // Insert P s Column(s) (default = 1) (DECIC), VT420 and up. + // case '}': + // if (this.postfix === ' ') { + // this.insertColumns(this.params); + // } + // break; + + // CSI P m SP ~ + // Delete P s Column(s) (default = 1) (DECDC), VT420 and up + // case '~': + // if (this.postfix === ' ') { + // this.deleteColumns(this.params); + // } + // break; + + default: + this.error('Unknown CSI code: %s.', ch); + break; + } + + this.prefix = ''; + this.postfix = ''; + break; + + case dcs: + if (ch === '\x1b' || ch === '\x07') { + if (ch === '\x1b') i++; + + switch (this.prefix) { + // User-Defined Keys (DECUDK). + case '': + break; + + // Request Status String (DECRQSS). + // test: echo -e '\eP$q"p\e\\' + case '$q': + var pt = this.currentParam + , valid = false; + + switch (pt) { + // DECSCA + case '"q': + pt = '0"q'; + break; + + // DECSCL + case '"p': + pt = '61"p'; + break; + + // DECSTBM + case 'r': + pt = '' + + (this.scrollTop + 1) + + ';' + + (this.scrollBottom + 1) + + 'r'; + break; + + // SGR + case 'm': + pt = '0m'; + break; + + default: + this.error('Unknown DCS Pt: %s.', pt); + pt = ''; + break; + } + + this.send('\x1bP' + +valid + '$r' + pt + '\x1b\\'); + break; + + // Set Termcap/Terminfo Data (xterm, experimental). + case '+p': + break; + + // Request Termcap/Terminfo String (xterm, experimental) + // Regular xterm does not even respond to this sequence. + // This can cause a small glitch in vim. + // test: echo -ne '\eP+q6b64\e\\' + case '+q': + var pt = this.currentParam + , valid = false; + + this.send('\x1bP' + +valid + '+r' + pt + '\x1b\\'); + break; + + default: + this.error('Unknown DCS prefix: %s.', this.prefix); + break; + } + + this.currentParam = 0; + this.prefix = ''; + this.state = normal; + } else if (!this.currentParam) { + if (!this.prefix && ch !== '$' && ch !== '+') { + this.currentParam = ch; + } else if (this.prefix.length === 2) { + this.currentParam = ch; + } else { + this.prefix += ch; + } + } else { + this.currentParam += ch; + } + break; + + case ignore: + // For PM and APC. + if (ch === '\x1b' || ch === '\x07') { + if (ch === '\x1b') i++; + this.state = normal; + } + break; + } + } + + this.updateRange(this.y); + this.refresh(this.refreshStart, this.refreshEnd); +}; + +Terminal.prototype.writeln = function(data) { + this.write(data + '\r\n'); +}; + +Terminal.prototype.keyDown = function(ev) { + var key; + + switch (ev.keyCode) { + // backspace + case 8: + if (ev.shiftKey) { + key = '\x08'; // ^H + break; + } + key = '\x7f'; // ^? + break; + // tab + case 9: + if (ev.shiftKey) { + key = '\x1b[Z'; + break; + } + key = '\t'; + break; + // return/enter + case 13: + key = '\r'; + break; + // escape + case 27: + key = '\x1b'; + break; + // left-arrow + case 37: + if (this.applicationKeypad) { + key = '\x1bOD'; // SS3 as ^[O for 7-bit + //key = '\x8fD'; // SS3 as 0x8f for 8-bit + break; + } + key = '\x1b[D'; + break; + // right-arrow + case 39: + if (this.applicationKeypad) { + key = '\x1bOC'; + break; + } + key = '\x1b[C'; + break; + // up-arrow + case 38: + if (this.applicationKeypad) { + key = '\x1bOA'; + break; + } + if (ev.ctrlKey) { + this.scrollDisp(-1); + return cancel(ev); + } else { + key = '\x1b[A'; + } + break; + // down-arrow + case 40: + if (this.applicationKeypad) { + key = '\x1bOB'; + break; + } + if (ev.ctrlKey) { + this.scrollDisp(1); + return cancel(ev); + } else { + key = '\x1b[B'; + } + break; + // delete + case 46: + key = '\x1b[3~'; + break; + // insert + case 45: + key = '\x1b[2~'; + break; + // home + case 36: + if (this.applicationKeypad) { + key = '\x1bOH'; + break; + } + key = '\x1bOH'; + break; + // end + case 35: + if (this.applicationKeypad) { + key = '\x1bOF'; + break; + } + key = '\x1bOF'; + break; + // page up + case 33: + if (ev.shiftKey) { + this.scrollDisp(-(this.rows - 1)); + return cancel(ev); + } else { + key = '\x1b[5~'; + } + break; + // page down + case 34: + if (ev.shiftKey) { + this.scrollDisp(this.rows - 1); + return cancel(ev); + } else { + key = '\x1b[6~'; + } + break; + // F1 + case 112: + key = '\x1bOP'; + break; + // F2 + case 113: + key = '\x1bOQ'; + break; + // F3 + case 114: + key = '\x1bOR'; + break; + // F4 + case 115: + key = '\x1bOS'; + break; + // F5 + case 116: + key = '\x1b[15~'; + break; + // F6 + case 117: + key = '\x1b[17~'; + break; + // F7 + case 118: + key = '\x1b[18~'; + break; + // F8 + case 119: + key = '\x1b[19~'; + break; + // F9 + case 120: + key = '\x1b[20~'; + break; + // F10 + case 121: + key = '\x1b[21~'; + break; + // F11 + case 122: + key = '\x1b[23~'; + break; + // F12 + case 123: + key = '\x1b[24~'; + break; + default: + // a-z and space + if (ev.ctrlKey) { + if (ev.keyCode >= 65 && ev.keyCode <= 90) { + key = String.fromCharCode(ev.keyCode - 64); + } else if (ev.keyCode === 32) { + // NUL + key = String.fromCharCode(0); + } else if (ev.keyCode >= 51 && ev.keyCode <= 55) { + // escape, file sep, group sep, record sep, unit sep + key = String.fromCharCode(ev.keyCode - 51 + 27); + } else if (ev.keyCode === 56) { + // delete + key = String.fromCharCode(127); + } else if (ev.keyCode === 219) { + // ^[ - escape + key = String.fromCharCode(27); + } else if (ev.keyCode === 221) { + // ^] - group sep + key = String.fromCharCode(29); + } + } else if ((!isMac && ev.altKey) || (isMac && ev.metaKey)) { + if (ev.keyCode >= 65 && ev.keyCode <= 90) { + key = '\x1b' + String.fromCharCode(ev.keyCode + 32); + } else if (ev.keyCode === 192) { + key = '\x1b`'; + } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { + key = '\x1b' + (ev.keyCode - 48); + } + } + break; + } + + this.emit('keydown', ev); + + if (key) { + this.emit('key', key, ev); + + this.showCursor(); + this.handler(key); + + return cancel(ev); + } + + return true; +}; + +Terminal.prototype.setgLevel = function(g) { + this.glevel = g; + this.charset = this.charsets[g]; +}; + +Terminal.prototype.setgCharset = function(g, charset) { + this.charsets[g] = charset; + if (this.glevel === g) { + this.charset = charset; + } +}; + +Terminal.prototype.keyPress = function(ev) { + var key; + + cancel(ev); + + if (ev.charCode) { + key = ev.charCode; + } else if (ev.which == null) { + key = ev.keyCode; + } else if (ev.which !== 0 && ev.charCode !== 0) { + key = ev.which; + } else { + return false; + } + + if (!key || ev.ctrlKey || ev.altKey || ev.metaKey) return false; + + key = String.fromCharCode(key); + + this.emit('keypress', key, ev); + this.emit('key', key, ev); + + this.showCursor(); + this.handler(key); + + return false; +}; + +Terminal.prototype.send = function(data) { + var self = this; + + if (!this.queue) { + setTimeout(function() { + self.handler(self.queue); + self.queue = ''; + }, 1); + } + + this.queue += data; +}; + +Terminal.prototype.bell = function() { + if (!Terminal.visualBell) return; + var self = this; + this.element.style.borderColor = 'white'; + setTimeout(function() { + self.element.style.borderColor = ''; + }, 10); + if (Terminal.popOnBell) this.focus(); +}; + +Terminal.prototype.log = function() { + if (!Terminal.debug) return; + if (!window.console || !window.console.log) return; + var args = Array.prototype.slice.call(arguments); + window.console.log.apply(window.console, args); +}; + +Terminal.prototype.error = function() { + if (!Terminal.debug) return; + if (!window.console || !window.console.error) return; + var args = Array.prototype.slice.call(arguments); + window.console.error.apply(window.console, args); +}; + +Terminal.prototype.resize = function(x, y) { + var line + , el + , i + , j + , ch; + + if (x < 1) x = 1; + if (y < 1) y = 1; + + // resize cols + j = this.cols; + if (j < x) { + ch = [this.defAttr, ' ']; + i = this.lines.length; + while (i--) { + while (this.lines[i].length < x) { + this.lines[i].push(ch); + } + } + } else if (j > x) { + i = this.lines.length; + while (i--) { + while (this.lines[i].length > x) { + this.lines[i].pop(); + } + } + } + this.setupStops(j); + this.cols = x; + + // resize rows + j = this.rows; + if (j < y) { + el = this.element; + while (j++ < y) { + if (this.lines.length < y + this.ybase) { + this.lines.push(this.blankLine()); + } + if (this.children.length < y) { + line = document.createElement('div'); + el.appendChild(line); + this.children.push(line); + } + } + } else if (j > y) { + while (j-- > y) { + if (this.lines.length > y + this.ybase) { + this.lines.pop(); + } + if (this.children.length > y) { + el = this.children.pop(); + if (!el) continue; + el.parentNode.removeChild(el); + } + } + } + this.rows = y; + + // make sure the cursor stays on screen + if (this.y >= y) this.y = y - 1; + if (this.x >= x) this.x = x - 1; + + this.scrollTop = 0; + this.scrollBottom = y - 1; + + this.refresh(0, this.rows - 1); + + // it's a real nightmare trying + // to resize the original + // screen buffer. just set it + // to null for now. + this.normal = null; +}; + +Terminal.prototype.updateRange = function(y) { + if (y < this.refreshStart) this.refreshStart = y; + if (y > this.refreshEnd) this.refreshEnd = y; +}; + +Terminal.prototype.maxRange = function() { + this.refreshStart = 0; + this.refreshEnd = this.rows - 1; +}; + +Terminal.prototype.setupStops = function(i) { + if (i != null) { + if (!this.tabs[i]) { + i = this.prevStop(i); + } + } else { + this.tabs = {}; + i = 0; + } + + for (; i < this.cols; i += 8) { + this.tabs[i] = true; + } +}; + +Terminal.prototype.prevStop = function(x) { + if (x == null) x = this.x; + while (!this.tabs[--x] && x > 0); + return x >= this.cols + ? this.cols - 1 + : x < 0 ? 0 : x; +}; + +Terminal.prototype.nextStop = function(x) { + if (x == null) x = this.x; + while (!this.tabs[++x] && x < this.cols); + return x >= this.cols + ? this.cols - 1 + : x < 0 ? 0 : x; +}; + +Terminal.prototype.eraseRight = function(x, y) { + var line = this.lines[this.ybase + y] + , ch = [this.curAttr, ' ']; // xterm + + for (; x < this.cols; x++) { + line[x] = ch; + } + + this.updateRange(y); +}; + +Terminal.prototype.eraseLeft = function(x, y) { + var line = this.lines[this.ybase + y] + , ch = [this.curAttr, ' ']; // xterm + + x++; + while (x--) line[x] = ch; + + this.updateRange(y); +}; + +Terminal.prototype.eraseLine = function(y) { + this.eraseRight(0, y); +}; + +Terminal.prototype.blankLine = function(cur) { + var attr = cur + ? this.curAttr + : this.defAttr; + + var ch = [attr, ' '] + , line = [] + , i = 0; + + for (; i < this.cols; i++) { + line[i] = ch; + } + + return line; +}; + +Terminal.prototype.ch = function(cur) { + return cur + ? [this.curAttr, ' '] + : [this.defAttr, ' ']; +}; + +Terminal.prototype.is = function(term) { + var name = this.termName || Terminal.termName; + return (name + '').indexOf(term) === 0; +}; + +Terminal.prototype.handler = function(data) { + this.emit('data', data); +}; + +Terminal.prototype.handleTitle = function(title) { + this.emit('title', title); +}; + +/** + * ESC + */ + +// ESC D Index (IND is 0x84). +Terminal.prototype.index = function() { + this.y++; + if (this.y > this.scrollBottom) { + this.y--; + this.scroll(); + } + this.state = normal; +}; + +// ESC M Reverse Index (RI is 0x8d). +Terminal.prototype.reverseIndex = function() { + var j; + this.y--; + if (this.y < this.scrollTop) { + this.y++; + // possibly move the code below to term.reverseScroll(); + // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' + // blankLine(true) is xterm/linux behavior + this.lines.splice(this.y + this.ybase, 0, this.blankLine(true)); + j = this.rows - 1 - this.scrollBottom; + this.lines.splice(this.rows - 1 + this.ybase - j + 1, 1); + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); + } + this.state = normal; +}; + +// ESC c Full Reset (RIS). +Terminal.prototype.reset = function() { + //Terminal.call(this, this.cols, this.rows); + Terminal.call(this, this.cols, this.rows, this.handler); + this.refresh(0, this.rows - 1); +}; + +// ESC H Tab Set (HTS is 0x88). +Terminal.prototype.tabSet = function() { + this.tabs[this.x] = true; + this.state = normal; +}; + +/** + * CSI + */ + +// CSI Ps A +// Cursor Up Ps Times (default = 1) (CUU). +Terminal.prototype.cursorUp = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y -= param; + if (this.y < 0) this.y = 0; +}; + +// CSI Ps B +// Cursor Down Ps Times (default = 1) (CUD). +Terminal.prototype.cursorDown = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y += param; + if (this.y >= this.rows) { + this.y = this.rows - 1; + } +}; + +// CSI Ps C +// Cursor Forward Ps Times (default = 1) (CUF). +Terminal.prototype.cursorForward = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.x += param; + if (this.x >= this.cols) { + this.x = this.cols - 1; + } +}; + +// CSI Ps D +// Cursor Backward Ps Times (default = 1) (CUB). +Terminal.prototype.cursorBackward = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.x -= param; + if (this.x < 0) this.x = 0; +}; + +// CSI Ps ; Ps H +// Cursor Position [row;column] (default = [1,1]) (CUP). +Terminal.prototype.cursorPos = function(params) { + var row, col; + + row = params[0] - 1; + + if (params.length >= 2) { + col = params[1] - 1; + } else { + col = 0; + } + + if (row < 0) { + row = 0; + } else if (row >= this.rows) { + row = this.rows - 1; + } + + if (col < 0) { + col = 0; + } else if (col >= this.cols) { + col = this.cols - 1; + } + + this.x = col; + this.y = row; +}; + +// CSI Ps J Erase in Display (ED). +// Ps = 0 -> Erase Below (default). +// Ps = 1 -> Erase Above. +// Ps = 2 -> Erase All. +// Ps = 3 -> Erase Saved Lines (xterm). +// CSI ? Ps J +// Erase in Display (DECSED). +// Ps = 0 -> Selective Erase Below (default). +// Ps = 1 -> Selective Erase Above. +// Ps = 2 -> Selective Erase All. +Terminal.prototype.eraseInDisplay = function(params) { + var j; + switch (params[0]) { + case 0: + this.eraseRight(this.x, this.y); + j = this.y + 1; + for (; j < this.rows; j++) { + this.eraseLine(j); + } + break; + case 1: + this.eraseLeft(this.x, this.y); + j = this.y; + while (j--) { + this.eraseLine(j); + } + break; + case 2: + j = this.rows; + while (j--) this.eraseLine(j); + break; + case 3: + ; // no saved lines + break; + } +}; + +// CSI Ps K Erase in Line (EL). +// Ps = 0 -> Erase to Right (default). +// Ps = 1 -> Erase to Left. +// Ps = 2 -> Erase All. +// CSI ? Ps K +// Erase in Line (DECSEL). +// Ps = 0 -> Selective Erase to Right (default). +// Ps = 1 -> Selective Erase to Left. +// Ps = 2 -> Selective Erase All. +Terminal.prototype.eraseInLine = function(params) { + switch (params[0]) { + case 0: + this.eraseRight(this.x, this.y); + break; + case 1: + this.eraseLeft(this.x, this.y); + break; + case 2: + this.eraseLine(this.y); + break; + } +}; + +// CSI Pm m Character Attributes (SGR). +// Ps = 0 -> Normal (default). +// Ps = 1 -> Bold. +// Ps = 4 -> Underlined. +// Ps = 5 -> Blink (appears as Bold). +// Ps = 7 -> Inverse. +// Ps = 8 -> Invisible, i.e., hidden (VT300). +// Ps = 2 2 -> Normal (neither bold nor faint). +// Ps = 2 4 -> Not underlined. +// Ps = 2 5 -> Steady (not blinking). +// Ps = 2 7 -> Positive (not inverse). +// Ps = 2 8 -> Visible, i.e., not hidden (VT300). +// Ps = 3 0 -> Set foreground color to Black. +// Ps = 3 1 -> Set foreground color to Red. +// Ps = 3 2 -> Set foreground color to Green. +// Ps = 3 3 -> Set foreground color to Yellow. +// Ps = 3 4 -> Set foreground color to Blue. +// Ps = 3 5 -> Set foreground color to Magenta. +// Ps = 3 6 -> Set foreground color to Cyan. +// Ps = 3 7 -> Set foreground color to White. +// Ps = 3 9 -> Set foreground color to default (original). +// Ps = 4 0 -> Set background color to Black. +// Ps = 4 1 -> Set background color to Red. +// Ps = 4 2 -> Set background color to Green. +// Ps = 4 3 -> Set background color to Yellow. +// Ps = 4 4 -> Set background color to Blue. +// Ps = 4 5 -> Set background color to Magenta. +// Ps = 4 6 -> Set background color to Cyan. +// Ps = 4 7 -> Set background color to White. +// Ps = 4 9 -> Set background color to default (original). + +// If 16-color support is compiled, the following apply. Assume +// that xterm's resources are set so that the ISO color codes are +// the first 8 of a set of 16. Then the aixterm colors are the +// bright versions of the ISO colors: +// Ps = 9 0 -> Set foreground color to Black. +// Ps = 9 1 -> Set foreground color to Red. +// Ps = 9 2 -> Set foreground color to Green. +// Ps = 9 3 -> Set foreground color to Yellow. +// Ps = 9 4 -> Set foreground color to Blue. +// Ps = 9 5 -> Set foreground color to Magenta. +// Ps = 9 6 -> Set foreground color to Cyan. +// Ps = 9 7 -> Set foreground color to White. +// Ps = 1 0 0 -> Set background color to Black. +// Ps = 1 0 1 -> Set background color to Red. +// Ps = 1 0 2 -> Set background color to Green. +// Ps = 1 0 3 -> Set background color to Yellow. +// Ps = 1 0 4 -> Set background color to Blue. +// Ps = 1 0 5 -> Set background color to Magenta. +// Ps = 1 0 6 -> Set background color to Cyan. +// Ps = 1 0 7 -> Set background color to White. + +// If xterm is compiled with the 16-color support disabled, it +// supports the following, from rxvt: +// Ps = 1 0 0 -> Set foreground and background color to +// default. + +// If 88- or 256-color support is compiled, the following apply. +// Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second +// Ps. +// Ps = 4 8 ; 5 ; Ps -> Set background color to the second +// Ps. +Terminal.prototype.charAttributes = function(params) { + var l = params.length + , i = 0 + , bg + , fg + , p; + + for (; i < l; i++) { + p = params[i]; + if (p >= 30 && p <= 37) { + // fg color 8 + this.curAttr = (this.curAttr & ~(0x1ff << 9)) | ((p - 30) << 9); + } else if (p >= 40 && p <= 47) { + // bg color 8 + this.curAttr = (this.curAttr & ~0x1ff) | (p - 40); + } else if (p >= 90 && p <= 97) { + // fg color 16 + p += 8; + this.curAttr = (this.curAttr & ~(0x1ff << 9)) | ((p - 90) << 9); + } else if (p >= 100 && p <= 107) { + // bg color 16 + p += 8; + this.curAttr = (this.curAttr & ~0x1ff) | (p - 100); + } else if (p === 0) { + // default + this.curAttr = this.defAttr; + } else if (p === 1) { + // bold text + this.curAttr = this.curAttr | (1 << 18); + } else if (p === 4) { + // underlined text + this.curAttr = this.curAttr | (2 << 18); + } else if (p === 7 || p === 27) { + // inverse and positive + // test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m' + if (p === 7) { + if ((this.curAttr >> 18) & 4) continue; + this.curAttr = this.curAttr | (4 << 18); + } else if (p === 27) { + if (~(this.curAttr >> 18) & 4) continue; + this.curAttr = this.curAttr & ~(4 << 18); + } + + bg = this.curAttr & 0x1ff; + fg = (this.curAttr >> 9) & 0x1ff; + + this.curAttr = (this.curAttr & ~0x3ffff) | ((bg << 9) | fg); + } else if (p === 22) { + // not bold + this.curAttr = this.curAttr & ~(1 << 18); + } else if (p === 24) { + // not underlined + this.curAttr = this.curAttr & ~(2 << 18); + } else if (p === 39) { + // reset fg + this.curAttr = this.curAttr & ~(0x1ff << 9); + this.curAttr = this.curAttr | (((this.defAttr >> 9) & 0x1ff) << 9); + } else if (p === 49) { + // reset bg + this.curAttr = this.curAttr & ~0x1ff; + this.curAttr = this.curAttr | (this.defAttr & 0x1ff); + } else if (p === 38) { + // fg color 256 + if (params[i+1] !== 5) continue; + i += 2; + p = params[i] & 0xff; + // convert 88 colors to 256 + // if (this.is('rxvt-unicode') && p < 88) p = p * 2.9090 | 0; + this.curAttr = (this.curAttr & ~(0x1ff << 9)) | (p << 9); + } else if (p === 48) { + // bg color 256 + if (params[i+1] !== 5) continue; + i += 2; + p = params[i] & 0xff; + // convert 88 colors to 256 + // if (this.is('rxvt-unicode') && p < 88) p = p * 2.9090 | 0; + this.curAttr = (this.curAttr & ~0x1ff) | p; + } + } +}; + +// CSI Ps n Device Status Report (DSR). +// Ps = 5 -> Status Report. Result (``OK'') is +// CSI 0 n +// Ps = 6 -> Report Cursor Position (CPR) [row;column]. +// Result is +// CSI r ; c R +// CSI ? Ps n +// Device Status Report (DSR, DEC-specific). +// Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI +// ? r ; c R (assumes page is zero). +// Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready). +// or CSI ? 1 1 n (not ready). +// Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked) +// or CSI ? 2 1 n (locked). +// Ps = 2 6 -> Report Keyboard status as +// CSI ? 2 7 ; 1 ; 0 ; 0 n (North American). +// The last two parameters apply to VT400 & up, and denote key- +// board ready and LK01 respectively. +// Ps = 5 3 -> Report Locator status as +// CSI ? 5 3 n Locator available, if compiled-in, or +// CSI ? 5 0 n No Locator, if not. +Terminal.prototype.deviceStatus = function(params) { + if (!this.prefix) { + switch (params[0]) { + case 5: + // status report + this.send('\x1b[0n'); + break; + case 6: + // cursor position + this.send('\x1b[' + + (this.y + 1) + + ';' + + (this.x + 1) + + 'R'); + break; + } + } else if (this.prefix === '?') { + // modern xterm doesnt seem to + // respond to any of these except ?6, 6, and 5 + switch (params[0]) { + case 6: + // cursor position + this.send('\x1b[?' + + (this.y + 1) + + ';' + + (this.x + 1) + + 'R'); + break; + case 15: + // no printer + // this.send('\x1b[?11n'); + break; + case 25: + // dont support user defined keys + // this.send('\x1b[?21n'); + break; + case 26: + // north american keyboard + // this.send('\x1b[?27;1;0;0n'); + break; + case 53: + // no dec locator/mouse + // this.send('\x1b[?50n'); + break; + } + } +}; + +/** + * Additions + */ + +// CSI Ps @ +// Insert Ps (Blank) Character(s) (default = 1) (ICH). +Terminal.prototype.insertChars = function(params) { + var param, row, j, ch; + + param = params[0]; + if (param < 1) param = 1; + + row = this.y + this.ybase; + j = this.x; + ch = [this.curAttr, ' ']; // xterm + + while (param-- && j < this.cols) { + this.lines[row].splice(j++, 0, ch); + this.lines[row].pop(); + } +}; + +// CSI Ps E +// Cursor Next Line Ps Times (default = 1) (CNL). +// same as CSI Ps B ? +Terminal.prototype.cursorNextLine = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y += param; + if (this.y >= this.rows) { + this.y = this.rows - 1; + } + this.x = 0; +}; + +// CSI Ps F +// Cursor Preceding Line Ps Times (default = 1) (CNL). +// reuse CSI Ps A ? +Terminal.prototype.cursorPrecedingLine = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y -= param; + if (this.y < 0) this.y = 0; + this.x = 0; +}; + +// CSI Ps G +// Cursor Character Absolute [column] (default = [row,1]) (CHA). +Terminal.prototype.cursorCharAbsolute = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.x = param - 1; +}; + +// CSI Ps L +// Insert Ps Line(s) (default = 1) (IL). +Terminal.prototype.insertLines = function(params) { + var param, row, j; + + param = params[0]; + if (param < 1) param = 1; + row = this.y + this.ybase; + + j = this.rows - 1 - this.scrollBottom; + j = this.rows - 1 + this.ybase - j + 1; + + while (param--) { + // test: echo -e '\e[44m\e[1L\e[0m' + // blankLine(true) - xterm/linux behavior + this.lines.splice(row, 0, this.blankLine(true)); + this.lines.splice(j, 1); + } + + // this.maxRange(); + this.updateRange(this.y); + this.updateRange(this.scrollBottom); +}; + +// CSI Ps M +// Delete Ps Line(s) (default = 1) (DL). +Terminal.prototype.deleteLines = function(params) { + var param, row, j; + + param = params[0]; + if (param < 1) param = 1; + row = this.y + this.ybase; + + j = this.rows - 1 - this.scrollBottom; + j = this.rows - 1 + this.ybase - j; + + while (param--) { + // test: echo -e '\e[44m\e[1M\e[0m' + // blankLine(true) - xterm/linux behavior + this.lines.splice(j + 1, 0, this.blankLine(true)); + this.lines.splice(row, 1); + } + + // this.maxRange(); + this.updateRange(this.y); + this.updateRange(this.scrollBottom); +}; + +// CSI Ps P +// Delete Ps Character(s) (default = 1) (DCH). +Terminal.prototype.deleteChars = function(params) { + var param, row, ch; + + param = params[0]; + if (param < 1) param = 1; + + row = this.y + this.ybase; + ch = [this.curAttr, ' ']; // xterm + + while (param--) { + this.lines[row].splice(this.x, 1); + this.lines[row].push(ch); + } +}; + +// CSI Ps X +// Erase Ps Character(s) (default = 1) (ECH). +Terminal.prototype.eraseChars = function(params) { + var param, row, j, ch; + + param = params[0]; + if (param < 1) param = 1; + + row = this.y + this.ybase; + j = this.x; + ch = [this.curAttr, ' ']; // xterm + + while (param-- && j < this.cols) { + this.lines[row][j++] = ch; + } +}; + +// CSI Pm ` Character Position Absolute +// [column] (default = [row,1]) (HPA). +Terminal.prototype.charPosAbsolute = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.x = param - 1; + if (this.x >= this.cols) { + this.x = this.cols - 1; + } +}; + +// 141 61 a * HPR - +// Horizontal Position Relative +// reuse CSI Ps C ? +Terminal.prototype.HPositionRelative = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.x += param; + if (this.x >= this.cols) { + this.x = this.cols - 1; + } +}; + +// CSI Ps c Send Device Attributes (Primary DA). +// Ps = 0 or omitted -> request attributes from terminal. The +// response depends on the decTerminalID resource setting. +// -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'') +// -> CSI ? 1 ; 0 c (``VT101 with No Options'') +// -> CSI ? 6 c (``VT102'') +// -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'') +// The VT100-style response parameters do not mean anything by +// themselves. VT220 parameters do, telling the host what fea- +// tures the terminal supports: +// Ps = 1 -> 132-columns. +// Ps = 2 -> Printer. +// Ps = 6 -> Selective erase. +// Ps = 8 -> User-defined keys. +// Ps = 9 -> National replacement character sets. +// Ps = 1 5 -> Technical characters. +// Ps = 2 2 -> ANSI color, e.g., VT525. +// Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode). +// CSI > Ps c +// Send Device Attributes (Secondary DA). +// Ps = 0 or omitted -> request the terminal's identification +// code. The response depends on the decTerminalID resource set- +// ting. It should apply only to VT220 and up, but xterm extends +// this to VT100. +// -> CSI > Pp ; Pv ; Pc c +// where Pp denotes the terminal type +// Pp = 0 -> ``VT100''. +// Pp = 1 -> ``VT220''. +// and Pv is the firmware version (for xterm, this was originally +// the XFree86 patch number, starting with 95). In a DEC termi- +// nal, Pc indicates the ROM cartridge registration number and is +// always zero. +// More information: +// xterm/charproc.c - line 2012, for more information. +// vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) +Terminal.prototype.sendDeviceAttributes = function(params) { + if (params[0] > 0) return; + + if (!this.prefix) { + if (this.is('xterm') + || this.is('rxvt-unicode') + || this.is('screen')) { + this.send('\x1b[?1;2c'); + } else if (this.is('linux')) { + this.send('\x1b[?6c'); + } + } else if (this.prefix === '>') { + // xterm and urxvt + // seem to spit this + // out around ~370 times (?). + if (this.is('xterm')) { + this.send('\x1b[>0;276;0c'); + } else if (this.is('rxvt-unicode')) { + this.send('\x1b[>85;95;0c'); + } else if (this.is('linux')) { + // not supported by linux console. + // linux console echoes parameters. + this.send(params[0] + 'c'); + } else if (this.is('screen')) { + this.send('\x1b[>83;40003;0c'); + } + } +}; + +// CSI Pm d +// Line Position Absolute [row] (default = [1,column]) (VPA). +Terminal.prototype.linePosAbsolute = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y = param - 1; + if (this.y >= this.rows) { + this.y = this.rows - 1; + } +}; + +// 145 65 e * VPR - Vertical Position Relative +// reuse CSI Ps B ? +Terminal.prototype.VPositionRelative = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y += param; + if (this.y >= this.rows) { + this.y = this.rows - 1; + } +}; + +// CSI Ps ; Ps f +// Horizontal and Vertical Position [row;column] (default = +// [1,1]) (HVP). +Terminal.prototype.HVPosition = function(params) { + if (params[0] < 1) params[0] = 1; + if (params[1] < 1) params[1] = 1; + + this.y = params[0] - 1; + if (this.y >= this.rows) { + this.y = this.rows - 1; + } + + this.x = params[1] - 1; + if (this.x >= this.cols) { + this.x = this.cols - 1; + } +}; + +// CSI Pm h Set Mode (SM). +// Ps = 2 -> Keyboard Action Mode (AM). +// Ps = 4 -> Insert Mode (IRM). +// Ps = 1 2 -> Send/receive (SRM). +// Ps = 2 0 -> Automatic Newline (LNM). +// CSI ? Pm h +// DEC Private Mode Set (DECSET). +// Ps = 1 -> Application Cursor Keys (DECCKM). +// Ps = 2 -> Designate USASCII for character sets G0-G3 +// (DECANM), and set VT100 mode. +// Ps = 3 -> 132 Column Mode (DECCOLM). +// Ps = 4 -> Smooth (Slow) Scroll (DECSCLM). +// Ps = 5 -> Reverse Video (DECSCNM). +// Ps = 6 -> Origin Mode (DECOM). +// Ps = 7 -> Wraparound Mode (DECAWM). +// Ps = 8 -> Auto-repeat Keys (DECARM). +// Ps = 9 -> Send Mouse X & Y on button press. See the sec- +// tion Mouse Tracking. +// Ps = 1 0 -> Show toolbar (rxvt). +// Ps = 1 2 -> Start Blinking Cursor (att610). +// Ps = 1 8 -> Print form feed (DECPFF). +// Ps = 1 9 -> Set print extent to full screen (DECPEX). +// Ps = 2 5 -> Show Cursor (DECTCEM). +// Ps = 3 0 -> Show scrollbar (rxvt). +// Ps = 3 5 -> Enable font-shifting functions (rxvt). +// Ps = 3 8 -> Enter Tektronix Mode (DECTEK). +// Ps = 4 0 -> Allow 80 -> 132 Mode. +// Ps = 4 1 -> more(1) fix (see curses resource). +// Ps = 4 2 -> Enable Nation Replacement Character sets (DECN- +// RCM). +// Ps = 4 4 -> Turn On Margin Bell. +// Ps = 4 5 -> Reverse-wraparound Mode. +// Ps = 4 6 -> Start Logging. This is normally disabled by a +// compile-time option. +// Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis- +// abled by the titeInhibit resource). +// Ps = 6 6 -> Application keypad (DECNKM). +// Ps = 6 7 -> Backarrow key sends backspace (DECBKM). +// Ps = 1 0 0 0 -> Send Mouse X & Y on button press and +// release. See the section Mouse Tracking. +// Ps = 1 0 0 1 -> Use Hilite Mouse Tracking. +// Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking. +// Ps = 1 0 0 3 -> Use All Motion Mouse Tracking. +// Ps = 1 0 0 4 -> Send FocusIn/FocusOut events. +// Ps = 1 0 0 5 -> Enable Extended Mouse Mode. +// Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt). +// Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt). +// Ps = 1 0 3 4 -> Interpret "meta" key, sets eighth bit. +// (enables the eightBitInput resource). +// Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num- +// Lock keys. (This enables the numLock resource). +// Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This +// enables the metaSendsEscape resource). +// Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete +// key. +// Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This +// enables the altSendsEscape resource). +// Ps = 1 0 4 0 -> Keep selection even if not highlighted. +// (This enables the keepSelection resource). +// Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables +// the selectToClipboard resource). +// Ps = 1 0 4 2 -> Enable Urgency window manager hint when +// Control-G is received. (This enables the bellIsUrgent +// resource). +// Ps = 1 0 4 3 -> Enable raising of the window when Control-G +// is received. (enables the popOnBell resource). +// Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be +// disabled by the titeInhibit resource). +// Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis- +// abled by the titeInhibit resource). +// Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate +// Screen Buffer, clearing it first. (This may be disabled by +// the titeInhibit resource). This combines the effects of the 1 +// 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based +// applications rather than the 4 7 mode. +// Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode. +// Ps = 1 0 5 1 -> Set Sun function-key mode. +// Ps = 1 0 5 2 -> Set HP function-key mode. +// Ps = 1 0 5 3 -> Set SCO function-key mode. +// Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6). +// Ps = 1 0 6 1 -> Set VT220 keyboard emulation. +// Ps = 2 0 0 4 -> Set bracketed paste mode. +// Modes: +// http://vt100.net/docs/vt220-rm/chapter4.html +Terminal.prototype.setMode = function(params) { + if (typeof params === 'object') { + var l = params.length + , i = 0; + + for (; i < l; i++) { + this.setMode(params[i]); + } + + return; + } + + if (!this.prefix) { + switch (params) { + case 4: + this.insertMode = true; + break; + case 20: + //this.convertEol = true; + break; + } + } else if (this.prefix === '?') { + switch (params) { + case 1: + this.applicationKeypad = true; + break; + case 2: + this.setgCharset(0, Terminal.charsets.US); + this.setgCharset(1, Terminal.charsets.US); + this.setgCharset(2, Terminal.charsets.US); + this.setgCharset(3, Terminal.charsets.US); + // set VT100 mode here + break; + case 3: // 132 col mode + this.savedCols = this.cols; + this.resize(132, this.rows); + break; + case 6: + this.originMode = true; + break; + case 7: + this.wraparoundMode = true; + break; + case 12: + // this.cursorBlink = true; + break; + case 9: // X10 Mouse + // no release, no motion, no wheel, no modifiers. + case 1000: // vt200 mouse + // no motion. + // no modifiers, except control on the wheel. + case 1002: // button event mouse + case 1003: // any event mouse + // any event - sends motion events, + // even if there is no button held down. + this.x10Mouse = params === 9; + this.vt200Mouse = params === 1000; + this.normalMouse = params > 1000; + this.mouseEvents = true; + this.element.style.cursor = 'default'; + this.log('Binding to mouse events.'); + break; + case 1004: // send focusin/focusout events + // focusin: ^[[I + // focusout: ^[[O + this.sendFocus = true; + break; + case 1005: // utf8 ext mode mouse + this.utfMouse = true; + // for wide terminals + // simply encodes large values as utf8 characters + break; + case 1006: // sgr ext mode mouse + this.sgrMouse = true; + // for wide terminals + // does not add 32 to fields + // press: ^[[ Keyboard Action Mode (AM). +// Ps = 4 -> Replace Mode (IRM). +// Ps = 1 2 -> Send/receive (SRM). +// Ps = 2 0 -> Normal Linefeed (LNM). +// CSI ? Pm l +// DEC Private Mode Reset (DECRST). +// Ps = 1 -> Normal Cursor Keys (DECCKM). +// Ps = 2 -> Designate VT52 mode (DECANM). +// Ps = 3 -> 80 Column Mode (DECCOLM). +// Ps = 4 -> Jump (Fast) Scroll (DECSCLM). +// Ps = 5 -> Normal Video (DECSCNM). +// Ps = 6 -> Normal Cursor Mode (DECOM). +// Ps = 7 -> No Wraparound Mode (DECAWM). +// Ps = 8 -> No Auto-repeat Keys (DECARM). +// Ps = 9 -> Don't send Mouse X & Y on button press. +// Ps = 1 0 -> Hide toolbar (rxvt). +// Ps = 1 2 -> Stop Blinking Cursor (att610). +// Ps = 1 8 -> Don't print form feed (DECPFF). +// Ps = 1 9 -> Limit print to scrolling region (DECPEX). +// Ps = 2 5 -> Hide Cursor (DECTCEM). +// Ps = 3 0 -> Don't show scrollbar (rxvt). +// Ps = 3 5 -> Disable font-shifting functions (rxvt). +// Ps = 4 0 -> Disallow 80 -> 132 Mode. +// Ps = 4 1 -> No more(1) fix (see curses resource). +// Ps = 4 2 -> Disable Nation Replacement Character sets (DEC- +// NRCM). +// Ps = 4 4 -> Turn Off Margin Bell. +// Ps = 4 5 -> No Reverse-wraparound Mode. +// Ps = 4 6 -> Stop Logging. (This is normally disabled by a +// compile-time option). +// Ps = 4 7 -> Use Normal Screen Buffer. +// Ps = 6 6 -> Numeric keypad (DECNKM). +// Ps = 6 7 -> Backarrow key sends delete (DECBKM). +// Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and +// release. See the section Mouse Tracking. +// Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking. +// Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking. +// Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking. +// Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events. +// Ps = 1 0 0 5 -> Disable Extended Mouse Mode. +// Ps = 1 0 1 0 -> Don't scroll to bottom on tty output +// (rxvt). +// Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt). +// Ps = 1 0 3 4 -> Don't interpret "meta" key. (This disables +// the eightBitInput resource). +// Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num- +// Lock keys. (This disables the numLock resource). +// Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key. +// (This disables the metaSendsEscape resource). +// Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad +// Delete key. +// Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key. +// (This disables the altSendsEscape resource). +// Ps = 1 0 4 0 -> Do not keep selection when not highlighted. +// (This disables the keepSelection resource). +// Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables +// the selectToClipboard resource). +// Ps = 1 0 4 2 -> Disable Urgency window manager hint when +// Control-G is received. (This disables the bellIsUrgent +// resource). +// Ps = 1 0 4 3 -> Disable raising of the window when Control- +// G is received. (This disables the popOnBell resource). +// Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen +// first if in the Alternate Screen. (This may be disabled by +// the titeInhibit resource). +// Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be +// disabled by the titeInhibit resource). +// Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor +// as in DECRC. (This may be disabled by the titeInhibit +// resource). This combines the effects of the 1 0 4 7 and 1 0 +// 4 8 modes. Use this with terminfo-based applications rather +// than the 4 7 mode. +// Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode. +// Ps = 1 0 5 1 -> Reset Sun function-key mode. +// Ps = 1 0 5 2 -> Reset HP function-key mode. +// Ps = 1 0 5 3 -> Reset SCO function-key mode. +// Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6). +// Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. +// Ps = 2 0 0 4 -> Reset bracketed paste mode. +Terminal.prototype.resetMode = function(params) { + if (typeof params === 'object') { + var l = params.length + , i = 0; + + for (; i < l; i++) { + this.resetMode(params[i]); + } + + return; + } + + if (!this.prefix) { + switch (params) { + case 4: + this.insertMode = false; + break; + case 20: + //this.convertEol = false; + break; + } + } else if (this.prefix === '?') { + switch (params) { + case 1: + this.applicationKeypad = false; + break; + case 3: + if (this.cols === 132 && this.savedCols) { + this.resize(this.savedCols, this.rows); + } + delete this.savedCols; + break; + case 6: + this.originMode = false; + break; + case 7: + this.wraparoundMode = false; + break; + case 12: + // this.cursorBlink = false; + break; + case 9: // X10 Mouse + case 1000: // vt200 mouse + case 1002: // button event mouse + case 1003: // any event mouse + this.x10Mouse = false; + this.vt200Mouse = false; + this.normalMouse = false; + this.mouseEvents = false; + this.element.style.cursor = ''; + break; + case 1004: // send focusin/focusout events + this.sendFocus = false; + break; + case 1005: // utf8 ext mode mouse + this.utfMouse = false; + break; + case 1006: // sgr ext mode mouse + this.sgrMouse = false; + break; + case 1015: // urxvt ext mode mouse + this.urxvtMouse = false; + break; + case 25: // hide cursor + this.cursorHidden = true; + break; + case 1049: // alt screen buffer cursor + ; // FALL-THROUGH + case 47: // normal screen buffer + case 1047: // normal screen buffer - clearing it first + if (this.normal) { + this.lines = this.normal.lines; + this.ybase = this.normal.ybase; + this.ydisp = this.normal.ydisp; + this.x = this.normal.x; + this.y = this.normal.y; + this.scrollTop = this.normal.scrollTop; + this.scrollBottom = this.normal.scrollBottom; + this.tabs = this.normal.tabs; + this.normal = null; + // if (params === 1049) { + // this.x = this.savedX; + // this.y = this.savedY; + // } + this.refresh(0, this.rows - 1); + this.showCursor(); + } + break; + } + } +}; + +// CSI Ps ; Ps r +// Set Scrolling Region [top;bottom] (default = full size of win- +// dow) (DECSTBM). +// CSI ? Pm r +Terminal.prototype.setScrollRegion = function(params) { + if (this.prefix) return; + this.scrollTop = (params[0] || 1) - 1; + this.scrollBottom = (params[1] || this.rows) - 1; + this.x = 0; + this.y = 0; +}; + +// CSI s +// Save cursor (ANSI.SYS). +Terminal.prototype.saveCursor = function(params) { + this.savedX = this.x; + this.savedY = this.y; +}; + +// CSI u +// Restore cursor (ANSI.SYS). +Terminal.prototype.restoreCursor = function(params) { + this.x = this.savedX || 0; + this.y = this.savedY || 0; +}; + +/** + * Lesser Used + */ + +// CSI Ps I +// Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). +Terminal.prototype.cursorForwardTab = function(params) { + var param = params[0] || 1; + while (param--) { + this.x = this.nextStop(); + } +}; + +// CSI Ps S Scroll up Ps lines (default = 1) (SU). +Terminal.prototype.scrollUp = function(params) { + var param = params[0] || 1; + while (param--) { + this.lines.splice(this.ybase + this.scrollTop, 1); + this.lines.splice(this.ybase + this.scrollBottom, 0, this.blankLine()); + } + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); +}; + +// CSI Ps T Scroll down Ps lines (default = 1) (SD). +Terminal.prototype.scrollDown = function(params) { + var param = params[0] || 1; + while (param--) { + this.lines.splice(this.ybase + this.scrollBottom, 1); + this.lines.splice(this.ybase + this.scrollTop, 0, this.blankLine()); + } + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); +}; + +// CSI Ps ; Ps ; Ps ; Ps ; Ps T +// Initiate highlight mouse tracking. Parameters are +// [func;startx;starty;firstrow;lastrow]. See the section Mouse +// Tracking. +Terminal.prototype.initMouseTracking = function(params) { + // Relevant: DECSET 1001 +}; + +// CSI > Ps; Ps T +// Reset one or more features of the title modes to the default +// value. Normally, "reset" disables the feature. It is possi- +// ble to disable the ability to reset features by compiling a +// different default for the title modes into xterm. +// Ps = 0 -> Do not set window/icon labels using hexadecimal. +// Ps = 1 -> Do not query window/icon labels using hexadeci- +// mal. +// Ps = 2 -> Do not set window/icon labels using UTF-8. +// Ps = 3 -> Do not query window/icon labels using UTF-8. +// (See discussion of "Title Modes"). +Terminal.prototype.resetTitleModes = function(params) { + ; +}; + +// CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). +Terminal.prototype.cursorBackwardTab = function(params) { + var param = params[0] || 1; + while (param--) { + this.x = this.prevStop(); + } +}; + +// CSI Ps b Repeat the preceding graphic character Ps times (REP). +Terminal.prototype.repeatPrecedingCharacter = function(params) { + var param = params[0] || 1 + , line = this.lines[this.ybase + this.y] + , ch = line[this.x - 1] || [this.defAttr, ' ']; + + while (param--) line[this.x++] = ch; +}; + +// CSI Ps g Tab Clear (TBC). +// Ps = 0 -> Clear Current Column (default). +// Ps = 3 -> Clear All. +// Potentially: +// Ps = 2 -> Clear Stops on Line. +// http://vt100.net/annarbor/aaa-ug/section6.html +Terminal.prototype.tabClear = function(params) { + var param = params[0]; + if (param <= 0) { + delete this.tabs[this.x]; + } else if (param === 3) { + this.tabs = {}; + } +}; + +// CSI Pm i Media Copy (MC). +// Ps = 0 -> Print screen (default). +// Ps = 4 -> Turn off printer controller mode. +// Ps = 5 -> Turn on printer controller mode. +// CSI ? Pm i +// Media Copy (MC, DEC-specific). +// Ps = 1 -> Print line containing cursor. +// Ps = 4 -> Turn off autoprint mode. +// Ps = 5 -> Turn on autoprint mode. +// Ps = 1 0 -> Print composed display, ignores DECPEX. +// Ps = 1 1 -> Print all pages. +Terminal.prototype.mediaCopy = function(params) { + ; +}; + +// CSI > Ps; Ps m +// Set or reset resource-values used by xterm to decide whether +// to construct escape sequences holding information about the +// modifiers pressed with a given key. The first parameter iden- +// tifies the resource to set/reset. The second parameter is the +// value to assign to the resource. If the second parameter is +// omitted, the resource is reset to its initial value. +// Ps = 1 -> modifyCursorKeys. +// Ps = 2 -> modifyFunctionKeys. +// Ps = 4 -> modifyOtherKeys. +// If no parameters are given, all resources are reset to their +// initial values. +Terminal.prototype.setResources = function(params) { + ; +}; + +// CSI > Ps n +// Disable modifiers which may be enabled via the CSI > Ps; Ps m +// sequence. This corresponds to a resource value of "-1", which +// cannot be set with the other sequence. The parameter identi- +// fies the resource to be disabled: +// Ps = 1 -> modifyCursorKeys. +// Ps = 2 -> modifyFunctionKeys. +// Ps = 4 -> modifyOtherKeys. +// If the parameter is omitted, modifyFunctionKeys is disabled. +// When modifyFunctionKeys is disabled, xterm uses the modifier +// keys to make an extended sequence of functions rather than +// adding a parameter to each function key to denote the modi- +// fiers. +Terminal.prototype.disableModifiers = function(params) { + ; +}; + +// CSI > Ps p +// Set resource value pointerMode. This is used by xterm to +// decide whether to hide the pointer cursor as the user types. +// Valid values for the parameter: +// Ps = 0 -> never hide the pointer. +// Ps = 1 -> hide if the mouse tracking mode is not enabled. +// Ps = 2 -> always hide the pointer. If no parameter is +// given, xterm uses the default, which is 1 . +Terminal.prototype.setPointerMode = function(params) { + ; +}; + +// CSI ! p Soft terminal reset (DECSTR). +// http://vt100.net/docs/vt220-rm/table4-10.html +Terminal.prototype.softReset = function(params) { + this.cursorHidden = false; + this.insertMode = false; + this.originMode = false; + this.wraparoundMode = false; // autowrap + this.applicationKeypad = false; // ? + this.scrollTop = 0; + this.scrollBottom = this.rows - 1; + this.curAttr = this.defAttr; + this.x = this.y = 0; // ? + this.charset = null; + this.glevel = 0; // ?? + this.charsets = [null]; // ?? +}; + +// CSI Ps$ p +// Request ANSI mode (DECRQM). For VT300 and up, reply is +// CSI Ps; Pm$ y +// where Ps is the mode number as in RM, and Pm is the mode +// value: +// 0 - not recognized +// 1 - set +// 2 - reset +// 3 - permanently set +// 4 - permanently reset +Terminal.prototype.requestAnsiMode = function(params) { + ; +}; + +// CSI ? Ps$ p +// Request DEC private mode (DECRQM). For VT300 and up, reply is +// CSI ? Ps; Pm$ p +// where Ps is the mode number as in DECSET, Pm is the mode value +// as in the ANSI DECRQM. +Terminal.prototype.requestPrivateMode = function(params) { + ; +}; + +// CSI Ps ; Ps " p +// Set conformance level (DECSCL). Valid values for the first +// parameter: +// Ps = 6 1 -> VT100. +// Ps = 6 2 -> VT200. +// Ps = 6 3 -> VT300. +// Valid values for the second parameter: +// Ps = 0 -> 8-bit controls. +// Ps = 1 -> 7-bit controls (always set for VT100). +// Ps = 2 -> 8-bit controls. +Terminal.prototype.setConformanceLevel = function(params) { + ; +}; + +// CSI Ps q Load LEDs (DECLL). +// Ps = 0 -> Clear all LEDS (default). +// Ps = 1 -> Light Num Lock. +// Ps = 2 -> Light Caps Lock. +// Ps = 3 -> Light Scroll Lock. +// Ps = 2 1 -> Extinguish Num Lock. +// Ps = 2 2 -> Extinguish Caps Lock. +// Ps = 2 3 -> Extinguish Scroll Lock. +Terminal.prototype.loadLEDs = function(params) { + ; +}; + +// CSI Ps SP q +// Set cursor style (DECSCUSR, VT520). +// Ps = 0 -> blinking block. +// Ps = 1 -> blinking block (default). +// Ps = 2 -> steady block. +// Ps = 3 -> blinking underline. +// Ps = 4 -> steady underline. +Terminal.prototype.setCursorStyle = function(params) { + ; +}; + +// CSI Ps " q +// Select character protection attribute (DECSCA). Valid values +// for the parameter: +// Ps = 0 -> DECSED and DECSEL can erase (default). +// Ps = 1 -> DECSED and DECSEL cannot erase. +// Ps = 2 -> DECSED and DECSEL can erase. +Terminal.prototype.setCharProtectionAttr = function(params) { + ; +}; + +// CSI ? Pm r +// Restore DEC Private Mode Values. The value of Ps previously +// saved is restored. Ps values are the same as for DECSET. +Terminal.prototype.restorePrivateValues = function(params) { + ; +}; + +// CSI Pt; Pl; Pb; Pr; Ps$ r +// Change Attributes in Rectangular Area (DECCARA), VT400 and up. +// Pt; Pl; Pb; Pr denotes the rectangle. +// Ps denotes the SGR attributes to change: 0, 1, 4, 5, 7. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.setAttrInRectangle = function(params) { + var t = params[0] + , l = params[1] + , b = params[2] + , r = params[3] + , attr = params[4]; + + var line + , i; + + for (; t < b + 1; t++) { + line = this.lines[this.ybase + t]; + for (i = l; i < r; i++) { + line[i] = [attr, line[i][1]]; + } + } + + // this.maxRange(); + this.updateRange(params[0]); + this.updateRange(params[2]); +}; + +// CSI ? Pm s +// Save DEC Private Mode Values. Ps values are the same as for +// DECSET. +Terminal.prototype.savePrivateValues = function(params) { + ; +}; + +// CSI Ps ; Ps ; Ps t +// Window manipulation (from dtterm, as well as extensions). +// These controls may be disabled using the allowWindowOps +// resource. Valid values for the first (and any additional +// parameters) are: +// Ps = 1 -> De-iconify window. +// Ps = 2 -> Iconify window. +// Ps = 3 ; x ; y -> Move window to [x, y]. +// Ps = 4 ; height ; width -> Resize the xterm window to +// height and width in pixels. +// Ps = 5 -> Raise the xterm window to the front of the stack- +// ing order. +// Ps = 6 -> Lower the xterm window to the bottom of the +// stacking order. +// Ps = 7 -> Refresh the xterm window. +// Ps = 8 ; height ; width -> Resize the text area to +// [height;width] in characters. +// Ps = 9 ; 0 -> Restore maximized window. +// Ps = 9 ; 1 -> Maximize window (i.e., resize to screen +// size). +// Ps = 1 0 ; 0 -> Undo full-screen mode. +// Ps = 1 0 ; 1 -> Change to full-screen. +// Ps = 1 1 -> Report xterm window state. If the xterm window +// is open (non-iconified), it returns CSI 1 t . If the xterm +// window is iconified, it returns CSI 2 t . +// Ps = 1 3 -> Report xterm window position. Result is CSI 3 +// ; x ; y t +// Ps = 1 4 -> Report xterm window in pixels. Result is CSI +// 4 ; height ; width t +// Ps = 1 8 -> Report the size of the text area in characters. +// Result is CSI 8 ; height ; width t +// Ps = 1 9 -> Report the size of the screen in characters. +// Result is CSI 9 ; height ; width t +// Ps = 2 0 -> Report xterm window's icon label. Result is +// OSC L label ST +// Ps = 2 1 -> Report xterm window's title. Result is OSC l +// label ST +// Ps = 2 2 ; 0 -> Save xterm icon and window title on +// stack. +// Ps = 2 2 ; 1 -> Save xterm icon title on stack. +// Ps = 2 2 ; 2 -> Save xterm window title on stack. +// Ps = 2 3 ; 0 -> Restore xterm icon and window title from +// stack. +// Ps = 2 3 ; 1 -> Restore xterm icon title from stack. +// Ps = 2 3 ; 2 -> Restore xterm window title from stack. +// Ps >= 2 4 -> Resize to Ps lines (DECSLPP). +Terminal.prototype.manipulateWindow = function(params) { + ; +}; + +// CSI Pt; Pl; Pb; Pr; Ps$ t +// Reverse Attributes in Rectangular Area (DECRARA), VT400 and +// up. +// Pt; Pl; Pb; Pr denotes the rectangle. +// Ps denotes the attributes to reverse, i.e., 1, 4, 5, 7. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.reverseAttrInRectangle = function(params) { + ; +}; + +// CSI > Ps; Ps t +// Set one or more features of the title modes. Each parameter +// enables a single feature. +// Ps = 0 -> Set window/icon labels using hexadecimal. +// Ps = 1 -> Query window/icon labels using hexadecimal. +// Ps = 2 -> Set window/icon labels using UTF-8. +// Ps = 3 -> Query window/icon labels using UTF-8. (See dis- +// cussion of "Title Modes") +Terminal.prototype.setTitleModeFeature = function(params) { + ; +}; + +// CSI Ps SP t +// Set warning-bell volume (DECSWBV, VT520). +// Ps = 0 or 1 -> off. +// Ps = 2 , 3 or 4 -> low. +// Ps = 5 , 6 , 7 , or 8 -> high. +Terminal.prototype.setWarningBellVolume = function(params) { + ; +}; + +// CSI Ps SP u +// Set margin-bell volume (DECSMBV, VT520). +// Ps = 1 -> off. +// Ps = 2 , 3 or 4 -> low. +// Ps = 0 , 5 , 6 , 7 , or 8 -> high. +Terminal.prototype.setMarginBellVolume = function(params) { + ; +}; + +// CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v +// Copy Rectangular Area (DECCRA, VT400 and up). +// Pt; Pl; Pb; Pr denotes the rectangle. +// Pp denotes the source page. +// Pt; Pl denotes the target location. +// Pp denotes the target page. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.copyRectangle = function(params) { + ; +}; + +// CSI Pt ; Pl ; Pb ; Pr ' w +// Enable Filter Rectangle (DECEFR), VT420 and up. +// Parameters are [top;left;bottom;right]. +// Defines the coordinates of a filter rectangle and activates +// it. Anytime the locator is detected outside of the filter +// rectangle, an outside rectangle event is generated and the +// rectangle is disabled. Filter rectangles are always treated +// as "one-shot" events. Any parameters that are omitted default +// to the current locator position. If all parameters are omit- +// ted, any locator motion will be reported. DECELR always can- +// cels any prevous rectangle definition. +Terminal.prototype.enableFilterRectangle = function(params) { + ; +}; + +// CSI Ps x Request Terminal Parameters (DECREQTPARM). +// if Ps is a "0" (default) or "1", and xterm is emulating VT100, +// the control sequence elicits a response of the same form whose +// parameters describe the terminal: +// Ps -> the given Ps incremented by 2. +// Pn = 1 <- no parity. +// Pn = 1 <- eight bits. +// Pn = 1 <- 2 8 transmit 38.4k baud. +// Pn = 1 <- 2 8 receive 38.4k baud. +// Pn = 1 <- clock multiplier. +// Pn = 0 <- STP flags. +Terminal.prototype.requestParameters = function(params) { + ; +}; + +// CSI Ps x Select Attribute Change Extent (DECSACE). +// Ps = 0 -> from start to end position, wrapped. +// Ps = 1 -> from start to end position, wrapped. +// Ps = 2 -> rectangle (exact). +Terminal.prototype.selectChangeExtent = function(params) { + ; +}; + +// CSI Pc; Pt; Pl; Pb; Pr$ x +// Fill Rectangular Area (DECFRA), VT420 and up. +// Pc is the character to use. +// Pt; Pl; Pb; Pr denotes the rectangle. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.fillRectangle = function(params) { + var ch = params[0] + , t = params[1] + , l = params[2] + , b = params[3] + , r = params[4]; + + var line + , i; + + for (; t < b + 1; t++) { + line = this.lines[this.ybase + t]; + for (i = l; i < r; i++) { + line[i] = [line[i][0], String.fromCharCode(ch)]; + } + } + + // this.maxRange(); + this.updateRange(params[1]); + this.updateRange(params[3]); +}; + +// CSI Ps ; Pu ' z +// Enable Locator Reporting (DECELR). +// Valid values for the first parameter: +// Ps = 0 -> Locator disabled (default). +// Ps = 1 -> Locator enabled. +// Ps = 2 -> Locator enabled for one report, then disabled. +// The second parameter specifies the coordinate unit for locator +// reports. +// Valid values for the second parameter: +// Pu = 0 <- or omitted -> default to character cells. +// Pu = 1 <- device physical pixels. +// Pu = 2 <- character cells. +Terminal.prototype.enableLocatorReporting = function(params) { + var val = params[0] > 0; + //this.mouseEvents = val; + //this.decLocator = val; +}; + +// CSI Pt; Pl; Pb; Pr$ z +// Erase Rectangular Area (DECERA), VT400 and up. +// Pt; Pl; Pb; Pr denotes the rectangle. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.eraseRectangle = function(params) { + var t = params[0] + , l = params[1] + , b = params[2] + , r = params[3]; + + var line + , i + , ch; + + ch = [this.curAttr, ' ']; // xterm? + + for (; t < b + 1; t++) { + line = this.lines[this.ybase + t]; + for (i = l; i < r; i++) { + line[i] = ch; + } + } + + // this.maxRange(); + this.updateRange(params[0]); + this.updateRange(params[2]); +}; + +// CSI Pm ' { +// Select Locator Events (DECSLE). +// Valid values for the first (and any additional parameters) +// are: +// Ps = 0 -> only respond to explicit host requests (DECRQLP). +// (This is default). It also cancels any filter +// rectangle. +// Ps = 1 -> report button down transitions. +// Ps = 2 -> do not report button down transitions. +// Ps = 3 -> report button up transitions. +// Ps = 4 -> do not report button up transitions. +Terminal.prototype.setLocatorEvents = function(params) { + ; +}; + +// CSI Pt; Pl; Pb; Pr$ { +// Selective Erase Rectangular Area (DECSERA), VT400 and up. +// Pt; Pl; Pb; Pr denotes the rectangle. +Terminal.prototype.selectiveEraseRectangle = function(params) { + ; +}; + +// CSI Ps ' | +// Request Locator Position (DECRQLP). +// Valid values for the parameter are: +// Ps = 0 , 1 or omitted -> transmit a single DECLRP locator +// report. + +// If Locator Reporting has been enabled by a DECELR, xterm will +// respond with a DECLRP Locator Report. This report is also +// generated on button up and down events if they have been +// enabled with a DECSLE, or when the locator is detected outside +// of a filter rectangle, if filter rectangles have been enabled +// with a DECEFR. + +// -> CSI Pe ; Pb ; Pr ; Pc ; Pp & w + +// Parameters are [event;button;row;column;page]. +// Valid values for the event: +// Pe = 0 -> locator unavailable - no other parameters sent. +// Pe = 1 -> request - xterm received a DECRQLP. +// Pe = 2 -> left button down. +// Pe = 3 -> left button up. +// Pe = 4 -> middle button down. +// Pe = 5 -> middle button up. +// Pe = 6 -> right button down. +// Pe = 7 -> right button up. +// Pe = 8 -> M4 button down. +// Pe = 9 -> M4 button up. +// Pe = 1 0 -> locator outside filter rectangle. +// ``button'' parameter is a bitmask indicating which buttons are +// pressed: +// Pb = 0 <- no buttons down. +// Pb & 1 <- right button down. +// Pb & 2 <- middle button down. +// Pb & 4 <- left button down. +// Pb & 8 <- M4 button down. +// ``row'' and ``column'' parameters are the coordinates of the +// locator position in the xterm window, encoded as ASCII deci- +// mal. +// The ``page'' parameter is not used by xterm, and will be omit- +// ted. +Terminal.prototype.requestLocatorPosition = function(params) { + ; +}; + +// CSI P m SP } +// Insert P s Column(s) (default = 1) (DECIC), VT420 and up. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.insertColumns = function() { + var param = params[0] + , l = this.ybase + this.rows + , ch = [this.curAttr, ' '] // xterm? + , i; + + while (param--) { + for (i = this.ybase; i < l; i++) { + this.lines[i].splice(this.x + 1, 0, ch); + this.lines[i].pop(); + } + } + + this.maxRange(); +}; + +// CSI P m SP ~ +// Delete P s Column(s) (default = 1) (DECDC), VT420 and up +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.deleteColumns = function() { + var param = params[0] + , l = this.ybase + this.rows + , ch = [this.curAttr, ' '] // xterm? + , i; + + while (param--) { + for (i = this.ybase; i < l; i++) { + this.lines[i].splice(this.x, 1); + this.lines[i].push(ch); + } + } + + this.maxRange(); +}; + +/** + * Character Sets + */ + +Terminal.charsets = {}; + +// DEC Special Character and Line Drawing Set. +// http://vt100.net/docs/vt102-ug/table5-13.html +// A lot of curses apps use this if they see TERM=xterm. +// testing: echo -e '\e(0a\e(B' +// The xterm output sometimes seems to conflict with the +// reference above. xterm seems in line with the reference +// when running vttest however. +// The table below now uses xterm's output from vttest. +Terminal.charsets.SCLD = { // (0 + '`': '\u25c6', // '◆' + 'a': '\u2592', // '▒' + 'b': '\u0009', // '\t' + 'c': '\u000c', // '\f' + 'd': '\u000d', // '\r' + 'e': '\u000a', // '\n' + 'f': '\u00b0', // '°' + 'g': '\u00b1', // '±' + 'h': '\u2424', // '\u2424' (NL) + 'i': '\u000b', // '\v' + 'j': '\u2518', // '┘' + 'k': '\u2510', // '┐' + 'l': '\u250c', // '┌' + 'm': '\u2514', // '└' + 'n': '\u253c', // '┼' + 'o': '\u23ba', // '⎺' + 'p': '\u23bb', // '⎻' + 'q': '\u2500', // '─' + 'r': '\u23bc', // '⎼' + 's': '\u23bd', // '⎽' + 't': '\u251c', // '├' + 'u': '\u2524', // '┤' + 'v': '\u2534', // '┴' + 'w': '\u252c', // '┬' + 'x': '\u2502', // '│' + 'y': '\u2264', // '≤' + 'z': '\u2265', // '≥' + '{': '\u03c0', // 'π' + '|': '\u2260', // '≠' + '}': '\u00a3', // '£' + '~': '\u00b7' // '·' +}; + +Terminal.charsets.UK = null; // (A +Terminal.charsets.US = null; // (B (USASCII) +Terminal.charsets.Dutch = null; // (4 +Terminal.charsets.Finnish = null; // (C or (5 +Terminal.charsets.French = null; // (R +Terminal.charsets.FrenchCanadian = null; // (Q +Terminal.charsets.German = null; // (K +Terminal.charsets.Italian = null; // (Y +Terminal.charsets.NorwegianDanish = null; // (E or (6 +Terminal.charsets.Spanish = null; // (Z +Terminal.charsets.Swedish = null; // (H or (7 +Terminal.charsets.Swiss = null; // (= +Terminal.charsets.ISOLatin = null; // /A + +/** + * Helpers + */ + +function on(el, type, handler, capture) { + el.addEventListener(type, handler, capture || false); +} + +function off(el, type, handler, capture) { + el.removeEventListener(type, handler, capture || false); +} + +function cancel(ev) { + if (ev.preventDefault) ev.preventDefault(); + ev.returnValue = false; + if (ev.stopPropagation) ev.stopPropagation(); + ev.cancelBubble = true; + return false; +} + +function inherits(child, parent) { + function f() { + this.constructor = child; + } + f.prototype = parent.prototype; + child.prototype = new f; +} + +var isMac = ~navigator.userAgent.indexOf('Mac'); + +// if bold is broken, we can't +// use it in the terminal. +function isBoldBroken() { + var el = document.createElement('span'); + el.innerHTML = 'hello world'; + document.body.appendChild(el); + var w1 = el.scrollWidth; + el.style.fontWeight = 'bold'; + var w2 = el.scrollWidth; + document.body.removeChild(el); + return w1 !== w2; +} + +var String = this.String; +var setTimeout = this.setTimeout; +var setInterval = this.setInterval; + +/** + * Expose + */ + +Terminal.EventEmitter = EventEmitter; +Terminal.isMac = isMac; +Terminal.inherits = inherits; +Terminal.on = on; +Terminal.off = off; +Terminal.cancel = cancel; + +if (typeof module !== 'undefined') { + module.exports = Terminal; +} else { + this.Terminal = Terminal; +} + +}).call(function() { + return this || (typeof window !== 'undefined' ? window : global); +}()); diff --git a/static/js/wssh.js b/static/js/wssh.js new file mode 100644 index 000000000..54d4260e2 --- /dev/null +++ b/static/js/wssh.js @@ -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})); +}; diff --git a/templates/head_script.html b/templates/head_script.html index ef5f7a190..28a394488 100644 --- a/templates/head_script.html +++ b/templates/head_script.html @@ -8,4 +8,5 @@ + diff --git a/templates/jasset/asset_add.html b/templates/jasset/asset_add.html index bbf8b196f..13125ca0d 100644 --- a/templates/jasset/asset_add.html +++ b/templates/jasset/asset_add.html @@ -42,27 +42,24 @@
      -
      - -
      - -
      -
      - -
      -
      - +
      +