Merge branch 'master' into dev

pull/71/head
ibuler 2016-02-23 13:33:32 +08:00
commit 2691cc0b6d
31 changed files with 320 additions and 132 deletions

1
.gitignore vendored
View File

@ -37,6 +37,7 @@ nosetests.xml
.mr.developer.cfg
.project
.pydevproject
.settings
*.log
logs/*
keys/*

View File

@ -1,3 +1,7 @@
## 写在前面
- 目前本版本处于beta阶段请不要用于生产环境除非你知道你在做什么
- 本版本暂时没加入LDAP接口稳定版会将LDAP和无Agent方式抽象成API2.x版本支持LDAP请移步release中下载
#欢迎使用Jumpserver
**Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统实现了跳板机应有的功能。基于ssh协议来管理客户端无需安装agent。
支持常见系统:

View File

@ -21,7 +21,7 @@ from io import open as copen
import uuid
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
if django.get_version() != '1.6':
if not django.get_version().startswith('1.6'):
setup = django.setup()
from django.contrib.sessions.models import Session
from jumpserver.api import ServerError, User, Asset, PermRole, AssetGroup, get_object, mkdir, get_asset_info
@ -33,7 +33,10 @@ from jperm.ansible_api import MyRunner
from jlog.models import ExecLog, FileLog
login_user = get_object(User, username=getpass.getuser())
remote_ip = os.popen("who -m | awk '{ print $NF }'").read().strip('()\n')
try:
remote_ip = os.environ.get('SSH_CLIENT').split()[0]
except (IndexError, AttributeError):
remote_ip = os.popen("who -m | awk '{ print $NF }'").read().strip('()\n')
try:
import termios
@ -412,7 +415,10 @@ class SshTty(Tty):
pass
if sys.stdin in r:
x = os.read(sys.stdin.fileno(), 4096)
try:
x = os.read(sys.stdin.fileno(), 4096)
except OSError:
pass
input_mode = True
if str(x) in ['\r', '\n', '\r\n']:
if self.vim_flag:
@ -576,12 +582,15 @@ class Nav(object):
role = role_check[int(role_id)]
elif len(roles) == 1: # 授权角色数为1
role = roles[0]
else:
color_print('当前用户未被授予角色,无法执行任何操作,如有疑问请联系管理员。')
return
assets = list(self.user_perm.get('role', {}).get(role).get('asset')) # 获取该用户,角色授权主机
print "授权包含该系统用户的所有主机"
for asset in assets:
print ' %s' % asset.hostname
print
print "请输入主机名或ansile支持的pattern, 多个主机:分隔, q退出"
print "请输入主机名或ansible支持的pattern, 多个主机:分隔, q退出"
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
if pattern == 'q':
break
@ -623,7 +632,7 @@ class Nav(object):
self.user_perm = get_group_user_perm(self.user)
try:
print "进入批量上传模式"
print "请输入主机名或ansile支持的pattern, 多个主机:分隔 q退出"
print "请输入主机名或ansible支持的pattern, 多个主机:分隔 q退出"
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
if pattern == 'q':
break
@ -676,7 +685,7 @@ class Nav(object):
self.user_perm = get_group_user_perm(self.user)
try:
print "进入批量下载模式"
print "请输入主机名或ansile支持的pattern, 多个主机:分隔,q退出"
print "请输入主机名或ansible支持的pattern, 多个主机:分隔,q退出"
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
if pattern == 'q':
break
@ -800,7 +809,7 @@ def main():
color_print('请输入正确ID', 'red')
except ServerError, e:
color_print(e, 'red')
except Exception, e:
except IndexError, e:
color_print(e)
time.sleep(5)
pass

View File

@ -12,6 +12,8 @@ import socket
import fcntl
import struct
import readline
import random
import string
jms_dir = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
sys.path.append(jms_dir)
@ -71,12 +73,15 @@ class PreSetup(object):
self.mail_addr = 'hello@jumpserver.org'
self.mail_pass = ''
self.ip = ''
self.key = ''.join(random.choice(string.ascii_lowercase + string.digits) \
for _ in range(16))
def write_conf(self, conf_file=os.path.join(jms_dir, 'jumpserver.conf')):
color_print('开始写入配置文件', 'green')
conf = ConfigParser.ConfigParser()
conf.read(conf_file)
conf.set('base', 'url', 'http://%s' % self.ip)
conf.set('base', 'key', self.key)
conf.set('db', 'host', self.db_host)
conf.set('db', 'port', self.db_port)
conf.set('db', 'user', self.db_user)
@ -96,6 +101,7 @@ class PreSetup(object):
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,
@ -105,6 +111,7 @@ class PreSetup(object):
@staticmethod
def _set_env():
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')
def _test_db_conn(self):

View File

@ -18,7 +18,7 @@ if django.get_version() != '1.6':
from juser.user_api import db_add_user, get_object, User
from install import color_print
from jumpserver.api import get_mac_address
from jumpserver.api import get_mac_address, bash
socket.setdefaulttimeout(2)
@ -81,9 +81,10 @@ class Setup(object):
os.system('id %s &> /dev/null || useradd %s' % (self.admin_user, self.admin_user))
@staticmethod
def _ensure_sh():
jshell = os.path.join(jms_dir, 'connect.py')
os.chmod(jshell, 0755)
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)
@staticmethod
def _run_service():
@ -97,7 +98,7 @@ class Setup(object):
self._sync_db()
self._input_admin()
self._create_admin()
self._ensure_sh()
self._cp_zzsh()
self._run_service()

13
install/zzjumpserver.sh Normal file
View File

@ -0,0 +1,13 @@
#!/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

View File

@ -311,13 +311,12 @@ def excel_to_db(excel_file):
def get_ansible_asset_info(asset_ip, setup_info):
print asset_ip
print setup_info, '***'
disk_need = {}
disk_all = setup_info.get("ansible_devices")
if disk_all:
for disk_name, disk_info in disk_all.iteritems():
print disk_name, disk_info
if disk_name.startswith('sd') or disk_name.startswith('hd') or disk_name.startswith('vd'):
if disk_name.startswith('sd') or disk_name.startswith('hd') or disk_name.startswith('vd') or disk_name.startswith('xvd'):
disk_size = disk_info.get("size", '')
if 'M' in disk_size:
disk_format = round(float(disk_size[:-2]) / 1000, 0)
@ -334,7 +333,7 @@ def get_ansible_asset_info(asset_ip, setup_info):
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_count")
cpu_cores = setup_info.get("ansible_processor_vcpus")
cpu = cpu_type + ' * ' + unicode(cpu_cores)
memory = setup_info.get("ansible_memtotal_mb")
try:

View File

@ -75,7 +75,7 @@ class Asset(models.Model):
brand = models.CharField(max_length=64, blank=True, null=True, verbose_name=u'硬件厂商型号')
cpu = models.CharField(max_length=64, blank=True, null=True, verbose_name=u'CPU')
memory = models.CharField(max_length=128, blank=True, null=True, verbose_name=u'内存')
disk = models.CharField(max_length=128, blank=True, null=True, verbose_name=u'硬盘')
disk = models.CharField(max_length=1024, blank=True, null=True, verbose_name=u'硬盘')
system_type = models.CharField(max_length=32, blank=True, null=True, verbose_name=u"系统类型")
system_version = models.CharField(max_length=8, blank=True, null=True, verbose_name=u"系统版本号")
system_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=u"系统平台")

View File

@ -227,7 +227,7 @@ def asset_edit(request):
if use_default_auth:
af_save.username = ''
af_save.password = ''
af_save.port = None
# af_save.port = None
else:
if password:
password_encode = CRYPTOR.encrypt(password)

View File

@ -6,7 +6,10 @@ from contextlib import closing
from io import open as copen
from json import dumps
from math import ceil
import datetime
import time
import re
import os
from os.path import basename, dirname, exists, join
from struct import unpack
from subprocess import Popen
@ -17,6 +20,7 @@ from jinja2 import FileSystemLoader, Template
from jinja2.environment import Environment
from jumpserver.api import BASE_DIR
from jlog.models import Log
DEFAULT_TEMPLATE = join(BASE_DIR, 'templates', 'jlog', 'static.jinja2')
@ -75,3 +79,28 @@ def renderTemplate(script_path, time_file_path, dimensions=(24, 80), templatenam
return rendered
def kill_invalid_connection():
long_time_logs = []
unfinished_logs = Log.objects.filter(is_finished=False)
now = datetime.datetime.now()
now_timestamp = int(time.mktime(now.timetuple()))
for log in unfinished_logs:
if (now - log.start_time).days > 1:
long_time_logs.append(log)
for log in long_time_logs:
try:
log_file_mtime = int(os.stat(log.log_path).st_mtime)
except OSError:
log_file_mtime = 0
if (now_timestamp - log_file_mtime) > 3600:
try:
os.kill(int(log.pid), 9)
except OSError:
pass
log.is_finished = True
log.end_time = now
log.save()

View File

@ -66,7 +66,7 @@ def log_list(request, offset):
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
web_monitor_uri = 'ws://%s/monitor' % WEB_SOCKET_HOST
web_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))

View File

@ -365,6 +365,16 @@ class MyTask(MyRunner):
self.run("user", module_args, become=True)
return self.results
def del_user_sudo(self, username):
"""
delete a role sudo item
:param username:
:return:
"""
module_args = "sed -i 's/^%s.*//' /etc/sudoers" % username
self.run("command", module_args, become=True)
return self.results
@staticmethod
def gen_sudo_script(role_list, sudo_list):
# receive role_list = [role1, role2] sudo_list = [sudo1, sudo2]

View File

@ -16,8 +16,8 @@ from jperm.perm_api import get_role_info, get_role_push_host
from jumpserver.api import my_render, get_object, CRYPTOR
# 设置PERM APP Log
from jumpserver.settings import LOG_LEVEL
logger = set_log(LOG_LEVEL, filename='jumpserver_perm.log')
from jumpserver.api import logger
#logger = set_log(LOG_LEVEL, filename='jumpserver_perm.log')
@require_role('admin')
@ -277,7 +277,7 @@ def perm_role_add(request):
if request.method == "POST":
# 获取参数: name, comment
name = request.POST.get("role_name", "")
name = request.POST.get("role_name", "").strip()
comment = request.POST.get("role_comment", "")
password = request.POST.get("role_password", "")
key_content = request.POST.get("role_key", "")
@ -286,6 +286,8 @@ def perm_role_add(request):
try:
if get_object(PermRole, name=name):
raise ServerError(u'已经存在该用户 %s' % name)
if name == "root":
raise ServerError(u'禁止使用root用户作为系统用户这样非常危险')
default = get_object(Setting, name='default')
if password:
@ -317,14 +319,37 @@ def perm_role_delete(request):
"""
delete role page
"""
if request.method == "GET":
try:
# 获取参数删除的role对象
role_id = request.GET.get("id")
role = get_object(PermRole, id=role_id)
if not role:
logger.warning(u"Delete Role: role_id %s not exist" % role_id)
raise ServerError(u"role_id %s 无数据记录" % role_id)
# 删除推送到主机上的role
filter_type = request.GET.get("filter_type")
print filter_type
if filter_type:
if filter_type == "recycle_assets":
recycle_assets = [push.asset for push in role.perm_push.all() if push.success]
print recycle_assets
recycle_assets_ip = ','.join([asset.ip for asset in recycle_assets])
return HttpResponse(recycle_assets_ip)
else:
return HttpResponse("no such filter_type: %s" % filter_type)
else:
return HttpResponse("filter_type: ?")
except ServerError, e:
return HttpResponse(e)
if request.method == "POST":
try:
# 获取参数删除的role对象
role_id = request.POST.get("id")
role = get_object(PermRole, id=role_id)
if not role:
logger.warning(u"Delete Role: %s not exist" % role.name)
raise ServerError(u"%s 无数据记录" % role.name)
logger.warning(u"Delete Role: role_id %s not exist" % role_id)
raise ServerError(u"role_id %s 无数据记录" % role_id)
role_key = role.key_path
# 删除推送到主机上的role
recycle_assets = [push.asset for push in role.perm_push.all() if push.success]
@ -333,11 +358,13 @@ def perm_role_delete(request):
recycle_resource = gen_resource(recycle_assets)
task = MyTask(recycle_resource)
try:
msg = task.del_user(get_object(PermRole, id=role_id).name)
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)
except Exception, e:
logger.warning(u"Recycle Role failed: %s" % e)
raise ServerError(u"回收已推送的系统用户失败: %s" % e)
logger.info(u"delete role %s - execute delete user: %s" % (role.name, msg))
logger.info(u"delete role %s - execute delete user: %s" % (role.name, msg_del_user))
logger.info(u"delete role %s - execute delete sudo: %s" % (role.name, msg_del_sudo))
# TODO: 判断返回结果,处理异常
# 删除存储的秘钥,以及目录
try:
@ -423,6 +450,9 @@ def perm_role_edit(request):
if not role:
raise ServerError('该系统用户不能存在')
if role_name == "root":
raise ServerError(u'禁止使用root用户作为系统用户这样非常危险')
if role_password:
encrypt_pass = CRYPTOR.encrypt(role_password)
role.password = encrypt_pass
@ -473,6 +503,7 @@ def perm_role_push(request):
for asset_group in asset_groups_obj:
group_assets_obj.extend(asset_group.asset_set.all())
calc_assets = list(set(assets_obj) | set(group_assets_obj))
push_resource = gen_resource(calc_assets)
# 调用Ansible API 进行推送
@ -577,25 +608,28 @@ def perm_sudo_add(request):
"""
# 渲染数据
header_title, path1, path2 = "Sudo命令", "别名管理", "添加别名"
try:
if request.method == "POST":
# 获取参数: name, comment
name = request.POST.get("sudo_name").strip().upper()
comment = request.POST.get("sudo_comment").strip()
commands = request.POST.get("sudo_commands").strip()
if request.method == "POST":
# 获取参数: name, comment
name = request.POST.get("sudo_name").strip().upper()
comment = request.POST.get("sudo_comment").strip()
commands = request.POST.get("sudo_commands").strip()
if not name or not commands:
raise ServerError(u"sudo name 和 commands是必填项!")
pattern = re.compile(r'[ \n,\r]')
commands = ', '.join(list_drop_str(pattern.split(commands), u''))
logger.debug(u'添加sudo %s: %s' % (name, commands))
if get_object(PermSudo, name=name):
error = 'Sudo别名 %s已经存在' % name
else:
sudo = PermSudo(name=name.strip(), comment=comment, commands=commands)
sudo.save()
msg = u"添加Sudo命令别名: %s" % name
# 渲染数据
pattern = re.compile(r'[\n,\r]')
commands = ', '.join(list_drop_str(pattern.split(commands), u''))
logger.debug(u'添加sudo %s: %s' % (name, commands))
if get_object(PermSudo, name=name):
error = 'Sudo别名 %s已经存在' % name
else:
sudo = PermSudo(name=name.strip(), comment=comment, commands=commands)
sudo.save()
msg = u"添加Sudo命令别名: %s" % name
except ServerError, e:
error = e
return my_render('jperm/perm_sudo_add.html', locals(), request)
@ -612,22 +646,27 @@ def perm_sudo_edit(request):
sudo_id = request.GET.get("id")
sudo = PermSudo.objects.get(id=sudo_id)
if request.method == "POST":
name = request.POST.get("sudo_name").upper()
commands = request.POST.get("sudo_commands")
comment = request.POST.get("sudo_comment")
try:
if request.method == "POST":
name = request.POST.get("sudo_name").upper()
commands = request.POST.get("sudo_commands")
comment = request.POST.get("sudo_comment")
pattern = re.compile(r'[ \n,\r]')
commands = ', '.join(list_drop_str(pattern.split(commands), u'')).strip()
logger.debug(u'添加sudo %s: %s' % (name, commands))
if not name or not commands:
raise ServerError(u"sudo name 和 commands是必填项!")
sudo.name = name.strip()
sudo.commands = commands
sudo.comment = comment
sudo.save()
pattern = re.compile(r'[\n,\r]')
commands = ', '.join(list_drop_str(pattern.split(commands), u'')).strip()
logger.debug(u'添加sudo %s: %s' % (name, commands))
msg = u"更新命令别名: %s" % name
sudo.name = name.strip()
sudo.commands = commands
sudo.comment = comment
sudo.save()
msg = u"更新命令别名: %s" % name
except ServerError, e:
error = e
return my_render('jperm/perm_sudo_edit.html', locals(), request)

View File

@ -64,7 +64,6 @@ def get_asset_info(asset):
info = {'hostname': asset.hostname, 'ip': asset.ip}
if asset.use_default_auth:
if default:
info['port'] = int(default.field2)
info['username'] = default.field1
try:
info['password'] = CRYPTOR.decrypt(default.field3)
@ -73,9 +72,12 @@ def get_asset_info(asset):
if os.path.isfile(default.field4):
info['ssh_key'] = default.field4
else:
info['port'] = int(asset.port)
info['username'] = asset.username
info['password'] = CRYPTOR.decrypt(asset.password)
try:
info['port'] = int(asset.port)
except TypeError:
info['port'] = int(default.field2)
return info

View File

@ -152,5 +152,6 @@ STATIC_URL = '/static/'
BOOTSTRAP_COLUMN_COUNT = 10
CRONJOBS = [
('0 1 * * *', 'jasset.asset_api.asset_ansible_update_all')
('0 1 * * *', 'jasset.asset_api.asset_ansible_update_all'),
('1 * * * *', 'jlog.log_api.kill_invalid_connection'),
]

View File

@ -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 = 'ws://%s/exec?role=%s' % (WEB_SOCKET_HOST, role)
web_terminal_uri = '%s/exec?role=%s' % (WEB_SOCKET_HOST, role)
return my_render('exec_cmd.html', locals(), request)
@ -356,7 +356,7 @@ def web_terminal(request):
if asset:
print asset
hostname = asset.hostname
web_terminal_uri = 'ws://%s/terminal?id=%s&role=%s' % (WEB_SOCKET_HOST, asset_id, role_name)
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())

View File

@ -151,8 +151,8 @@ def server_add_user(username, password, ssh_key_pwd='', ssh_key_login_need=True)
add a system user in jumpserver
在jumpserver服务器上添加一个用户
"""
bash("useradd -s %s/connect.py '%s'; echo '%s'; echo '%s' | passwd --stdin '%s'" %
(BASE_DIR, username, password, password, username))
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)

View File

@ -6,7 +6,7 @@
# from Crypto.PublicKey import RSA
import uuid
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404
from django.db.models import Q
from juser.user_api import *
from jperm.perm_api import get_group_user_perm
@ -111,22 +111,18 @@ def group_edit(request):
if len(UserGroup.objects.filter(name=group_name)) > 1:
raise ServerError(u'%s 用户组已存在' % group_name)
# add user group
user_group = get_object_or_404(UserGroup, id=group_id)
user_group.user_set.clear()
for user in User.objects.filter(id__in=users_selected):
user.group.add(UserGroup.objects.get(id=group_id))
# delete user group
user_group = UserGroup.objects.get(id=group_id)
for user in [user for user in User.objects.filter(group=user_group) if user not in User.objects.filter(id__in=users_selected)]:
user_group_all = user.group.all()
user.group.clear()
for g in user_group_all:
if g == user_group:
continue
user.group.add(g)
user_group.name = group_name
user_group.comment = comment
user_group.save()
except ServerError, e:
error = e
if not error:
return HttpResponseRedirect(reverse('user_group_list'))
else:
@ -313,6 +309,12 @@ def reset_password(request):
hash_encode = request.GET.get('hash', '')
action = '/juser/password/reset/?uuid=%s&timestamp=%s&hash=%s' % (uuid_r, timestamp, hash_encode)
if hash_encode == PyCrypt.md5_crypt(uuid_r + timestamp + KEY):
if int(time.time()) - int(timestamp) > 600:
return http_error(request, u'链接已超时')
else:
return HttpResponse('hash校验失败')
if request.method == 'POST':
password = request.POST.get('password')
password_confirm = request.POST.get('password_confirm')
@ -328,11 +330,8 @@ def reset_password(request):
else:
return HttpResponse('用户不存在')
if hash_encode == PyCrypt.md5_crypt(uuid_r + timestamp + KEY):
if int(time.time()) - int(timestamp) > 600:
return http_error(request, u'链接已超时')
else:
return render_to_response('juser/reset_password.html', locals())
else:
return render_to_response('juser/reset_password.html', locals())
return http_error(request, u'错误请求')

View File

@ -230,15 +230,14 @@ class ExecHandler(tornado.websocket.WebSocketHandler):
def on_message(self, message):
data = json.loads(message)
pattern = data.get('pattern', '')
command = data.get('command', '')
asset_name_str = ''
if pattern and command:
self.command = data.get('command', '')
self.asset_name_str = ''
if pattern and self.command:
for inv in self.runner.inventory.get_hosts(pattern=pattern):
asset_name_str += '%s ' % inv.name
self.write_message('匹配主机: ' + asset_name_str)
self.write_message('<span style="color: yellow">Ansible> %s</span>\n\n' % command)
self.__class__.tasks.append(MyThread(target=self.run_cmd, args=(command, pattern)))
ExecLog(host=asset_name_str, cmd=command, user=self.user.username, remote_ip=self.remote_ip).save()
self.asset_name_str += '%s ' % inv.name
self.write_message('匹配主机: ' + self.asset_name_str)
self.write_message('<span style="color: yellow">Ansible> %s</span>\n\n' % self.command)
self.__class__.tasks.append(MyThread(target=self.run_cmd, args=(self.command, pattern)))
for t in self.__class__.tasks:
if t.is_alive():
@ -251,11 +250,12 @@ class ExecHandler(tornado.websocket.WebSocketHandler):
def run_cmd(self, command, pattern):
self.runner.run('shell', command, pattern=pattern)
ExecLog(host=self.asset_name_str, cmd=self.command, user=self.user.username,
remote_ip=self.remote_ip, result=self.runner.results).save()
newline_pattern = re.compile(r'\n')
for k, v in self.runner.results.items():
for host, output in v.items():
output = newline_pattern.sub('<br />', output)
logger.debug(output)
if k == 'ok':
header = "<span style='color: green'>[ %s => %s]</span>\n" % (host, 'Ok')
else:

View File

@ -23,7 +23,13 @@
<script type="text/javascript">
var wsUri = "{{ web_terminal_uri }}"; //请求的websocket url
var protocol = "ws://";
if (window.location.protocol == 'https:') {
protocol = 'wss://';
}
var wsUri = protocol + "{{ web_terminal_uri }}"; //请求的websocket url
var ws = new WebSocket(wsUri);
function createSystemMessage(message) {

View File

@ -224,6 +224,7 @@
}
else if (dataArray.length == 1 && data != 'error' && navigator.platform == 'Win32'){
var title = 'Jumpserver Web Terminal' + '<span class="text-info"> '+ hostname +'</span>';
/*
layer.open({
type: 2,
title: title,
@ -232,8 +233,11 @@
area: ['628px', '420px'],
content: new_url+data
});
*/
window.open(new_url+data, '', 'width=628px, height=380px');
} else if (dataArray.length == 1 && data != 'error'){
layer.open({
/*
layer.open({
type: 2,
title: title,
maxmin: true,
@ -241,6 +245,8 @@
area: ['628px', '452px'],
content: new_url+data
});
*/
window.open(new_url+data, '', 'width=628px, height=452px')
}
else {
aUrl = '';
@ -266,6 +272,7 @@
var hostname = $(a).attr('value');
var title = 'Jumpserver Web Terminal - ' + '<span class="text-info"> '+ hostname +'</span>';
if (navigator.platform == 'Win32'){
/*
layer.open({
type: 2,
title: title,
@ -274,8 +281,11 @@
shade: false,
content: new_url
});
*/
window.open(new_url+data, '', 'width=628px, height=380px');
} else {
/*
layer.open({
type: 2,
title: title,
@ -284,6 +294,8 @@
shade: false,
content: new_url
});
*/
window.open(new_url+data, '', 'width=628px, height=452px');
}
return false
@ -351,4 +363,4 @@
});
</script>
{% endblock %}
{% endblock %}

View File

@ -243,8 +243,9 @@
area: ['628px', '420px'],
content: new_url+data
});
window.open(new_url+data, '_blank', 'toolbar=yes, location=yes, scrollbars=yes, resizable=yes, copyhistory=yes, width=628, height=400')
*/
window.open(new_url+data, '', 'width=628px, height=420px')
window.open(new_url+data, '', 'width=628px, height=380px');
} else if (dataArray.length == 1 && data != 'error'){
/*layer.open({
type: 2,
@ -255,7 +256,7 @@
content: new_url+data
});
*/
window.open(new_url+data, '', 'width=628px, height=440px')
window.open(new_url+data, '_blank', 'toolbar=yes, location=yes, copyhistory=yes, scrollbars=yes, width=628, height=410');
}
else {
@ -292,7 +293,7 @@
content: new_url
});
*/
window.open(new_url, '', 'height=628px, width=420px')
window.open(new_url, '_blank', 'toolbar=yes, location=yes, copyhistory=yes, scrollbars=yes, width=628, height=400')
} else {
/*
@ -305,7 +306,7 @@
content: new_url
});
*/
window.open(new_url, '', 'height=628px, width=452px')
window.open(new_url, '_blank', 'toolbar=yes, location=yes, copyhistory=yes, scrollbars=yes, width=628, height=410');
}
return false
@ -429,4 +430,4 @@
});
</script>
{% endblock %}
{% endblock %}

View File

@ -136,14 +136,20 @@
{# })#}
{# });#}
function init(obj){
var protocol = "ws://";
if (window.location.protocol == 'https:') {
protocol = 'wss://';
}
var file_path = obj.attr('file_path');
var wsUri = '{{ web_monitor_uri }}';
var wsUri = protocol + '{{ web_monitor_uri }}';
var socket = new WebSocket(wsUri + '?file_path=' + file_path);
var term = new Terminal({
cols: 80,
rows: 24,
screenKeys: false
cols: 80,
rows: 24,
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>');

View File

@ -43,7 +43,12 @@
}
WSSHClient.prototype.connect = function(options) {
var endpoint = '{{ web_terminal_uri }}';
var protocol = "ws://";
if (window.location.protocol == 'https:') {
protocol = 'wss://';
}
var endpoint = protocol + '{{ web_terminal_uri }}';
if (window.WebSocket) {
this._connection = new WebSocket(endpoint);
@ -112,7 +117,7 @@
$('.terminal').css('width', window.innerWidth-25);
console.log(window.innerWidth);
console.log(window.innerWidth-10);
var rows = Math.floor(window.innerHeight/rowHeight) - 1;
var rows = Math.floor(window.innerHeight/rowHeight) - 2;
var cols = Math.floor(window.innerWidth/colWidth) - 1;
return {rows: rows, cols: cols};

View File

@ -92,11 +92,9 @@ $('#roleForm').validator({
timely: 2,
theme: "yellow_right_effect",
rules: {
check_name: [/^\w{2,20}$/, '大小写字母数字和下划线,2-20位'],
check_begin: [/^[\-]+BEGIN RSA PRIVATE KEY[\-]+/gm, 'RSA Key填写有误请检查'],
{# either: function(){#}
{# return $('#role_password').val() == ''#}
{# }#}
check_name: [/(?!^root$)^[\w.]{2,20}$/i, '大小写字母数字和下划线小数点,2-20位,并且非root'],
check_begin: [/^[\-]+BEGIN R|DSA PRIVATE KEY[\-]+/gm, 'RSA|DSA Key填写有误请检查']
},
fields: {
@ -110,13 +108,8 @@ $('#roleForm').validator({
rule: "check_begin",
ok: "",
empty: true
},
{# "role_key": {#}
{# rule: "required(either)",#}
{# tip: "输入密钥",#}
{# ok: "",#}
{# msg: {required: "密码和密钥必填一个!"}#}
{# }#}
}
},
valid: function(form) {
form.submit();

View File

@ -94,16 +94,24 @@ $('#roleForm').validator({
timely: 2,
theme: "yellow_right_effect",
rules: {
check_name: [/^\w{2,20}$/, '大小写字母数字和下划线,2-20位']
check_name: [/(?!^root$)^\w{2,20}$/i, '大小写字母数字和下划线,2-20位,并且非root'],
check_begin: [/^[\-]+BEGIN RSA PRIVATE KEY[\-]+/gm, 'RSA Key填写有误请检查'],
},
fields: {
"role_name": {
rule: "required;check_name",
rule: "required;check_name;check_name_root",
tip: "输入系统用户名称",
ok: "",
msg: {required: "系统用户名称必填"}
}
},
"role_key": {
rule: "check_begin",
ok: "",
empty: true
},
},
valid: function(form) {
form.submit();

View File

@ -84,22 +84,43 @@
<script>
function remove_role(role_id){
if (confirm("确认删除")) {
$.ajax({
type: "POST",
url: "{% url 'role_del' %}",
data: "id=" + role_id,
success: function(msg){
alert( "成功: " + msg );
var del_row = $('tbody#edittbody>tr#' + role_id);
del_row.remove()
},
error: function (msg) {
console.log(msg);
alert("失败: " + msg.responseText)
}
});
}
$.ajax({
type: "GET",
url: "{% url 'role_del' %}",
data: {id: role_id, filter_type: 'recycle_assets'},
success: function(data) {
console.log(data)
if (data) {
msg = data + "的系统用户会被删除,包括其家目录,请谨慎操作!"
}
else {
msg = "该角色无已推送的系统用户, 可以安全删除"
}
if (confirm(msg)) {
$.ajax({
type: "POST",
url: "{% url 'role_del' %}",
data: "id=" + role_id,
success: function(msg){
alert( "成功: " + msg );
var del_row = $('tbody#edittbody>tr#' + role_id);
del_row.remove()
},
error: function (msg) {
console.log(msg);
alert("失败: " + msg.responseText)
}
});
}
},
error: function(error) {
alert(error)
},
});
}
</script>

View File

@ -112,7 +112,28 @@ var config = {
for (var selector in config) {
$(selector).chosen(config[selector]);
}
$('#sudoForm').validator({
timely: 2,
theme: "yellow_right_effect",
rules: {
check_name: [/^\w{2,20}$/, '大写字母,2-20位']
},
fields: {
"sudo_name": {
rule: "required;check_name"
},
"sudo_runas": {
rule: "required;check_name"
},
"sudo_commands": {
rule: "required"
}
},
valid: function(form) {
form.submit();
}
});
</script>
<script src="/static/js/cropper/cropper.min.js"></script>
<script src="/static/js/datapicker/bootstrap-datepicker.js"></script>

View File

@ -46,7 +46,7 @@ add_role_chosen() {
check_syntax(){
visudo -c -f $1
/usr/sbin/visudo -c -f $1
}
cp $real_file $tmp_file && add_cmd_alias $tmp_file && add_role_chosen $tmp_file || exit 1

View File

@ -131,7 +131,7 @@ $('#userForm').validator({
timely: 2,
theme: "yellow_right_effect",
rules: {
check_username: [/^\w{3,20}$/, '大小写字母数字和下划线'],
check_username: [/^[\w.]{3,20}$/, '大小写字母数字和下划线小数点'],
type_m: function(element){
return $("#M").is(":checked");
}

View File

@ -99,6 +99,7 @@
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 100,
maxFilesize: 2048,
maxFiles: 100,
url: '/file/upload/',