Commit 26804b40 authored by ibuler's avatar ibuler

Merge pull request #174 from jumpserver/dev

Release 0.3.1 version, Fix most bugs
parents d7442b48 8ebcd475
......@@ -16,6 +16,8 @@ import readline
import django
import paramiko
import errno
import pyte
import operator
import struct, fcntl, signal, socket, select
from io import open as copen
import uuid
......@@ -31,6 +33,7 @@ from jumpserver.settings import LOG_DIR
from jperm.ansible_api import MyRunner
# from jlog.log_api import escapeString
from jlog.models import ExecLog, FileLog
from jlog.views import TermLogRecorder
login_user = get_object(User, username=getpass.getuser())
try:
......@@ -90,8 +93,21 @@ class Tty(object):
self.remote_ip = ''
self.login_type = login_type
self.vim_flag = False
self.ps1_pattern = re.compile('\[.*@.*\][\$#]')
self.vim_end_flag = False
self.vim_end_pattern = re.compile(r'\x1b\[\?1049', re.X)
self.vim_pattern = re.compile(r'\W?vi[m]?\s.* | \W?fg\s.*', re.X)
self.vim_data = ''
self.stream = None
self.screen = None
self.__init_screen_stream()
def __init_screen_stream(self):
"""
初始化虚拟屏幕和字符流
"""
self.stream = pyte.ByteStream()
self.screen = pyte.Screen(80, 24)
self.stream.attach(self.screen)
@staticmethod
def is_output(strings):
......@@ -101,135 +117,55 @@ class Tty(object):
return True
return False
@staticmethod
def remove_obstruct_char(cmd_str):
'''删除一些干扰的特殊符号'''
control_char = re.compile(r'\x07 | \x1b\[1P | \r ', re.X)
cmd_str = control_char.sub('',cmd_str.strip())
patch_char = re.compile('\x08\x1b\[C') #删除方向左右一起的按键
while patch_char.search(cmd_str):
cmd_str = patch_char.sub('', cmd_str.rstrip())
return cmd_str
@staticmethod
def deal_backspace(match_str, result_command, pattern_str, backspace_num):
'''
处理删除确认键
'''
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(match_str)-3
if del_len > 0:
result_command = result_command[0:-del_len]
return result_command, len(match_str)
@staticmethod
def deal_replace_char(match_str,result_command,backspace_num):
'''
处理替换命令
'''
str_lists = re.findall(r'(?<=\x1b\[1@)\w',match_str)
tmp_str =''.join(str_lists)
result_command_list = list(result_command)
if len(tmp_str) > 1:
result_command_list[-backspace_num:-(backspace_num-len(tmp_str))] = tmp_str
elif len(tmp_str) > 0:
if result_command_list[-backspace_num] == ' ':
result_command_list.insert(-backspace_num, tmp_str)
else:
result_command_list[-backspace_num] = tmp_str
result_command = ''.join(result_command_list)
return result_command, len(match_str)
def remove_control_char(self, result_command):
def command_parser(self, command):
"""
处理日志特殊字符
处理命令中如果有ps1或者mysql的特殊情况,极端情况下会有ps1和mysql
:param command:要处理的字符传
:return:返回去除PS1或者mysql字符串的结果
"""
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] | (?:\x1b\]0.*) | \[.*@.*\][\$#] | (.*mysql>.*) #匹配 所有控制字符
""", re.X)
result_command = control_char.sub('', result_command.strip())
if not self.vim_flag:
if result_command.startswith('vi') or result_command.startswith('fg'):
self.vim_flag = True
return result_command.decode('utf8',"ignore")
result = None
match = re.compile('\[?.*@.*\]?[\$#]\s').split(command)
if match:
# 只需要最后的一个PS1后面的字符串
result = match[-1].strip()
else:
return ''
def deal_command(self, str_r):
# PS1没找到,查找mysql
match = re.split('mysql>\s', command)
if match:
# 只需要最后一个mysql后面的字符串
result = match[-1].strip()
return result
def deal_command(self, data):
"""
处理命令中特殊字符
处理截获的命令
:param data: 要处理的命令
:return:返回最后的处理结果
"""
str_r = self.remove_obstruct_char(str_r)
result_command = '' # 最后的结果
backspace_num = 0 # 光标移动的个数
reach_backspace_flag = False # 没有检测到光标键则为true
pattern_str = ''
while str_r:
tmp = re.match(r'\s*\w+\s*', str_r)
if tmp:
str_r = str_r[len(str(tmp.group(0))):]
if reach_backspace_flag:
pattern_str += str(tmp.group(0))
continue
else:
result_command += str(tmp.group(0))
continue
tmp = re.match(r'\x1b\[K[\x08]*', str_r)
if tmp:
result_command, del_len = self.deal_backspace(str(tmp.group(0)), result_command, pattern_str, backspace_num)
reach_backspace_flag = False
backspace_num = 0
pattern_str = ''
str_r = str_r[del_len:]
continue
tmp = re.match(r'\x08+', str_r)
if tmp:
str_r = str_r[len(str(tmp.group(0))):]
if len(str_r) != 0:
if reach_backspace_flag:
result_command = result_command[0:-backspace_num] + pattern_str
pattern_str = ''
command = ''
try:
self.stream.feed(data)
# 从虚拟屏幕中获取处理后的数据
for line in reversed(self.screen.buffer):
line_data = "".join(map(operator.attrgetter("data"), line)).strip()
if len(line_data) > 0:
parser_result = self.command_parser(line_data)
if parser_result is not None:
# 2个条件写一起会有错误的数据
if len(parser_result) > 0:
command = parser_result
else:
reach_backspace_flag = True
backspace_num = len(str(tmp.group(0)))
continue
else:
command = line_data
break
tmp = re.match(r'(\x1b\[1@\w)+', str_r) #处理替换的命令
if tmp:
result_command,del_len = self.deal_replace_char(str(tmp.group(0)), result_command, backspace_num)
str_r = str_r[del_len:]
backspace_num = 0
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:
result_command = result_command[0:-backspace_num] + pattern_str
result_command = self.remove_control_char(result_command)
return result_command
if command != '':
# 判断用户输入的是否是vim 或者fg命令
if self.vim_pattern.search(command):
self.vim_flag = True
# 虚拟屏幕清空
self.screen.reset()
except Exception:
pass
return command
def get_log(self):
"""
......@@ -294,7 +230,7 @@ class Tty(object):
# 发起ssh连接请求 Make a ssh connection
ssh = paramiko.SSHClient()
#ssh.load_system_host_keys()
# ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
role_key = connect_info.get('role_key')
......@@ -364,9 +300,12 @@ class SshTty(Tty):
使用paramiko模块的channel,连接后端,进入交互式
"""
log_file_f, log_time_f, log = self.get_log()
termlog = TermLogRecorder(User.objects.get(id=self.user.id))
termlog.setid(log.id)
old_tty = termios.tcgetattr(sys.stdin)
pre_timestamp = time.time()
data = ''
input_str = ''
input_mode = False
try:
tty.setraw(sys.stdin.fileno())
......@@ -398,9 +337,9 @@ class SshTty(Tty):
except OSError as msg:
if msg.errno == errno.EAGAIN:
continue
#sys.stdout.write(x)
#sys.stdout.flush()
now_timestamp = time.time()
termlog.write(x)
termlog.recoder = False
log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(x)))
log_time_f.flush()
log_file_f.write(x)
......@@ -411,6 +350,8 @@ class SshTty(Tty):
if input_mode and not self.is_output(x):
data += x
input_str = ''
except socket.timeout:
pass
......@@ -419,20 +360,27 @@ class SshTty(Tty):
x = os.read(sys.stdin.fileno(), 4096)
except OSError:
pass
termlog.recoder = True
input_mode = True
input_str += x
if str(x) in ['\r', '\n', '\r\n']:
# 这个是用来处理用户的复制操作
if input_str != x:
data += input_str
if self.vim_flag:
match = self.ps1_pattern.search(self.vim_data)
match = self.vim_end_pattern.findall(self.vim_data)
if match:
self.vim_flag = False
data = self.deal_command(data)[0:200]
if len(data) > 0:
TtyLog(log=log, datetime=datetime.datetime.now(), cmd=data).save()
if self.vim_end_flag or len(match) == 2:
self.vim_flag = False
self.vim_end_flag = False
else:
self.vim_end_flag = True
else:
data = self.deal_command(data)[0:200]
if len(data) > 0:
TtyLog(log=log, datetime=datetime.datetime.now(), cmd=data).save()
data = ''
input_str = ''
self.vim_data = ''
input_mode = False
......@@ -445,6 +393,8 @@ class SshTty(Tty):
log_file_f.write('End time is %s' % datetime.datetime.now())
log_file_f.close()
log_time_f.close()
termlog.save()
log.filename = termlog.filename
log.is_finished = True
log.end_time = datetime.datetime.now()
log.save()
......@@ -464,7 +414,7 @@ class SshTty(Tty):
# 获取连接的隧道并设置窗口大小 Make a channel and set windows size
global channel
win_size = self.get_win_size()
#self.channel = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1], term='xterm')
# self.channel = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1], term='xterm')
self.channel = channel = transport.open_session()
channel.get_pty(term='xterm', height=win_size[0], width=win_size[1])
channel.invoke_shell()
......@@ -523,7 +473,13 @@ class Nav(object):
if gid_pattern.match(str_r):
gid = int(str_r.lstrip('g'))
# 获取资产组包含的资产
user_asset_search = get_object(AssetGroup, id=gid).asset_set.all()
asset_group = get_object(AssetGroup, id=gid)
if asset_group:
user_asset_search = asset_group.asset_set.all()
else:
color_print('没有该资产组或没有权限')
return
else:
# 匹配 ip, hostname, 备注
for asset in user_asset_all:
......@@ -609,6 +565,9 @@ class Nav(object):
command = raw_input("\033[1;32mCmds>:\033[0m ").strip()
if command == 'q':
break
elif not command:
color_print('命令不能为空...')
continue
runner.run('shell', command, pattern=pattern)
ExecLog(host=asset_name_str, user=self.user.username, cmd=command, remote_ip=remote_ip,
result=runner.results).save()
......@@ -661,7 +620,7 @@ class Nav(object):
runner = MyRunner(res)
runner.run('copy', module_args='src=%s dest=%s directory_mode'
% (tmp_dir, tmp_dir), pattern=pattern)
% (tmp_dir, '/tmp'), pattern=pattern)
ret = runner.results
FileLog(user=self.user.name, host=asset_name_str, filename=filename_str,
remote_ip=remote_ip, type='upload', result=ret).save()
......@@ -746,6 +705,9 @@ def main():
if not login_user: # 判断用户是否存在
color_print('没有该用户,或许你是以root运行的 No that user.', exits=True)
if not login_user.is_active:
color_print('您的用户已禁用,请联系管理员.', exits=True)
gid_pattern = re.compile(r'^g\d+$')
nav = Nav(login_user)
nav.print_nav()
......@@ -812,7 +774,6 @@ def main():
except IndexError, e:
color_print(e)
time.sleep(5)
pass
if __name__ == '__main__':
main()
#!/bin/bash
#
trap '' SIGINT
base_dir=$(dirname $0)
export LANG='zh_CN.UTF-8'
python $base_dir/connect.py
exit
# -*-Shell-script-*-
#
# functions This file contains functions to be used by most or all
# shell scripts in the /etc/init.d directory.
#
TEXTDOMAIN=initscripts
# Make sure umask is sane
umask 022
# Set up a default search path.
PATH="/sbin:/usr/sbin:/bin:/usr/bin"
export PATH
if [ $PPID -ne 1 -a -z "$SYSTEMCTL_SKIP_REDIRECT" ] && \
( /bin/mountpoint -q /cgroup/systemd || /bin/mountpoint -q /sys/fs/cgroup/systemd ) ; then
case "$0" in
/etc/init.d/*|/etc/rc.d/init.d/*)
_use_systemctl=1
;;
esac
fi
systemctl_redirect () {
local s
local prog=${1##*/}
local command=$2
local options=""
case "$command" in
start)
s=$"Starting $prog (via systemctl): "
;;
stop)
s=$"Stopping $prog (via systemctl): "
;;
reload|try-reload)
s=$"Reloading $prog configuration (via systemctl): "
;;
restart|try-restart|condrestart)
s=$"Restarting $prog (via systemctl): "
;;
esac
if [ -n "$SYSTEMCTL_IGNORE_DEPENDENCIES" ] ; then
options="--ignore-dependencies"
fi
action "$s" /bin/systemctl $options $command "$prog.service"
}
# Get a sane screen width
[ -z "${COLUMNS:-}" ] && COLUMNS=80
if [ -z "${CONSOLETYPE:-}" ]; then
if [ -c "/dev/stderr" -a -r "/dev/stderr" ]; then
CONSOLETYPE="$(/sbin/consoletype < /dev/stderr 2>/dev/null)"
else
CONSOLETYPE="serial"
fi
fi
if [ -z "${NOLOCALE:-}" ] && [ -z "${LANGSH_SOURCED:-}" ] && [ -f /etc/sysconfig/i18n -o -f /etc/locale.conf ] ; then
. /etc/profile.d/lang.sh 2>/dev/null
# avoid propagating LANGSH_SOURCED any further
unset LANGSH_SOURCED
fi
# Read in our configuration
if [ -z "${BOOTUP:-}" ]; then
if [ -f /etc/sysconfig/init ]; then
. /etc/sysconfig/init
else
# This all seem confusing? Look in /etc/sysconfig/init,
# or in /usr/share/doc/initscripts-*/sysconfig.txt
BOOTUP=color
RES_COL=60
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
SETCOLOR_SUCCESS="echo -en \\033[1;32m"
SETCOLOR_FAILURE="echo -en \\033[1;31m"
SETCOLOR_WARNING="echo -en \\033[1;33m"
SETCOLOR_NORMAL="echo -en \\033[0;39m"
LOGLEVEL=1
fi
if [ "$CONSOLETYPE" = "serial" ]; then
BOOTUP=serial
MOVE_TO_COL=
SETCOLOR_SUCCESS=
SETCOLOR_FAILURE=
SETCOLOR_WARNING=
SETCOLOR_NORMAL=
fi
fi
# Check if any of $pid (could be plural) are running
checkpid() {
local i
for i in $* ; do
[ -d "/proc/$i" ] && return 0
done
return 1
}
# __proc_pids {program} [pidfile]
# Set $pid to pids from /var/run* for {program}. $pid should be declared
# local in the caller.
# Returns LSB exit code for the 'status' action.
__pids_var_run() {
local base=${1##*/}
local pid_file=${2:-/var/run/$base.pid}
pid=
if [ -f "$pid_file" ] ; then
local line p
[ ! -r "$pid_file" ] && return 4 # "user had insufficient privilege"
while : ; do
read line
[ -z "$line" ] && break
for p in $line ; do
[ -z "${p//[0-9]/}" ] && [ -d "/proc/$p" ] && pid="$pid $p"
done
done < "$pid_file"
if [ -n "$pid" ]; then
return 0
fi
return 1 # "Program is dead and /var/run pid file exists"
fi
return 3 # "Program is not running"
}
# Output PIDs of matching processes, found using pidof
__pids_pidof() {
pidof -c -m -o $$ -o $PPID -o %PPID -x "$1" || \
pidof -c -m -o $$ -o $PPID -o %PPID -x "${1##*/}"
}
# A function to start a program.
daemon() {
# Test syntax.
local gotbase= force= nicelevel corelimit
local pid base= user= nice= bg= pid_file=
local cgroup=
nicelevel=0
while [ "$1" != "${1##[-+]}" ]; do
case $1 in
'') echo $"$0: Usage: daemon [+/-nicelevel] {program}"
return 1;;
--check)
base=$2
gotbase="yes"
shift 2
;;
--check=?*)
base=${1#--check=}
gotbase="yes"
shift
;;
--user)
user=$2
shift 2
;;
--user=?*)
user=${1#--user=}
shift
;;
--pidfile)
pid_file=$2
shift 2
;;
--pidfile=?*)
pid_file=${1#--pidfile=}
shift
;;
--force)
force="force"
shift
;;
[-+][0-9]*)
nice="nice -n $1"
shift
;;
*) echo $"$0: Usage: daemon [+/-nicelevel] {program}"
return 1;;
esac
done
# Save basename.
[ -z "$gotbase" ] && base=${1##*/}
# See if it's already running. Look *only* at the pid file.
__pids_var_run "$base" "$pid_file"
[ -n "$pid" -a -z "$force" ] && return
# make sure it doesn't core dump anywhere unless requested
corelimit="ulimit -S -c ${DAEMON_COREFILE_LIMIT:-0}"
# if they set NICELEVEL in /etc/sysconfig/foo, honor it
[ -n "${NICELEVEL:-}" ] && nice="nice -n $NICELEVEL"
# if they set CGROUP_DAEMON in /etc/sysconfig/foo, honor it
if [ -n "${CGROUP_DAEMON}" ]; then
if [ ! -x /bin/cgexec ]; then
echo -n "Cgroups not installed"; warning
echo
else
cgroup="/bin/cgexec";
for i in $CGROUP_DAEMON; do
cgroup="$cgroup -g $i";
done
fi
fi
# Echo daemon
[ "${BOOTUP:-}" = "verbose" -a -z "${LSB:-}" ] && echo -n " $base"
# And start it up.
if [ -z "$user" ]; then
$cgroup $nice /bin/bash -c "$corelimit >/dev/null 2>&1 ; $*"
else
$cgroup $nice runuser -s /bin/bash $user -c "$corelimit >/dev/null 2>&1 ; $*"
fi
[ "$?" -eq 0 ] && success $"$base startup" || failure $"$base startup"
}
# A function to stop a program.
killproc() {
local RC killlevel= base pid pid_file= delay try
RC=0; delay=3; try=0
# Test syntax.
if [ "$#" -eq 0 ]; then
echo $"Usage: killproc [-p pidfile] [ -d delay] {program} [-signal]"
return 1
fi
if [ "$1" = "-p" ]; then
pid_file=$2
shift 2
fi
if [ "$1" = "-d" ]; then
delay=$(echo $2 | awk -v RS=' ' -v IGNORECASE=1 '{if($1!~/^[0-9.]+[smhd]?$/) exit 1;d=$1~/s$|^[0-9.]*$/?1:$1~/m$/?60:$1~/h$/?60*60:$1~/d$/?24*60*60:-1;if(d==-1) exit 1;delay+=d*$1} END {printf("%d",delay+0.5)}')
if [ "$?" -eq 1 ]; then
echo $"Usage: killproc [-p pidfile] [ -d delay] {program} [-signal]"
return 1
fi
shift 2
fi
# check for second arg to be kill level
[ -n "${2:-}" ] && killlevel=$2
# Save basename.
base=${1##*/}
# Find pid.
__pids_var_run "$1" "$pid_file"
RC=$?
if [ -z "$pid" ]; then
if [ -z "$pid_file" ]; then
pid="$(__pids_pidof "$1")"
else
[ "$RC" = "4" ] && { failure $"$base shutdown" ; return $RC ;}
fi
fi
# Kill it.
if [ -n "$pid" ] ; then
[ "$BOOTUP" = "verbose" -a -z "${LSB:-}" ] && echo -n "$base "
if [ -z "$killlevel" ] ; then
if checkpid $pid 2>&1; then
# TERM first, then KILL if not dead
kill -TERM $pid >/dev/null 2>&1
usleep 50000
if checkpid $pid ; then
try=0
while [ $try -lt $delay ] ; do
checkpid $pid || break
sleep 1
let try+=1
done
if checkpid $pid ; then
kill -KILL $pid >/dev/null 2>&1
usleep 50000
fi
fi
fi
checkpid $pid
RC=$?
[ "$RC" -eq 0 ] && failure $"$base shutdown" || success $"$base shutdown"
RC=$((! $RC))
# use specified level only
else
if checkpid $pid; then
kill $killlevel $pid >/dev/null 2>&1
RC=$?
[ "$RC" -eq 0 ] && success $"$base $killlevel" || failure $"$base $killlevel"
elif [ -n "${LSB:-}" ]; then
RC=7 # Program is not running
fi
fi
else
if [ -n "${LSB:-}" -a -n "$killlevel" ]; then
RC=7 # Program is not running
else
failure $"$base shutdown"
RC=0
fi
fi
# Remove pid file if any.
if [ -z "$killlevel" ]; then
rm -f "${pid_file:-/var/run/$base.pid}"
fi
return $RC
}
# A function to find the pid of a program. Looks *only* at the pidfile
pidfileofproc() {
local pid
# Test syntax.
if [ "$#" = 0 ] ; then
echo $"Usage: pidfileofproc {program}"
return 1
fi
__pids_var_run "$1"
[ -n "$pid" ] && echo $pid
return 0
}
# A function to find the pid of a program.
pidofproc() {
local RC pid pid_file=
# Test syntax.
if [ "$#" = 0 ]; then
echo $"Usage: pidofproc [-p pidfile] {program}"
return 1
fi
if [ "$1" = "-p" ]; then
pid_file=$2
shift 2
fi
fail_code=3 # "Program is not running"
# First try "/var/run/*.pid" files
__pids_var_run "$1" "$pid_file"
RC=$?
if [ -n "$pid" ]; then
echo $pid
return 0
fi
[ -n "$pid_file" ] && return $RC
__pids_pidof "$1" || return $RC
}
status() {
local base pid lock_file= pid_file=
# Test syntax.
if [ "$#" = 0 ] ; then
echo $"Usage: status [-p pidfile] {program}"
return 1
fi
if [ "$1" = "-p" ]; then
pid_file=$2
shift 2
fi
if [ "$1" = "-l" ]; then
lock_file=$2
shift 2
fi
base=${1##*/}
if [ "$_use_systemctl" = "1" ]; then
systemctl status ${0##*/}.service
return $?
fi
# First try "pidof"
__pids_var_run "$1" "$pid_file"
RC=$?
if [ -z "$pid_file" -a -z "$pid" ]; then
pid="$(__pids_pidof "$1")"
fi
if [ -n "$pid" ]; then
echo $"${base} (pid $pid) is running..."
return 0
fi
case "$RC" in
0)
echo $"${base} (pid $pid) is running..."
return 0
;;
1)
echo $"${base} dead but pid file exists"
return 1
;;
4)
echo $"${base} status unknown due to insufficient privileges."
return 4
;;
esac
if [ -z "${lock_file}" ]; then
lock_file=${base}
fi
# See if /var/lock/subsys/${lock_file} exists
if [ -f /var/lock/subsys/${lock_file} ]; then
echo $"${base} dead but subsys locked"
return 2
fi
echo $"${base} is stopped"
return 3
}
echo_success() {
[ "$BOOTUP" = "color" ] && $MOVE_TO_COL
echo -n "["
[ "$BOOTUP" = "color" ] && $SETCOLOR_SUCCESS
echo -n $" OK "
[ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
echo -n "]"
echo -ne "\r"
return 0
}
echo_failure() {
[ "$BOOTUP" = "color" ] && $MOVE_TO_COL
echo -n "["
[ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
echo -n $"FAILED"
[ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
echo -n "]"
echo -ne "\r"
return 1
}
echo_passed() {
[ "$BOOTUP" = "color" ] && $MOVE_TO_COL
echo -n "["
[ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
echo -n $"PASSED"
[ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
echo -n "]"
echo -ne "\r"
return 1
}
echo_warning() {
[ "$BOOTUP" = "color" ] && $MOVE_TO_COL
echo -n "["
[ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
echo -n $"WARNING"
[ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
echo -n "]"
echo -ne "\r"
return 1
}
# Inform the graphical boot of our current state
update_boot_stage() {
if [ -x /bin/plymouth ]; then
/bin/plymouth --update="$1"
fi
return 0
}
# Log that something succeeded
success() {
[ "$BOOTUP" != "verbose" -a -z "${LSB:-}" ] && echo_success
return 0
}
# Log that something failed
failure() {
local rc=$?
[ "$BOOTUP" != "verbose" -a -z "${LSB:-}" ] && echo_failure
[ -x /bin/plymouth ] && /bin/plymouth --details
return $rc
}
# Log that something passed, but may have had errors. Useful for fsck
passed() {
local rc=$?
[ "$BOOTUP" != "verbose" -a -z "${LSB:-}" ] && echo_passed
return $rc
}
# Log a warning
warning() {
local rc=$?
[ "$BOOTUP" != "verbose" -a -z "${LSB:-}" ] && echo_warning
return $rc
}
# Run some action. Log its output.
action() {
local STRING rc
STRING=$1
echo -n "$STRING "
shift
"$@" && success $"$STRING" || failure $"$STRING"
rc=$?
echo
return $rc
}
# returns OK if $1 contains $2
strstr() {
[ "${1#*$2*}" = "$1" ] && return 1
return 0
}
# Check whether file $1 is a backup or rpm-generated file and should be ignored
is_ignored_file() {
case "$1" in
*~ | *.bak | *.orig | *.rpmnew | *.rpmorig | *.rpmsave)
return 0
;;
esac
return 1
}
# Evaluate shvar-style booleans
is_true() {
case "$1" in
[tT] | [yY] | [yY][eE][sS] | [tT][rR][uU][eE])
return 0
;;
esac
return 1
}
# Evaluate shvar-style booleans
is_false() {
case "$1" in
[fF] | [nN] | [nN][oO] | [fF][aA][lL][sS][eE])
return 0
;;
esac
return 1
}
# Apply sysctl settings, including files in /etc/sysctl.d
apply_sysctl() {
if [ -x /lib/systemd/systemd-sysctl ]; then
/lib/systemd/systemd-sysctl
else
for file in /usr/lib/sysctl.d/*.conf ; do
is_ignored_file "$file" && continue
[ -f /run/sysctl.d/${file##*/} ] && continue
[ -f /etc/sysctl.d/${file##*/} ] && continue
test -f "$file" && sysctl -e -p "$file" >/dev/null 2>&1
done
for file in /run/sysctl.d/*.conf ; do
is_ignored_file "$file" && continue
[ -f /etc/sysctl.d/${file##*/} ] && continue
test -f "$file" && sysctl -e -p "$file" >/dev/null 2>&1
done
for file in /etc/sysctl.d/*.conf ; do
is_ignored_file "$file" && continue
test -f "$file" && sysctl -e -p "$file" >/dev/null 2>&1
done
sysctl -e -p /etc/sysctl.conf >/dev/null 2>&1
fi
}
# A sed expression to filter out the files that is_ignored_file recognizes
__sed_discard_ignored_files='/\(~\|\.bak\|\.orig\|\.rpmnew\|\.rpmorig\|\.rpmsave\)$/d'
if [ "$_use_systemctl" = "1" ]; then
if [ "x$1" = xstart -o \
"x$1" = xstop -o \
"x$1" = xrestart -o \
"x$1" = xreload -o \
"x$1" = xtry-restart -o \
"x$1" = xforce-reload -o \
"x$1" = xcondrestart ] ; then
systemctl_redirect $0 $1
exit $?
fi
fi
#!/usr/bin/python
# coding: utf-8
import subprocess
import time
import os
import sys
import MySQLdb
from smtplib import SMTP, SMTPAuthenticationError, SMTPConnectError, SMTPSenderRefused
from smtplib import SMTP, SMTP_SSL, SMTPAuthenticationError, SMTPConnectError, SMTPSenderRefused
import ConfigParser
import socket
import fcntl
import struct
import readline
import random
import string
import re
import platform
import shlex
jms_dir = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
sys.path.append(jms_dir)
......@@ -24,7 +23,14 @@ def bash(cmd):
run a bash shell command
执行bash命令
"""
return subprocess.call(cmd, shell=True)
return shlex.os.system(cmd)
def valid_ip(ip):
if ('255' in ip) or (ip == "0.0.0.0"):
return False
else:
return True
def color_print(msg, color='red', exits=False):
......@@ -46,18 +52,17 @@ def color_print(msg, color='red', exits=False):
return msg
def get_ip_addr(ifname='eth0'):
def get_ip_addr():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
0x8915,
struct.pack('256s', ifname[:15])
)[20:24])
except:
ips = os.popen("LANG=C ifconfig | grep \"inet addr\" | grep -v \"127.0.0.1\" | awk -F \":\" '{print $2}' | awk '{print $1}'").readlines()
if len(ips) > 0:
return ips[0]
s.connect(("8.8.8.8", 80))
return s.getsockname()[0]
except Exception:
if_data = ''.join(os.popen("LANG=C ifconfig").readlines())
ips = re.findall(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', if_data, flags=re.MULTILINE)
ip = filter(valid_ip, ips)
if ip:
return ip[0]
return ''
......@@ -75,6 +80,39 @@ class PreSetup(object):
self.ip = ''
self.key = ''.join(random.choice(string.ascii_lowercase + string.digits) \
for _ in range(16))
self.dist = platform.dist()[0].lower()
self.version = platform.dist()[1]
@property
def _is_redhat(self):
if self.dist == "centos" or self.dist == "redhat" or self.dist == "fedora":
return True
@property
def _is_centos7(self):
if self.dist == "centos" and self.version.startswith("7"):
return True
@property
def _is_fedora_new(self):
if self.dist == "fedora" and int(self.version) >= 20:
return True
@property
def _is_ubuntu(self):
if self.dist == "ubuntu" or self.dist == "debian":
return True
def check_platform(self):
if not (self._is_redhat or self._is_ubuntu):
print(u"支持的平台: CentOS, RedHat, Fedora, Debian, Ubuntu, 暂不支持其他平台安装.")
exit()
@staticmethod
def check_bash_return(ret_code, error_msg):
if ret_code != 0:
color_print(error_msg, 'red')
exit()
def write_conf(self, conf_file=os.path.join(jms_dir, 'jumpserver.conf')):
color_print('开始写入配置文件', 'green')
......@@ -87,7 +125,6 @@ class PreSetup(object):
conf.set('db', 'user', self.db_user)
conf.set('db', 'password', self.db_pass)
conf.set('db', 'database', self.db)
conf.set('websocket', 'web_socket_host', '%s:3000' % self.ip)
conf.set('mail', 'email_host', self.mail_host)
conf.set('mail', 'email_port', self.mail_port)
conf.set('mail', 'email_host_user', self.mail_addr)
......@@ -99,22 +136,60 @@ class PreSetup(object):
def _setup_mysql(self):
color_print('开始安装设置mysql (请手动设置mysql安全)', 'green')
color_print('默认用户名: %s 默认密码: %s' % (self.db_user, self.db_pass), 'green')
bash('yum -y install mysql-server')
bash('service mysqld start')
bash('chkconfig mysqld on')
bash('mysql -e "create database %s default charset=utf8"' % self.db)
bash('mysql -e "grant all on %s.* to \'%s\'@\'%s\' identified by \'%s\'"' % (self.db,
self.db_user,
self.db_host,
self.db_pass))
if self._is_redhat:
if self._is_centos7 or self._is_fedora_new:
ret_code = bash('yum -y install mariadb-server mariadb-devel')
self.check_bash_return(ret_code, "安装mysql(mariadb)失败, 请检查安装源是否更新或手动安装!")
@staticmethod
def _set_env():
bash('systemctl enable mariadb.service')
bash('systemctl start mariadb.service')
else:
ret_code = bash('yum -y install mysql-server')
self.check_bash_return(ret_code, "安装mysql失败, 请检查安装源是否更新或手动安装!")
bash('service mysqld start')
bash('chkconfig mysqld on')
bash('mysql -e "create database %s default charset=utf8"' % self.db)
bash('mysql -e "grant all on %s.* to \'%s\'@\'%s\' identified by \'%s\'"' % (self.db,
self.db_user,
self.db_host,
self.db_pass))
if self._is_ubuntu:
cmd1 = "echo mysql-server mysql-server/root_password select '' | debconf-set-selections"
cmd2 = "echo mysql-server mysql-server/root_password_again select '' | debconf-set-selections"
cmd3 = "apt-get -y install mysql-server"
ret_code = bash('%s; %s; %s' % (cmd1, cmd2, cmd3))
self.check_bash_return(ret_code, "安装mysql失败, 请检查安装源是否更新或手动安装!")
bash('service mysql start')
bash('mysql -e "create database %s default charset=utf8"' % self.db)
bash('mysql -e "grant all on %s.* to \'%s\'@\'%s\' identified by \'%s\'"' % (self.db,
self.db_user,
self.db_host,
self.db_pass))
def _set_env(self):
color_print('开始关闭防火墙和selinux', 'green')
os.system("export LANG='en_US.UTF-8' && sed -i 's/LANG=.*/LANG=en_US.UTF-8/g' /etc/sysconfig/i18n")
bash('service iptables stop && chkconfig iptables off && setenforce 0')
if self._is_redhat:
os.system("export LANG='en_US.UTF-8'")
if self._is_centos7 or self._is_fedora_new:
cmd1 = "systemctl status firewalld 2> /dev/null 1> /dev/null"
cmd2 = "systemctl stop firewalld"
cmd3 = "systemctl disable firewalld"
bash('%s && %s && %s' % (cmd1, cmd2, cmd3))
bash('localectl set-locale LANG=en_US.UTF-8')
bash('which setenforce 2> /dev/null 1> /dev/null && setenforce 0')
else:
bash("sed -i 's/LANG=.*/LANG=en_US.UTF-8/g' /etc/sysconfig/i18n")
bash('service iptables stop && chkconfig iptables off && setenforce 0')
if self._is_ubuntu:
os.system("export LANG='en_US.UTF-8'")
bash("which iptables && iptables -F")
bash('which setenforce && setenforce 0')
def _test_db_conn(self):
import MySQLdb
try:
MySQLdb.connect(host=self.db_host, port=int(self.db_port),
user=self.db_user, passwd=self.db_pass, db=self.db)
......@@ -126,7 +201,10 @@ class PreSetup(object):
def _test_mail(self):
try:
smtp = SMTP(self.mail_host, port=self.mail_port, timeout=2)
if self.mail_port == 465:
smtp = SMTP_SSL(self.mail_host, port=self.mail_port, timeout=2)
else:
smtp = SMTP(self.mail_host, port=self.mail_port, timeout=2)
smtp.login(self.mail_addr, self.mail_pass)
smtp.sendmail(self.mail_addr, (self.mail_addr, ),
'''From:%s\r\nTo:%s\r\nSubject:Jumpserver Mail Test!\r\n\r\n Mail test passed!\r\n''' %
......@@ -141,20 +219,26 @@ class PreSetup(object):
return True
return False
@staticmethod
def _rpm_repo():
color_print('开始安装epel源', 'green')
bash('yum -y install epel-release')
def _rpm_repo(self):
if self._is_redhat:
color_print('开始安装epel源', 'green')
bash('yum -y install epel-release')
@staticmethod
def _depend_rpm():
color_print('开始安装依赖rpm包', 'green')
bash('yum -y install git python-pip mysql-devel gcc automake autoconf python-devel vim sshpass')
def _depend_rpm(self):
color_print('开始安装依赖包', 'green')
if self._is_redhat:
cmd = 'yum -y install git python-pip mysql-devel rpm-build gcc automake autoconf python-devel vim sshpass lrzsz readline-devel'
ret_code = bash(cmd)
self.check_bash_return(ret_code, "安装依赖失败, 请检查安装源是否更新或手动安装!")
if self._is_ubuntu:
cmd = "apt-get -y --force-yes install git python-pip gcc automake autoconf vim sshpass libmysqld-dev python-all-dev lrzsz libreadline-dev"
ret_code = bash(cmd)
self.check_bash_return(ret_code, "安装依赖失败, 请检查安装源是否更新或手动安装!")
@staticmethod
def _require_pip():
def _require_pip(self):
color_print('开始安装依赖pip包', 'green')
bash('pip install -r requirements.txt')
ret_code = bash('pip install -r requirements.txt')
self.check_bash_return(ret_code, "安装JumpServer 依赖的python库失败!")
def _input_ip(self):
ip = raw_input('\n请输入您服务器的IP地址,用户浏览器可以访问 [%s]: ' % get_ip_addr()).strip()
......@@ -168,7 +252,7 @@ class PreSetup(object):
else:
db_host = raw_input('请输入数据库服务器IP [127.0.0.1]: ').strip()
db_port = raw_input('请输入数据库服务器端口 [3306]: ').strip()
db_user = raw_input('请输入数据库服务器用户 [root]: ').strip()
db_user = raw_input('请输入数据库服务器用户 [jumpserver]: ').strip()
db_pass = raw_input('请输入数据库服务器密码: ').strip()
db = raw_input('请输入使用的数据库 [jumpserver]: ').strip()
......@@ -202,11 +286,12 @@ class PreSetup(object):
print
def start(self):
# self._rpm_repo()
# self._depend_rpm()
# self._require_pip()
color_print('请务必先查看wiki https://github.com/ibuler/jumpserver/wiki/Quickinstall')
color_print('请务必先查看wiki https://github.com/jumpserver/jumpserver/wiki')
time.sleep(3)
self.check_platform()
self._rpm_repo()
self._depend_rpm()
self._require_pip()
self._set_env()
self._input_ip()
self._input_mysql()
......
......@@ -5,9 +5,11 @@ import sys
import os
import django
from django.core.management import execute_from_command_line
import shutil
import shlex
import urllib
import socket
import subprocess
jms_dir = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
sys.path.append(jms_dir)
......@@ -41,9 +43,6 @@ class Setup(object):
version = urllib.urlopen('http://jumpserver.org/version/?id=%s' % mac)
except:
pass
os.chdir(jms_dir)
os.chmod('logs', 0777)
os.chmod('keys', 0777)
def _input_admin(self):
while True:
......@@ -78,27 +77,34 @@ class Setup(object):
user.delete()
db_add_user(username=self.admin_user, password=self.admin_pass, role='SU', name='admin', groups='',
admin_groups='', email='admin@jumpserver.org', uuid='MayBeYouAreTheFirstUser', is_active=True)
os.system('id %s &> /dev/null || useradd %s' % (self.admin_user, self.admin_user))
cmd = 'id %s 2> /dev/null 1> /dev/null || useradd %s' % (self.admin_user, self.admin_user)
shlex.os.system(cmd)
@staticmethod
def _cp_zzsh():
os.chdir(os.path.join(jms_dir, 'install'))
shutil.copy('zzjumpserver.sh', '/etc/profile.d/')
bash("sed -i 's#/opt/jumpserver#%s#g' /etc/profile.d/zzjumpserver.sh" % jms_dir)
def _chmod_file():
os.chdir(jms_dir)
os.chmod('init.sh', 0755)
os.chmod('connect.py', 0755)
os.chmod('manage.py', 0755)
os.chmod('run_server.py', 0755)
os.chmod('service.sh', 0755)
os.chmod('logs', 0777)
os.chmod('keys', 0777)
@staticmethod
def _run_service():
os.system('sh %s start' % os.path.join(jms_dir, 'service.sh'))
cmd = 'bash %s start' % os.path.join(jms_dir, 'service.sh')
shlex.os.system(cmd)
print
color_print('安装成功,请访问web, 祝你使用愉快。\n请访问 https://github.com/ibuler/jumpserver 查看文档', 'green')
color_print('安装成功,请访问web, 祝你使用愉快。\n请访问 https://github.com/jumpserver/jumpserver/wiki 查看文档', 'green')
def start(self):
print "开始安装Jumpserver, 要求环境为 CentOS 6.5 x86_64"
print "开始安装Jumpserver ..."
self._pull()
self._sync_db()
self._input_admin()
self._create_admin()
self._cp_zzsh()
self._chmod_file()
self._run_service()
......
......@@ -14,4 +14,6 @@ ansible==1.9.4
pyinotify==0.9.6
passlib==1.6.5
argparse==1.4.0
django_crontab==0.6.0
\ No newline at end of file
django_crontab==0.6.0
django-smtp-ssl==1.0
pyte==0.5.2
\ No newline at end of file
#!/bin/bash
export LANG='zh_CN.UTF-8'
if [ "$USER" != "admin" ] && [ "$USER" != "root" ];then
python /opt/jumpserver/connect.py
if [ $USER == 'guanghongwei' ];then
echo
else
exit 3
echo
fi
fi
......@@ -9,6 +9,8 @@ from jperm.ansible_api import MyRunner
from jperm.perm_api import gen_resource
from jumpserver.templatetags.mytags import get_disk_info
import traceback
def group_add_asset(group, asset_id=None, asset_ip=None):
"""
......@@ -333,9 +335,11 @@ def get_ansible_asset_info(asset_ip, setup_info):
# ip = setup_info.get("ansible_default_ipv4").get("address")
mac = setup_info.get("ansible_default_ipv4").get("macaddress")
brand = setup_info.get("ansible_product_name")
cpu_type = setup_info.get("ansible_processor")[1]
cpu_cores = setup_info.get("ansible_processor_vcpus")
cpu = cpu_type + ' * ' + unicode(cpu_cores)
try:
cpu_type = setup_info.get("ansible_processor")[1]
except IndexError:
cpu_type = ' '.join(setup_info.get("ansible_processor")[0].split(' ')[:6])
memory = setup_info.get("ansible_memtotal_mb")
try:
memory_format = int(round((int(memory) / 1000), 0))
......@@ -343,7 +347,13 @@ def get_ansible_asset_info(asset_ip, setup_info):
memory_format = memory
disk = disk_need
system_type = setup_info.get("ansible_distribution")
system_version = setup_info.get("ansible_distribution_version")
if system_type.lower() == "freebsd":
system_version = setup_info.get("ansible_distribution_release")
cpu_cores = setup_info.get("ansible_processor_count")
else:
system_version = setup_info.get("ansible_distribution_version")
cpu_cores = setup_info.get("ansible_processor_vcpus")
cpu = cpu_type + ' * ' + unicode(cpu_cores)
system_arch = setup_info.get("ansible_architecture")
# asset_type = setup_info.get("ansible_system")
sn = setup_info.get("ansible_product_serial")
......@@ -359,24 +369,31 @@ def asset_ansible_update(obj_list, name=''):
for asset in obj_list:
try:
setup_info = ansible_asset_info['contacted'][asset.hostname]['ansible_facts']
except KeyError:
logger.debug("setup_info: %s" % setup_info)
except KeyError, e:
logger.error("获取setup_info失败: %s" % e)
continue
else:
asset_info = get_ansible_asset_info(asset.ip, setup_info)
other_ip, mac, cpu, memory, disk, sn, system_type, system_version, brand, system_arch = asset_info
asset_dic = {"other_ip": other_ip,
"mac": mac,
"cpu": cpu,
"memory": memory,
"disk": disk,
"sn": sn,
"system_type": system_type,
"system_version": system_version,
"system_arch": system_arch,
"brand": brand
}
ansible_record(asset, asset_dic, name)
try:
asset_info = get_ansible_asset_info(asset.ip, setup_info)
print asset_info
other_ip, mac, cpu, memory, disk, sn, system_type, system_version, brand, system_arch = asset_info
asset_dic = {"other_ip": other_ip,
"mac": mac,
"cpu": cpu,
"memory": memory,
"disk": disk,
"sn": sn,
"system_type": system_type,
"system_version": system_version,
"system_arch": system_arch,
"brand": brand
}
ansible_record(asset, asset_dic, name)
except Exception as e:
logger.error("save setup info failed! %s" % e)
traceback.print_exc()
def asset_ansible_update_all():
......
......@@ -15,12 +15,11 @@ from struct import unpack
from subprocess import Popen
from sys import platform, prefix, stderr
from tempfile import NamedTemporaryFile
from jumpserver.api import logger
from jinja2 import FileSystemLoader, Template
from jinja2.environment import Environment
from jumpserver.api import BASE_DIR
from jumpserver.api import BASE_DIR, logger
from jlog.models import Log
......@@ -104,3 +103,4 @@ def kill_invalid_connection():
log.end_time = now
log.save()
logger.warn('kill log %s' % log.log_path)
from django.db import models
from juser.models import User
import time
class Log(models.Model):
......@@ -11,6 +13,20 @@ class Log(models.Model):
pid = models.IntegerField()
is_finished = models.BooleanField(default=False)
end_time = models.DateTimeField(null=True)
filename = models.CharField(max_length=40)
'''
add by liuzheng
'''
# userMM = models.ManyToManyField(User)
# logPath = models.TextField()
# filename = models.CharField(max_length=40)
# logPWD = models.TextField() # log zip file's
# nick = models.TextField(null=True) # log's nick name
# log = models.TextField(null=True)
# history = models.TextField(null=True)
# timestamp = models.IntegerField(default=int(time.time()))
# datetimestamp = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.log_path
......@@ -47,3 +63,13 @@ class FileLog(models.Model):
datetime = models.DateTimeField(auto_now=True)
class TermLog(models.Model):
user = models.ManyToManyField(User)
logPath = models.TextField()
filename = models.CharField(max_length=40)
logPWD = models.TextField() # log zip file's
nick = models.TextField(null=True) # log's nick name
log = models.TextField(null=True)
history = models.TextField(null=True)
timestamp = models.IntegerField(default=int(time.time()))
datetimestamp = models.DateTimeField(auto_now_add=True)
# coding:utf-8
from django.db.models import Q
from django.template import RequestContext
from django.shortcuts import render_to_response
from django.shortcuts import render_to_response, render
from jumpserver.api import *
from jperm.perm_api import user_have_perm
from django.http import HttpResponseNotFound
from jlog.log_api import renderTemplate
from jlog.models import Log, ExecLog, FileLog
from jumpserver.settings import WEB_SOCKET_HOST
from jlog.models import Log, ExecLog, FileLog, TermLog
from jumpserver.settings import LOG_DIR
import zipfile
import json
import pyte
@require_role('admin')
......@@ -33,12 +35,13 @@ def log_list(request, offset):
posts = ExecLog.objects.all().order_by('-id')
keyword = request.GET.get('keyword', '')
if keyword:
posts = posts.filter(Q(user__icontains=keyword)|Q(host__icontains=keyword)|Q(cmd__icontains=keyword))
posts = posts.filter(Q(user__icontains=keyword) | Q(host__icontains=keyword) | Q(cmd__icontains=keyword))
elif offset == 'file':
posts = FileLog.objects.all().order_by('-id')
keyword = request.GET.get('keyword', '')
if keyword:
posts = posts.filter(Q(user__icontains=keyword)|Q(host__icontains=keyword)|Q(filename__icontains=keyword))
posts = posts.filter(
Q(user__icontains=keyword) | Q(host__icontains=keyword) | Q(filename__icontains=keyword))
else:
posts = Log.objects.filter(is_finished=True).order_by('-start_time')
username_all = set([log.user for log in Log.objects.all()])
......@@ -66,8 +69,6 @@ def log_list(request, offset):
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
web_monitor_uri = '%s/monitor' % WEB_SOCKET_HOST
web_kill_uri = 'http://%s/kill' % WEB_SOCKET_HOST
session_id = request.session.session_key
return render_to_response('jlog/log_%s.html' % offset, locals(), context_instance=RequestContext(request))
......@@ -112,19 +113,36 @@ def log_history(request):
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]
# 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 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]
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)
"""
Author: liuzheng712@gmail.com
"""
if request.method == "GET":
return render(request, 'jlog/record.html')
elif request.method == "POST":
log_id = request.REQUEST.get('id', None)
if log_id:
TermL = TermLogRecorder(request.user)
log = Log.objects.get(id=int(log_id))
return HttpResponse(TermL.load_full_log(filename=log.filename))
else:
return HttpResponse('无日志记录!')
return HttpResponse("ERROR")
else:
return HttpResponse("ERROR METHOD!")
@require_role('admin')
......@@ -147,3 +165,206 @@ def log_detail(request, offset):
except (SyntaxError, NameError):
result = {}
return my_render('jlog/file_detail.html', locals(), request)
class TermLogRecorder(object):
"""
TermLogRecorder
---
Author: liuzheng <liuzheng712@gmail>
This class is use for record the terminal output log.
self.commands is pure commands list, it will have empty item '' because in vi/vim model , I made it log noting.
self.CMD is the command with timestamp, like this {'1458723794.88': u'ls', '1458723799.82': u'tree'}.
self.log is the all output with delta time log.
self.vim_pattern is the regexp for check vi/vim/fg model.
Usage:
recorder = TermLogRecorder(user=UserObject) # or recorder = TermLogRecorder(uid=UserID)
recoder.write(messages)
recoder.save() # save all log into database
# The following methods all have `user`,`uid`,args. Same as __init__
list = recoder.list() # will give a object about this user's all log info
recoder.load_full_log(filemane) # will get full log
recoder.load_history(filename) # will only get the command history list
recoder.share_to(filename,user=UserObject) # or recoder.share_to(filename,uid=UserID). will share this commands to someone
recoder.unshare_to(filename,user=UserObject) # or recoder.unshare_to(filename,uid=UserID). will unshare this commands to someone
recoder.setid(id) # registered this term with an id, for monitor
"""
loglist = dict()
def __init__(self, user=None, uid=None):
self.log = {}
self.id = 0
if isinstance(user, User):
self.user = user
elif uid:
self.user = User.objects.get(id=uid)
else:
self.user = None
self.recoderStartTime = time.time()
self.__init_screen_stream()
self.recoder = False
self.commands = []
self._lists = None
self.file = None
self.filename = None
self._data = None
self.vim_pattern = re.compile(r'\W?vi[m]?\s.* | \W?fg\s.*', re.X)
self._in_vim = False
self.CMD = {}
def __init_screen_stream(self):
"""
Initializing the virtual screen and the character stream
"""
self._stream = pyte.ByteStream()
self._screen = pyte.Screen(80, 24)
self._stream.attach(self._screen)
def _command(self):
for i in self._screen.display:
if i.strip().__len__() > 0:
self.commands.append(i.strip())
if not i.strip() == '':
self.CMD[str(time.time())] = self.commands[-1]
self._screen.reset()
def setid(self, id):
self.id = id
TermLogRecorder.loglist[str(id)] = [self]
def write(self, msg):
if self.recoder and (not self._in_vim):
if self.commands.__len__() == 0:
self._stream.feed(msg)
elif not self.vim_pattern.search(self.commands[-1]):
self._stream.feed(msg)
else:
self._in_vim = True
self._command()
else:
if self._in_vim:
if re.compile(r'\[\?1049', re.X).search(msg.decode('utf-8', 'replace')):
self._in_vim = False
self.commands.append('')
self._screen.reset()
else:
self._command()
try:
self.write_message(msg)
except:
pass
# print "<<<<<<<<<<<<<<<<"
# print self.commands
# print self.CMD
# print ">>>>>>>>>>>>>>>>"
self.log[str(time.time() - self.recoderStartTime)] = msg.decode('utf-8', 'replace')
def save(self, path=LOG_DIR):
date = datetime.datetime.now().strftime('%Y%m%d')
filename = str(uuid.uuid4())
self.filename = filename
filepath = os.path.join(path, 'tty', date, filename + '.zip')
if not os.path.isdir(os.path.join(path, 'tty', date)):
os.makedirs(os.path.join(path, 'tty', date), mode=0777)
while os.path.isfile(filepath):
filename = str(uuid.uuid4())
filepath = os.path.join(path, 'tty', date, filename + '.zip')
password = str(uuid.uuid4())
try:
zf = zipfile.ZipFile(filepath, 'w', zipfile.ZIP_DEFLATED)
zf.setpassword(password)
zf.writestr(filename, json.dumps(self.log))
zf.close()
record = TermLog.objects.create(logPath=filepath, logPWD=password, filename=filename,
history=json.dumps(self.CMD), timestamp=int(self.recoderStartTime))
if self.user:
record.user.add(self.user)
except:
record = TermLog.objects.create(logPath='locale', logPWD=password, log=json.dumps(self.log),
filename=filename, history=json.dumps(self.CMD),
timestamp=int(self.recoderStartTime))
if self.user:
record.user.add(self.user)
try:
del TermLogRecorder.loglist[str(self.id)]
except KeyError:
pass
def list(self, user=None, uid=None):
tmp = []
if isinstance(user, User):
user = user
elif uid:
user = User.objects.get(id=uid)
else:
user = self.user
if user:
self._lists = TermLog.objects.filter(user=user.id)
for i in self._lists.all():
tmp.append(
{'filename': i.filename, 'locale': i.logPath == 'locale', 'nick': i.nick, 'timestamp': i.timestamp,
'date': i.datetimestamp})
return tmp
def load_full_log(self, filename, user=None, uid=None):
if isinstance(user, User):
user = user
elif uid:
user = User.objects.get(id=uid)
else:
user = self.user
if user:
if self._lists:
self.file = self._lists.get(filename=filename)
else:
self.file = TermLog.objects.get(user=user.id, filename=filename)
if self.file.logPath == 'locale':
return self.file.log
else:
try:
zf = zipfile.ZipFile(self.file.logPath, 'r', zipfile.ZIP_DEFLATED)
zf.setpassword(self.file.logPWD)
self._data = zf.read(zf.namelist()[0])
return self._data
except KeyError:
return 'ERROR: Did not find %s file' % filename
return 'ERROR User(None)'
def load_history(self, filename, user=None, uid=None):
if isinstance(user, User):
user = user
elif uid:
user = User.objects.get(id=uid)
else:
user = self.user
if user:
if self._lists:
self.file = self._lists.get(filename=filename)
else:
self.file = TermLog.objects.get(user=user.id, filename=filename)
return self.file.history
return 'ERROR User(None)'
def share_to(self, filename, user=None, uid=None):
if isinstance(user, User):
user = user
elif uid:
user = User.objects.get(id=uid)
else:
pass
if user:
TermLog.objects.get(filename=filename).user.add(user)
return True
return False
def unshare_to(self, filename, user=None, uid=None):
if isinstance(user, User):
user = user
elif uid:
user = User.objects.get(id=uid)
else:
pass
if user:
TermLog.objects.get(filename=filename).user.remove(user)
return True
return False
......@@ -26,7 +26,7 @@ class PermSudo(models.Model):
class PermRole(models.Model):
name = models.CharField(max_length=100, unique=True)
comment = models.CharField(max_length=100, null=True, blank=True, default='')
password = models.CharField(max_length=100)
password = models.CharField(max_length=128)
key_path = models.CharField(max_length=100)
date_added = models.DateTimeField(auto_now=True)
sudo = models.ManyToManyField(PermSudo, related_name='perm_role')
......
......@@ -182,8 +182,9 @@ def gen_resource(ob, perm=None):
info = {'hostname': asset.hostname,
'ip': asset.ip,
'port': asset_info.get('port', 22),
'ansible_ssh_private_key_file': role_key,
'username': role.name,
'password': CRYPTOR.decrypt(role.password)
# 'password': CRYPTOR.decrypt(role.password)
}
if os.path.isfile(role_key):
......
......@@ -68,6 +68,12 @@ def gen_keys(key="", key_path_dir=""):
return key_path_dir
def trans_all(str):
if str.strip().lower() == "all":
return str.upper()
else:
return str
if __name__ == "__main__":
print gen_keys()
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db.models import Q
from django.http import HttpResponseBadRequest, HttpResponseNotAllowed
......@@ -10,7 +11,7 @@ from jasset.models import Asset, AssetGroup
from jperm.models import PermRole, PermRule, PermSudo, PermPush
from jumpserver.models import Setting
from jperm.utils import gen_keys
from jperm.utils import gen_keys, trans_all
from jperm.ansible_api import MyTask
from jperm.perm_api import get_role_info, get_role_push_host
from jumpserver.api import my_render, get_object, CRYPTOR
......@@ -512,18 +513,18 @@ def perm_role_push(request):
task = MyTask(push_resource)
ret = {}
# 因为要先建立用户,所以password 是必选项,而push key是在 password也完成的情况下的 可选项
# 因为要先建立用户,而push key是在 password也完成的情况下的 可选项
# 1. 以秘钥 方式推送角色
if key_push:
ret["pass_push"] = task.add_user(role.name, CRYPTOR.decrypt(role.password))
ret["pass_push"] = task.add_user(role.name)
ret["key_push"] = task.push_key(role.name, os.path.join(role.key_path, 'id_rsa.pub'))
# 2. 推送账号密码
elif password_push:
ret["pass_push"] = task.add_user(role.name, CRYPTOR.decrypt(role.password))
# 2. 推送账号密码 <为了安全 系统用户统一使用秘钥进行通信, 不再提供密码方式的推送>
# elif password_push:
# ret["pass_push"] = task.add_user(role.name, CRYPTOR.decrypt(role.password))
# 3. 推送sudo配置文件
if password_push or key_push:
if key_push:
sudo_list = set([sudo for sudo in role.sudo.all()]) # set(sudo1, sudo2, sudo3)
if sudo_list:
ret['sudo'] = task.push_sudo_file([role], sudo_list)
......@@ -619,7 +620,9 @@ def perm_sudo_add(request):
raise ServerError(u"sudo name 和 commands是必填项!")
pattern = re.compile(r'[\n,\r]')
commands = ', '.join(list_drop_str(pattern.split(commands), u''))
deal_space_commands = list_drop_str(pattern.split(commands), u'')
deal_all_commands = map(trans_all, deal_space_commands)
commands = ', '.join(deal_all_commands)
logger.debug(u'添加sudo %s: %s' % (name, commands))
if get_object(PermSudo, name=name):
......@@ -656,7 +659,9 @@ def perm_sudo_edit(request):
raise ServerError(u"sudo name 和 commands是必填项!")
pattern = re.compile(r'[\n,\r]')
commands = ', '.join(list_drop_str(pattern.split(commands), u'')).strip()
deal_space_commands = list_drop_str(pattern.split(commands), u'')
deal_all_commands = map(trans_all, deal_space_commands)
commands = ', '.join(deal_all_commands).strip()
logger.debug(u'添加sudo %s: %s' % (name, commands))
sudo.name = name.strip()
......@@ -701,8 +706,14 @@ def perm_role_recycle(request):
recycle_assets.append(asset)
recycle_resource = gen_resource(recycle_assets)
task = MyTask(recycle_resource)
# TODO: 判断返回结果,处理异常
msg = task.del_user(get_object(PermRole, id=role_id).name)
try:
msg_del_user = task.del_user(get_object(PermRole, id=role_id).name)
msg_del_sudo = task.del_user_sudo(get_object(PermRole, id=role_id).name)
logger.info("recycle user msg: %s" % msg_del_user)
logger.info("recycle sudo msg: %s" % msg_del_sudo)
except Exception, e:
logger.warning("Recycle Role failed: %s" % e)
raise ServerError(u"回收已推送的系统用户失败: %s" % e)
for asset_id in asset_ids:
asset = get_object(Asset, id=asset_id)
......
[base]
url = http://192.168.244.129
key = 88aaaf7ffe3c6c04
url = http://127.0.0.1
key = 941enj9neshd1wes
ip = 0.0.0.0
port = 8000
log = debug
[db]
......@@ -10,14 +12,11 @@ user = jumpserver
password = mysql234
database = jumpserver
[websocket]
web_socket_host = 192.168.244.129:3000
[mail]
mail_enable = 1
email_host = smtp.qq.com
email_port = 25
email_host_user = xxxxxxxx@qq.com
email_host_password = xxxxxx
email_use_tls = False
email_host =
email_port = 587
email_host_user =
email_host_password =
email_use_tls = True
email_use_ssl = False
......@@ -484,7 +484,8 @@ def my_render(template, data, request):
def get_tmp_dir():
dir_name = os.path.join('/tmp', uuid.uuid4().hex)
seed = uuid.uuid4().hex[:4]
dir_name = os.path.join('/tmp', '%s-%s' % (datetime.datetime.now().strftime('%Y%m%d-%H%M%S'), seed))
mkdir(dir_name, mode=0777)
return dir_name
......
......@@ -33,6 +33,11 @@ EMAIL_PORT = config.get('mail', 'email_port')
EMAIL_HOST_USER = config.get('mail', 'email_host_user')
EMAIL_HOST_PASSWORD = config.get('mail', 'email_host_password')
EMAIL_USE_TLS = config.getboolean('mail', 'email_use_tls')
try:
EMAIL_USE_SSL = config.getboolean('mail', 'email_use_ssl')
except ConfigParser.NoOptionError:
EMAIL_USE_SSL = False
EMAIL_BACKEND = 'django_smtp_ssl.SSLEmailBackend' if EMAIL_USE_SSL else 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_TIMEOUT = 5
# ======== Log ==========
......@@ -41,7 +46,8 @@ SSH_KEY_DIR = os.path.join(BASE_DIR, 'keys/role_keys')
KEY = config.get('base', 'key')
URL = config.get('base', 'url')
LOG_LEVEL = config.get('base', 'log')
WEB_SOCKET_HOST = config.get('websocket', 'web_socket_host')
IP = config.get('base', 'ip')
PORT = config.get('base', 'port')
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
......
......@@ -286,7 +286,7 @@ def upload(request):
res = gen_resource({'user': user, 'asset': asset_select})
runner = MyRunner(res)
runner.run('copy', module_args='src=%s dest=%s directory_mode'
% (upload_dir, upload_dir), pattern='*')
% (upload_dir, '/tmp'), pattern='*')
ret = runner.results
logger.debug(ret)
FileLog(user=request.user.username, host=' '.join([asset.hostname for asset in asset_select]),
......@@ -344,7 +344,7 @@ def download(request):
def exec_cmd(request):
role = request.GET.get('role')
check_assets = request.GET.get('check_assets', '')
web_terminal_uri = '%s/exec?role=%s' % (WEB_SOCKET_HOST, role)
web_terminal_uri = '/ws/exec?role=%s' % (role)
return my_render('exec_cmd.html', locals(), request)
......@@ -354,9 +354,7 @@ def web_terminal(request):
role_name = request.GET.get('role')
asset = get_object(Asset, id=asset_id)
if asset:
print asset
hostname = asset.hostname
web_terminal_uri = '%s/terminal?id=%s&role=%s' % (WEB_SOCKET_HOST, asset_id, role_name)
return render_to_response('jlog/web_terminal.html', locals())
......@@ -87,12 +87,12 @@ def db_update_user(**kwargs):
admin_groups_post = kwargs.pop('admin_groups')
user_id = kwargs.pop('user_id')
user = User.objects.filter(id=user_id)
user_get = User.objects.get(id=user_id)
if user:
pwd = kwargs.pop('password')
user_get = user[0]
password = kwargs.pop('password')
user.update(**kwargs)
if pwd != '':
user_get.set_password(pwd)
if password.strip():
user_get.set_password(password)
user_get.save()
else:
return None
......@@ -137,7 +137,7 @@ def gen_ssh_key(username, password='',
if authorized_keys:
auth_key_dir = os.path.join(home, username, '.ssh')
mkdir(auth_key_dir, username=username , mode=0700)
mkdir(auth_key_dir, username=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:
......@@ -146,15 +146,13 @@ def gen_ssh_key(username, password='',
chown(authorized_key_file, username)
def server_add_user(username, password, ssh_key_pwd='', ssh_key_login_need=True):
def server_add_user(username, ssh_key_pwd=''):
"""
add a system user in jumpserver
在jumpserver服务器上添加一个用户
"""
bash("useradd '%s'; echo '%s'; echo '%s:%s' | chpasswd " %
(username, password, username, password))
if ssh_key_login_need:
gen_ssh_key(username, ssh_key_pwd)
bash("useradd -s '%s' '%s'" % (os.path.join(BASE_DIR, 'init.sh'), username))
gen_ssh_key(username, ssh_key_pwd)
def user_add_mail(user, kwargs):
......@@ -171,7 +169,7 @@ def user_add_mail(user, kwargs):
您的web登录密码: %s
您的ssh密钥文件密码: %s
密钥下载地址: %s/juser/key/down/?uuid=%s
说明: 请登陆后再下载密钥
说明: 请登陆跳板机后台下载密钥, 然后使用密钥登陆跳板机
""" % (user.name, user.username, user_role.get(user.role, u'普通用户'),
kwargs.get('password'), kwargs.get('ssh_key_pwd'), URL, user.uuid)
send_mail(mail_title, mail_msg, MAIL_FROM, [user.email], fail_silently=False)
......@@ -182,30 +180,20 @@ def server_del_user(username):
delete a user from jumpserver linux system
删除系统上的某用户
"""
bash('userdel -r %s' % username)
bash('userdel -r -f %s' % username)
def get_display_msg(user, password, ssh_key_pwd, ssh_key_login_need, send_mail_need):
def get_display_msg(user, password='', ssh_key_pwd='', send_mail_need=False):
if send_mail_need:
msg = u'添加用户 %s 成功! 用户密码已发送到 %s 邮箱!' % (user.name, user.email)
return msg
if ssh_key_login_need:
msg = u"""
跳板机地址: %s
用户名:%s
密码:%s
密钥密码:%s
密钥下载url: %s/juser/key/down/?uuid=%s
该账号密码可以登陆web和跳板机。
""" % (URL, user.username, password, ssh_key_pwd, URL, user.uuid)
else:
msg = u"""
跳板机地址: %s \n
用户名:%s \n
密码:%s \n
跳板机地址: %s <br />
用户名:%s <br />
密码:%s <br />
密钥密码:%s <br />
密钥下载url: %s/juser/key/down/?uuid=%s <br />
该账号密码可以登陆web和跳板机。
""" % (URL, user.username, password)
""" % (URL, user.username, password, ssh_key_pwd, URL, user.uuid)
return msg
......@@ -153,8 +153,7 @@ def user_add(request):
ssh_key_pwd = PyCrypt.gen_rand_pass(16)
extra = request.POST.getlist('extra', [])
is_active = False if '0' in extra else True
ssh_key_login_need = True
send_mail_need = True if '2' in extra else False
send_mail_need = True if '1' in extra else False
try:
if '' in [username, password, ssh_key_pwd, name, role]:
......@@ -176,7 +175,7 @@ def user_add(request):
ssh_key_pwd=ssh_key_pwd,
is_active=is_active,
date_joined=datetime.datetime.now())
server_add_user(username, password, ssh_key_pwd, ssh_key_login_need)
server_add_user(username=username, ssh_key_pwd=ssh_key_pwd)
user = get_object(User, username=username)
if groups:
user_groups = []
......@@ -193,7 +192,7 @@ def user_add(request):
else:
if MAIL_ENABLE and send_mail_need:
user_add_mail(user, kwargs=locals())
msg = get_display_msg(user, password, ssh_key_pwd, ssh_key_login_need, send_mail_need)
msg = get_display_msg(user, password=password, ssh_key_pwd=ssh_key_pwd, send_mail_need=send_mail_need)
return my_render('juser/user_add.html', locals(), request)
......@@ -361,7 +360,7 @@ def user_edit(request):
admin_groups = request.POST.getlist('admin_groups', [])
extra = request.POST.getlist('extra', [])
is_active = True if '0' in extra else False
email_need = True if '2' in extra else False
email_need = True if '1' in extra else False
user_role = {'SU': u'超级管理员', 'GA': u'部门管理员', 'CU': u'普通用户'}
if user_id:
......@@ -369,11 +368,6 @@ def user_edit(request):
else:
return HttpResponseRedirect(reverse('user_list'))
if password != '':
password_decode = password
else:
password_decode = None
db_update_user(user_id=user_id,
password=password,
name=name,
......@@ -392,7 +386,7 @@ def user_edit(request):
密码:%s (如果密码为None代表密码为原密码)
权限::%s
""" % (user.name, URL, user.username, password_decode, user_role.get(role_post, u''))
""" % (user.name, URL, user.username, password, user_role.get(role_post, u''))
send_mail('您的信息已修改', msg, MAIL_FROM, [email], fail_silently=False)
return HttpResponseRedirect(reverse('user_list'))
......@@ -453,7 +447,6 @@ def down_key(request):
uuid_r = request.GET.get('uuid', '')
else:
uuid_r = request.user.uuid
if uuid_r:
user = get_object(User, uuid=uuid_r)
if user:
......@@ -466,6 +459,8 @@ def down_key(request):
f.close()
response = HttpResponse(data, content_type='application/octet-stream')
response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(private_key_file)
if request.user.role == 'CU':
os.unlink(private_key_file)
return response
return HttpResponse('No Key File. Contact Admin.')
#!/usr/bin/env python
# coding: utf-8
import time
......@@ -7,8 +8,9 @@ import os
import sys
import os.path
import threading
import datetime
import re
import functools
from django.core.signals import request_started, request_finished
import tornado.ioloop
import tornado.options
......@@ -20,10 +22,10 @@ import tornado.httpclient
from tornado.websocket import WebSocketClosedError
from tornado.options import define, options
from pyinotify import WatchManager, Notifier, ProcessEvent, IN_DELETE, IN_CREATE, IN_MODIFY, AsyncNotifier
from pyinotify import WatchManager, ProcessEvent, IN_DELETE, IN_CREATE, IN_MODIFY, AsyncNotifier
import select
from connect import Tty, User, Asset, PermRole, logger, get_object, PermRole, gen_resource
from connect import Tty, User, Asset, PermRole, logger, get_object, gen_resource
from connect import TtyLog, Log, Session, user_have_perm, get_group_user_perm, MyRunner, ExecLog
try:
......@@ -31,9 +33,23 @@ try:
except ImportError:
import json
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
from jumpserver.settings import IP, PORT
define("port", default=PORT, help="run on the given port", type=int)
define("host", default=IP, help="run port on given host", type=str)
from jlog.views import TermLogRecorder
define("port", default=3000, help="run on the given port", type=int)
define("host", default='0.0.0.0', help="run port on given host", type=str)
def django_request_support(func):
@functools.wraps(func)
def _deco(*args, **kwargs):
request_started.send_robust(func)
response = func(*args, **kwargs)
request_finished.send_robust(func)
return response
return _deco
def require_auth(role='user'):
......@@ -50,6 +66,7 @@ def require_auth(role='user'):
logger.debug('Websocket: session: %s' % session)
if session and datetime.datetime.now() < session.expire_date:
user_id = session.get_decoded().get('_auth_user_id')
request.user_id = user_id
user = get_object(User, id=user_id)
if user:
logger.debug('Websocket: user [ %s ] request websocket' % user.username)
......@@ -67,7 +84,9 @@ def require_auth(role='user'):
except AttributeError:
pass
logger.warning('Websocket: Request auth failed.')
return _deco2
return _deco
......@@ -87,7 +106,7 @@ class EventHandler(ProcessEvent):
self.client = client
def process_IN_MODIFY(self, event):
self.client.write_message(f.read())
self.client.write_message(f.read().decode('utf-8', 'replace'))
def file_monitor(path='.', client=None):
......@@ -127,6 +146,7 @@ class MonitorHandler(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True
@django_request_support
@require_auth('admin')
def open(self):
# 获取监控的path
......@@ -178,6 +198,7 @@ class WebTty(Tty):
class WebTerminalKillHandler(tornado.web.RequestHandler):
@django_request_support
@require_auth('admin')
def get(self):
ws_id = self.get_argument('id')
......@@ -207,6 +228,7 @@ class ExecHandler(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True
@django_request_support
@require_auth('user')
def open(self):
logger.debug('Websocket: Open exec request')
......@@ -287,12 +309,14 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True
@django_request_support
@require_auth('user')
def open(self):
logger.debug('Websocket: Open request')
role_name = self.get_argument('role', 'sb')
asset_id = self.get_argument('id', 9999)
asset = get_object(Asset, id=asset_id)
self.termlog = TermLogRecorder(User.objects.get(id=self.user_id))
if asset:
roles = user_have_perm(self.user, asset)
logger.debug(roles)
......@@ -314,7 +338,10 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler):
logger.debug('Websocket: request web terminal Host: %s User: %s Role: %s' % (asset.hostname, self.user.username,
login_role.name))
self.term = WebTty(self.user, asset, login_role, login_type='web')
self.term.remote_ip = self.request.remote_ip
# self.term.remote_ip = self.request.remote_ip
self.term.remote_ip = self.request.headers.get("X-Real-IP")
if not self.term.remote_ip:
self.term.remote_ip = self.request.remote_ip
self.ssh = self.term.get_connection()
self.channel = self.ssh.invoke_shell(term='xterm')
WebTerminalHandler.tasks.append(MyThread(target=self.forward_outbound))
......@@ -330,43 +357,49 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler):
pass
def on_message(self, message):
data = json.loads(message)
if not data:
jsondata = json.loads(message)
if not jsondata:
return
if 'resize' in data.get('data'):
if 'resize' in jsondata.get('data'):
self.channel.resize_pty(
data.get('data').get('resize').get('cols', 80),
data.get('data').get('resize').get('rows', 24)
jsondata.get('data').get('resize').get('cols', 80),
jsondata.get('data').get('resize').get('rows', 24)
)
elif data.get('data'):
elif jsondata.get('data'):
self.termlog.recoder = True
self.term.input_mode = True
if str(data['data']) in ['\r', '\n', '\r\n']:
if str(jsondata['data']) in ['\r', '\n', '\r\n']:
if self.term.vim_flag:
match = self.term.ps1_pattern.search(self.term.vim_data)
match = re.compile(r'\x1b\[\?1049', re.X).findall(self.term.vim_data)
if match:
self.term.vim_flag = False
vim_data = self.term.deal_command(self.term.vim_data)[0:200]
if len(data) > 0:
TtyLog(log=self.log, datetime=datetime.datetime.now(), cmd=vim_data).save()
TtyLog(log=self.log, datetime=datetime.datetime.now(),
cmd=self.term.deal_command(self.term.data)[0:200]).save()
if self.term.vim_end_flag or len(match) == 2:
self.term.vim_flag = False
self.term.vim_end_flag = False
else:
self.term.vim_end_flag = True
else:
result = self.term.deal_command(self.term.data)[0:200]
if len(result) > 0:
TtyLog(log=self.log, datetime=datetime.datetime.now(), cmd=result).save()
self.term.vim_data = ''
self.term.data = ''
self.term.input_mode = False
self.channel.send(data['data'])
self.channel.send(jsondata['data'])
else:
pass
def on_close(self):
logger.debug('Websocket: Close request')
print self.termlog.CMD
self.termlog.save()
if self in WebTerminalHandler.clients:
WebTerminalHandler.clients.remove(self)
try:
self.log_file_f.write('End time is %s' % datetime.datetime.now())
self.log.is_finished = True
self.log.end_time = datetime.datetime.now()
self.log.filename = self.termlog.filename
self.log.save()
self.log_time_f.close()
self.ssh.close()
......@@ -377,6 +410,7 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler):
def forward_outbound(self):
self.log_file_f, self.log_time_f, self.log = self.term.get_log()
self.id = self.log.id
self.termlog.setid(self.id)
try:
data = ''
pre_timestamp = time.time()
......@@ -390,9 +424,11 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler):
if self.term.vim_flag:
self.term.vim_data += recv
try:
self.write_message(json.dumps({'data': data}))
self.write_message(data.decode('utf-8', 'replace'))
self.termlog.write(data)
self.termlog.recoder = False
now_timestamp = time.time()
self.log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(data)))
self.log_time_f.write('%s %s\n' % (round(now_timestamp - pre_timestamp, 4), len(data)))
self.log_file_f.write(data)
pre_timestamp = now_timestamp
self.log_file_f.flush()
......@@ -406,6 +442,24 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler):
pass
# class MonitorHandler(WebTerminalHandler):
# @django_request_support
# @require_auth('user')
# def open(self):
# try:
# self.returnlog = TermLogRecorder.loglist[self.get_argument('id')]
# self.returnlog.write_message = self.write_message
# except:
# self.write_message('Log is None')
# self.close()
#
# def on_message(self, message):
# pass
#
# def on_close(self):
# self.close()
class Application(tornado.web.Application):
def __init__(self):
handlers = [
......@@ -425,12 +479,41 @@ class Application(tornado.web.Application):
tornado.web.Application.__init__(self, handlers, **setting)
def main():
from django.core.wsgi import get_wsgi_application
import tornado.wsgi
wsgi_app = get_wsgi_application()
container = tornado.wsgi.WSGIContainer(wsgi_app)
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': False,
}
tornado_app = tornado.web.Application(
[
(r'/ws/monitor', MonitorHandler),
(r'/ws/terminal', WebTerminalHandler),
(r'/kill', WebTerminalKillHandler),
(r'/ws/exec', ExecHandler),
(r"/static/(.*)", tornado.web.StaticFileHandler,
dict(path=os.path.join(os.path.dirname(__file__), "static"))),
('.*', tornado.web.FallbackHandler, dict(fallback=container)),
], **setting)
server = tornado.httpserver.HTTPServer(tornado_app)
server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
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=5)
# 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=5)
# tornado.ioloop.IOLoop.instance().start()
print "Run server on %s:%s" % (options.host, options.port)
tornado.ioloop.IOLoop.instance().start()
main()
......@@ -4,70 +4,88 @@
# chkconfig: - 85 12
# description: Open source detecting system
# processname: jumpserver
# Date: 2015-04-12
# Version: 2.0.0
# Date: 2016-02-27
# Version: 3.0.1
# Site: http://www.jumpserver.org
# Author: Jumpserver Team
. /etc/init.d/functions
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/opt/node/bin
jumpserver_dir=
base_dir=$(dirname $0)
PROC_NAME="jumpsever"
jumpserver_dir=${jumpserver_dir:-$base_dir}
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
if [ -f ${jumpserver_dir}/install/functions ];then
. ${jumpserver_dir}/install/functions
elif [ -f /etc/init.d/functions ];then
. /etc/init.d/functions
else
echo "No functions script found in [./functions, ./install/functions, /etc/init.d/functions]"
exit 1
fi
PROC_NAME="jumpserver"
lockfile=/var/lock/subsys/${PROC_NAME}
start() {
jump_start=$"Starting ${PROC_NAME} service:"
if [ -f $lockfile ];then
echo "jumpserver is running..."
success "$jump_start"
else
daemon python $base_dir/manage.py runserver 0.0.0.0:80 &>> /tmp/jumpserver.log 2>&1 &
daemon python $base_dir/run_websocket.py &> /dev/null 2>&1 &
sleep 4
echo -n "$jump_start"
nums=0
for i in manage.py run_websocket.py;do
ps aux | grep "$i" | grep -v 'grep' &> /dev/null && let nums+=1 || echo "$i not running"
done
if [ "x$nums" == "x2" ];then
success "$jump_start"
touch "$lockfile"
echo
else
failure "$jump_start"
echo
fi
fi
jump_start=$"Starting ${PROC_NAME} service:"
if [ -f $lockfile ];then
echo -n "jumpserver is running..."
success "$jump_start"
echo
else
daemon python $jumpserver_dir/manage.py crontab add &>> /var/log/jumpserver.log 2>&1
daemon python $jumpserver_dir/run_server.py &> /dev/null 2>&1 &
sleep 1
echo -n "$jump_start"
ps axu | grep 'run_server' | grep -v 'grep' &> /dev/null
if [ $? == '0' ];then
success "$jump_start"
if [ ! -e $lockfile ]; then
lockfile_dir=`dirname $lockfile`
mkdir -pv $lockfile_dir
fi
touch "$lockfile"
echo
else
failure "$jump_start"
echo
fi
fi
}
stop() {
echo -n $"Stopping ${PROC_NAME} service:"
ps aux | grep -E 'manage.py|run_websocket.py' | grep -v grep | awk '{print $2}' | xargs kill -9 &> /dev/null
ret=$?
if [ $ret -eq 0 ]; then
echo_success
echo
echo -n $"Stopping ${PROC_NAME} service:"
daemon python $jumpserver_dir/manage.py crontab remove &>> /var/log/jumpserver.log 2>&1
ps aux | grep -E 'run_server.py' | grep -v grep | awk '{print $2}' | xargs kill -9 &> /dev/null
ret=$?
if [ $ret -eq 0 ]; then
echo_success
echo
rm -f "$lockfile"
else
echo_failure
echo
else
echo_failure
echo
rm -f "$lockfile"
fi
fi
}
status(){
ps axu | grep 'run_server' | grep -v 'grep' &> /dev/null
if [ $? == '0' ];then
echo -n "jumpserver is running..."
success
touch "$lockfile"
echo
else
echo -n "jumpserver is not running."
failure
echo
fi
}
restart(){
......@@ -76,22 +94,22 @@ restart(){
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
*)
echo $"Usage: $0 {start|stop|restart}"
exit 2
esac
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
*)
echo $"Usage: $0 {start|stop|restart|status}"
exit 2
esac
/*
AngularJS v1.2.5
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(h,e,A){'use strict';function u(w,q,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,n){function y(){l&&(l.$destroy(),l=null);g&&(k.leave(g),g=null)}function v(){var b=w.current&&w.current.locals;if(b&&b.$template){var b=a.$new(),f=w.current;g=n(b,function(d){k.enter(d,null,g||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||q()});y()});l=f.scope=b;l.$emit("$viewContentLoaded");l.$eval(h)}else y()}var l,g,t=b.autoscroll,h=b.onload||"";a.$on("$routeChangeSuccess",
v);v()}}}function z(e,h,k){return{restrict:"ECA",priority:-400,link:function(a,c){var b=k.current,f=b.locals;c.html(f.$template);var n=e(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));n(a)}}}h=e.module("ngRoute",["ng"]).provider("$route",function(){function h(a,c){return e.extend(new (e.extend(function(){},{prototype:a})),c)}function q(a,e){var b=e.caseInsensitiveMatch,
f={originalPath:a,regexp:a},h=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?|\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var k={};this.when=function(a,c){k[a]=e.extend({reloadOnSearch:!0},c,a&&q(a,c));if(a){var b="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";k[b]=e.extend({redirectTo:a},
q(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,n,q,v,l){function g(){var d=t(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!x)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)x=!1,a.$broadcast("$routeChangeStart",d,m),(r.current=d)&&d.redirectTo&&(e.isString(d.redirectTo)?
c.path(u(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?n.get(d):n.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=l.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl=b,c=q.get(b,{cache:v}).then(function(a){return a.data})));
e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function t(){var a,b;e.forEach(k,function(f,k){var p;if(p=!b){var s=c.path();p=f.keys;var l={};if(f.regexp)if(s=f.regexp.exec(s)){for(var g=1,q=s.length;g<q;++g){var n=p[g-1],r="string"==typeof s[g]?decodeURIComponent(s[g]):s[g];n&&r&&(l[n.name]=r)}p=l}else p=null;else p=null;
p=a=p}p&&(b=h(f,{params:e.extend({},c.search(),a),pathParams:a}),b.$$route=f)});return b||k[null]&&h(k[null],{params:{},pathParams:{}})}function u(a,c){var b=[];e.forEach((a||"").split(":"),function(a,d){if(0===d)b.push(a);else{var e=a.match(/(\w+)(.*)/),f=e[1];b.push(c[f]);b.push(e[2]||"");delete c[f]}});return b.join("")}var x=!1,r={routes:k,reload:function(){x=!0;a.$evalAsync(g)}};a.$on("$locationChangeSuccess",g);return r}]});h.provider("$routeParams",function(){this.$get=function(){return{}}});
h.directive("ngView",u);h.directive("ngView",z);u.$inject=["$route","$anchorScroll","$animate"];z.$inject=["$compile","$controller","$route"]})(window,window.angular);
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
* Created by liuzheng on 3/25/16.
*/
'use strict';
var NgApp = angular.module('NgApp', ['ngRoute']);
NgApp.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.transformRequest = function (obj) {
var str = [];
for (var p in obj) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
};
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
$httpProvider.defaults.headers.post = {
'Content-Type': 'application/x-www-form-urlencoded'
}
}]);
NgApp.controller('TerminalRecordCtrl', function ($scope, $http) {
$http.post(window.location.href).success(function (data) {
var toggle = true;
var totalTime = 0;
var TICK = 33;
var TIMESTEP = 33;
var time = 33;
var timer;
var pos = 0;
// Thanks http://stackoverflow.com/a/2998822
function zeroPad(num, size) {
var s = "0" + num;
return s.substr(s.length - size);
}
$scope.scrub = function () {
var setPercent = document.getElementById('scrubber').value;
time = (setPercent / 100) * totalTime;
$scope.restart(time);
};
function buildTimeString(millis) {
var hours = zeroPad(Math.floor(millis / (1000 * 60 * 60)), 2);
millis -= hours * (1000 * 60 * 60);
var minutes = zeroPad(Math.floor(millis / (1000 * 60)), 2);
millis -= minutes * (1000 * 60);
var seconds = zeroPad(Math.floor(millis / 1000), 2);
return hours + ':' + minutes + ':' + seconds;
}
function advance() {
document.getElementById('scrubber').value =
Math.ceil((time / totalTime) * 100);
document.getElementById("beforeScrubberText").innerHTML = buildTimeString(time);
for (; pos < timelist.length; pos++) {
if (timelist[pos] * 1000 <= time) {
term.write(data[timelist[pos]]);
} else {
break;
}
}
if (pos >= timelist.length) {
clearInterval(timer);
}
time += TIMESTEP;
}
$scope.pause = function (test) {
if (!toggle && test) {
return;
}
if (toggle) {
clearInterval(timer);
toggle = !toggle;
} else {
timer = setInterval(advance, TICK);
toggle = !toggle;
}
};
$scope.setSpeed = function () {
var speed = document.getElementById('speed').value;
if (speed == 0) {
TIMESTEP = TICK;
} else if (speed < 0) {
TIMESTEP = TICK / -speed;
} else {
TIMESTEP = TICK * speed;
}
};
$scope.restart = function (millis) {
clearInterval(timer);
term.reset();
time = millis;
pos = 0;
toggle = true;
timer = setInterval(advance, TICK);
};
var term = new Terminal({
rows: 24,
cols: 80,
useStyle: true,
screenKeys: true
});
var timelist = [];
for (var i in data) {
totalTime = Math.max(totalTime, i);
timelist.push(i);
}
timelist = timelist.sort(function(a, b){return a-b});
totalTime = totalTime * 1000;
document.getElementById("afterScrubberText").innerHTML = buildTimeString(totalTime);
term.open(document.getElementById('terminal'));
timer = setInterval(advance, TICK);
})
})
\ No newline at end of file
/**
* tty.js - an xterm emulator
* Christopher Jeffrey (https://github.com/chjj/tty.js)
* term.js - an xterm emulator
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
*
* Originally forked from (with the author's permission):
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
;(function() {
......@@ -42,7 +57,7 @@ var window = this
*/
function EventEmitter() {
this._events = {};
this._events = this._events || {};
}
EventEmitter.prototype.addListener = function(type, listener) {
......@@ -99,6 +114,54 @@ EventEmitter.prototype.listeners = function(type) {
return this._events[type] = this._events[type] || [];
};
/**
* Stream
*/
function Stream() {
EventEmitter.call(this);
}
inherits(Stream, EventEmitter);
Stream.prototype.pipe = function(dest, options) {
var src = this
, ondata
, onerror
, onend;
function unbind() {
src.removeListener('data', ondata);
src.removeListener('error', onerror);
src.removeListener('end', onend);
dest.removeListener('error', onerror);
dest.removeListener('close', unbind);
}
src.on('data', ondata = function(data) {
dest.write(data);
});
src.on('error', onerror = function(err) {
unbind();
if (!this.listeners('error').length) {
throw err;
}
});
src.on('end', onend = function() {
dest.end();
unbind();
});
dest.on('error', onerror);
dest.on('close', unbind);
dest.emit('pipe', src);
return dest;
};
/**
* States
*/
......@@ -109,33 +172,75 @@ var normal = 0
, osc = 3
, charset = 4
, dcs = 5
, ignore = 6;
, ignore = 6
, UDK = { type: 'udk' };
/**
* Terminal
*/
function Terminal(cols, rows, handler) {
EventEmitter.call(this);
function Terminal(options) {
var self = this;
var options;
if (typeof cols === 'object') {
options = cols;
cols = options.cols;
rows = options.rows;
handler = options.handler;
if (!(this instanceof Terminal)) {
return new Terminal(arguments[0], arguments[1], arguments[2]);
}
this._options = options || {};
this.cols = cols || Terminal.geometry[0];
this.rows = rows || Terminal.geometry[1];
Stream.call(this);
//if (handler) {
// this.on('data', handler);
//}
this.handler = handler;
if (this.handler){
this.on('data', this.handler);
if (typeof options === 'number') {
options = {
cols: arguments[0],
rows: arguments[1],
handler: arguments[2]
};
}
options = options || {};
each(keys(Terminal.defaults), function(key) {
if (options[key] == null) {
options[key] = Terminal.options[key];
// Legacy:
if (Terminal[key] !== Terminal.defaults[key]) {
options[key] = Terminal[key];
}
}
self[key] = options[key];
});
if (options.colors.length === 8) {
options.colors = options.colors.concat(Terminal._colors.slice(8));
} else if (options.colors.length === 16) {
options.colors = options.colors.concat(Terminal._colors.slice(16));
} else if (options.colors.length === 10) {
options.colors = options.colors.slice(0, -2).concat(
Terminal._colors.slice(8, -2), options.colors.slice(-2));
} else if (options.colors.length === 18) {
options.colors = options.colors.slice(0, -2).concat(
Terminal._colors.slice(16, -2), options.colors.slice(-2));
}
this.colors = options.colors;
this.options = options;
// this.context = options.context || window;
// this.document = options.document || document;
this.parent = options.body || options.parent
|| (document ? document.getElementsByTagName('body')[0] : null);
this.cols = options.cols || options.geometry[0];
this.rows = options.rows || options.geometry[1];
// Act as though we are a node TTY stream:
this.setRawMode;
this.isTTY = true;
this.isRaw = true;
this.columns = this.cols;
this.rows = this.rows;
if (options.handler) {
this.on('data', options.handler);
}
this.ybase = 0;
......@@ -144,7 +249,7 @@ function Terminal(cols, rows, handler) {
this.y = 0;
this.cursorState = 0;
this.cursorHidden = false;
this.convertEol = false;
this.convertEol;
this.state = 0;
this.queue = '';
this.scrollTop = 0;
......@@ -152,11 +257,24 @@ function Terminal(cols, rows, handler) {
// modes
this.applicationKeypad = false;
this.applicationCursor = false;
this.originMode = false;
this.insertMode = false;
this.wraparoundMode = false;
this.normal = null;
// select modes
this.prefixMode = false;
this.selectMode = false;
this.visualMode = false;
this.searchMode = false;
this.searchDown;
this.entry = '';
this.entryPrefix = 'Search: ';
this._real;
this._selected;
this._textarea;
// charset
this.charset = null;
this.gcharset = null;
......@@ -188,7 +306,7 @@ function Terminal(cols, rows, handler) {
this.readable = true;
this.writable = true;
this.defAttr = (257 << 9) | 256;
this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);
this.curAttr = this.defAttr;
this.params = [];
......@@ -206,14 +324,14 @@ function Terminal(cols, rows, handler) {
this.setupStops();
}
inherits(Terminal, EventEmitter);
inherits(Terminal, Stream);
/**
* Colors
*/
// Colors 0-15
Terminal.colors = [
Terminal.tangoColors = [
// dark:
'#2e3436',
'#cc0000',
......@@ -234,10 +352,31 @@ Terminal.colors = [
'#eeeeec'
];
// Colors 16-255
Terminal.xtermColors = [
// dark:
'#000000', // black
'#cd0000', // red3
'#00cd00', // green3
'#cdcd00', // yellow3
'#0000ee', // blue2
'#cd00cd', // magenta3
'#00cdcd', // cyan3
'#e5e5e5', // gray90
// bright:
'#7f7f7f', // gray50
'#ff0000', // red
'#00ff00', // green
'#ffff00', // yellow
'#5c5cff', // rgb:5c/5c/ff
'#ff00ff', // magenta
'#00ffff', // cyan
'#ffffff' // white
];
// Colors 0-15 + 16-255
// Much thanks to TooTallNate for writing this.
Terminal.colors = (function() {
var colors = Terminal.colors
var colors = Terminal.tangoColors.slice()
, r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
, i;
......@@ -267,27 +406,55 @@ Terminal.colors = (function() {
})();
// Default BG/FG
Terminal.defaultColors = {
bg: '#000000',
fg: '#f0f0f0'
};
Terminal.colors[256] = '#000000';
Terminal.colors[257] = '#f0f0f0';
Terminal._colors = Terminal.colors.slice();
Terminal.vcolors = (function() {
var out = []
, colors = Terminal.colors
, i = 0
, color;
for (; i < 256; i++) {
color = parseInt(colors[i].substring(1), 16);
out.push([
(color >> 16) & 0xff,
(color >> 8) & 0xff,
color & 0xff
]);
}
Terminal.colors[256] = Terminal.defaultColors.bg;
Terminal.colors[257] = Terminal.defaultColors.fg;
return out;
})();
/**
* 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;
Terminal.defaults = {
colors: Terminal.colors,
convertEol: false,
termName: 'xterm',
geometry: [80, 24],
cursorBlink: true,
visualBell: false,
popOnBell: false,
scrollback: 1000,
screenKeys: false,
debug: false,
useStyle: false
// programFeatures: false,
// focusKeys: false,
};
Terminal.options = {};
each(keys(Terminal.defaults), function(key) {
Terminal[key] = Terminal.defaults[key];
Terminal.options[key] = Terminal.defaults[key];
});
/**
* Focused Terminal
......@@ -296,115 +463,640 @@ Terminal.debug = false;
Terminal.focus = null;
Terminal.prototype.focus = function() {
if (this._textarea) {
this._textarea.focus();
}
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.blur();
}
Terminal.focus = this;
if (this.sendFocus) this.send('\x1b[I');
this.showCursor();
// try {
// this.element.focus();
// } catch (e) {
// ;
// }
// this.emit('focus');
Terminal.focus = this;
};
Terminal.prototype.blur = function() {
if (Terminal.focus !== this) return;
this.cursorState = 0;
this.refresh(this.y, this.y);
if (this.sendFocus) this.send('\x1b[O');
// try {
// this.element.blur();
// } catch (e) {
// ;
// }
// this.emit('blur');
Terminal.focus = null;
};
/**
* Global Events for key handling
* Initialize global behavior
*/
Terminal.prototype.initGlobal = function() {
var document = this.document;
Terminal._boundDocs = Terminal._boundDocs || [];
if (~indexOf(Terminal._boundDocs, document)) {
return;
}
Terminal._boundDocs.push(document);
Terminal.bindPaste(document);
Terminal.bindKeys(document);
Terminal.bindCopy(document);
if (this.useStyle) {
Terminal.insertStyle(document, this.colors[256], this.colors[257]);
}
};
/**
* Bind to paste event
*/
Terminal.bindKeys = function() {
if (Terminal.focus) return;
Terminal.bindPaste = function(document) {
// This seems to work well for ctrl-V and middle-click,
// even without the contentEditable workaround.
var window = document.defaultView;
on(window, 'paste', function(ev) {
var term = Terminal.focus;
if (!term) return;
if (term._textarea) return;
if (ev.clipboardData) {
term.send(ev.clipboardData.getData('text/plain'));
} else if (term.context.clipboardData) {
term.send(term.context.clipboardData.getData('Text'));
}
// Not necessary. Do it anyway for good measure.
term.element.contentEditable = 'inherit';
return cancel(ev);
});
};
/**
* Global Events for key handling
*/
// We could put an "if (Terminal.focus)" check
// here, but it shouldn't be necessary.
Terminal.bindKeys = function(document) {
// We should only need to check `target === body` below,
// but we can check everything for good measure.
on(document, 'keydown', function(ev) {
return Terminal.focus.keyDown(ev);
if (!Terminal.focus) return;
var target = ev.target || ev.srcElement;
if (!target) return;
if (target === Terminal.focus.element
|| target === Terminal.focus.context
|| target === Terminal.focus.document
|| target === Terminal.focus.body
|| target === Terminal.focus._textarea
|| target === Terminal.focus.parent) {
return Terminal.focus.keyDown(ev);
}
}, true);
on(document, 'keypress', function(ev) {
return Terminal.focus.keyPress(ev);
if (!Terminal.focus) return;
var target = ev.target || ev.srcElement;
if (!target) return;
if (target === Terminal.focus.element
|| target === Terminal.focus.context
|| target === Terminal.focus.document
|| target === Terminal.focus.body
|| target === Terminal.focus._textarea
|| target === Terminal.focus.parent) {
return Terminal.focus.keyPress(ev);
}
}, true);
// If we click somewhere other than a
// terminal, unfocus the terminal.
on(document, 'mousedown', function(ev) {
if (!Terminal.focus) return;
var el = ev.target || ev.srcElement;
if (!el) return;
if (!el.parentNode) return;
if (!el.parentNode.parentNode) return;
do {
if (el === Terminal.focus.element) return;
} while (el = el.parentNode);
Terminal.focus.blur();
});
};
/**
* Copy Selection w/ Ctrl-C (Select Mode)
*/
Terminal.bindCopy = function(document) {
var window = document.defaultView;
// if (!('onbeforecopy' in document)) {
// // Copies to *only* the clipboard.
// on(window, 'copy', function fn(ev) {
// var term = Terminal.focus;
// if (!term) return;
// if (!term._selected) return;
// var text = term.grabText(
// term._selected.x1, term._selected.x2,
// term._selected.y1, term._selected.y2);
// term.emit('copy', text);
// ev.clipboardData.setData('text/plain', text);
// });
// return;
// }
// Copies to primary selection *and* clipboard.
// NOTE: This may work better on capture phase,
// or using the `beforecopy` event.
on(window, 'copy', function(ev) {
var term = Terminal.focus;
if (!term) return;
if (!term._selected) return;
var textarea = term.getCopyTextarea();
var text = term.grabText(
term._selected.x1, term._selected.x2,
term._selected.y1, term._selected.y2);
term.emit('copy', text);
textarea.focus();
textarea.textContent = text;
textarea.value = text;
textarea.setSelectionRange(0, text.length);
setTimeout(function() {
term.element.focus();
term.focus();
}, 1);
});
};
/**
* Fix Mobile
*/
Terminal.prototype.getTextarea = function(document) {
var self = this;
var textarea = document.createElement('textarea');
textarea.style.position = 'absolute';
textarea.style.left = '-32000px';
textarea.style.top = '-32000px';
textarea.style.width = '100em';
textarea.style.height = '2em';
textarea.style.padding = '0';
textarea.style.opacity = '0';
textarea.style.color = 'inherit';
textarea.style.font = 'inherit';
textarea.style.textIndent = '-1em'; /* Hide text cursor on IE */
textarea.style.backgroundColor = 'transparent';
textarea.style.borderStyle = 'none';
textarea.style.outlineStyle = 'none';
textarea.autocapitalize = 'none';
textarea.autocorrect = 'off';
var onInputTimestamp;
var onInput = function(ev){
if(ev.timeStamp && ev.timeStamp === onInputTimestamp){
return;
}
onInputTimestamp = ev.timeStamp;
var value = textarea.textContent || textarea.value;
if (typeof self.select.startPos !== 'undefined'){
self.select = {};
self.clearSelectedText();
self.refresh(0, self.rows - 1);
}
if (!self.compositionStatus) {
textarea.value = '';
textarea.textContent = '';
self.send(value);
}
};
on(textarea, 'compositionstart', function() {
textarea.style.opacity = "1.0";
textarea.style.textIndent = "0";
self.compositionStatus = true;
});
on(textarea, 'compositionend', function(ev) {
textarea.style.opacity = "0.0";
textarea.style.textIndent = "-1em";
self.compositionStatus = false;
setTimeout(function(){
onInput(ev); // for IE that does not trigger 'input' after the IME composition.
}, 1);
});
on(textarea, 'keydown', function(){
var value = textarea.textContent || textarea.value;
});
on(textarea, 'input', onInput);
if (Terminal.isAndroid) {
on(textarea, 'change', function() {
var value = textarea.textContent || textarea.value;
textarea.value = '';
textarea.textContent = '';
self.send(value + '\r');
});
}
return textarea;
};
/**
* Insert a default style
*/
Terminal.insertStyle = function(document, bg, fg) {
var style = document.getElementById('term-style');
if (style) return;
var head = document.getElementsByTagName('head')[0];
if (!head) return;
var style = document.createElement('style');
style.id = 'term-style';
// textContent doesn't work well with IE for <style> elements.
style.innerHTML = ''
+ '.terminal {\n'
+ ' float: left;\n'
+ ' border: ' + bg + ' solid 5px;\n'
+ ' font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;\n'
+ ' font-size: 11px;\n'
+ ' color: ' + fg + ';\n'
+ ' background: ' + bg + ';\n'
+ '}\n'
+ '\n'
+ '.terminal-cursor {\n'
+ ' color: ' + bg + ';\n'
+ ' background: ' + fg + ';\n'
+ '}\n';
// var out = '';
// each(Terminal.colors, function(color, i) {
// if (i === 256) {
// out += '\n.term-bg-color-default { background-color: ' + color + '; }';
// }
// if (i === 257) {
// out += '\n.term-fg-color-default { color: ' + color + '; }';
// }
// out += '\n.term-bg-color-' + i + ' { background-color: ' + color + '; }';
// out += '\n.term-fg-color-' + i + ' { color: ' + color + '; }';
// });
// style.innerHTML += out + '\n';
head.insertBefore(style, head.firstChild);
};
/**
* Open Terminal
*/
Terminal.prototype.open = function() {
Terminal.prototype.open = function(parent) {
var self = this
, i = 0
, div;
this.element = document.createElement('div');
this.parent = parent || this.parent;
if (!this.parent) {
throw new Error('Terminal requires a parent element.');
}
// Grab global elements.
this.context = this.parent.ownerDocument.defaultView;
this.document = this.parent.ownerDocument;
this.body = this.document.getElementsByTagName('body')[0];
// Parse user-agent strings.
if (this.context.navigator && this.context.navigator.userAgent) {
this.isMac = !!~this.context.navigator.userAgent.indexOf('Mac');
this.isIpad = !!~this.context.navigator.userAgent.indexOf('iPad');
this.isIphone = !!~this.context.navigator.userAgent.indexOf('iPhone');
this.isAndroid = !!~this.context.navigator.userAgent.indexOf('Android');
this.isMobile = this.isIpad || this.isIphone || this.isAndroid;
this.isMSIE = !!~this.context.navigator.userAgent.indexOf('MSIE');
}
// Create our main terminal element.
this.element = this.document.createElement('div');
this.element.className = 'terminal';
this.children = [];
this.element.style.outline = 'none';
this.element.setAttribute('tabindex', 0);
this.element.setAttribute('spellcheck', 'false');
this.element.style.backgroundColor = this.colors[256];
this.element.style.color = this.colors[257];
// Create the lines for our terminal.
this.children = [];
for (; i < this.rows; i++) {
div = document.createElement('div');
div = this.document.createElement('div');
this.element.appendChild(div);
this.children.push(div);
}
document.body.appendChild(this.element);
this._textarea = this.getTextarea(this.document);
this.element.appendChild(this._textarea);
this.parent.appendChild(this.element);
this.select = {};
// Draw the screen.
this.refresh(0, this.rows - 1);
Terminal.bindKeys();
this.focus();
this.startBlink();
var updateSelect = function(){
var startPos = self.select.startPos;
var endPos = self.select.endPos;
if(endPos.y < startPos.y || (startPos.y == endPos.y && endPos.x < startPos.x)){
var tmp = startPos;
startPos = endPos;
endPos = tmp;
}
if (self.select.clicks === 2){
var j = i;
var isMark = function(ch){
var code = ch.charCodeAt(0);
return (code <= 0x2f) || (0x3a <= code && code <= 0x40) || (0x5b <= code && code < 0x60) || (0x7b <= code && code <= 0x7f);
}
while (startPos.x > 0 && !isMark(self.lines[startPos.y][startPos.x-1][1])){
startPos.x--;
}
while (endPos.x < self.cols && !isMark(self.lines[endPos.y][endPos.x][1])){
endPos.x++;
}
}else if(self.select.clicks === 3){
startPos.x = 0;
endPos.y ++;
endPos.x = 0;
}
on(this.element, 'mousedown', function() {
self.focus();
});
if (startPos.x === endPos.x && startPos.y === endPos.y){
self.clearSelectedText();
}else{
var x2 = endPos.x;
var y2 = endPos.y;
x2 --;
if(x2<0){
y2--;
x2 = self.cols - 1;
}
self.selectText(startPos.x, x2, startPos.y, y2);
}
};
var copySelectToTextarea = function (){
var textarea = self._textarea;
if (textarea) {
if (self.select.startPos.x === self.select.endPos.x && self.select.startPos.y === self.select.endPos.y){
textarea.value = "";
textarea.select();
return;
}
var x2 = self.select.endPos.x;
var y2 = self.select.endPos.y;
x2 --;
if(x2<0){
y2--;
x2 = self.cols - 1;
}
// This probably shouldn't work,
// ... but it does. Firefox's paste
// event seems to only work for textareas?
var value = self.grabText(self.select.startPos.x, x2, self.select.startPos.y, y2);
textarea.value = value;
textarea.select();
}
};
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(ev.button === 2){
var r = self.element.getBoundingClientRect();
var x = ev.pageX - r.left + self.element.offsetLeft;
var y = ev.pageY - r.top + self.element.offsetTop;
self._textarea.style.left = x + 'px';
self._textarea.style.top = y + 'px';
return;
}
if (button !== 2) return;
if (ev.button != 0){
return;
}
if (navigator.userAgent.indexOf("Trident")){
/* IE does not hold click number as "detail" property. */
if (self.select.timer){
self.select.clicks ++;
clearTimeout(self.select.timer);
self.select.timer = null;
}else{
self.select.clicks = 1;
}
self.select.timer = setTimeout(function(){
self.select.timer = null;
}, 600);
}else{
self.select.clicks = ev.detail;
}
self.element.contentEditable = 'true';
setTimeout(function() {
self.element.contentEditable = 'inherit'; // 'false';
}, 1);
}, true);
if (! ev.shiftKey){
self.clearSelectedText();
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'));
self.select.startPos = self.getCoords(ev);
self.select.startPos.y += self.ydisp;
}
self.select.endPos = self.getCoords(ev);
self.select.endPos.y += self.ydisp;
updateSelect();
copySelectToTextarea();
self.refresh(0, self.rows - 1);
self.select.selecting = true;
});
on(this.element, 'mousemove', function(ev) {
if(self.select.selecting){
self.select.endPos = self.getCoords(ev);
self.select.endPos.y += self.ydisp;
updateSelect();
self.refresh(0, self.rows - 1);
}
});
on(document, 'mouseup', function(ev) {
if(ev.button === 2){
var r = self.element.getBoundingClientRect();
var x = ev.pageX - r.left + self.element.offsetLeft;
var y = ev.pageY - r.top + self.element.offsetTop;
self._textarea.style.left = x - 1 + 'px';
self._textarea.style.top = y - 1 + 'px';
return;
}
if(self.select.selecting){
self.select.selecting = false;
copySelectToTextarea();
}
// Not necessary. Do it anyway for good measure.
self.element.contentEditable = 'inherit';
return cancel(ev);
});
this.bindMouse();
// XXX - hack, move this somewhere else.
if (!('useEvents' in this.options) || this.options.useEvents) {
// Initialize global actions that
// need to be taken on the document.
this.initGlobal();
}
if (!('useFocus' in this.options) || this.options.useFocus) {
// Ensure there is a Terminal.focus.
this.focus();
// Start blinking the cursor.
this.startBlink();
// Bind to DOM events related
// to focus and paste behavior.
on(this.element, 'focus', function() {
self.focus();
});
// This causes slightly funky behavior.
// on(this.element, 'blur', function() {
// self.blur();
// });
on(this.element, 'mousedown', function() {
self.focus();
});
// Clickable paste workaround, using contentEditable.
// 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 (self.isMSIE) {
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);
}
if (!('useMouse' in this.options) || this.options.useMouse) {
// Listen for mouse events and translate
// them into terminal mouse protocols.
this.bindMouse();
}
// this.emit('open');
if (!('useFocus' in this.options) || this.options.useFocus) {
// This can be useful for pasting,
// as well as the iPad fix.
setTimeout(function() {
self.element.focus();
self.focus();
}, 100);
}
// Figure out whether boldness affects
// the character width of monospace fonts.
if (Terminal.brokenBold == null) {
Terminal.brokenBold = isBoldBroken();
Terminal.brokenBold = isBoldBroken(this.document);
}
// sync default bg/fg colors
this.element.style.backgroundColor = Terminal.defaultColors.bg;
this.element.style.color = Terminal.defaultColors.fg;
this.emit('open');
};
//this.emit('open');
Terminal.prototype.setRawMode = function(value) {
this.isRaw = !!value;
};
Terminal.prototype.getCoords = function(ev) {
var x, y, w, h, el;
var self = this;
// ignore browsers without pageX for now
if (ev.pageX == null) return;
x = ev.pageX;
y = ev.pageY;
el = self.element;
x -= el.clientLeft;
y -= el.clientTop;
// should probably check offsetParent
// but this is more portable
while (el && el !== self.document.documentElement) {
x -= el.offsetLeft;
y -= el.offsetTop;
el = 'offsetParent' in el
? el.offsetParent
: el.parentNode;
}
// convert to cols/rows
w = self.element.clientWidth;
h = self.element.clientHeight;
var cols = Math.floor((x / w) * self.cols);
var rows = Math.floor((y / h) * self.rows);
// be sure to avoid sending
// bad positions to the program
if (cols < 0) cols = 0;
if (cols > self.cols) cols = self.cols;
if (rows < 0) rows = 0;
if (rows > self.rows) rows = self.rows;
// xterm sends raw bytes and
// starts at 32 (SP) for each.
//x += 32;
//y += 32;
return {
x: cols,
y: rows,
};
}
// XTerm mouse events
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
// To better understand these
......@@ -418,7 +1110,7 @@ Terminal.prototype.bindMouse = function() {
, self = this
, pressed = 32;
var wheelEvent = 'onmousewheel' in window
var wheelEvent = 'onmousewheel' in this.context
? 'mousewheel'
: 'DOMMouseScroll';
......@@ -596,7 +1288,7 @@ Terminal.prototype.bindMouse = function() {
? ev.which - 1
: null;
if (~navigator.userAgent.indexOf('MSIE')) {
if (self.isMSIE) {
button = button === 1 ? 0 : button === 4 ? 1 : button;
}
break;
......@@ -649,17 +1341,19 @@ Terminal.prototype.bindMouse = function() {
// should probably check offsetParent
// but this is more portable
while (el !== document.documentElement) {
while (el && el !== self.document.documentElement) {
x -= el.offsetLeft;
y -= el.offsetTop;
el = el.parentNode;
el = 'offsetParent' in el
? el.offsetParent
: 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;
x = Math.round((x / w) * self.cols);
y = Math.round((y / h) * self.rows);
// be sure to avoid sending
// bad positions to the program
......@@ -676,10 +1370,9 @@ Terminal.prototype.bindMouse = function() {
return {
x: x,
y: y,
down: ev.type === 'mousedown',
up: ev.type === 'mouseup',
wheel: ev.type === wheelEvent,
move: ev.type === 'mousemove'
type: ev.type === wheelEvent
? 'mousewheel'
: ev.type
};
}
......@@ -693,20 +1386,22 @@ Terminal.prototype.bindMouse = function() {
self.focus();
// fix for odd bug
if (self.vt200Mouse) {
sendButton({ __proto__: ev, type: 'mouseup' });
return cancel(ev);
}
//if (self.vt200Mouse && !self.normalMouse) {
// XXX This seems to break certain programs.
// if (self.vt200Mouse) {
// sendButton({ __proto__: ev, type: 'mouseup' });
// return cancel(ev);
// }
// bind events
if (self.normalMouse) on(document, 'mousemove', sendMove);
if (self.normalMouse) on(self.document, 'mousemove', sendMove);
// x10 compatibility mode can't send button releases
if (!self.x10Mouse) {
on(document, 'mouseup', function up(ev) {
on(self.document, 'mouseup', function up(ev) {
sendButton(ev);
if (self.normalMouse) off(document, 'mousemove', sendMove);
off(document, 'mouseup', up);
if (self.normalMouse) off(self.document, 'mousemove', sendMove);
off(self.document, 'mouseup', up);
return cancel(ev);
});
}
......@@ -714,6 +1409,10 @@ Terminal.prototype.bindMouse = function() {
return cancel(ev);
});
//if (self.normalMouse) {
// on(self.document, 'mousemove', sendMove);
//}
on(el, wheelEvent, function(ev) {
if (!self.mouseEvents) return;
if (self.x10Mouse
......@@ -741,13 +1440,35 @@ Terminal.prototype.bindMouse = function() {
* Destroy Terminal
*/
Terminal.prototype.close =
Terminal.prototype.destroySoon =
Terminal.prototype.destroy = function() {
if (this.destroyed) {
return;
}
if (this._blink) {
clearInterval(this._blink);
delete this._blink;
}
this.readable = false;
this.writable = false;
this.destroyed = true;
this._events = {};
this.handler = function() {};
this.write = function() {};
//this.emit('close');
this.end = function() {};
if (this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
this.emit('end');
this.emit('close');
this.emit('finish');
this.emit('destroy');
};
/**
......@@ -762,7 +1483,7 @@ Terminal.prototype.destroy = function() {
// 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
// 1=bold, 2=underline, 4=blink, 8=inverse, 16=invisible
Terminal.prototype.refresh = function(start, end) {
var x
......@@ -774,13 +1495,18 @@ Terminal.prototype.refresh = function(start, end) {
, width
, data
, attr
, fgColor
, bgColor
, bg
, fg
, flags
, row
, parent;
var characterWidth = this.element.clientWidth / this.cols;
var characterHeight = this.element.clientHeight / this.rows;
var focused;
if (end - start >= this.rows / 2) {
focused = (Terminal.focus == this);
parent = this.element.parentNode;
if (parent) parent.removeChild(this.element);
}
......@@ -788,6 +1514,11 @@ Terminal.prototype.refresh = function(start, end) {
width = this.cols;
y = start;
if (end >= this.lines.length) {
this.log('`end` is too large. Most likely a bad CSR.');
end = this.lines.length - 1;
}
for (; y <= end; y++) {
row = y + this.ydisp;
......@@ -796,7 +1527,7 @@ Terminal.prototype.refresh = function(start, end) {
if (y === this.y
&& this.cursorState
&& this.ydisp === this.ybase
&& (this.ydisp === this.ybase || this.selectMode)
&& !this.cursorHidden) {
x = this.x;
} else {
......@@ -818,35 +1549,67 @@ Terminal.prototype.refresh = function(start, end) {
}
if (data !== this.defAttr) {
if (data === -1) {
out += '<span class="reverse-video">';
out += '<span class="reverse-video terminal-cursor">';
} else {
out += '<span style="';
bgColor = data & 0x1ff;
fgColor = (data >> 9) & 0x1ff;
bg = data & 0x1ff;
fg = (data >> 9) & 0x1ff;
flags = data >> 18;
// bold
if (flags & 1) {
if (!Terminal.brokenBold) {
out += 'font-weight:bold;';
}
// see: XTerm*boldColors
if (fgColor < 8) fgColor += 8;
// See: XTerm*boldColors
if (fg < 8) fg += 8;
}
// underline
if (flags & 2) {
out += 'text-decoration:underline;';
}
if (bgColor !== 256) {
// blink
if (flags & 4) {
if (flags & 2) {
out = out.slice(0, -1);
out += ' blink;';
} else {
out += 'text-decoration:blink;';
}
}
// inverse
if (flags & 8) {
bg = (data >> 9) & 0x1ff;
fg = data & 0x1ff;
// Should inverse just be before the
// above boldColors effect instead?
if ((flags & 1) && fg < 8) fg += 8;
}
// invisible
if (flags & 16) {
out += 'visibility:hidden;';
}
// out += '" class="'
// + 'term-bg-color-' + bg
// + ' '
// + 'term-fg-color-' + fg
// + '">';
if (bg !== 256) {
out += 'background-color:'
+ Terminal.colors[bgColor]
+ this.colors[bg]
+ ';';
}
if (fgColor !== 257) {
if (fg !== 257) {
out += 'color:'
+ Terminal.colors[fgColor]
+ this.colors[fg]
+ ';';
}
......@@ -869,7 +1632,12 @@ Terminal.prototype.refresh = function(start, end) {
if (ch <= ' ') {
out += '&nbsp;';
} else {
out += ch;
if (isWide(ch)) {
i++;
out += '<span style="display:inline-block; width:' + characterWidth * 2 + 'px; height:' + characterHeight + 'px; line-height:' + characterHeight + 'px;">' + ch + '</span>';
} else {
out += ch;
}
}
break;
}
......@@ -884,10 +1652,26 @@ Terminal.prototype.refresh = function(start, end) {
this.children[y].innerHTML = out;
}
if (parent) parent.appendChild(this.element);
if (parent) {
parent.appendChild(this.element);
if (focused) {
this.focus();
}
}
if (this._textarea) {
var cursorElement = this.element.querySelector('.terminal-cursor');
if(cursorElement){
var cursor_x = cursorElement.offsetLeft;
var cursor_y = cursorElement.offsetTop;
this._textarea.style.left = cursor_x + 'px';
this._textarea.style.top = cursor_y + 'px';
}
}
};
Terminal.prototype.cursorBlink = function() {
Terminal.prototype._cursorBlink = function() {
if (Terminal.focus !== this) return;
this.cursorState ^= 1;
this.refresh(this.y, this.y);
......@@ -904,16 +1688,16 @@ Terminal.prototype.showCursor = function() {
};
Terminal.prototype.startBlink = function() {
if (!Terminal.cursorBlink) return;
if (!this.cursorBlink) return;
var self = this;
this._blinker = function() {
self.cursorBlink();
self._cursorBlink();
};
this._blink = setInterval(this._blinker, 500);
};
Terminal.prototype.refreshBlink = function() {
if (!Terminal.cursorBlink) return;
if (!this.cursorBlink || !this._blink) return;
clearInterval(this._blink);
this._blink = setInterval(this._blinker, 500);
};
......@@ -921,7 +1705,7 @@ Terminal.prototype.refreshBlink = function() {
Terminal.prototype.scroll = function() {
var row;
if (++this.ybase === Terminal.scrollback) {
if (++this.ybase === this.scrollback) {
this.ybase = this.ybase / 2 | 0;
this.lines = this.lines.slice(-(this.ybase + this.rows) + 1);
}
......@@ -973,6 +1757,7 @@ Terminal.prototype.scrollDisp = function(disp) {
Terminal.prototype.write = function(data) {
var l = data.length
, i = 0
, j
, cs
, ch;
......@@ -986,13 +1771,14 @@ Terminal.prototype.write = function(data) {
// this.log(JSON.stringify(data.replace(/\x1b/g, '^[')));
for (; i < l; i++) {
for (; i < l; i++, this.lch = ch) {
ch = data[i];
switch (this.state) {
case normal:
switch (ch) {
// '\0'
// case '\0':
// case '\200':
// break;
// '\a'
......@@ -1007,6 +1793,9 @@ Terminal.prototype.write = function(data) {
if (this.convertEol) {
this.x = 0;
}
// TODO: Implement eat_newline_glitch.
// if (this.realX >= this.cols) break;
// this.realX = 0;
this.y++;
if (this.y > this.scrollBottom) {
this.y--;
......@@ -1052,6 +1841,7 @@ Terminal.prototype.write = function(data) {
if (this.charset && this.charset[ch]) {
ch = this.charset[ch];
}
if (this.x >= this.cols) {
this.x = 0;
this.y++;
......@@ -1060,10 +1850,21 @@ Terminal.prototype.write = function(data) {
this.scroll();
}
}
this.lines[this.y + this.ybase][this.x] = [this.curAttr, ch];
this.x++;
this.updateRange(this.y);
}
if (isWide(ch)) {
j = this.y + this.ybase;
if (this.cols < 2 || this.x >= this.cols) {
this.lines[j][this.x - 1] = [this.curAttr, ' '];
break;
}
this.lines[j][this.x] = [this.curAttr, ' '];
this.x++;
}
}
break;
}
break;
......@@ -1086,7 +1887,8 @@ Terminal.prototype.write = function(data) {
// ESC P Device Control String ( DCS is 0x90).
case 'P':
this.params = [];
this.currentParam = 0;
this.prefix = '';
this.currentParam = '';
this.state = dcs;
break;
......@@ -1309,8 +2111,14 @@ Terminal.prototype.write = function(data) {
// OSC Ps ; Pt ST
// OSC Ps ; Pt BEL
// Set Text Parameters.
if (ch === '\x1b' || ch === '\x07') {
if (ch === '\x1b') i++;
if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') {
if (this.lch === '\x1b') {
if (typeof this.currentParam === 'string') {
this.currentParam = this.currentParam.slice(0, -1);
} else if (typeof this.currentParam == 'number') {
this.currentParam = (this.currentParam - ('\x1b'.charCodeAt(0) - 48)) / 10;
}
}
this.params.push(this.currentParam);
......@@ -1457,12 +2265,16 @@ Terminal.prototype.write = function(data) {
// CSI Pm m Character Attributes (SGR).
case 'm':
this.charAttributes(this.params);
if (!this.prefix) {
this.charAttributes(this.params);
}
break;
// CSI Ps n Device Status Report (DSR).
case 'n':
this.deviceStatus(this.params);
if (!this.prefix) {
this.deviceStatus(this.params);
}
break;
/**
......@@ -1838,94 +2650,158 @@ Terminal.prototype.write = function(data) {
break;
case dcs:
if (ch === '\x1b' || ch === '\x07') {
if (ch === '\x1b') i++;
if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') {
// Workarounds:
if (this.prefix === 'tmux;\x1b') {
// `DCS tmux; Pt ST` may contain a Pt with an ST
// XXX Does tmux work this way?
// if (this.lch === '\x1b' & data[i + 1] === '\x1b' && data[i + 2] === '\\') {
// this.currentParam += ch;
// continue;
// }
// Tmux only accepts ST, not BEL:
if (ch === '\x07') {
this.currentParam += ch;
continue;
}
}
if (this.lch === '\x1b') {
if (typeof this.currentParam === 'string') {
this.currentParam = this.currentParam.slice(0, -1);
} else if (typeof this.currentParam == 'number') {
this.currentParam = (this.currentParam - ('\x1b'.charCodeAt(0) - 48)) / 10;
}
}
this.params.push(this.currentParam);
var pt = this.params[this.params.length - 1];
switch (this.prefix) {
// User-Defined Keys (DECUDK).
case '':
// DCS Ps; Ps| Pt ST
case UDK:
this.emit('udk', {
clearAll: this.params[0] === 0,
eraseBelow: this.params[0] === 1,
lockKeys: this.params[1] === 0,
dontLockKeys: this.params[1] === 1,
keyList: (this.params[2] + '').split(';').map(function(part) {
part = part.split('/');
return {
keyCode: part[0],
hexKeyValue: part[1]
};
})
});
break;
// Request Status String (DECRQSS).
// DCS $ q Pt ST
// test: echo -e '\eP$q"p\e\\'
case '$q':
var pt = this.currentParam
, valid = false;
var valid = 0;
switch (pt) {
// DECSCA
// CSI Ps " q
case '"q':
pt = '0"q';
valid = 1;
break;
// DECSCL
// CSI Ps ; Ps " p
case '"p':
pt = '61"p';
pt = '61;0"p';
valid = 1;
break;
// DECSTBM
// CSI Ps ; Ps r
case 'r':
pt = ''
+ (this.scrollTop + 1)
+ ';'
+ (this.scrollBottom + 1)
+ 'r';
valid = 1;
break;
// SGR
// CSI Pm m
case 'm':
pt = '0m';
// TODO: Parse this.curAttr here.
// pt = '0m';
// valid = 1;
valid = 0; // Not implemented.
break;
default:
this.error('Unknown DCS Pt: %s.', pt);
pt = '';
valid = 0; // unimplemented
break;
}
this.send('\x1bP' + +valid + '$r' + pt + '\x1b\\');
this.send('\x1bP' + valid + '$r' + pt + '\x1b\\');
break;
// Set Termcap/Terminfo Data (xterm, experimental).
// DCS + p Pt ST
case '+p':
this.emit('set terminfo', {
name: this.params[0]
});
break;
// Request Termcap/Terminfo String (xterm, experimental)
// Regular xterm does not even respond to this sequence.
// This can cause a small glitch in vim.
// DCS + q Pt ST
// test: echo -ne '\eP+q6b64\e\\'
case '+q':
var pt = this.currentParam
, valid = false;
var valid = false;
this.send('\x1bP' + +valid + '+r' + pt + '\x1b\\');
break;
// Implement tmux sequence forwarding is
// someone uses term.js for a multiplexer.
// DCS tmux; ESC Pt ST
case 'tmux;\x1b':
this.emit('passthrough', pt);
break;
default:
this.error('Unknown DCS prefix: %s.', this.prefix);
this.error('Unknown DCS prefix: %s.', pt);
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;
if (!this.prefix) {
if (/^\d*;\d*\|/.test(this.currentParam)) {
this.prefix = UDK;
this.params = this.currentParam.split(/[;|]/).map(function(n) {
if (!n.length) return 0;
return +n;
}).slice(0, -1);
this.currentParam = '';
} else if (/^[$+][a-zA-Z]/.test(this.currentParam)
|| /^\w+;\x1b/.test(this.currentParam)) {
this.prefix = this.currentParam;
this.currentParam = '';
}
}
}
break;
case ignore:
// For PM and APC.
if (ch === '\x1b' || ch === '\x07') {
if (ch === '\x1b') i++;
if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') {
this.state = normal;
}
break;
......@@ -1934,18 +2810,44 @@ Terminal.prototype.write = function(data) {
this.updateRange(this.y);
this.refresh(this.refreshStart, this.refreshEnd);
return true;
};
Terminal.prototype.writeln = function(data) {
this.write(data + '\r\n');
return this.write(data + '\r\n');
};
Terminal.prototype.end = function(data) {
var ret = true;
if (data) {
ret = this.write(data);
}
this.destroySoon();
return ret;
};
Terminal.prototype.resume = function() {
;
};
Terminal.prototype.pause = function() {
;
};
// Key Resources:
// https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
Terminal.prototype.keyDown = function(ev) {
var key;
var self = this
, key;
switch (ev.keyCode) {
// backspace
case 8:
if (ev.altKey) {
key = '\x17';
break;
}
if (ev.shiftKey) {
key = '\x08'; // ^H
break;
......@@ -1970,24 +2872,32 @@ Terminal.prototype.keyDown = function(ev) {
break;
// left-arrow
case 37:
if (this.applicationKeypad) {
if (this.applicationCursor) {
key = '\x1bOD'; // SS3 as ^[O for 7-bit
//key = '\x8fD'; // SS3 as 0x8f for 8-bit
break;
}
if (ev.ctrlKey) {
key = '\x1b[5D';
break;
}
key = '\x1b[D';
break;
// right-arrow
case 39:
if (this.applicationKeypad) {
if (this.applicationCursor) {
key = '\x1bOC';
break;
}
if (ev.ctrlKey) {
key = '\x1b[5C';
break;
}
key = '\x1b[C';
break;
// up-arrow
case 38:
if (this.applicationKeypad) {
if (this.applicationCursor) {
key = '\x1bOA';
break;
}
......@@ -2000,7 +2910,7 @@ Terminal.prototype.keyDown = function(ev) {
break;
// down-arrow
case 40:
if (this.applicationKeypad) {
if (this.applicationCursor) {
key = '\x1bOB';
break;
}
......@@ -2105,6 +3015,27 @@ Terminal.prototype.keyDown = function(ev) {
// a-z and space
if (ev.ctrlKey) {
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
// Ctrl-A
if (this.screenKeys) {
if (!this.prefixMode && !this.selectMode && ev.keyCode === 65) {
this.enterPrefix();
return cancel(ev);
}
}
// Ctrl-V
if (this.prefixMode && ev.keyCode === 86) {
this.leavePrefix();
return;
}
// Ctrl-C
if ((this.prefixMode || this.selectMode) && ev.keyCode === 67) {
if (this.visualMode) {
setTimeout(function() {
self.leaveVisual();
}, 1);
}
return;
}
key = String.fromCharCode(ev.keyCode - 64);
} else if (ev.keyCode === 32) {
// NUL
......@@ -2122,7 +3053,7 @@ Terminal.prototype.keyDown = function(ev) {
// ^] - group sep
key = String.fromCharCode(29);
}
} else if ((!isMac && ev.altKey) || (isMac && ev.metaKey)) {
} else if (ev.altKey) {
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
key = '\x1b' + String.fromCharCode(ev.keyCode + 32);
} else if (ev.keyCode === 192) {
......@@ -2134,18 +3065,25 @@ Terminal.prototype.keyDown = function(ev) {
break;
}
this.emit('keydown', ev);
if (key) {
this.emit('key', key, ev);
if (!key) return true;
this.showCursor();
this.handler(key);
if (this.prefixMode) {
this.leavePrefix();
return cancel(ev);
}
if (this.selectMode) {
this.keySelect(ev, key);
return cancel(ev);
}
return true;
this.emit('keydown', ev);
this.emit('key', key, ev);
this.showCursor();
this.handler(key);
return cancel(ev);
};
Terminal.prototype.setgLevel = function(g) {
......@@ -2162,6 +3100,9 @@ Terminal.prototype.setgCharset = function(g, charset) {
Terminal.prototype.keyPress = function(ev) {
var key;
if (this._textarea) {
return;
}
cancel(ev);
......@@ -2179,6 +3120,17 @@ Terminal.prototype.keyPress = function(ev) {
key = String.fromCharCode(key);
if (this.prefixMode) {
this.leavePrefix();
this.keyPrefix(ev, key);
return false;
}
if (this.selectMode) {
this.keySelect(ev, key);
return false;
}
this.emit('keypress', key, ev);
this.emit('key', key, ev);
......@@ -2202,27 +3154,28 @@ Terminal.prototype.send = function(data) {
};
Terminal.prototype.bell = function() {
if (!Terminal.visualBell) return;
this.emit('bell');
if (!this.visualBell) return;
var self = this;
this.element.style.borderColor = 'white';
setTimeout(function() {
self.element.style.borderColor = '';
}, 10);
if (Terminal.popOnBell) this.focus();
if (this.popOnBell) this.focus();
};
Terminal.prototype.log = function() {
if (!Terminal.debug) return;
if (!window.console || !window.console.log) return;
if (!this.debug) return;
if (!this.context.console || !this.context.console.log) return;
var args = Array.prototype.slice.call(arguments);
window.console.log.apply(window.console, args);
this.context.console.log.apply(this.context.console, args);
};
Terminal.prototype.error = function() {
if (!Terminal.debug) return;
if (!window.console || !window.console.error) return;
if (!this.debug) return;
if (!this.context.console || !this.context.console.error) return;
var args = Array.prototype.slice.call(arguments);
window.console.error.apply(window.console, args);
this.context.console.error.apply(this.context.console, args);
};
Terminal.prototype.resize = function(x, y) {
......@@ -2238,7 +3191,7 @@ Terminal.prototype.resize = function(x, y) {
// resize cols
j = this.cols;
if (j < x) {
ch = [this.defAttr, ' '];
ch = [this.defAttr, ' ']; // does xterm use the default attr?
i = this.lines.length;
while (i--) {
while (this.lines[i].length < x) {
......@@ -2255,6 +3208,7 @@ Terminal.prototype.resize = function(x, y) {
}
this.setupStops(j);
this.cols = x;
this.columns = x;
// resize rows
j = this.rows;
......@@ -2265,7 +3219,7 @@ Terminal.prototype.resize = function(x, y) {
this.lines.push(this.blankLine());
}
if (this.children.length < y) {
line = document.createElement('div');
line = this.document.createElement('div');
el.appendChild(line);
this.children.push(line);
}
......@@ -2298,11 +3252,20 @@ Terminal.prototype.resize = function(x, y) {
// screen buffer. just set it
// to null for now.
this.normal = null;
// Act as though we are a node TTY stream:
this.emit('resize');
};
Terminal.prototype.updateRange = function(y) {
if (y < this.refreshStart) this.refreshStart = y;
if (y > this.refreshEnd) this.refreshEnd = y;
// if (y > this.refreshEnd) {
// this.refreshEnd = y;
// if (y > this.rows - 1) {
// this.refreshEnd = this.rows - 1;
// }
// }
};
Terminal.prototype.maxRange = function() {
......@@ -2341,9 +3304,16 @@ Terminal.prototype.nextStop = function(x) {
: x < 0 ? 0 : x;
};
// back_color_erase feature for xterm.
Terminal.prototype.eraseAttr = function() {
// if (this.is('screen')) return this.defAttr;
return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);
};
Terminal.prototype.eraseRight = function(x, y) {
var line = this.lines[this.ybase + y]
, ch = [this.curAttr, ' ']; // xterm
, ch = [this.eraseAttr(), ' ']; // xterm
for (; x < this.cols; x++) {
line[x] = ch;
......@@ -2354,7 +3324,7 @@ Terminal.prototype.eraseRight = function(x, y) {
Terminal.prototype.eraseLeft = function(x, y) {
var line = this.lines[this.ybase + y]
, ch = [this.curAttr, ' ']; // xterm
, ch = [this.eraseAttr(), ' ']; // xterm
x++;
while (x--) line[x] = ch;
......@@ -2368,7 +3338,7 @@ Terminal.prototype.eraseLine = function(y) {
Terminal.prototype.blankLine = function(cur) {
var attr = cur
? this.curAttr
? this.eraseAttr()
: this.defAttr;
var ch = [attr, ' ']
......@@ -2384,12 +3354,12 @@ Terminal.prototype.blankLine = function(cur) {
Terminal.prototype.ch = function(cur) {
return cur
? [this.curAttr, ' ']
? [this.eraseAttr(), ' ']
: [this.defAttr, ' '];
};
Terminal.prototype.is = function(term) {
var name = this.termName || Terminal.termName;
var name = this.termName;
return (name + '').indexOf(term) === 0;
};
......@@ -2436,8 +3406,9 @@ Terminal.prototype.reverseIndex = function() {
// 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.options.rows = this.rows;
this.options.cols = this.cols;
Terminal.call(this, this.options);
this.refresh(0, this.rows - 1);
};
......@@ -2643,84 +3614,120 @@ Terminal.prototype.eraseInLine = function(params) {
// Ps = 4 8 ; 5 ; Ps -> Set background color to the second
// Ps.
Terminal.prototype.charAttributes = function(params) {
// Optimize a single SGR0.
if (params.length === 1 && params[0] === 0) {
this.curAttr = this.defAttr;
return;
}
var l = params.length
, i = 0
, bg
, fg
, flags = this.curAttr >> 18
, fg = (this.curAttr >> 9) & 0x1ff
, bg = this.curAttr & 0x1ff
, 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);
fg = p - 30;
} else if (p >= 40 && p <= 47) {
// bg color 8
this.curAttr = (this.curAttr & ~0x1ff) | (p - 40);
bg = p - 40;
} else if (p >= 90 && p <= 97) {
// fg color 16
p += 8;
this.curAttr = (this.curAttr & ~(0x1ff << 9)) | ((p - 90) << 9);
fg = p - 90;
} else if (p >= 100 && p <= 107) {
// bg color 16
p += 8;
this.curAttr = (this.curAttr & ~0x1ff) | (p - 100);
bg = p - 100;
} else if (p === 0) {
// default
this.curAttr = this.defAttr;
flags = this.defAttr >> 18;
fg = (this.defAttr >> 9) & 0x1ff;
bg = this.defAttr & 0x1ff;
// flags = 0;
// fg = 0x1ff;
// bg = 0x1ff;
} else if (p === 1) {
// bold text
this.curAttr = this.curAttr | (1 << 18);
flags |= 1;
} else if (p === 4) {
// underlined text
this.curAttr = this.curAttr | (2 << 18);
} else if (p === 7 || p === 27) {
flags |= 2;
} else if (p === 5) {
// blink
flags |= 4;
} else if (p === 7) {
// 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);
flags |= 8;
} else if (p === 8) {
// invisible
flags |= 16;
} else if (p === 22) {
// not bold
this.curAttr = this.curAttr & ~(1 << 18);
flags &= ~1;
} else if (p === 24) {
// not underlined
this.curAttr = this.curAttr & ~(2 << 18);
flags &= ~2;
} else if (p === 25) {
// not blink
flags &= ~4;
} else if (p === 27) {
// not inverse
flags &= ~8;
} else if (p === 28) {
// not invisible
flags &= ~16;
} else if (p === 39) {
// reset fg
this.curAttr = this.curAttr & ~(0x1ff << 9);
this.curAttr = this.curAttr | (((this.defAttr >> 9) & 0x1ff) << 9);
fg = (this.defAttr >> 9) & 0x1ff;
} else if (p === 49) {
// reset bg
this.curAttr = this.curAttr & ~0x1ff;
this.curAttr = this.curAttr | (this.defAttr & 0x1ff);
bg = 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);
if (params[i + 1] === 2) {
i += 2;
fg = matchColor(
params[i] & 0xff,
params[i + 1] & 0xff,
params[i + 2] & 0xff);
if (fg === -1) fg = 0x1ff;
i += 2;
} else if (params[i + 1] === 5) {
i += 2;
p = params[i] & 0xff;
fg = p;
}
} 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;
if (params[i + 1] === 2) {
i += 2;
bg = matchColor(
params[i] & 0xff,
params[i + 1] & 0xff,
params[i + 2] & 0xff);
if (bg === -1) bg = 0x1ff;
i += 2;
} else if (params[i + 1] === 5) {
i += 2;
p = params[i] & 0xff;
bg = p;
}
} else if (p === 100) {
// reset fg/bg
fg = (this.defAttr >> 9) & 0x1ff;
bg = this.defAttr & 0x1ff;
} else {
this.error('Unknown SGR attribute: %d.', p);
}
}
this.curAttr = (flags << 18) | (fg << 9) | bg;
};
// CSI Ps n Device Status Report (DSR).
......@@ -2777,7 +3784,7 @@ Terminal.prototype.deviceStatus = function(params) {
// this.send('\x1b[?11n');
break;
case 25:
// dont support user defined role_keys
// dont support user defined keys
// this.send('\x1b[?21n');
break;
case 26:
......@@ -2806,7 +3813,7 @@ Terminal.prototype.insertChars = function(params) {
row = this.y + this.ybase;
j = this.x;
ch = [this.curAttr, ' ']; // xterm
ch = [this.eraseAttr(), ' ']; // xterm
while (param-- && j < this.cols) {
this.lines[row].splice(j++, 0, ch);
......@@ -2903,7 +3910,7 @@ Terminal.prototype.deleteChars = function(params) {
if (param < 1) param = 1;
row = this.y + this.ybase;
ch = [this.curAttr, ' ']; // xterm
ch = [this.eraseAttr(), ' ']; // xterm
while (param--) {
this.lines[row].splice(this.x, 1);
......@@ -2921,7 +3928,7 @@ Terminal.prototype.eraseChars = function(params) {
row = this.y + this.ybase;
j = this.x;
ch = [this.curAttr, ' ']; // xterm
ch = [this.eraseAttr(), ' ']; // xterm
while (param-- && j < this.cols) {
this.lines[row][j++] = ch;
......@@ -2964,7 +3971,7 @@ Terminal.prototype.HPositionRelative = function(params) {
// Ps = 1 -> 132-columns.
// Ps = 2 -> Printer.
// Ps = 6 -> Selective erase.
// Ps = 8 -> User-defined role_keys.
// Ps = 8 -> User-defined keys.
// Ps = 9 -> National replacement character sets.
// Ps = 1 5 -> Technical characters.
// Ps = 2 2 -> ANSI color, e.g., VT525.
......@@ -3105,7 +4112,7 @@ Terminal.prototype.HVPosition = function(params) {
// 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 role_keys. (This enables the numLock resource).
// 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
......@@ -3163,7 +4170,7 @@ Terminal.prototype.setMode = function(params) {
} else if (this.prefix === '?') {
switch (params) {
case 1:
this.applicationKeypad = true;
this.applicationCursor = true;
break;
case 2:
this.setgCharset(0, Terminal.charsets.US);
......@@ -3185,6 +4192,10 @@ Terminal.prototype.setMode = function(params) {
case 12:
// this.cursorBlink = true;
break;
case 66:
this.log('Serial port requested application keypad.');
this.applicationKeypad = true;
break;
case 9: // X10 Mouse
// no release, no motion, no wheel, no modifiers.
case 1000: // vt200 mouse
......@@ -3304,7 +4315,7 @@ Terminal.prototype.setMode = function(params) {
// 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 role_keys. (This disables the numLock resource).
// 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
......@@ -3361,7 +4372,7 @@ Terminal.prototype.resetMode = function(params) {
} else if (this.prefix === '?') {
switch (params) {
case 1:
this.applicationKeypad = false;
this.applicationCursor = false;
break;
case 3:
if (this.cols === 132 && this.savedCols) {
......@@ -3378,6 +4389,10 @@ Terminal.prototype.resetMode = function(params) {
case 12:
// this.cursorBlink = false;
break;
case 66:
this.log('Switching back to normal keypad.');
this.applicationKeypad = false;
break;
case 9: // X10 Mouse
case 1000: // vt200 mouse
case 1002: // button event mouse
......@@ -3588,7 +4603,7 @@ Terminal.prototype.setResources = function(params) {
// Ps = 4 -> modifyOtherKeys.
// If the parameter is omitted, modifyFunctionKeys is disabled.
// When modifyFunctionKeys is disabled, xterm uses the modifier
// role_keys to make an extended sequence of functions rather than
// 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) {
......@@ -3615,6 +4630,7 @@ Terminal.prototype.softReset = function(params) {
this.originMode = false;
this.wraparoundMode = false; // autowrap
this.applicationKeypad = false; // ?
this.applicationCursor = false;
this.scrollTop = 0;
this.scrollBottom = this.rows - 1;
this.curAttr = this.defAttr;
......@@ -3933,7 +4949,7 @@ Terminal.prototype.eraseRectangle = function(params) {
, i
, ch;
ch = [this.curAttr, ' ']; // xterm?
ch = [this.eraseAttr(), ' ']; // xterm?
for (; t < b + 1; t++) {
line = this.lines[this.ybase + t];
......@@ -4019,7 +5035,7 @@ Terminal.prototype.requestLocatorPosition = function(params) {
Terminal.prototype.insertColumns = function() {
var param = params[0]
, l = this.ybase + this.rows
, ch = [this.curAttr, ' '] // xterm?
, ch = [this.eraseAttr(), ' '] // xterm?
, i;
while (param--) {
......@@ -4038,7 +5054,7 @@ Terminal.prototype.insertColumns = function() {
Terminal.prototype.deleteColumns = function() {
var param = params[0]
, l = this.ybase + this.rows
, ch = [this.curAttr, ' '] // xterm?
, ch = [this.eraseAttr(), ' '] // xterm?
, i;
while (param--) {
......@@ -4051,6 +5067,961 @@ Terminal.prototype.deleteColumns = function() {
this.maxRange();
};
/**
* Prefix/Select/Visual/Search Modes
*/
Terminal.prototype.enterPrefix = function() {
this.prefixMode = true;
};
Terminal.prototype.leavePrefix = function() {
this.prefixMode = false;
};
Terminal.prototype.enterSelect = function() {
this._real = {
x: this.x,
y: this.y,
ydisp: this.ydisp,
ybase: this.ybase,
cursorHidden: this.cursorHidden,
lines: this.copyBuffer(this.lines),
write: this.write
};
this.write = function() {};
this.selectMode = true;
this.visualMode = false;
this.cursorHidden = false;
this.refresh(this.y, this.y);
};
Terminal.prototype.leaveSelect = function() {
this.x = this._real.x;
this.y = this._real.y;
this.ydisp = this._real.ydisp;
this.ybase = this._real.ybase;
this.cursorHidden = this._real.cursorHidden;
this.lines = this._real.lines;
this.write = this._real.write;
delete this._real;
this.selectMode = false;
this.visualMode = false;
this.refresh(0, this.rows - 1);
};
Terminal.prototype.enterVisual = function() {
this._real.preVisual = this.copyBuffer(this.lines);
this.selectText(this.x, this.x, this.ydisp + this.y, this.ydisp + this.y);
this.visualMode = true;
};
Terminal.prototype.leaveVisual = function() {
this.lines = this._real.preVisual;
delete this._real.preVisual;
delete this._selected;
this.visualMode = false;
this.refresh(0, this.rows - 1);
};
Terminal.prototype.enterSearch = function(down) {
this.entry = '';
this.searchMode = true;
this.searchDown = down;
this._real.preSearch = this.copyBuffer(this.lines);
this._real.preSearchX = this.x;
this._real.preSearchY = this.y;
var bottom = this.ydisp + this.rows - 1;
for (var i = 0; i < this.entryPrefix.length; i++) {
//this.lines[bottom][i][0] = (this.defAttr & ~0x1ff) | 4;
//this.lines[bottom][i][1] = this.entryPrefix[i];
this.lines[bottom][i] = [
(this.defAttr & ~0x1ff) | 4,
this.entryPrefix[i]
];
}
this.y = this.rows - 1;
this.x = this.entryPrefix.length;
this.refresh(this.rows - 1, this.rows - 1);
};
Terminal.prototype.leaveSearch = function() {
this.searchMode = false;
if (this._real.preSearch) {
this.lines = this._real.preSearch;
this.x = this._real.preSearchX;
this.y = this._real.preSearchY;
delete this._real.preSearch;
delete this._real.preSearchX;
delete this._real.preSearchY;
}
this.refresh(this.rows - 1, this.rows - 1);
};
Terminal.prototype.copyBuffer = function(lines) {
var lines = lines || this.lines
, out = [];
for (var y = 0; y < lines.length; y++) {
out[y] = [];
for (var x = 0; x < lines[y].length; x++) {
out[y][x] = [lines[y][x][0], lines[y][x][1]];
}
}
return out;
};
Terminal.prototype.getCopyTextarea = function(text) {
var textarea = this._copyTextarea
, document = this.document;
if (!textarea) {
textarea = document.createElement('textarea');
textarea.style.position = 'absolute';
textarea.style.left = '-32000px';
textarea.style.top = '-32000px';
textarea.style.width = '0px';
textarea.style.height = '0px';
textarea.style.opacity = '0';
textarea.style.backgroundColor = 'transparent';
textarea.style.borderStyle = 'none';
textarea.style.outlineStyle = 'none';
document.getElementsByTagName('body')[0].appendChild(textarea);
this._copyTextarea = textarea;
}
return textarea;
};
// NOTE: Only works for primary selection on X11.
// Non-X11 users should use Ctrl-C instead.
Terminal.prototype.copyText = function(text) {
var self = this
, textarea = this.getCopyTextarea();
this.emit('copy', text);
textarea.focus();
textarea.textContent = text;
textarea.value = text;
textarea.setSelectionRange(0, text.length);
setTimeout(function() {
self.element.focus();
self.focus();
}, 1);
};
Terminal.prototype.clearSelectedText = function() {
if (this._selected) {
var ox1
, ox2
, oy1
, oy2
, tmp
, x
, y
, xl
, attr;
ox1 = this._selected.x1;
ox2 = this._selected.x2;
oy1 = this._selected.y1;
oy2 = this._selected.y2;
if (oy2 < oy1) {
tmp = ox2;
ox2 = ox1;
ox1 = tmp;
tmp = oy2;
oy2 = oy1;
oy1 = tmp;
}
if (ox2 < ox1 && oy1 === oy2) {
tmp = ox2;
ox2 = ox1;
ox1 = tmp;
}
for (y = oy1; y <= oy2; y++) {
x = 0;
xl = this.cols - 1;
if (y === oy1) {
x = ox1;
}
if (y === oy2) {
xl = ox2;
}
for (; x <= xl; x++) {
if (this.lines[y][x].old != null) {
//this.lines[y][x][0] = this.lines[y][x].old;
//delete this.lines[y][x].old;
attr = this.lines[y][x].old;
delete this.lines[y][x].old;
this.lines[y][x] = [attr, this.lines[y][x][1]];
}
}
}
delete this._selected;
}
};
Terminal.prototype.selectText = function(x1, x2, y1, y2) {
var tmp
, x
, y
, xl
, attr;
if (this._selected) {
this.clearSelectedText();
}
y1 = Math.max(y1, 0);
y1 = Math.min(y1, this.ydisp + this.rows - 1);
y2 = Math.max(y2, 0);
y2 = Math.min(y2, this.ydisp + this.rows - 1);
this._selected = { x1: x1, x2: x2, y1: y1, y2: y2 };
if (y2 < y1) {
tmp = x2;
x2 = x1;
x1 = tmp;
tmp = y2;
y2 = y1;
y1 = tmp;
}
if (x2 < x1 && y1 === y2) {
tmp = x2;
x2 = x1;
x1 = tmp;
}
for (y = y1; y <= y2; y++) {
x = 0;
xl = this.cols - 1;
if (y === y1) {
x = x1;
}
if (y === y2) {
xl = x2;
}
for (; x <= xl; x++) {
//this.lines[y][x].old = this.lines[y][x][0];
//this.lines[y][x][0] &= ~0x1ff;
//this.lines[y][x][0] |= (0x1ff << 9) | 4;
attr = this.lines[y][x][0];
this.lines[y][x] = [
(attr & ~0x1ff) | ((0x1ff << 9) | 4),
this.lines[y][x][1]
];
this.lines[y][x].old = attr;
}
}
y1 = y1 - this.ydisp;
y2 = y2 - this.ydisp;
y1 = Math.max(y1, 0);
y1 = Math.min(y1, this.rows - 1);
y2 = Math.max(y2, 0);
y2 = Math.min(y2, this.rows - 1);
//this.refresh(y1, y2);
this.refresh(0, this.rows - 1);
};
Terminal.prototype.grabText = function(x1, x2, y1, y2) {
var out = ''
, buf = ''
, ch
, x
, y
, xl
, tmp;
if (y2 < y1) {
tmp = x2;
x2 = x1;
x1 = tmp;
tmp = y2;
y2 = y1;
y1 = tmp;
}
if (x2 < x1 && y1 === y2) {
tmp = x2;
x2 = x1;
x1 = tmp;
}
for (y = y1; y <= y2; y++) {
x = 0;
xl = this.cols - 1;
if (y === y1) {
x = x1;
}
if (y === y2) {
xl = x2;
}
for (; x <= xl; x++) {
ch = this.lines[y][x][1];
if (ch === ' ') {
buf += ch;
continue;
}
if (buf) {
out += buf;
buf = '';
}
out += ch;
if (isWide(ch)) x++;
}
buf = '';
out += '\n';
}
// If we're not at the end of the
// line, don't add a newline.
for (x = x2, y = y2; x < this.cols; x++) {
if (this.lines[y][x][1] !== ' ') {
out = out.slice(0, -1);
break;
}
}
return out;
};
Terminal.prototype.keyPrefix = function(ev, key) {
if (key === 'k' || key === '&') {
this.destroy();
} else if (key === 'p' || key === ']') {
this.emit('request paste');
} else if (key === 'c') {
this.emit('request create');
} else if (key >= '0' && key <= '9') {
key = +key - 1;
if (!~key) key = 9;
this.emit('request term', key);
} else if (key === 'n') {
this.emit('request term next');
} else if (key === 'P') {
this.emit('request term previous');
} else if (key === ':') {
this.emit('request command mode');
} else if (key === '[') {
this.enterSelect();
}
};
Terminal.prototype.keySelect = function(ev, key) {
this.showCursor();
if (this.searchMode || key === 'n' || key === 'N') {
return this.keySearch(ev, key);
}
if (key === '\x04') { // ctrl-d
var y = this.ydisp + this.y;
if (this.ydisp === this.ybase) {
// Mimic vim behavior
this.y = Math.min(this.y + (this.rows - 1) / 2 | 0, this.rows - 1);
this.refresh(0, this.rows - 1);
} else {
this.scrollDisp((this.rows - 1) / 2 | 0);
}
if (this.visualMode) {
this.selectText(this.x, this.x, y, this.ydisp + this.y);
}
return;
}
if (key === '\x15') { // ctrl-u
var y = this.ydisp + this.y;
if (this.ydisp === 0) {
// Mimic vim behavior
this.y = Math.max(this.y - (this.rows - 1) / 2 | 0, 0);
this.refresh(0, this.rows - 1);
} else {
this.scrollDisp(-(this.rows - 1) / 2 | 0);
}
if (this.visualMode) {
this.selectText(this.x, this.x, y, this.ydisp + this.y);
}
return;
}
if (key === '\x06') { // ctrl-f
var y = this.ydisp + this.y;
this.scrollDisp(this.rows - 1);
if (this.visualMode) {
this.selectText(this.x, this.x, y, this.ydisp + this.y);
}
return;
}
if (key === '\x02') { // ctrl-b
var y = this.ydisp + this.y;
this.scrollDisp(-(this.rows - 1));
if (this.visualMode) {
this.selectText(this.x, this.x, y, this.ydisp + this.y);
}
return;
}
if (key === 'k' || key === '\x1b[A') {
var y = this.ydisp + this.y;
this.y--;
if (this.y < 0) {
this.y = 0;
this.scrollDisp(-1);
}
if (this.visualMode) {
this.selectText(this.x, this.x, y, this.ydisp + this.y);
} else {
this.refresh(this.y, this.y + 1);
}
return;
}
if (key === 'j' || key === '\x1b[B') {
var y = this.ydisp + this.y;
this.y++;
if (this.y >= this.rows) {
this.y = this.rows - 1;
this.scrollDisp(1);
}
if (this.visualMode) {
this.selectText(this.x, this.x, y, this.ydisp + this.y);
} else {
this.refresh(this.y - 1, this.y);
}
return;
}
if (key === 'h' || key === '\x1b[D') {
var x = this.x;
this.x--;
if (this.x < 0) {
this.x = 0;
}
if (this.visualMode) {
this.selectText(x, this.x, this.ydisp + this.y, this.ydisp + this.y);
} else {
this.refresh(this.y, this.y);
}
return;
}
if (key === 'l' || key === '\x1b[C') {
var x = this.x;
this.x++;
if (this.x >= this.cols) {
this.x = this.cols - 1;
}
if (this.visualMode) {
this.selectText(x, this.x, this.ydisp + this.y, this.ydisp + this.y);
} else {
this.refresh(this.y, this.y);
}
return;
}
if (key === 'v' || key === ' ') {
if (!this.visualMode) {
this.enterVisual();
} else {
this.leaveVisual();
}
return;
}
if (key === 'y') {
if (this.visualMode) {
var text = this.grabText(
this._selected.x1, this._selected.x2,
this._selected.y1, this._selected.y2);
this.copyText(text);
this.leaveVisual();
// this.leaveSelect();
}
return;
}
if (key === 'q' || key === '\x1b') {
if (this.visualMode) {
this.leaveVisual();
} else {
this.leaveSelect();
}
return;
}
if (key === 'w' || key === 'W') {
var ox = this.x;
var oy = this.y;
var oyd = this.ydisp;
var x = this.x;
var y = this.y;
var yb = this.ydisp;
var saw_space = false;
for (;;) {
var line = this.lines[yb + y];
while (x < this.cols) {
if (line[x][1] <= ' ') {
saw_space = true;
} else if (saw_space) {
break;
}
x++;
}
if (x >= this.cols) x = this.cols - 1;
if (x === this.cols - 1 && line[x][1] <= ' ') {
x = 0;
if (++y >= this.rows) {
y--;
if (++yb > this.ybase) {
yb = this.ybase;
x = this.x;
break;
}
}
continue;
}
break;
}
this.x = x, this.y = y;
this.scrollDisp(-this.ydisp + yb);
if (this.visualMode) {
this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y);
}
return;
}
if (key === 'b' || key === 'B') {
var ox = this.x;
var oy = this.y;
var oyd = this.ydisp;
var x = this.x;
var y = this.y;
var yb = this.ydisp;
for (;;) {
var line = this.lines[yb + y];
var saw_space = x > 0 && line[x][1] > ' ' && line[x - 1][1] > ' ';
while (x >= 0) {
if (line[x][1] <= ' ') {
if (saw_space && (x + 1 < this.cols && line[x + 1][1] > ' ')) {
x++;
break;
} else {
saw_space = true;
}
}
x--;
}
if (x < 0) x = 0;
if (x === 0 && (line[x][1] <= ' ' || !saw_space)) {
x = this.cols - 1;
if (--y < 0) {
y++;
if (--yb < 0) {
yb++;
x = 0;
break;
}
}
continue;
}
break;
}
this.x = x, this.y = y;
this.scrollDisp(-this.ydisp + yb);
if (this.visualMode) {
this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y);
}
return;
}
if (key === 'e' || key === 'E') {
var x = this.x + 1;
var y = this.y;
var yb = this.ydisp;
if (x >= this.cols) x--;
for (;;) {
var line = this.lines[yb + y];
while (x < this.cols) {
if (line[x][1] <= ' ') {
x++;
} else {
break;
}
}
while (x < this.cols) {
if (line[x][1] <= ' ') {
if (x - 1 >= 0 && line[x - 1][1] > ' ') {
x--;
break;
}
}
x++;
}
if (x >= this.cols) x = this.cols - 1;
if (x === this.cols - 1 && line[x][1] <= ' ') {
x = 0;
if (++y >= this.rows) {
y--;
if (++yb > this.ybase) {
yb = this.ybase;
break;
}
}
continue;
}
break;
}
this.x = x, this.y = y;
this.scrollDisp(-this.ydisp + yb);
if (this.visualMode) {
this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y);
}
return;
}
if (key === '^' || key === '0') {
var ox = this.x;
if (key === '0') {
this.x = 0;
} else if (key === '^') {
var line = this.lines[this.ydisp + this.y];
var x = 0;
while (x < this.cols) {
if (line[x][1] > ' ') {
break;
}
x++;
}
if (x >= this.cols) x = this.cols - 1;
this.x = x;
}
if (this.visualMode) {
this.selectText(ox, this.x, this.ydisp + this.y, this.ydisp + this.y);
} else {
this.refresh(this.y, this.y);
}
return;
}
if (key === '$') {
var ox = this.x;
var line = this.lines[this.ydisp + this.y];
var x = this.cols - 1;
while (x >= 0) {
if (line[x][1] > ' ') {
if (this.visualMode && x < this.cols - 1) x++;
break;
}
x--;
}
if (x < 0) x = 0;
this.x = x;
if (this.visualMode) {
this.selectText(ox, this.x, this.ydisp + this.y, this.ydisp + this.y);
} else {
this.refresh(this.y, this.y);
}
return;
}
if (key === 'g' || key === 'G') {
var ox = this.x;
var oy = this.y;
var oyd = this.ydisp;
if (key === 'g') {
this.x = 0, this.y = 0;
this.scrollDisp(-this.ydisp);
} else if (key === 'G') {
this.x = 0, this.y = this.rows - 1;
this.scrollDisp(this.ybase);
}
if (this.visualMode) {
this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y);
}
return;
}
if (key === 'H' || key === 'M' || key === 'L') {
var ox = this.x;
var oy = this.y;
if (key === 'H') {
this.x = 0, this.y = 0;
} else if (key === 'M') {
this.x = 0, this.y = this.rows / 2 | 0;
} else if (key === 'L') {
this.x = 0, this.y = this.rows - 1;
}
if (this.visualMode) {
this.selectText(ox, this.x, this.ydisp + oy, this.ydisp + this.y);
} else {
this.refresh(oy, oy);
this.refresh(this.y, this.y);
}
return;
}
if (key === '{' || key === '}') {
var ox = this.x;
var oy = this.y;
var oyd = this.ydisp;
var line;
var saw_full = false;
var found = false;
var first_is_space = -1;
var y = this.y + (key === '{' ? -1 : 1);
var yb = this.ydisp;
var i;
if (key === '{') {
if (y < 0) {
y++;
if (yb > 0) yb--;
}
} else if (key === '}') {
if (y >= this.rows) {
y--;
if (yb < this.ybase) yb++;
}
}
for (;;) {
line = this.lines[yb + y];
for (i = 0; i < this.cols; i++) {
if (line[i][1] > ' ') {
if (first_is_space === -1) {
first_is_space = 0;
}
saw_full = true;
break;
} else if (i === this.cols - 1) {
if (first_is_space === -1) {
first_is_space = 1;
} else if (first_is_space === 0) {
found = true;
} else if (first_is_space === 1) {
if (saw_full) found = true;
}
break;
}
}
if (found) break;
if (key === '{') {
y--;
if (y < 0) {
y++;
if (yb > 0) yb--;
else break;
}
} else if (key === '}') {
y++;
if (y >= this.rows) {
y--;
if (yb < this.ybase) yb++;
else break;
}
}
}
if (!found) {
if (key === '{') {
y = 0;
yb = 0;
} else if (key === '}') {
y = this.rows - 1;
yb = this.ybase;
}
}
this.x = 0, this.y = y;
this.scrollDisp(-this.ydisp + yb);
if (this.visualMode) {
this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y);
}
return;
}
if (key === '/' || key === '?') {
if (!this.visualMode) {
this.enterSearch(key === '/');
}
return;
}
return false;
};
Terminal.prototype.keySearch = function(ev, key) {
if (key === '\x1b') {
this.leaveSearch();
return;
}
if (key === '\r' || (!this.searchMode && (key === 'n' || key === 'N'))) {
this.leaveSearch();
var entry = this.entry;
if (!entry) {
this.refresh(0, this.rows - 1);
return;
}
var ox = this.x;
var oy = this.y;
var oyd = this.ydisp;
var line;
var found = false;
var wrapped = false;
var x = this.x + 1;
var y = this.ydisp + this.y;
var yb, i;
var up = key === 'N'
? this.searchDown
: !this.searchDown;
for (;;) {
line = this.lines[y];
while (x < this.cols) {
for (i = 0; i < entry.length; i++) {
if (x + i >= this.cols) break;
if (line[x + i][1] !== entry[i]) {
break;
} else if (line[x + i][1] === entry[i] && i === entry.length - 1) {
found = true;
break;
}
}
if (found) break;
x += i + 1;
}
if (found) break;
x = 0;
if (!up) {
y++;
if (y > this.ybase + this.rows - 1) {
if (wrapped) break;
// this.setMessage('Search wrapped. Continuing at TOP.');
wrapped = true;
y = 0;
}
} else {
y--;
if (y < 0) {
if (wrapped) break;
// this.setMessage('Search wrapped. Continuing at BOTTOM.');
wrapped = true;
y = this.ybase + this.rows - 1;
}
}
}
if (found) {
if (y - this.ybase < 0) {
yb = y;
y = 0;
if (yb > this.ybase) {
y = yb - this.ybase;
yb = this.ybase;
}
} else {
yb = this.ybase;
y -= this.ybase;
}
this.x = x, this.y = y;
this.scrollDisp(-this.ydisp + yb);
if (this.visualMode) {
this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y);
}
return;
}
// this.setMessage("No matches found.");
this.refresh(0, this.rows - 1);
return;
}
if (key === '\b' || key === '\x7f') {
if (this.entry.length === 0) return;
var bottom = this.ydisp + this.rows - 1;
this.entry = this.entry.slice(0, -1);
var i = this.entryPrefix.length + this.entry.length;
//this.lines[bottom][i][1] = ' ';
this.lines[bottom][i] = [
this.lines[bottom][i][0],
' '
];
this.x--;
this.refresh(this.rows - 1, this.rows - 1);
this.refresh(this.y, this.y);
return;
}
if (key.length === 1 && key >= ' ' && key <= '~') {
var bottom = this.ydisp + this.rows - 1;
this.entry += key;
var i = this.entryPrefix.length + this.entry.length - 1;
//this.lines[bottom][i][0] = (this.defAttr & ~0x1ff) | 4;
//this.lines[bottom][i][1] = key;
this.lines[bottom][i] = [
(this.defAttr & ~0x1ff) | 4,
key
];
this.x++;
this.refresh(this.rows - 1, this.rows - 1);
this.refresh(this.y, this.y);
return;
}
return false;
};
/**
* Character Sets
*/
......@@ -4141,18 +6112,22 @@ function inherits(child, parent) {
child.prototype = new f;
}
var isMac = ~navigator.userAgent.indexOf('Mac');
// if bold is broken, we can't
// use it in the terminal.
function isBoldBroken() {
function isBoldBroken(document) {
var body = document.getElementsByTagName('body')[0];
var terminal = document.createElement('div');
terminal.className = 'terminal';
var line = document.createElement('div');
var el = document.createElement('span');
el.innerHTML = 'hello world';
document.body.appendChild(el);
line.appendChild(el);
terminal.appendChild(line);
body.appendChild(terminal);
var w1 = el.scrollWidth;
el.style.fontWeight = 'bold';
var w2 = el.scrollWidth;
document.body.removeChild(el);
body.removeChild(terminal);
return w1 !== w2;
}
......@@ -4160,12 +6135,128 @@ var String = this.String;
var setTimeout = this.setTimeout;
var setInterval = this.setInterval;
function indexOf(obj, el) {
var i = obj.length;
while (i--) {
if (obj[i] === el) return i;
}
return -1;
}
/* Ref: https://github.com/ajaxorg/ace/blob/0c66e1eda418477a9efbd0d3ef61698478cc607f/lib/ace/edit_session.js#L2434 */
function isFullWidth(c) {
if (c < 0x1100)
return false;
return c >= 0x1100 && c <= 0x115F ||
c >= 0x11A3 && c <= 0x11A7 ||
c >= 0x11FA && c <= 0x11FF ||
c >= 0x2329 && c <= 0x232A ||
c >= 0x2E80 && c <= 0x2E99 ||
c >= 0x2E9B && c <= 0x2EF3 ||
c >= 0x2F00 && c <= 0x2FD5 ||
c >= 0x2FF0 && c <= 0x2FFB ||
c >= 0x3000 && c <= 0x303E ||
c >= 0x3041 && c <= 0x3096 ||
c >= 0x3099 && c <= 0x30FF ||
c >= 0x3105 && c <= 0x312D ||
c >= 0x3131 && c <= 0x318E ||
c >= 0x3190 && c <= 0x31BA ||
c >= 0x31C0 && c <= 0x31E3 ||
c >= 0x31F0 && c <= 0x321E ||
c >= 0x3220 && c <= 0x3247 ||
c >= 0x3250 && c <= 0x32FE ||
c >= 0x3300 && c <= 0x4DBF ||
c >= 0x4E00 && c <= 0xA48C ||
c >= 0xA490 && c <= 0xA4C6 ||
c >= 0xA960 && c <= 0xA97C ||
c >= 0xAC00 && c <= 0xD7A3 ||
c >= 0xD7B0 && c <= 0xD7C6 ||
c >= 0xD7CB && c <= 0xD7FB ||
c >= 0xF900 && c <= 0xFAFF ||
c >= 0xFE10 && c <= 0xFE19 ||
c >= 0xFE30 && c <= 0xFE52 ||
c >= 0xFE54 && c <= 0xFE66 ||
c >= 0xFE68 && c <= 0xFE6B ||
c >= 0xFF01 && c <= 0xFF60 ||
c >= 0xFFE0 && c <= 0xFFE6;
};
function isWide(ch) {
var c = ch.charCodeAt(0);
return isFullWidth(c);
}
function matchColor(r1, g1, b1) {
var hash = (r1 << 16) | (g1 << 8) | b1;
if (matchColor._cache[hash] != null) {
return matchColor._cache[hash];
}
var ldiff = Infinity
, li = -1
, i = 0
, c
, r2
, g2
, b2
, diff;
for (; i < Terminal.vcolors.length; i++) {
c = Terminal.vcolors[i];
r2 = c[0];
g2 = c[1];
b2 = c[2];
diff = matchColor.distance(r1, g1, b1, r2, g2, b2);
if (diff === 0) {
li = i;
break;
}
if (diff < ldiff) {
ldiff = diff;
li = i;
}
}
return matchColor._cache[hash] = li;
}
matchColor._cache = {};
// http://stackoverflow.com/questions/1633828
matchColor.distance = function(r1, g1, b1, r2, g2, b2) {
return Math.pow(30 * (r1 - r2), 2)
+ Math.pow(59 * (g1 - g2), 2)
+ Math.pow(11 * (b1 - b2), 2);
};
function each(obj, iter, con) {
if (obj.forEach) return obj.forEach(iter, con);
for (var i = 0; i < obj.length; i++) {
iter.call(con, obj[i], i, obj);
}
}
function keys(obj) {
if (Object.keys) return Object.keys(obj);
var key, keys = [];
for (key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
keys.push(key);
}
}
return keys;
}
/**
* Expose
*/
Terminal.EventEmitter = EventEmitter;
Terminal.isMac = isMac;
Terminal.Stream = Stream;
Terminal.inherits = inherits;
Terminal.on = on;
Terminal.off = off;
......
/**
* Created by liuzheng on 3/3/16.
*/
var rowHeight = 1;
var colWidth = 1;
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 + document.URL.match(RegExp('//(.*?)/'))[1] + '/ws/terminal' + document.URL.match(/(\?.*)/);
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) {
try{
options.onData(evt.data);
} catch (e) {
var data = JSON.parse(evt.data.toString());
options.onError(data.error);
}
};
this._connection.onclose = function (evt) {
options.onClose();
};
};
WSSHClient.prototype.send = function (data) {
this._connection.send(JSON.stringify({'data': data}));
};
function openTerminal(options) {
var client = new WSSHClient();
var term = new Terminal({
rows: rowHeight,
cols: colWidth,
useStyle: true,
screenKeys: true
});
term.open();
term.on('data', function (data) {
client.send(data)
});
$('.terminal').detach().appendTo('#term');
term.resize(80, 24);
term.write('Connecting...');
client.connect($.extend(options, {
onError: function (error) {
term.write('Error: ' + error + '\r\n');
},
onConnect: function () {
// Erase our connecting message
term.write('\r');
},
onClose: function () {
term.write('Connection Reset By Peer');
},
onData: function (data) {
term.write(data);
}
}));
rowHeight = 0.0 + 1.00 * $('.terminal').height() / 24;
colWidth = 0.0 + 1.00 * $('.terminal').width() / 80;
return {'term': term, 'client': client};
}
function resize() {
$('.terminal').css('width', window.innerWidth - 25);
console.log(window.innerWidth);
console.log(window.innerWidth - 10);
var rows = Math.floor(window.innerHeight / rowHeight) - 2;
var cols = Math.floor(window.innerWidth / colWidth) - 1;
return {rows: rows, cols: cols};
}
$(document).ready(function () {
var options = {};
$('#ssh').show();
var term_client = openTerminal(options);
console.log(rowHeight);
// by liuzheng712 because it will bring record bug
//window.onresize = function () {
// var geom = resize();
// console.log(geom);
// term_client.term.resize(geom.cols, geom.rows);
// term_client.client.send({'resize': {'rows': geom.rows, 'cols': geom.cols}});
// $('#ssh').show();
//}
});
\ No newline at end of file
......@@ -29,7 +29,7 @@
protocol = 'wss://';
}
var wsUri = protocol + "{{ web_terminal_uri }}"; //请求的websocket url
var wsUri = protocol + document.URL.match(RegExp('//(.*?)/'))[1] + "{{ web_terminal_uri }}"; //请求的websocket url
var ws = new WebSocket(wsUri);
function createSystemMessage(message) {
......@@ -82,7 +82,7 @@
<style type="text/css">
* {
font-family: "Monaco", "DejaVu Sans Mono", "Liberation Mono", monospace;
font-family: "Monaco", "Microsoft Yahei", "DejaVu Sans Mono", "Liberation Mono", monospace;
font-size: 11px;
}
......
......@@ -36,6 +36,5 @@
} else {
$("#"+s1).addClass('active');
$('#'+s1+' .'+s2).addClass('active');
console.log(s1)
}
</script>
<div class="footer fixed">
<div class="pull-right">
Version <strong>0.3.0</strong> GPL.
Version <strong>0.3.1</strong> GPL.
</div>
<div>
<strong>Copyright</strong> Jumpserver.org Team &copy; 2014-2015
......
......@@ -210,7 +210,7 @@
{% if login_10 %}
{% for login in login_10 %}
<div class="feed-element">
<a href="profile.html" class="pull-left">
<a href="#" class="pull-left">
<img alt="image" class="img-circle" src="/static/img/root.png">
</a>
<div class="media-body ">
......@@ -232,7 +232,7 @@
<div class="feed-activity-list">
{% for login in login_more_10 %}
<div class="feed-element">
<a href="profile.html" class="pull-left">
<a href="#" class="pull-left">
<img alt="image" class="img-circle" src="/static/img/root.png">
</a>
<div class="media-body ">
......
......@@ -50,24 +50,23 @@
<div class="hr-line-dashed"></div>
<div class="form-group">
<label for="j_group" class="col-sm-2 control-label">管理账号<span class="red-fonts"> *</span></label>
<label for="j_group" class="col-sm-2 control-label">管理用户<span class="red-fonts"> *</span></label>
<div class="col-sm-2">
<div class="radio i-checks">
<label>
<label style="padding-left: 0">
<input type="checkbox" checked="checked" id="id_use_default_auth" name="use_default_auth"><span> 使用默认 </span>
</label>
</div>
</div>
</div>
<p class="col-sm-offset-2">Tips: 管理账号是服务器存在的root等高权限账号,用来推送新建系统用户</p>
<p class="col-sm-offset-2">Tips: 管理用户是服务器存在的root或拥有sudo的用户,用来推送系统用户</p>
<div class="form-group" id="admin_account" style="display: none">
<div class="hr-line-dashed"></div>
<label class="col-sm-2 control-label"> 管理用户名<span class="red-fonts">*</span> </label>
<label class="col-sm-2 control-label"> <span class="red-fonts"></span> </label>
<div class="col-sm-3">
<input type="text" placeholder="Username" name="username" class="form-control">
</div>
<label class="col-sm-1 control-label"> 密码<span class="red-fonts">*</span> </label>
<label class="col-sm-1 control-label"> <span class="red-fonts"></span> </label>
<div class="col-sm-4">
<input type="password" placeholder="Password" name="password" class="form-control">
</div>
......
......@@ -57,20 +57,19 @@
<label for="j_group" class="col-sm-2 control-label">管理账号 <span class="red-fonts">*</span></label>
<div class="col-sm-2">
<div class="radio i-checks">
<label>
<label style="padding-left: 0">
<input type="checkbox" {% if asset.use_default_auth %} checked="checked" {% endif %} id="id_use_default_auth" name="use_default_auth"><span> 使用默认 </span>
</label>
</div>
</div>
</div>
<div class="form-group" id="admin_account" {% if asset.use_default_auth %} style="display: none" {% endif %}>
<div class="hr-line-dashed"></div>
<label class="col-sm-2 control-label"> 管理用户名 <span class="red-fonts">*</span> </label>
<label class="col-sm-2 control-label"> </label>
<div class="col-sm-3">
<input type="text" value="{{ asset.username }}" name="username" class="form-control">
</div>
<label class="col-sm-1 control-label"> 密码<span class="red-fonts">*</span> </label>
<label class="col-sm-1 control-label"> </label>
<div class="col-sm-4">
<input type="password" value="" name="password" placeholder="不填写即不更改密码." class="form-control">
</div>
......
......@@ -8,6 +8,11 @@
{% include 'nav_cat_bar.html' %}
<style>
iframe {
overflow:hidden;
}
.bootstrap-dialog-body {
background-color: rgba(0, 0, 0, 0);
}
......@@ -133,7 +138,7 @@
</div>
{% include 'paginator.html' %}
</div>
</div>
</div>
</div>
</div>
</div>
......@@ -157,7 +162,7 @@
title: title,
maxmin: true,
shade: false,
area: ['800px', '520px'],
area: ['620px', '450px'],
content: url
});
return false;
......
......@@ -3,14 +3,16 @@
<style>
.terminal {
border: #000 solid 5px;
font-family: "Monaco", "DejaVu Sans Mono", "Liberation Mono", monospace;
font-family: "Monaco", "Microsoft Yahei", "DejaVu Sans Mono", "Liberation Mono", monospace;
font-size: 11px;
color: #f0f0f0;
background: rgba(0, 0, 0, 0.6);
width: 600px;
box-shadow: rgba(0, 0, 0, 0.6) 2px 2px 20px;
}
white-space: nowrap;
display: inline-block;
}
.reverse-video {
color: #000;
background: #f0f0f0;
......@@ -109,7 +111,7 @@
<td id="remote_ip" class="text-center"> {{ post.remote_ip }} </td>
<td class="text-center"> {{ post.login_type }} </td>
<td class="text-center"><a href="{% url 'log_history' %}?id={{ post.id }}" class="log_command"> 统计 </a></td>
<td class="text-center"><a class="monitor" file_path="{{ post.log_path }}"> 监控 </a></td>
<td class="text-center"><a class="monitor" monitor-id="{{ post.id }}" file_path="{{ post.log_path }}"> 监控 </a></td>
<td class="text-center"><input type="button" id="cut" class="btn btn-danger btn-xs" name="cut" value="阻断" onclick='cut("{{ post.pid }}", "{{ post.login_type }}")' /></td>
<td class="text-center" id="start_time"> {{ post.start_time|date:"Y-m-d H:i:s" }} </td>
</tr>
......@@ -128,36 +130,33 @@
</div>
{#<script src="http://{{ web_socket_host }}/socket.io/socket.io.js"></script>#}
<script>
{# $(document).ready(function(){#}
{# $('.monitor').click(function(){#}
{# window.open('/jlog/monitor/', '监控', 'height=500, width=910, top=89px, left=99px,toolbar=no,menubar=no,scrollbars=auto,resizeable=no,location=no,status=no');#}
{# })#}
{# });#}
function init(obj){
var protocol = "ws://";
if (window.location.protocol == 'https:') {
protocol = 'wss://';
var protocol = 'wss://';
} else {
var protocol = 'ws://';
}
var endpoint = protocol + document.URL.match(RegExp('//(.*?)/'))[1] + '/ws/monitor';
var monitorid = obj.attr('monitor-id');
var file_path = obj.attr('file_path');
var wsUri = protocol + '{{ web_monitor_uri }}';
var socket = new WebSocket(wsUri + '?file_path=' + file_path);
{# var socket = new WebSocket(endpoint + '?id=' + monitorid);#}
var socket = new WebSocket(endpoint + '?file_path=' + file_path);
var term = new Terminal({
cols: 80,
rows: 24,
cols: 98,
rows: 28,
screenKeys: false,
handler: function(){return false}
});
var tag = $('<div id="term" style="height:500px; overflow: auto;background-color: rgba(0, 0, 0, 0);border: none"></div>');
term.open();
term.resize(80, 24);
$('.terminal').hide();
term.resize(98, 28);
socket.onopen = function(evt){
socket.send('hello');
{# socket.send('hello');#}
term.write('~.~ Connect WebSocket Success.~.~ \r\n');
};
......@@ -170,11 +169,11 @@
BootstrapDialog.show({message: function(){
//服务器端认证
{# socket.send('login', {userid:message.id, filename:message.filename,username:username,seed:seed});#}
window.setTimeout(function(){
$('.terminal').detach().appendTo('#term');
$('.terminal').show();
socket.onmessage = function(evt){
term.write(evt.data);
term.write(evt.data);
}}, 1000);
return tag[0];
......@@ -209,19 +208,18 @@
});
function cut(num, login_type){
console.log(login_type);
var protocol = window.location.protocol;
var endpoint = protocol + '//' + document.URL.match(RegExp('//(.*?)/'))[1] + '/kill';
if (login_type=='web'){
var g_url = '{{ web_kill_uri }}' + '?id=' + num;
var g_url = endpoint + '?id=' + num;
console.log(g_url);
} else {
var g_url = "{% url 'log_kill' %} }?id=" + num;
var g_url = "{% url 'log_kill' %}?id=" + num;
}
$.ajax({
type: "GET",
url: g_url+"&sessionid={{ session_id }}",
success: window.open("{% url 'log_list' 'online' %}", "_self")
});
$.get(g_url+"&sessionid={{ session_id }}", function () {
window.open("{% url 'log_list' 'online' %}", "_self")
})
}
</script>
{% endblock %}
\ No newline at end of file
<!DOCTYPE html>
<html ng-app="NgApp" style=" overflow:hidden;">
<head lang="en">
<title>Jumpserver 录像回放</title>
<script type="application/javascript" src='/static/js/jquery-2.1.1.js'></script>
<script type="application/javascript" src='/static/js/angular.min.js'></script>
<script type="application/javascript" src='/static/js/angular-route.min.js'></script>
<script type="application/javascript" src='/static/js/term.js'></script>
</head>
<body>
{% csrf_token %}
<div ng-controller="TerminalRecordCtrl">
<input type="button" value="Play/Pause" ng-click="pause(false);"/>
<input type="button" value="Restart" ng-click="restart(1);"/>
<span id="beforeScrubberText"></span>
<input id="scrubber" type="range" value="0" min=0 max=100
ng-mousedown="pause(true);" ng-mouseup="scrub();"/>
<span id="afterScrubberText"></span>
-5x <input id="speed" type="range" value="0" min=-5 max=5
ng-mouseup="setSpeed();"/> +5x
<div id="terminal"></div>
</div>
<script type="application/javascript" src='/static/js/record.js'></script>
</body>
</html>
\ No newline at end of file
{% extends "base.jinja2" %}
{% block head %}
<!-- term.js released and distributed under the MIT License; Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/) -->
<script>(function(){var i=this,p=this.document;function h(){this._events=this._events||{}}h.prototype.addListener=function(y,z){this._events[y]=this._events[y]||[];this._events[y].push(z)};h.prototype.on=h.prototype.addListener;h.prototype.removeListener=function(z,A){if(!this._events[z]){return}var B=this._events[z],y=B.length;while(y--){if(B[y]===A||B[y].listener===A){B.splice(y,1);return}}};h.prototype.off=h.prototype.removeListener;h.prototype.removeAllListeners=function(y){if(this._events[y]){delete this._events[y]}};h.prototype.once=function(z,A){function y(){var B=Array.prototype.slice.call(arguments);this.removeListener(z,y);return A.apply(this,B)}y.listener=A;return this.on(z,y)};h.prototype.emit=function(B){if(!this._events[B]){return}var z=Array.prototype.slice.call(arguments,1),C=this._events[B],y=C.length,A=0;for(;A<y;A++){C[A].apply(this,z)}};h.prototype.listeners=function(y){return this._events[y]=this._events[y]||[]};var x=0,g=1,q=2,j=3,f=4,o=5,s=6;function t(z){var y=this;if(!(this instanceof t)){return new t(arguments[0],arguments[1],arguments[2])}h.call(this);if(typeof z==="number"){z={cols:arguments[0],rows:arguments[1],handler:arguments[2]}}z=z||{};e(n(t.defaults),function(B){if(z[B]==null){z[B]=t.options[B];if(t[B]!==t.defaults[B]){z[B]=t[B]}}y[B]=z[B]});if(z.colors.length===8){z.colors=z.colors.concat(t._colors.slice(8))}else{if(z.colors.length===16){z.colors=z.colors.concat(t._colors.slice(16))}else{if(z.colors.length===10){z.colors=z.colors.slice(0,-2).concat(t._colors.slice(8,-2),z.colors.slice(-2))}else{if(z.colors.length===18){z.colors=z.colors.concat(t._colors.slice(16,-2),z.colors.slice(-2))}}}}this.colors=z.colors;this.options=z;this.parent=z.body||z.parent||(p?p.getElementsByTagName("body")[0]:null);this.cols=z.cols||z.geometry[0];this.rows=z.rows||z.geometry[1];if(z.handler){this.on("data",z.handler)}this.ybase=0;this.ydisp=0;this.x=0;this.y=0;this.cursorState=0;this.cursorHidden=false;this.convertEol;this.state=0;this.queue="";this.scrollTop=0;this.scrollBottom=this.rows-1;this.applicationKeypad=false;this.applicationCursor=false;this.originMode=false;this.insertMode=false;this.wraparoundMode=false;this.normal=null;this.prefixMode=false;this.selectMode=false;this.visualMode=false;this.searchMode=false;this.searchDown;this.entry="";this.entryPrefix="Search: ";this._real;this._selected;this._textarea;this.charset=null;this.gcharset=null;this.glevel=0;this.charsets=[null];this.decLocator;this.x10Mouse;this.vt200Mouse;this.vt300Mouse;this.normalMouse;this.mouseEvents;this.sendFocus;this.utfMouse;this.sgrMouse;this.urxvtMouse;this.element;this.children;this.refreshStart;this.refreshEnd;this.savedX;this.savedY;this.savedCols;this.readable=true;this.writable=true;this.defAttr=(0<<18)|(257<<9)|(256<<0);this.curAttr=this.defAttr;this.params=[];this.currentParam=0;this.prefix="";this.postfix="";this.lines=[];var A=this.rows;while(A--){this.lines.push(this.blankLine())}this.tabs;this.setupStops()}l(t,h);t.prototype.eraseAttr=function(){return(this.defAttr&~511)|(this.curAttr&511)};t.tangoColors=["#2e3436","#cc0000","#4e9a06","#c4a000","#3465a4","#75507b","#06989a","#d3d7cf","#555753","#ef2929","#8ae234","#fce94f","#729fcf","#ad7fa8","#34e2e2","#eeeeec"];t.xtermColors=["#000000","#cd0000","#00cd00","#cdcd00","#0000ee","#cd00cd","#00cdcd","#e5e5e5","#7f7f7f","#ff0000","#00ff00","#ffff00","#5c5cff","#ff00ff","#00ffff","#ffffff"];t.colors=(function(){var y=t.tangoColors.slice(),C=[0,95,135,175,215,255],A;A=0;for(;A<216;A++){z(C[(A/36)%6|0],C[(A/6)%6|0],C[A%6])}A=0;for(;A<24;A++){C=8+A*10;z(C,C,C)}function z(F,E,D){y.push("#"+B(F)+B(E)+B(D))}function B(D){D=D.toString(16);return D.length<2?"0"+D:D}return y})();t.colors[256]="#000000";t.colors[257]="#f0f0f0";t._colors=t.colors.slice();t.vcolors=(function(){var A=[],y=t.colors,B=0,z;for(;B<256;B++){z=parseInt(y[B].substring(1),16);A.push([(z>>16)&255,(z>>8)&255,z&255])}return A})();t.defaults={colors:t.colors,convertEol:false,termName:"xterm",geometry:[80,24],cursorBlink:true,visualBell:false,popOnBell:false,scrollback:1000,screenKeys:false,debug:false,useStyle:false};t.options={};e(n(t.defaults),function(y){t[y]=t.defaults[y];t.options[y]=t.defaults[y]});t.focus=null;t.prototype.focus=function(){if(t.focus===this){return}if(t.focus){t.focus.blur()}if(this.sendFocus){this.send("\x1b[I")}this.showCursor();t.focus=this};t.prototype.blur=function(){if(t.focus!==this){return}this.cursorState=0;this.refresh(this.y,this.y);if(this.sendFocus){this.send("\x1b[O")}t.focus=null};t.prototype.initGlobal=function(){var y=this.document;t._boundDocs=t._boundDocs||[];if(~c(t._boundDocs,y)){return}t._boundDocs.push(y);t.bindPaste(y);t.bindKeys(y);t.bindCopy(y);if(this.isIpad||this.isIphone){t.fixIpad(y)}if(this.useStyle){t.insertStyle(y,this.colors[256],this.colors[257])}};t.bindPaste=function(y){var z=y.defaultView;k(z,"paste",function(B){var A=t.focus;if(!A){return}if(B.clipboardData){A.send(B.clipboardData.getData("text/plain"))}else{if(A.context.clipboardData){A.send(A.context.clipboardData.getData("Text"))}}A.element.contentEditable="inherit";return d(B)})};t.bindKeys=function(y){k(y,"keydown",function(z){if(!t.focus){return}var A=z.target||z.srcElement;if(!A){return}if(A===t.focus.element||A===t.focus.context||A===t.focus.document||A===t.focus.body||A===t._textarea||A===t.focus.parent){return t.focus.keyDown(z)}},true);k(y,"keypress",function(z){if(!t.focus){return}var A=z.target||z.srcElement;if(!A){return}if(A===t.focus.element||A===t.focus.context||A===t.focus.document||A===t.focus.body||A===t._textarea||A===t.focus.parent){return t.focus.keyPress(z)}},true);k(y,"mousedown",function(A){if(!t.focus){return}var z=A.target||A.srcElement;if(!z){return}do{if(z===t.focus.element){return}}while(z=z.parentNode);t.focus.blur()})};t.bindCopy=function(y){var z=y.defaultView;k(z,"copy",function(C){var B=t.focus;if(!B){return}if(!B._selected){return}var A=B.getCopyTextarea();var D=B.grabText(B._selected.x1,B._selected.x2,B._selected.y1,B._selected.y2);B.emit("copy",D);A.focus();A.textContent=D;A.value=D;A.setSelectionRange(0,D.length);b(function(){B.element.focus();B.focus()},1)})};t.fixIpad=function(y){var z=y.createElement("textarea");z.style.position="absolute";z.style.left="-32000px";z.style.top="-32000px";z.style.width="0px";z.style.height="0px";z.style.opacity="0";z.style.backgroundColor="transparent";z.style.borderStyle="none";z.style.outlineStyle="none";z.autocapitalize="none";z.autocorrect="off";y.getElementsByTagName("body")[0].appendChild(z);t._textarea=z;b(function(){z.focus()},1000)};t.insertStyle=function(y,B,z){var C=y.getElementById("term-style");if(C){return}var A=y.getElementsByTagName("head")[0];if(!A){return}var C=y.createElement("style");C.id="term-style";C.innerHTML=".terminal {\n float: left;\n border: "+B+' solid 5px;\n font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;\n font-size: 11px;\n color: '+z+";\n background: "+B+";\n}\n\n.terminal-cursor {\n color: "+B+";\n background: "+z+";\n}\n";A.insertBefore(C,A.firstChild)};t.prototype.open=function(A){var y=this,z=0,B;this.parent=A||this.parent;if(!this.parent){throw new Error("Terminal requires a parent element.")}this.context=this.parent.ownerDocument.defaultView;this.document=this.parent.ownerDocument;this.body=this.document.getElementsByTagName("body")[0];if(this.context.navigator&&this.context.navigator.userAgent){this.isMac=!!~this.context.navigator.userAgent.indexOf("Mac");this.isIpad=!!~this.context.navigator.userAgent.indexOf("iPad");this.isIphone=!!~this.context.navigator.userAgent.indexOf("iPhone");this.isMSIE=!!~this.context.navigator.userAgent.indexOf("MSIE")}this.element=this.document.createElement("div");this.element.className="terminal";this.element.style.outline="none";this.element.setAttribute("tabindex",0);this.element.style.backgroundColor=this.colors[256];this.element.style.color=this.colors[257];this.children=[];for(;z<this.rows;z++){B=this.document.createElement("div");this.element.appendChild(B);this.children.push(B)}this.parent.appendChild(this.element);this.refresh(0,this.rows-1);this.initGlobal();this.focus();this.startBlink();k(this.element,"focus",function(){y.focus();if(y.isIpad||y.isIphone){t._textarea.focus()}});k(this.element,"mousedown",function(){y.focus()});k(this.element,"mousedown",function(D){var C=D.button!=null?+D.button:D.which!=null?D.which-1:null;if(y.isMSIE){C=C===1?0:C===4?1:C}if(C!==2){return}y.element.contentEditable="true";b(function(){y.element.contentEditable="inherit"},1)},true);this.bindMouse();if(t.brokenBold==null){t.brokenBold=v(this.document)}b(function(){y.element.focus()},100)};t.prototype.bindMouse=function(){var z=this.element,H=this,B=32;var C="onmousewheel" in this.context?"mousewheel":"DOMMouseScroll";function F(J){var I,K;I=y(J);K=A(J);if(!K){return}G(I,K);switch(J.type){case"mousedown":B=I;break;case"mouseup":B=32;break;case C:break}}function E(J){var I=B,K;K=A(J);if(!K){return}I+=32;G(I,K)}function D(J,I){if(!H.utfMouse){if(I===255){return J.push(0)}if(I>127){I=127}J.push(I)}else{if(I===2047){return J.push(0)}if(I<127){J.push(I)}else{if(I>2047){I=2047}J.push(192|(I>>6));J.push(128|(I&63))}}}function G(I,K){if(H.vt300Mouse){I&=3;K.x-=32;K.y-=32;var J="\x1b[24";if(I===0){J+="1"}else{if(I===1){J+="3"}else{if(I===2){J+="5"}else{if(I===3){return}else{J+="0"}}}}J+="~["+K.x+","+K.y+"]\r";H.send(J);return}if(H.decLocator){I&=3;K.x-=32;K.y-=32;if(I===0){I=2}else{if(I===1){I=4}else{if(I===2){I=6}else{if(I===3){I=3}}}}H.send("\x1b["+I+";"+(I===3?4:0)+";"+K.y+";"+K.x+";"+(K.page||0)+"&w");return}if(H.urxvtMouse){K.x-=32;K.y-=32;K.x++;K.y++;H.send("\x1b["+I+";"+K.x+";"+K.y+"M");return}if(H.sgrMouse){K.x-=32;K.y-=32;H.send("\x1b[<"+((I&3)===3?I&~3:I)+";"+K.x+";"+K.y+((I&3)===3?"m":"M"));return}var J=[];D(J,I);D(J,K.x);D(J,K.y);H.send("\x1b[M"+u.fromCharCode.apply(u,J))}function y(L){var K,I,N,M,J;switch(L.type){case"mousedown":K=L.button!=null?+L.button:L.which!=null?L.which-1:null;if(H.isMSIE){K=K===1?0:K===4?1:K}break;case"mouseup":K=3;break;case"DOMMouseScroll":K=L.detail<0?64:65;break;case"mousewheel":K=L.wheelDeltaY>0?64:65;break}I=L.shiftKey?4:0;N=L.metaKey?8:0;M=L.ctrlKey?16:0;J=I|N|M;if(H.vt200Mouse){J&=M}else{if(!H.normalMouse){J=0}}K=(32+(J<<2))+K;return K}function A(M){var I,N,J,L,K;if(M.pageX==null){return}I=M.pageX;N=M.pageY;K=H.element;while(K&&K!==H.document.documentElement){I-=K.offsetLeft;N-=K.offsetTop;K="offsetParent" in K?K.offsetParent:K.parentNode}J=H.element.clientWidth;L=H.element.clientHeight;I=Math.round((I/J)*H.cols);N=Math.round((N/L)*H.rows);if(I<0){I=0}if(I>H.cols){I=H.cols}if(N<0){N=0}if(N>H.rows){N=H.rows}I+=32;N+=32;return{x:I,y:N,type:M.type===C?"mousewheel":M.type}}k(z,"mousedown",function(J){if(!H.mouseEvents){return}F(J);H.focus();if(H.vt200Mouse){F({__proto__:J,type:"mouseup"});return d(J)}if(H.normalMouse){k(H.document,"mousemove",E)}if(!H.x10Mouse){k(H.document,"mouseup",function I(K){F(K);if(H.normalMouse){w(H.document,"mousemove",E)}w(H.document,"mouseup",I);return d(K)})}return d(J)});k(z,C,function(I){if(!H.mouseEvents){return}if(H.x10Mouse||H.vt300Mouse||H.decLocator){return}F(I);return d(I)});k(z,C,function(I){if(H.mouseEvents){return}if(H.applicationKeypad){return}if(I.type==="DOMMouseScroll"){H.scrollDisp(I.detail<0?-5:5)}else{H.scrollDisp(I.wheelDeltaY>0?-5:5)}return d(I)})};t.prototype.destroy=function(){this.readable=false;this.writable=false;this._events={};this.handler=function(){};this.write=function(){};if(this.element.parentNode){this.element.parentNode.removeChild(this.element)}};t.prototype.refresh=function(C,E){var L,K,I,O,F,A,B,H,J,G,z,D,N,M;if(E-C>=this.rows/2){M=this.element.parentNode;if(M){M.removeChild(this.element)}}B=this.cols;K=C;if(E>=this.lines.length){this.log("`end` is too large. Most likely a bad CSR.");E=this.lines.length-1}for(;K<=E;K++){N=K+this.ydisp;O=this.lines[N];F="";if(K===this.y&&this.cursorState&&(this.ydisp===this.ybase||this.selectMode)&&!this.cursorHidden){L=this.x}else{L=-1}J=this.defAttr;I=0;for(;I<B;I++){H=O[I][0];A=O[I][1];if(I===L){H=-1}if(H!==J){if(J!==this.defAttr){F+="</span>"}if(H!==this.defAttr){if(H===-1){F+='<span class="reverse-video terminal-cursor">'}else{F+='<span style="';G=H&511;z=(H>>9)&511;D=H>>18;if(D&1){if(!t.brokenBold){F+="font-weight:bold;"}if(z<8){z+=8}}if(D&2){F+="text-decoration:underline;"}if(D&4){if(D&2){F=F.slice(0,-1);F+=" blink;"}else{F+="text-decoration:blink;"}}if(D&8){G=(H>>9)&511;z=H&511;if((D&1)&&z<8){z+=8}}if(D&16){F+="visibility:hidden;"}if(G!==256){F+="background-color:"+this.colors[G]+";"}if(z!==257){F+="color:"+this.colors[z]+";"}F+='">'}}}switch(A){case"&":F+="&amp;";break;case"<":F+="&lt;";break;case">":F+="&gt;";break;default:if(A<=" "){F+="&nbsp;"}else{if(r(A)){I++}F+=A}break}J=H}if(J!==this.defAttr){F+="</span>"}this.children[K].innerHTML=F}if(M){M.appendChild(this.element)}};t.prototype._cursorBlink=function(){if(t.focus!==this){return}this.cursorState^=1;this.refresh(this.y,this.y)};t.prototype.showCursor=function(){if(!this.cursorState){this.cursorState=1;this.refresh(this.y,this.y)}else{}};t.prototype.startBlink=function(){if(!this.cursorBlink){return}var y=this;this._blinker=function(){y._cursorBlink()};this._blink=a(this._blinker,500)};t.prototype.refreshBlink=function(){if(!this.cursorBlink){return}clearInterval(this._blink);this._blink=a(this._blinker,500)};t.prototype.scroll=function(){var y;if(++this.ybase===this.scrollback){this.ybase=this.ybase/2|0;this.lines=this.lines.slice(-(this.ybase+this.rows)+1)}this.ydisp=this.ybase;y=this.ybase+this.rows-1;y-=this.rows-1-this.scrollBottom;if(y===this.lines.length){this.lines.push(this.blankLine())}else{this.lines.splice(y,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.updateRange(this.scrollTop);this.updateRange(this.scrollBottom)};t.prototype.scrollDisp=function(y){this.ydisp+=y;if(this.ydisp>this.ybase){this.ydisp=this.ybase}else{if(this.ydisp<0){this.ydisp=0}}this.refresh(0,this.rows-1)};t.prototype.write=function(E){var y=E.length,A=0,z,C,B;this.refreshStart=this.y;this.refreshEnd=this.y;if(this.ybase!==this.ydisp){this.ydisp=this.ybase;this.maxRange()}for(;A<y;A++){B=E[A];switch(this.state){case x:switch(B){case"\x07":this.bell();break;case"\n":case"\x0b":case"\x0c":if(this.convertEol){this.x=0}this.y++;if(this.y>this.scrollBottom){this.y--;this.scroll()}break;case"\r":this.x=0;break;case"\x08":if(this.x>0){this.x--}break;case"\t":this.x=this.nextStop();break;case"\x0e":this.setgLevel(1);break;case"\x0f":this.setgLevel(0);break;case"\x1b":this.state=g;break;default:if(B>=" "){if(this.charset&&this.charset[B]){B=this.charset[B]}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,B];this.x++;this.updateRange(this.y);if(r(B)){z=this.y+this.ybase;if(this.cols<2||this.x>=this.cols){this.lines[z][this.x-1]=[this.curAttr," "];break}this.lines[z][this.x]=[this.curAttr," "];this.x++}}break}break;case g:switch(B){case"[":this.params=[];this.currentParam=0;this.state=q;break;case"]":this.params=[];this.currentParam=0;this.state=j;break;case"P":this.params=[];this.currentParam=0;this.state=o;break;case"_":this.state=s;break;case"^":this.state=s;break;case"c":this.reset();break;case"E":this.x=0;case"D":this.index();break;case"M":this.reverseIndex();break;case"%":this.setgLevel(0);this.setgCharset(0,t.charsets.US);this.state=x;A++;break;case"(":case")":case"*":case"+":case"-":case".":switch(B){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=f;break;case"/":this.gcharset=3;this.state=f;A--;break;case"N":break;case"O":break;case"n":this.setgLevel(2);break;case"o":this.setgLevel(3);break;case"|":this.setgLevel(3);break;case"}":this.setgLevel(2);break;case"~":this.setgLevel(1);break;case"7":this.saveCursor();this.state=x;break;case"8":this.restoreCursor();this.state=x;break;case"#":this.state=x;A++;break;case"H":this.tabSet();break;case"=":this.log("Serial port requested application keypad.");this.applicationKeypad=true;this.state=x;break;case">":this.log("Switching back to normal keypad.");this.applicationKeypad=false;this.state=x;break;default:this.state=x;this.error("Unknown ESC control: %s.",B);break}break;case f:switch(B){case"0":C=t.charsets.SCLD;break;case"A":C=t.charsets.UK;break;case"B":C=t.charsets.US;break;case"4":C=t.charsets.Dutch;break;case"C":case"5":C=t.charsets.Finnish;break;case"R":C=t.charsets.French;break;case"Q":C=t.charsets.FrenchCanadian;break;case"K":C=t.charsets.German;break;case"Y":C=t.charsets.Italian;break;case"E":case"6":C=t.charsets.NorwegianDanish;break;case"Z":C=t.charsets.Spanish;break;case"H":case"7":C=t.charsets.Swedish;break;case"=":C=t.charsets.Swiss;break;case"/":C=t.charsets.ISOLatin;A++;break;default:C=t.charsets.US;break}this.setgCharset(this.gcharset,C);this.gcharset=null;this.state=x;break;case j:if(B==="\x1b"||B==="\x07"){if(B==="\x1b"){A++}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:break;case 4:case 5:break;case 10:case 11:case 12:case 13:case 14:case 15:case 16:case 17:case 18:case 19:break;case 46:break;case 50:break;case 51:break;case 52:break;case 104:case 105:case 110:case 111:case 112:case 113:case 114:case 115:case 116:case 117:case 118:break}this.params=[];this.currentParam=0;this.state=x}else{if(!this.params.length){if(B>="0"&&B<="9"){this.currentParam=this.currentParam*10+B.charCodeAt(0)-48}else{if(B===";"){this.params.push(this.currentParam);this.currentParam=""}}}else{this.currentParam+=B}}break;case q:if(B==="?"||B===">"||B==="!"){this.prefix=B;break}if(B>="0"&&B<="9"){this.currentParam=this.currentParam*10+B.charCodeAt(0)-48;break}if(B==="$"||B==='"'||B===" "||B==="'"){this.postfix=B;break}this.params.push(this.currentParam);this.currentParam=0;if(B===";"){break}this.state=x;switch(B){case"A":this.cursorUp(this.params);break;case"B":this.cursorDown(this.params);break;case"C":this.cursorForward(this.params);break;case"D":this.cursorBackward(this.params);break;case"H":this.cursorPos(this.params);break;case"J":this.eraseInDisplay(this.params);break;case"K":this.eraseInLine(this.params);break;case"m":if(!this.prefix){this.charAttributes(this.params)}break;case"n":if(!this.prefix){this.deviceStatus(this.params)}break;case"@":this.insertChars(this.params);break;case"E":this.cursorNextLine(this.params);break;case"F":this.cursorPrecedingLine(this.params);break;case"G":this.cursorCharAbsolute(this.params);break;case"L":this.insertLines(this.params);break;case"M":this.deleteLines(this.params);break;case"P":this.deleteChars(this.params);break;case"X":this.eraseChars(this.params);break;case"`":this.charPosAbsolute(this.params);break;case"a":this.HPositionRelative(this.params);break;case"c":this.sendDeviceAttributes(this.params);break;case"d":this.linePosAbsolute(this.params);break;case"e":this.VPositionRelative(this.params);break;case"f":this.HVPosition(this.params);break;case"h":this.setMode(this.params);break;case"l":this.resetMode(this.params);break;case"r":this.setScrollRegion(this.params);break;case"s":this.saveCursor(this.params);break;case"u":this.restoreCursor(this.params);break;case"I":this.cursorForwardTab(this.params);break;case"S":this.scrollUp(this.params);break;case"T":if(this.params.length<2&&!this.prefix){this.scrollDown(this.params)}break;case"Z":this.cursorBackwardTab(this.params);break;case"b":this.repeatPrecedingCharacter(this.params);break;case"g":this.tabClear(this.params);break;case"p":switch(this.prefix){case"!":this.softReset(this.params);break}break;default:this.error("Unknown CSI code: %s.",B);break}this.prefix="";this.postfix="";break;case o:if(B==="\x1b"||B==="\x07"){if(B==="\x1b"){A++}switch(this.prefix){case"":break;case"$q":var F=this.currentParam,D=false;switch(F){case'"q':F='0"q';break;case'"p':F='61"p';break;case"r":F=""+(this.scrollTop+1)+";"+(this.scrollBottom+1)+"r";break;case"m":F="0m";break;default:this.error("Unknown DCS Pt: %s.",F);F="";break}this.send("\x1bP"+ +D+"$r"+F+"\x1b\\");break;case"+p":break;case"+q":var F=this.currentParam,D=false;this.send("\x1bP"+ +D+"+r"+F+"\x1b\\");break;default:this.error("Unknown DCS prefix: %s.",this.prefix);break}this.currentParam=0;this.prefix="";this.state=x}else{if(!this.currentParam){if(!this.prefix&&B!=="$"&&B!=="+"){this.currentParam=B}else{if(this.prefix.length===2){this.currentParam=B}else{this.prefix+=B}}}else{this.currentParam+=B}}break;case s:if(B==="\x1b"||B==="\x07"){if(B==="\x1b"){A++}this.state=x}break}}this.updateRange(this.y);this.refresh(this.refreshStart,this.refreshEnd)};t.prototype.writeln=function(y){this.write(y+"\r\n")};t.prototype.keyDown=function(A){var y=this,z;switch(A.keyCode){case 8:if(A.shiftKey){z="\x08";break}z="\x7f";break;case 9:if(A.shiftKey){z="\x1b[Z";break}z="\t";break;case 13:z="\r";break;case 27:z="\x1b";break;case 37:if(this.applicationCursor){z="\x1bOD";break}z="\x1b[D";break;case 39:if(this.applicationCursor){z="\x1bOC";break}z="\x1b[C";break;case 38:if(this.applicationCursor){z="\x1bOA";break}if(A.ctrlKey){this.scrollDisp(-1);return d(A)}else{z="\x1b[A"}break;case 40:if(this.applicationCursor){z="\x1bOB";break}if(A.ctrlKey){this.scrollDisp(1);return d(A)}else{z="\x1b[B"}break;case 46:z="\x1b[3~";break;case 45:z="\x1b[2~";break;case 36:if(this.applicationKeypad){z="\x1bOH";break}z="\x1bOH";break;case 35:if(this.applicationKeypad){z="\x1bOF";break}z="\x1bOF";break;case 33:if(A.shiftKey){this.scrollDisp(-(this.rows-1));return d(A)}else{z="\x1b[5~"}break;case 34:if(A.shiftKey){this.scrollDisp(this.rows-1);return d(A)}else{z="\x1b[6~"}break;case 112:z="\x1bOP";break;case 113:z="\x1bOQ";break;case 114:z="\x1bOR";break;case 115:z="\x1bOS";break;case 116:z="\x1b[15~";break;case 117:z="\x1b[17~";break;case 118:z="\x1b[18~";break;case 119:z="\x1b[19~";break;case 120:z="\x1b[20~";break;case 121:z="\x1b[21~";break;case 122:z="\x1b[23~";break;case 123:z="\x1b[24~";break;default:if(A.ctrlKey){if(A.keyCode>=65&&A.keyCode<=90){if(this.screenKeys){if(!this.prefixMode&&!this.selectMode&&A.keyCode===65){this.enterPrefix();return d(A)}}if(this.prefixMode&&A.keyCode===86){this.leavePrefix();return}if((this.prefixMode||this.selectMode)&&A.keyCode===67){if(this.visualMode){b(function(){y.leaveVisual()},1)}return}z=u.fromCharCode(A.keyCode-64)}else{if(A.keyCode===32){z=u.fromCharCode(0)}else{if(A.keyCode>=51&&A.keyCode<=55){z=u.fromCharCode(A.keyCode-51+27)}else{if(A.keyCode===56){z=u.fromCharCode(127)}else{if(A.keyCode===219){z=u.fromCharCode(27)}else{if(A.keyCode===221){z=u.fromCharCode(29)}}}}}}}else{if((!this.isMac&&A.altKey)||(this.isMac&&A.metaKey)){if(A.keyCode>=65&&A.keyCode<=90){z="\x1b"+u.fromCharCode(A.keyCode+32)}else{if(A.keyCode===192){z="\x1b`"}else{if(A.keyCode>=48&&A.keyCode<=57){z="\x1b"+(A.keyCode-48)}}}}}break}if(!z){return true}if(this.prefixMode){this.leavePrefix();return d(A)}if(this.selectMode){this.keySelect(A,z);return d(A)}this.emit("keydown",A);this.emit("key",z,A);this.showCursor();this.handler(z);return d(A)};t.prototype.setgLevel=function(y){this.glevel=y;this.charset=this.charsets[y]};t.prototype.setgCharset=function(y,z){this.charsets[y]=z;if(this.glevel===y){this.charset=z}};t.prototype.keyPress=function(z){var y;d(z);if(z.charCode){y=z.charCode}else{if(z.which==null){y=z.keyCode}else{if(z.which!==0&&z.charCode!==0){y=z.which}else{return false}}}if(!y||z.ctrlKey||z.altKey||z.metaKey){return false}y=u.fromCharCode(y);if(this.prefixMode){this.leavePrefix();this.keyPrefix(z,y);return false}if(this.selectMode){this.keySelect(z,y);return false}this.emit("keypress",y,z);this.emit("key",y,z);this.showCursor();this.handler(y);return false};t.prototype.send=function(z){var y=this;if(!this.queue){b(function(){y.handler(y.queue);y.queue=""},1)}this.queue+=z};t.prototype.bell=function(){if(!this.visualBell){return}var y=this;this.element.style.borderColor="white";b(function(){y.element.style.borderColor=""},10);if(this.popOnBell){this.focus()}};t.prototype.log=function(){if(!this.debug){return}if(!this.context.console||!this.context.console.log){return}var y=Array.prototype.slice.call(arguments);this.context.console.log.apply(this.context.console,y)};t.prototype.error=function(){if(!this.debug){return}if(!this.context.console||!this.context.console.error){return}var y=Array.prototype.slice.call(arguments);this.context.console.error.apply(this.context.console,y)};t.prototype.resize=function(z,F){var A,E,C,B,D;if(z<1){z=1}if(F<1){F=1}B=this.cols;if(B<z){D=[this.defAttr," "];C=this.lines.length;while(C--){while(this.lines[C].length<z){this.lines[C].push(D)}}}else{if(B>z){C=this.lines.length;while(C--){while(this.lines[C].length>z){this.lines[C].pop()}}}}this.setupStops(B);this.cols=z;B=this.rows;if(B<F){E=this.element;while(B++<F){if(this.lines.length<F+this.ybase){this.lines.push(this.blankLine())}if(this.children.length<F){A=this.document.createElement("div");E.appendChild(A);this.children.push(A)}}}else{if(B>F){while(B-->F){if(this.lines.length>F+this.ybase){this.lines.pop()}if(this.children.length>F){E=this.children.pop();if(!E){continue}E.parentNode.removeChild(E)}}}}this.rows=F;if(this.y>=F){this.y=F-1}if(this.x>=z){this.x=z-1}this.scrollTop=0;this.scrollBottom=F-1;this.refresh(0,this.rows-1);this.normal=null};t.prototype.updateRange=function(z){if(z<this.refreshStart){this.refreshStart=z}if(z>this.refreshEnd){this.refreshEnd=z}};t.prototype.maxRange=function(){this.refreshStart=0;this.refreshEnd=this.rows-1};t.prototype.setupStops=function(y){if(y!=null){if(!this.tabs[y]){y=this.prevStop(y)}}else{this.tabs={};y=0}for(;y<this.cols;y+=8){this.tabs[y]=true}};t.prototype.prevStop=function(y){if(y==null){y=this.x}while(!this.tabs[--y]&&y>0){}return y>=this.cols?this.cols-1:y<0?0:y};t.prototype.nextStop=function(y){if(y==null){y=this.x}while(!this.tabs[++y]&&y<this.cols){}return y>=this.cols?this.cols-1:y<0?0:y};t.prototype.eraseRight=function(z,C){var A=this.lines[this.ybase+C],B=[this.eraseAttr()," "];for(;z<this.cols;z++){A[z]=B}this.updateRange(C)};t.prototype.eraseLeft=function(z,C){var A=this.lines[this.ybase+C],B=[this.eraseAttr()," "];z++;while(z--){A[z]=B}this.updateRange(C)};t.prototype.eraseLine=function(z){this.eraseRight(0,z)};t.prototype.blankLine=function(C){var y=C?this.eraseAttr():this.defAttr;var B=[y," "],z=[],A=0;for(;A<this.cols;A++){z[A]=B}return z};t.prototype.ch=function(y){return y?[this.eraseAttr()," "]:[this.defAttr," "]};t.prototype.is=function(z){var y=this.termName;return(y+"").indexOf(z)===0};t.prototype.handler=function(y){this.emit("data",y)};t.prototype.handleTitle=function(y){this.emit("title",y)};t.prototype.index=function(){this.y++;if(this.y>this.scrollBottom){this.y--;this.scroll()}this.state=x};t.prototype.reverseIndex=function(){var y;this.y--;if(this.y<this.scrollTop){this.y++;this.lines.splice(this.y+this.ybase,0,this.blankLine(true));y=this.rows-1-this.scrollBottom;this.lines.splice(this.rows-1+this.ybase-y+1,1);this.updateRange(this.scrollTop);this.updateRange(this.scrollBottom)}this.state=x};t.prototype.reset=function(){this.options.rows=this.rows;this.options.cols=this.cols;t.call(this,this.options);this.refresh(0,this.rows-1)};t.prototype.tabSet=function(){this.tabs[this.x]=true;this.state=x};t.prototype.cursorUp=function(z){var y=z[0];if(y<1){y=1}this.y-=y;if(this.y<0){this.y=0}};t.prototype.cursorDown=function(z){var y=z[0];if(y<1){y=1}this.y+=y;if(this.y>=this.rows){this.y=this.rows-1}};t.prototype.cursorForward=function(z){var y=z[0];if(y<1){y=1}this.x+=y;if(this.x>=this.cols){this.x=this.cols-1}};t.prototype.cursorBackward=function(z){var y=z[0];if(y<1){y=1}this.x-=y;if(this.x<0){this.x=0}};t.prototype.cursorPos=function(A){var z,y;z=A[0]-1;if(A.length>=2){y=A[1]-1}else{y=0}if(z<0){z=0}else{if(z>=this.rows){z=this.rows-1}}if(y<0){y=0}else{if(y>=this.cols){y=this.cols-1}}this.x=y;this.y=z};t.prototype.eraseInDisplay=function(z){var y;switch(z[0]){case 0:this.eraseRight(this.x,this.y);y=this.y+1;for(;y<this.rows;y++){this.eraseLine(y)}break;case 1:this.eraseLeft(this.x,this.y);y=this.y;while(y--){this.eraseLine(y)}break;case 2:y=this.rows;while(y--){this.eraseLine(y)}break;case 3:break}};t.prototype.eraseInLine=function(y){switch(y[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}};t.prototype.charAttributes=function(E){if(E.length===1&&E[0]===0){this.curAttr=this.defAttr;return}var A=E.length,C=0,z=this.curAttr>>18,y=(this.curAttr>>9)&511,B=this.curAttr&511,D;for(;C<A;C++){D=E[C];if(D>=30&&D<=37){y=D-30}else{if(D>=40&&D<=47){B=D-40}else{if(D>=90&&D<=97){D+=8;y=D-90}else{if(D>=100&&D<=107){D+=8;B=D-100}else{if(D===0){z=this.defAttr>>18;y=(this.defAttr>>9)&511;B=this.defAttr&511}else{if(D===1){z|=1}else{if(D===4){z|=2}else{if(D===5){z|=4}else{if(D===7){z|=8}else{if(D===8){z|=16}else{if(D===22){z&=~1}else{if(D===24){z&=~2}else{if(D===25){z&=~4}else{if(D===27){z&=~8}else{if(D===28){z&=~16}else{if(D===39){y=(this.defAttr>>9)&511}else{if(D===49){B=this.defAttr&511}else{if(D===38){if(E[C+1]===2){C+=2;y=m(E[C]&255,E[C+1]&255,E[C+2]&255);if(y===-1){y=511}C+=2}else{if(E[C+1]===5){C+=2;D=E[C]&255;y=D}}}else{if(D===48){if(E[C+1]===2){C+=2;B=m(E[C]&255,E[C+1]&255,E[C+2]&255);if(B===-1){B=511}C+=2}else{if(E[C+1]===5){C+=2;D=E[C]&255;B=D}}}else{if(D===100){y=(this.defAttr>>9)&511;B=this.defAttr&511}else{this.error("Unknown SGR attribute: %d.",D)}}}}}}}}}}}}}}}}}}}}}this.curAttr=(z<<18)|(y<<9)|B};t.prototype.deviceStatus=function(y){if(!this.prefix){switch(y[0]){case 5:this.send("\x1b[0n");break;case 6:this.send("\x1b["+(this.y+1)+";"+(this.x+1)+"R");break}}else{if(this.prefix==="?"){switch(y[0]){case 6:this.send("\x1b[?"+(this.y+1)+";"+(this.x+1)+"R");break;case 15:break;case 25:break;case 26:break;case 53:break}}}};t.prototype.insertChars=function(C){var B,A,y,z;B=C[0];if(B<1){B=1}A=this.y+this.ybase;y=this.x;z=[this.eraseAttr()," "];while(B--&&y<this.cols){this.lines[A].splice(y++,0,z);this.lines[A].pop()}};t.prototype.cursorNextLine=function(z){var y=z[0];if(y<1){y=1}this.y+=y;if(this.y>=this.rows){this.y=this.rows-1}this.x=0};t.prototype.cursorPrecedingLine=function(z){var y=z[0];if(y<1){y=1}this.y-=y;if(this.y<0){this.y=0}this.x=0};t.prototype.cursorCharAbsolute=function(z){var y=z[0];if(y<1){y=1}this.x=y-1};t.prototype.insertLines=function(B){var A,z,y;A=B[0];if(A<1){A=1}z=this.y+this.ybase;y=this.rows-1-this.scrollBottom;y=this.rows-1+this.ybase-y+1;while(A--){this.lines.splice(z,0,this.blankLine(true));this.lines.splice(y,1)}this.updateRange(this.y);this.updateRange(this.scrollBottom)};t.prototype.deleteLines=function(B){var A,z,y;A=B[0];if(A<1){A=1}z=this.y+this.ybase;y=this.rows-1-this.scrollBottom;y=this.rows-1+this.ybase-y;while(A--){this.lines.splice(y+1,0,this.blankLine(true));this.lines.splice(z,1)}this.updateRange(this.y);this.updateRange(this.scrollBottom)};t.prototype.deleteChars=function(B){var A,z,y;A=B[0];if(A<1){A=1}z=this.y+this.ybase;y=[this.eraseAttr()," "];while(A--){this.lines[z].splice(this.x,1);this.lines[z].push(y)}};t.prototype.eraseChars=function(C){var B,A,y,z;B=C[0];if(B<1){B=1}A=this.y+this.ybase;y=this.x;z=[this.eraseAttr()," "];while(B--&&y<this.cols){this.lines[A][y++]=z}};t.prototype.charPosAbsolute=function(z){var y=z[0];if(y<1){y=1}this.x=y-1;if(this.x>=this.cols){this.x=this.cols-1}};t.prototype.HPositionRelative=function(z){var y=z[0];if(y<1){y=1}this.x+=y;if(this.x>=this.cols){this.x=this.cols-1}};t.prototype.sendDeviceAttributes=function(y){if(y[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===">"){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")){this.send(y[0]+"c")}else{if(this.is("screen")){this.send("\x1b[>83;40003;0c")}}}}}}};t.prototype.linePosAbsolute=function(z){var y=z[0];if(y<1){y=1}this.y=y-1;if(this.y>=this.rows){this.y=this.rows-1}};t.prototype.VPositionRelative=function(z){var y=z[0];if(y<1){y=1}this.y+=y;if(this.y>=this.rows){this.y=this.rows-1}};t.prototype.HVPosition=function(y){if(y[0]<1){y[0]=1}if(y[1]<1){y[1]=1}this.y=y[0]-1;if(this.y>=this.rows){this.y=this.rows-1}this.x=y[1]-1;if(this.x>=this.cols){this.x=this.cols-1}};t.prototype.setMode=function(B){if(typeof B==="object"){var y=B.length,z=0;for(;z<y;z++){this.setMode(B[z])}return}if(!this.prefix){switch(B){case 4:this.insertMode=true;break;case 20:break}}else{if(this.prefix==="?"){switch(B){case 1:this.applicationCursor=true;break;case 2:this.setgCharset(0,t.charsets.US);this.setgCharset(1,t.charsets.US);this.setgCharset(2,t.charsets.US);this.setgCharset(3,t.charsets.US);break;case 3:this.savedCols=this.cols;this.resize(132,this.rows);break;case 6:this.originMode=true;break;case 7:this.wraparoundMode=true;break;case 12:break;case 66:this.log("Serial port requested application keypad.");this.applicationKeypad=true;break;case 9:case 1000:case 1002:case 1003:this.x10Mouse=B===9;this.vt200Mouse=B===1000;this.normalMouse=B>1000;this.mouseEvents=true;this.element.style.cursor="default";this.log("Binding to mouse events.");break;case 1004:this.sendFocus=true;break;case 1005:this.utfMouse=true;break;case 1006:this.sgrMouse=true;break;case 1015:this.urxvtMouse=true;break;case 25:this.cursorHidden=false;break;case 1049:case 47:case 1047:if(!this.normal){var A={lines:this.lines,ybase:this.ybase,ydisp:this.ydisp,x:this.x,y:this.y,scrollTop:this.scrollTop,scrollBottom:this.scrollBottom,tabs:this.tabs};this.reset();this.normal=A;this.showCursor()}break}}}};t.prototype.resetMode=function(A){if(typeof A==="object"){var y=A.length,z=0;for(;z<y;z++){this.resetMode(A[z])}return}if(!this.prefix){switch(A){case 4:this.insertMode=false;break;case 20:break}}else{if(this.prefix==="?"){switch(A){case 1:this.applicationCursor=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:break;case 66:this.log("Switching back to normal keypad.");this.applicationKeypad=false;break;case 9:case 1000:case 1002:case 1003:this.x10Mouse=false;this.vt200Mouse=false;this.normalMouse=false;this.mouseEvents=false;this.element.style.cursor="";break;case 1004:this.sendFocus=false;break;case 1005:this.utfMouse=false;break;case 1006:this.sgrMouse=false;break;case 1015:this.urxvtMouse=false;break;case 25:this.cursorHidden=true;break;case 1049:case 47:case 1047: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;this.refresh(0,this.rows-1);this.showCursor()}break}}}};t.prototype.setScrollRegion=function(y){if(this.prefix){return}this.scrollTop=(y[0]||1)-1;this.scrollBottom=(y[1]||this.rows)-1;this.x=0;this.y=0};t.prototype.saveCursor=function(y){this.savedX=this.x;this.savedY=this.y};t.prototype.restoreCursor=function(y){this.x=this.savedX||0;this.y=this.savedY||0};t.prototype.cursorForwardTab=function(z){var y=z[0]||1;while(y--){this.x=this.nextStop()}};t.prototype.scrollUp=function(z){var y=z[0]||1;while(y--){this.lines.splice(this.ybase+this.scrollTop,1);this.lines.splice(this.ybase+this.scrollBottom,0,this.blankLine())}this.updateRange(this.scrollTop);this.updateRange(this.scrollBottom)};t.prototype.scrollDown=function(z){var y=z[0]||1;while(y--){this.lines.splice(this.ybase+this.scrollBottom,1);this.lines.splice(this.ybase+this.scrollTop,0,this.blankLine())}this.updateRange(this.scrollTop);this.updateRange(this.scrollBottom)};t.prototype.initMouseTracking=function(y){};t.prototype.resetTitleModes=function(y){};t.prototype.cursorBackwardTab=function(z){var y=z[0]||1;while(y--){this.x=this.prevStop()}};t.prototype.repeatPrecedingCharacter=function(B){var A=B[0]||1,y=this.lines[this.ybase+this.y],z=y[this.x-1]||[this.defAttr," "];while(A--){y[this.x++]=z}};t.prototype.tabClear=function(z){var y=z[0];if(y<=0){delete this.tabs[this.x]}else{if(y===3){this.tabs={}}}};t.prototype.mediaCopy=function(y){};t.prototype.setResources=function(y){};t.prototype.disableModifiers=function(y){};t.prototype.setPointerMode=function(y){};t.prototype.softReset=function(y){this.cursorHidden=false;this.insertMode=false;this.originMode=false;this.wraparoundMode=false;this.applicationKeypad=false;this.applicationCursor=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]};t.prototype.requestAnsiMode=function(y){};t.prototype.requestPrivateMode=function(y){};t.prototype.setConformanceLevel=function(y){};t.prototype.loadLEDs=function(y){};t.prototype.setCursorStyle=function(y){};t.prototype.setCharProtectionAttr=function(y){};t.prototype.restorePrivateValues=function(y){};t.prototype.setAttrInRectangle=function(F){var D=F[0],B=F[1],z=F[2],E=F[3],y=F[4];var A,C;for(;D<z+1;D++){A=this.lines[this.ybase+D];for(C=B;C<E;C++){A[C]=[y,A[C][1]]}}this.updateRange(F[0]);this.updateRange(F[2])};t.prototype.savePrivateValues=function(y){};t.prototype.manipulateWindow=function(y){};t.prototype.reverseAttrInRectangle=function(y){};t.prototype.setTitleModeFeature=function(y){};t.prototype.setWarningBellVolume=function(y){};t.prototype.setMarginBellVolume=function(y){};t.prototype.copyRectangle=function(y){};t.prototype.enableFilterRectangle=function(y){};t.prototype.requestParameters=function(y){};t.prototype.selectChangeExtent=function(y){};t.prototype.fillRectangle=function(F){var D=F[0],C=F[1],A=F[2],y=F[3],E=F[4];var z,B;for(;C<y+1;C++){z=this.lines[this.ybase+C];for(B=A;B<E;B++){z[B]=[z[B][0],u.fromCharCode(D)]}}this.updateRange(F[1]);this.updateRange(F[3])};t.prototype.enableLocatorReporting=function(z){var y=z[0]>0};t.prototype.eraseRectangle=function(F){var C=F[0],A=F[1],y=F[2],E=F[3];var z,B,D;D=[this.eraseAttr()," "];for(;C<y+1;C++){z=this.lines[this.ybase+C];for(B=A;B<E;B++){z[B]=D}}this.updateRange(F[0]);this.updateRange(F[2])};t.prototype.setLocatorEvents=function(y){};t.prototype.selectiveEraseRectangle=function(y){};t.prototype.requestLocatorPosition=function(y){};t.prototype.insertColumns=function(){var B=params[0],y=this.ybase+this.rows,A=[this.eraseAttr()," "],z;while(B--){for(z=this.ybase;z<y;z++){this.lines[z].splice(this.x+1,0,A);this.lines[z].pop()}}this.maxRange()};t.prototype.deleteColumns=function(){var B=params[0],y=this.ybase+this.rows,A=[this.eraseAttr()," "],z;while(B--){for(z=this.ybase;z<y;z++){this.lines[z].splice(this.x,1);this.lines[z].push(A)}}this.maxRange()};t.prototype.enterPrefix=function(){this.prefixMode=true};t.prototype.leavePrefix=function(){this.prefixMode=false};t.prototype.enterSelect=function(){this._real={x:this.x,y:this.y,ydisp:this.ydisp,ybase:this.ybase,cursorHidden:this.cursorHidden,lines:this.copyBuffer(this.lines),write:this.write};this.write=function(){};this.selectMode=true;this.visualMode=false;this.cursorHidden=false;this.refresh(this.y,this.y)};t.prototype.leaveSelect=function(){this.x=this._real.x;this.y=this._real.y;this.ydisp=this._real.ydisp;this.ybase=this._real.ybase;this.cursorHidden=this._real.cursorHidden;this.lines=this._real.lines;this.write=this._real.write;delete this._real;this.selectMode=false;this.visualMode=false;this.refresh(0,this.rows-1)};t.prototype.enterVisual=function(){this._real.preVisual=this.copyBuffer(this.lines);this.selectText(this.x,this.x,this.ydisp+this.y,this.ydisp+this.y);this.visualMode=true};t.prototype.leaveVisual=function(){this.lines=this._real.preVisual;delete this._real.preVisual;delete this._selected;this.visualMode=false;this.refresh(0,this.rows-1)};t.prototype.enterSearch=function(A){this.entry="";this.searchMode=true;this.searchDown=A;this._real.preSearch=this.copyBuffer(this.lines);this._real.preSearchX=this.x;this._real.preSearchY=this.y;var y=this.ydisp+this.rows-1;for(var z=0;z<this.entryPrefix.length;z++){this.lines[y][z]=[(this.defAttr&~511)|4,this.entryPrefix[z]]}this.y=this.rows-1;this.x=this.entryPrefix.length;this.refresh(this.rows-1,this.rows-1)};t.prototype.leaveSearch=function(){this.searchMode=false;if(this._real.preSearch){this.lines=this._real.preSearch;this.x=this._real.preSearchX;this.y=this._real.preSearchY;delete this._real.preSearch;delete this._real.preSearchX;delete this._real.preSearchY}this.refresh(this.rows-1,this.rows-1)};t.prototype.copyBuffer=function(A){var A=A||this.lines,B=[];for(var C=0;C<A.length;C++){B[C]=[];for(var z=0;z<A[C].length;z++){B[C][z]=[A[C][z][0],A[C][z][1]]}}return B};t.prototype.getCopyTextarea=function(A){var z=this._copyTextarea,y=this.document;if(!z){z=y.createElement("textarea");z.style.position="absolute";z.style.left="-32000px";z.style.top="-32000px";z.style.width="0px";z.style.height="0px";z.style.opacity="0";z.style.backgroundColor="transparent";z.style.borderStyle="none";z.style.outlineStyle="none";y.getElementsByTagName("body")[0].appendChild(z);this._copyTextarea=z}return z};t.prototype.copyText=function(A){var z=this,y=this.getCopyTextarea();this.emit("copy",A);y.focus();y.textContent=A;y.value=A;y.setSelectionRange(0,A.length);b(function(){z.element.focus();z.focus()},1)};t.prototype.selectText=function(A,z,J,H){var C,B,L,K,D,I,G,E,F;if(this._selected){C=this._selected.x1;B=this._selected.x2;L=this._selected.y1;K=this._selected.y2;if(K<L){D=B;B=C;C=D;D=K;K=L;L=D}if(B<C&&L===K){D=B;B=C;C=D}for(G=L;G<=K;G++){I=0;E=this.cols-1;if(G===L){I=C}if(G===K){E=B}for(;I<=E;I++){if(this.lines[G][I].old!=null){F=this.lines[G][I].old;delete this.lines[G][I].old;this.lines[G][I]=[F,this.lines[G][I][1]]}}}J=this._selected.y1;A=this._selected.x1}J=Math.max(J,0);J=Math.min(J,this.ydisp+this.rows-1);H=Math.max(H,0);H=Math.min(H,this.ydisp+this.rows-1);this._selected={x1:A,x2:z,y1:J,y2:H};if(H<J){D=z;z=A;A=D;D=H;H=J;J=D}if(z<A&&J===H){D=z;z=A;A=D}for(G=J;G<=H;G++){I=0;E=this.cols-1;if(G===J){I=A}if(G===H){E=z}for(;I<=E;I++){F=this.lines[G][I][0];this.lines[G][I]=[(F&~511)|((511<<9)|4),this.lines[G][I][1]];this.lines[G][I].old=F}}J=J-this.ydisp;H=H-this.ydisp;J=Math.max(J,0);J=Math.min(J,this.rows-1);H=Math.max(H,0);H=Math.min(H,this.rows-1);this.refresh(0,this.rows-1)};t.prototype.grabText=function(B,A,I,H){var D="",C="",z,J,G,F,E;if(H<I){E=A;A=B;B=E;E=H;H=I;I=E}if(A<B&&I===H){E=A;A=B;B=E}for(G=I;G<=H;G++){J=0;F=this.cols-1;if(G===I){J=B}if(G===H){F=A}for(;J<=F;J++){z=this.lines[G][J][1];if(z===" "){C+=z;continue}if(C){D+=C;C=""}D+=z;if(r(z)){J++}}C="";D+="\n"}for(J=A,G=H;J<this.cols;J++){if(this.lines[G][J][1]!==" "){D=D.slice(0,-1);break}}return D};t.prototype.keyPrefix=function(z,y){if(y==="k"||y==="&"){this.destroy()}else{if(y==="p"||y==="]"){this.emit("request paste")}else{if(y==="c"){this.emit("request create")}else{if(y>="0"&&y<="9"){y=+y-1;if(!~y){y=9}this.emit("request term",y)}else{if(y==="n"){this.emit("request term next")}else{if(y==="P"){this.emit("request term previous")}else{if(y===":"){this.emit("request command mode")}else{if(y==="["){this.enterSelect()}}}}}}}}};t.prototype.keySelect=function(H,J){this.showCursor();if(this.searchMode||J==="n"||J==="N"){return this.keySearch(H,J)}if(J==="\x04"){var G=this.ydisp+this.y;if(this.ydisp===this.ybase){this.y=Math.min(this.y+(this.rows-1)/2|0,this.rows-1);this.refresh(0,this.rows-1)}else{this.scrollDisp((this.rows-1)/2|0)}if(this.visualMode){this.selectText(this.x,this.x,G,this.ydisp+this.y)}return}if(J==="\x15"){var G=this.ydisp+this.y;if(this.ydisp===0){this.y=Math.max(this.y-(this.rows-1)/2|0,0);this.refresh(0,this.rows-1)}else{this.scrollDisp(-(this.rows-1)/2|0)}if(this.visualMode){this.selectText(this.x,this.x,G,this.ydisp+this.y)}return}if(J==="\x06"){var G=this.ydisp+this.y;this.scrollDisp(this.rows-1);if(this.visualMode){this.selectText(this.x,this.x,G,this.ydisp+this.y)}return}if(J==="\x02"){var G=this.ydisp+this.y;this.scrollDisp(-(this.rows-1));if(this.visualMode){this.selectText(this.x,this.x,G,this.ydisp+this.y)}return}if(J==="k"||J==="\x1b[A"){var G=this.ydisp+this.y;this.y--;if(this.y<0){this.y=0;this.scrollDisp(-1)}if(this.visualMode){this.selectText(this.x,this.x,G,this.ydisp+this.y)}else{this.refresh(this.y,this.y+1)}return}if(J==="j"||J==="\x1b[B"){var G=this.ydisp+this.y;this.y++;if(this.y>=this.rows){this.y=this.rows-1;this.scrollDisp(1)}if(this.visualMode){this.selectText(this.x,this.x,G,this.ydisp+this.y)}else{this.refresh(this.y-1,this.y)}return}if(J==="h"||J==="\x1b[D"){var I=this.x;this.x--;if(this.x<0){this.x=0}if(this.visualMode){this.selectText(I,this.x,this.ydisp+this.y,this.ydisp+this.y)}else{this.refresh(this.y,this.y)}return}if(J==="l"||J==="\x1b[C"){var I=this.x;this.x++;if(this.x>=this.cols){this.x=this.cols-1}if(this.visualMode){this.selectText(I,this.x,this.ydisp+this.y,this.ydisp+this.y)}else{this.refresh(this.y,this.y)}return}if(J==="v"||J===" "){if(!this.visualMode){this.enterVisual()}else{this.leaveVisual()}return}if(J==="y"){if(this.visualMode){var L=this.grabText(this._selected.x1,this._selected.x2,this._selected.y1,this._selected.y2);this.copyText(L);this.leaveVisual()}return}if(J==="q"||J==="\x1b"){if(this.visualMode){this.leaveVisual()}else{this.leaveSelect()}return}if(J==="w"||J==="W"){var B=this.x;var A=this.y;var F=this.ydisp;var I=this.x;var G=this.y;var E=this.ydisp;var z=false;for(;;){var N=this.lines[E+G];while(I<this.cols){if(N[I][1]<=" "){z=true}else{if(z){break}}I++}if(I>=this.cols){I=this.cols-1}if(I===this.cols-1&&N[I][1]<=" "){I=0;if(++G>=this.rows){G--;if(++E>this.ybase){E=this.ybase;I=this.x;break}}continue}break}this.x=I,this.y=G;this.scrollDisp(-this.ydisp+E);if(this.visualMode){this.selectText(B,this.x,A+F,this.ydisp+this.y)}return}if(J==="b"||J==="B"){var B=this.x;var A=this.y;var F=this.ydisp;var I=this.x;var G=this.y;var E=this.ydisp;for(;;){var N=this.lines[E+G];var z=I>0&&N[I][1]>" "&&N[I-1][1]>" ";while(I>=0){if(N[I][1]<=" "){if(z&&(I+1<this.cols&&N[I+1][1]>" ")){I++;break}else{z=true}}I--}if(I<0){I=0}if(I===0&&(N[I][1]<=" "||!z)){I=this.cols-1;if(--G<0){G++;if(--E<0){E++;I=0;break}}continue}break}this.x=I,this.y=G;this.scrollDisp(-this.ydisp+E);if(this.visualMode){this.selectText(B,this.x,A+F,this.ydisp+this.y)}return}if(J==="e"||J==="E"){var I=this.x+1;var G=this.y;var E=this.ydisp;if(I>=this.cols){I--}for(;;){var N=this.lines[E+G];while(I<this.cols){if(N[I][1]<=" "){I++}else{break}}while(I<this.cols){if(N[I][1]<=" "){if(I-1>=0&&N[I-1][1]>" "){I--;break}}I++}if(I>=this.cols){I=this.cols-1}if(I===this.cols-1&&N[I][1]<=" "){I=0;if(++G>=this.rows){G--;if(++E>this.ybase){E=this.ybase;break}}continue}break}this.x=I,this.y=G;this.scrollDisp(-this.ydisp+E);if(this.visualMode){this.selectText(B,this.x,A+F,this.ydisp+this.y)}return}if(J==="^"||J==="0"){var B=this.x;if(J==="0"){this.x=0}else{if(J==="^"){var N=this.lines[this.ydisp+this.y];var I=0;while(I<this.cols){if(N[I][1]>" "){break}I++}if(I>=this.cols){I=this.cols-1}this.x=I}}if(this.visualMode){this.selectText(B,this.x,this.ydisp+this.y,this.ydisp+this.y)}else{this.refresh(this.y,this.y)}return}if(J==="$"){var B=this.x;var N=this.lines[this.ydisp+this.y];var I=this.cols-1;while(I>=0){if(N[I][1]>" "){if(this.visualMode&&I<this.cols-1){I++}break}I--}if(I<0){I=0}this.x=I;if(this.visualMode){this.selectText(B,this.x,this.ydisp+this.y,this.ydisp+this.y)}else{this.refresh(this.y,this.y)}return}if(J==="g"||J==="G"){var B=this.x;var A=this.y;var F=this.ydisp;if(J==="g"){this.x=0,this.y=0;this.scrollDisp(-this.ydisp)}else{if(J==="G"){this.x=0,this.y=this.rows-1;this.scrollDisp(this.ybase)}}if(this.visualMode){this.selectText(B,this.x,A+F,this.ydisp+this.y)}return}if(J==="H"||J==="M"||J==="L"){var B=this.x;var A=this.y;if(J==="H"){this.x=0,this.y=0}else{if(J==="M"){this.x=0,this.y=this.rows/2|0}else{if(J==="L"){this.x=0,this.y=this.rows-1}}}if(this.visualMode){this.selectText(B,this.x,this.ydisp+A,this.ydisp+this.y)}else{this.refresh(A,A);this.refresh(this.y,this.y)}return}if(J==="{"||J==="}"){var B=this.x;var A=this.y;var F=this.ydisp;var N;var K=false;var M=false;var C=-1;var G=this.y+(J==="{"?-1:1);var E=this.ydisp;var D;if(J==="{"){if(G<0){G++;if(E>0){E--}}}else{if(J==="}"){if(G>=this.rows){G--;if(E<this.ybase){E++}}}}for(;;){N=this.lines[E+G];for(D=0;D<this.cols;D++){if(N[D][1]>" "){if(C===-1){C=0}K=true;break}else{if(D===this.cols-1){if(C===-1){C=1}else{if(C===0){M=true}else{if(C===1){if(K){M=true}}}}break}}}if(M){break}if(J==="{"){G--;if(G<0){G++;if(E>0){E--}else{break}}}else{if(J==="}"){G++;if(G>=this.rows){G--;if(E<this.ybase){E++}else{break}}}}}if(!M){if(J==="{"){G=0;E=0}else{if(J==="}"){G=this.rows-1;E=this.ybase}}}this.x=0,this.y=G;this.scrollDisp(-this.ydisp+E);if(this.visualMode){this.selectText(B,this.x,A+F,this.ydisp+this.y)}return}if(J==="/"||J==="?"){if(!this.visualMode){this.enterSearch(J==="/")}return}return false};t.prototype.keySearch=function(I,L){if(L==="\x1b"){this.leaveSearch();return}if(L==="\r"||(!this.searchMode&&(L==="n"||L==="N"))){this.leaveSearch();var K=this.entry;if(!K){this.refresh(0,this.rows-1);return}var B=this.x;var A=this.y;var G=this.ydisp;var N;var M=false;var F=false;var J=this.x+1;var H=this.ydisp+this.y;var E,D;var C=L==="N"?this.searchDown:!this.searchDown;for(;;){N=this.lines[H];while(J<this.cols){for(D=0;D<K.length;D++){if(J+D>=this.cols){break}if(N[J+D][1]!==K[D]){break}else{if(N[J+D][1]===K[D]&&D===K.length-1){M=true;break}}}if(M){break}J+=D+1}if(M){break}J=0;if(!C){H++;if(H>this.ybase+this.rows-1){if(F){break}F=true;H=0}}else{H--;if(H<0){if(F){break}F=true;H=this.ybase+this.rows-1}}}if(M){if(H-this.ybase<0){E=H;H=0;if(E>this.ybase){H=E-this.ybase;E=this.ybase}}else{E=this.ybase;H-=this.ybase}this.x=J,this.y=H;this.scrollDisp(-this.ydisp+E);if(this.visualMode){this.selectText(B,this.x,A+G,this.ydisp+this.y)}return}this.refresh(0,this.rows-1);return}if(L==="\b"||L==="\x7f"){if(this.entry.length===0){return}var z=this.ydisp+this.rows-1;this.entry=this.entry.slice(0,-1);var D=this.entryPrefix.length+this.entry.length;this.lines[z][D]=[this.lines[z][D][0]," "];this.x--;this.refresh(this.rows-1,this.rows-1);this.refresh(this.y,this.y);return}if(L.length===1&&L>=" "&&L<="~"){var z=this.ydisp+this.rows-1;this.entry+=L;var D=this.entryPrefix.length+this.entry.length-1;this.lines[z][D]=[(this.defAttr&~511)|4,L];this.x++;this.refresh(this.rows-1,this.rows-1);this.refresh(this.y,this.y);return}return false};t.charsets={};t.charsets.SCLD={"`":"\u25c6",a:"\u2592",b:"\u0009",c:"\u000c",d:"\u000d",e:"\u000a",f:"\u00b0",g:"\u00b1",h:"\u2424",i:"\u000b",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"};t.charsets.UK=null;t.charsets.US=null;t.charsets.Dutch=null;t.charsets.Finnish=null;t.charsets.French=null;t.charsets.FrenchCanadian=null;t.charsets.German=null;t.charsets.Italian=null;t.charsets.NorwegianDanish=null;t.charsets.Spanish=null;t.charsets.Swedish=null;t.charsets.Swiss=null;t.charsets.ISOLatin=null;function k(B,A,z,y){B.addEventListener(A,z,y||false)}function w(B,A,z,y){B.removeEventListener(A,z,y||false)}function d(y){if(y.preventDefault){y.preventDefault()}y.returnValue=false;if(y.stopPropagation){y.stopPropagation()}y.cancelBubble=true;return false}function l(A,y){function z(){this.constructor=A}z.prototype=y.prototype;A.prototype=new z}function v(A){var z=A.getElementsByTagName("body")[0];var C=A.createElement("span");C.innerHTML="hello world";z.appendChild(C);var B=C.scrollWidth;C.style.fontWeight="bold";var y=C.scrollWidth;z.removeChild(C);return B!==y}var u=this.String;var b=this.setTimeout;var a=this.setInterval;function c(A,z){var y=A.length;while(y--){if(A[y]===z){return y}}return -1}function r(y){if(y<="\uff00"){return false}return(y>="\uff01"&&y<="\uffbe")||(y>="\uffc2"&&y<="\uffc7")||(y>="\uffca"&&y<="\uffcf")||(y>="\uffd2"&&y<="\uffd7")||(y>="\uffda"&&y<="\uffdc")||(y>="\uffe0"&&y<="\uffe6")||(y>="\uffe8"&&y<="\uffee")}function m(A,y,G){var B=(A<<16)|(y<<8)|G;if(m._cache[B]!=null){return m._cache[B]}var C=Infinity,I=-1,D=0,E,z,J,F,H;for(;D<t.vcolors.length;D++){E=t.vcolors[D];z=E[0];J=E[1];F=E[2];H=m.distance(A,y,G,z,J,F);if(H===0){I=D;break}if(H<C){C=H;I=D}}return m._cache[B]=I}m._cache={};m.distance=function(C,B,D,z,y,A){return Math.pow(30*(C-z),2)+Math.pow(59*(B-y),2)+Math.pow(11*(D-A),2)};function e(B,z,y){if(B.forEach){return B.forEach(z,y)}for(var A=0;A<B.length;A++){z.call(y,B[A],A,B)}}function n(A){if(Object.keys){return Object.keys(A)}var y,z=[];for(y in A){if(Object.prototype.hasOwnProperty.call(A,y)){z.push(y)}}return z}t.EventEmitter=h;t.inherits=l;t.on=k;t.off=w;t.cancel=d;if(typeof module!=="undefined"){module.exports=t}else{this.Terminal=t}}).call(function(){return this||(typeof window!=="undefined"?window:global)}());</script>
<script type="application/javascript" src="/static/js/term.js"></script>
<style>
@font-face {
font-family: 'Ubuntu Mono';
......
......@@ -11,12 +11,14 @@
.terminal {
border: #000 solid 5px;
font-family: "Monaco", "DejaVu Sans Mono", "Liberation Mono", monospace;
font-family: "Monaco", "Microsoft Yahei", "DejaVu Sans Mono", "Liberation Mono", monospace;
font-size: 11px;
color: #f0f0f0;
background: #000;
width: 600px;
box-shadow: rgba(0, 0, 0, 0.8) 2px 2px 20px;
white-space: nowrap;
display: inline-block;
}
.reverse-video {
......@@ -36,114 +38,6 @@
</script>
<script type="application/javascript" src="/static/js/term.js">
</script>
<script type="application/javascript">
var rowHeight = 1;
var colWidth = 1;
function WSSHClient() {
}
WSSHClient.prototype.connect = function(options) {
var protocol = "ws://";
if (window.location.protocol == 'https:') {
protocol = 'wss://';
}
var endpoint = protocol + '{{ web_terminal_uri }}';
if (window.WebSocket) {
this._connection = new WebSocket(endpoint);
}
else if (window.MozWebSocket) {
this._connection = MozWebSocket(endpoint);
}
else {
options.onError('WebSocket Not Supported');
return ;
}
this._connection.onopen = function() {
options.onConnect();
};
this._connection.onmessage = function (evt) {
var data = JSON.parse(evt.data.toString());
if (data.error !== undefined) {
options.onError(data.error);
}
else {
options.onData(data.data);
}
};
this._connection.onclose = function(evt) {
options.onClose();
};
};
WSSHClient.prototype.send = function(data) {
this._connection.send(JSON.stringify({'data': data}));
};
function openTerminal(options) {
var client = new WSSHClient();
var term = new Terminal(80, 24, function(key) {
client.send(key);
});
term.open();
$('.terminal').detach().appendTo('#term');
term.resize(80, 24);
term.write('Connecting...');
client.connect($.extend(options, {
onError: function(error) {
term.write('Error: ' + error + '\r\n');
},
onConnect: function() {
// Erase our connecting message
term.write('\r');
},
onClose: function() {
term.write('Connection Reset By Peer');
},
onData: function(data) {
term.write(data);
}
}));
rowHeight = 0.0 + 1.00 * $('.terminal').height() / 24 ;
colWidth = 0.0 + 1.00 * $('.terminal').width() / 80;
return {'term': term, 'client': client};
}
function resize(){
$('.terminal').css('width', window.innerWidth-25);
console.log(window.innerWidth);
console.log(window.innerWidth-10);
var rows = Math.floor(window.innerHeight/rowHeight) - 2;
var cols = Math.floor(window.innerWidth/colWidth) - 1;
return {rows: rows, cols: cols};
}
</script>
<script type='application/javascript'>
$(document).ready(function() {
var options = {
};
$('#ssh').show();
var term_client = openTerminal(options);
console.log(rowHeight);
window.onresize = function(){
var geom = resize();
console.log(geom);
term_client.term.resize(geom.cols, geom.rows);
term_client.client.send({'resize': {'roles': geom.rows, 'cols': geom.cols}});
$('#ssh').show();
}
});
</script>
<script type="application/javascript" src="/static/js/webterminal.js"></script>
</body>
</html>
......@@ -104,6 +104,10 @@ $('#roleForm').validator({
ok: "",
msg: {required: "系统用户名称必填"}
},
"role_password": {
rule: "length[0~64]",
tip: "系统密码"
},
"role_key": {
rule: "check_begin",
ok: "",
......
......@@ -105,12 +105,16 @@ $('#roleForm').validator({
tip: "输入系统用户名称",
ok: "",
msg: {required: "系统用户名称必填"}
},
"role_password": {
rule: "length[0~64]",
tip: "系统密码"
},
"role_key": {
rule: "check_begin",
ok: "",
empty: true
},
}
},
valid: function(form) {
......
......@@ -74,16 +74,6 @@
</div>
</div>
</div>
<div class="form-group">
<label for="j_group" class="col-sm-2 control-label">使用密码</label>
<div class="col-sm-1">
<div class="radio i-checks">
<label>
<input type="checkbox" value="1" id="use_password" name="use_password">
</label>
</div>
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
......
......@@ -28,7 +28,7 @@
<div class="alert alert-warning text-center">{{ error }}</div>
{% endif %}
{% if msg %}
<div class="alert alert-success text-center">{{ msg }}</div>
<div class="alert alert-success">{{ msg | safe }}</div>
{% endif %}
<div class="form-group">
<label for="username" class="col-sm-2 control-label">用户名<span class="red-fonts">*</span></label>
......@@ -99,14 +99,9 @@
<label><input type="checkbox" value="0" name="extra" >禁用 </label>
</div>
</div>
{# <div class="col-sm-2">#}
{# <div class="checkbox i-checks">#}
{# <label><input type="checkbox" value="1" name="extra">ssh key登录 </label>#}
{# </div>#}
{# </div>#}
<div class="col-sm-2">
<div class="checkbox i-checks">
<label><input type="checkbox" value="2" name="extra">发送邮件 </label>
<label><input type="checkbox" value="1" name="extra" checked>发送邮件 </label>
</div>
</div>
</div>
......
......@@ -116,7 +116,7 @@
</div>
<div class="col-sm-2">
<div class="checkbox i-checks">
<label><input type="checkbox" value="2" name="extra">发送邮件 </label>
<label><input type="checkbox" value="1" name="extra">发送邮件 </label>
</div>
</div>
</div>
......
......@@ -70,7 +70,7 @@
{% if user.username|key_exist %}
<a href="{% url 'key_down' %}?uuid={{ user.uuid }}" >下载</a>
{% else %}
<span style="color: #586b7d">NoKey</span>
<a href="#" onclick="genSSH('{{ user.username }}','{% url 'key_gen' %}?uuid={{ user.uuid }}')">NoKey GenOne?</a>
{% endif %}
</td>
<td class="text-center">
......@@ -150,5 +150,13 @@
});
$('.shiftCheckbox').shiftcheckbox();
});
function genSSH(username, url) {
if (confirm('Are you sure to gen a sshkey for user ' + username)) {
$.get(url, function (data) {
alert(data);
location.reload()
})
}
}
</script>
{% endblock %}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment