mirror of https://github.com/jumpserver/jumpserver
Merge branch 'master' into dev
commit
2691cc0b6d
|
@ -37,6 +37,7 @@ nosetests.xml
|
||||||
.mr.developer.cfg
|
.mr.developer.cfg
|
||||||
.project
|
.project
|
||||||
.pydevproject
|
.pydevproject
|
||||||
|
.settings
|
||||||
*.log
|
*.log
|
||||||
logs/*
|
logs/*
|
||||||
keys/*
|
keys/*
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
## 写在前面
|
||||||
|
- 目前本版本处于beta阶段,请不要用于生产环境,除非你知道你在做什么
|
||||||
|
- 本版本暂时没加入LDAP接口,稳定版会将LDAP和无Agent方式抽象成API,2.x版本支持LDAP,请移步release中下载
|
||||||
|
|
||||||
#欢迎使用Jumpserver
|
#欢迎使用Jumpserver
|
||||||
**Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统,实现了跳板机应有的功能。基于ssh协议来管理,客户端无需安装agent。
|
**Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统,实现了跳板机应有的功能。基于ssh协议来管理,客户端无需安装agent。
|
||||||
支持常见系统:
|
支持常见系统:
|
||||||
|
|
23
connect.py
23
connect.py
|
@ -21,7 +21,7 @@ from io import open as copen
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
|
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
|
||||||
if django.get_version() != '1.6':
|
if not django.get_version().startswith('1.6'):
|
||||||
setup = django.setup()
|
setup = django.setup()
|
||||||
from django.contrib.sessions.models import Session
|
from django.contrib.sessions.models import Session
|
||||||
from jumpserver.api import ServerError, User, Asset, PermRole, AssetGroup, get_object, mkdir, get_asset_info
|
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
|
from jlog.models import ExecLog, FileLog
|
||||||
|
|
||||||
login_user = get_object(User, username=getpass.getuser())
|
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:
|
try:
|
||||||
import termios
|
import termios
|
||||||
|
@ -412,7 +415,10 @@ class SshTty(Tty):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if sys.stdin in r:
|
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
|
input_mode = True
|
||||||
if str(x) in ['\r', '\n', '\r\n']:
|
if str(x) in ['\r', '\n', '\r\n']:
|
||||||
if self.vim_flag:
|
if self.vim_flag:
|
||||||
|
@ -576,12 +582,15 @@ class Nav(object):
|
||||||
role = role_check[int(role_id)]
|
role = role_check[int(role_id)]
|
||||||
elif len(roles) == 1: # 授权角色数为1
|
elif len(roles) == 1: # 授权角色数为1
|
||||||
role = roles[0]
|
role = roles[0]
|
||||||
|
else:
|
||||||
|
color_print('当前用户未被授予角色,无法执行任何操作,如有疑问请联系管理员。')
|
||||||
|
return
|
||||||
assets = list(self.user_perm.get('role', {}).get(role).get('asset')) # 获取该用户,角色授权主机
|
assets = list(self.user_perm.get('role', {}).get(role).get('asset')) # 获取该用户,角色授权主机
|
||||||
print "授权包含该系统用户的所有主机"
|
print "授权包含该系统用户的所有主机"
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
print ' %s' % asset.hostname
|
print ' %s' % asset.hostname
|
||||||
print
|
print
|
||||||
print "请输入主机名或ansile支持的pattern, 多个主机:分隔, q退出"
|
print "请输入主机名或ansible支持的pattern, 多个主机:分隔, q退出"
|
||||||
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
|
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
|
||||||
if pattern == 'q':
|
if pattern == 'q':
|
||||||
break
|
break
|
||||||
|
@ -623,7 +632,7 @@ class Nav(object):
|
||||||
self.user_perm = get_group_user_perm(self.user)
|
self.user_perm = get_group_user_perm(self.user)
|
||||||
try:
|
try:
|
||||||
print "进入批量上传模式"
|
print "进入批量上传模式"
|
||||||
print "请输入主机名或ansile支持的pattern, 多个主机:分隔 q退出"
|
print "请输入主机名或ansible支持的pattern, 多个主机:分隔 q退出"
|
||||||
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
|
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
|
||||||
if pattern == 'q':
|
if pattern == 'q':
|
||||||
break
|
break
|
||||||
|
@ -676,7 +685,7 @@ class Nav(object):
|
||||||
self.user_perm = get_group_user_perm(self.user)
|
self.user_perm = get_group_user_perm(self.user)
|
||||||
try:
|
try:
|
||||||
print "进入批量下载模式"
|
print "进入批量下载模式"
|
||||||
print "请输入主机名或ansile支持的pattern, 多个主机:分隔,q退出"
|
print "请输入主机名或ansible支持的pattern, 多个主机:分隔,q退出"
|
||||||
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
|
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
|
||||||
if pattern == 'q':
|
if pattern == 'q':
|
||||||
break
|
break
|
||||||
|
@ -800,7 +809,7 @@ def main():
|
||||||
color_print('请输入正确ID', 'red')
|
color_print('请输入正确ID', 'red')
|
||||||
except ServerError, e:
|
except ServerError, e:
|
||||||
color_print(e, 'red')
|
color_print(e, 'red')
|
||||||
except Exception, e:
|
except IndexError, e:
|
||||||
color_print(e)
|
color_print(e)
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -12,6 +12,8 @@ import socket
|
||||||
import fcntl
|
import fcntl
|
||||||
import struct
|
import struct
|
||||||
import readline
|
import readline
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
jms_dir = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
|
jms_dir = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
|
||||||
sys.path.append(jms_dir)
|
sys.path.append(jms_dir)
|
||||||
|
@ -71,12 +73,15 @@ class PreSetup(object):
|
||||||
self.mail_addr = 'hello@jumpserver.org'
|
self.mail_addr = 'hello@jumpserver.org'
|
||||||
self.mail_pass = ''
|
self.mail_pass = ''
|
||||||
self.ip = ''
|
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')):
|
def write_conf(self, conf_file=os.path.join(jms_dir, 'jumpserver.conf')):
|
||||||
color_print('开始写入配置文件', 'green')
|
color_print('开始写入配置文件', 'green')
|
||||||
conf = ConfigParser.ConfigParser()
|
conf = ConfigParser.ConfigParser()
|
||||||
conf.read(conf_file)
|
conf.read(conf_file)
|
||||||
conf.set('base', 'url', 'http://%s' % self.ip)
|
conf.set('base', 'url', 'http://%s' % self.ip)
|
||||||
|
conf.set('base', 'key', self.key)
|
||||||
conf.set('db', 'host', self.db_host)
|
conf.set('db', 'host', self.db_host)
|
||||||
conf.set('db', 'port', self.db_port)
|
conf.set('db', 'port', self.db_port)
|
||||||
conf.set('db', 'user', self.db_user)
|
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')
|
color_print('默认用户名: %s 默认密码: %s' % (self.db_user, self.db_pass), 'green')
|
||||||
bash('yum -y install mysql-server')
|
bash('yum -y install mysql-server')
|
||||||
bash('service mysqld start')
|
bash('service mysqld start')
|
||||||
|
bash('chkconfig mysqld on')
|
||||||
bash('mysql -e "create database %s default charset=utf8"' % self.db)
|
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,
|
bash('mysql -e "grant all on %s.* to \'%s\'@\'%s\' identified by \'%s\'"' % (self.db,
|
||||||
self.db_user,
|
self.db_user,
|
||||||
|
@ -105,6 +111,7 @@ class PreSetup(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _set_env():
|
def _set_env():
|
||||||
color_print('开始关闭防火墙和selinux', 'green')
|
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')
|
bash('service iptables stop && chkconfig iptables off && setenforce 0')
|
||||||
|
|
||||||
def _test_db_conn(self):
|
def _test_db_conn(self):
|
||||||
|
|
|
@ -18,7 +18,7 @@ if django.get_version() != '1.6':
|
||||||
|
|
||||||
from juser.user_api import db_add_user, get_object, User
|
from juser.user_api import db_add_user, get_object, User
|
||||||
from install import color_print
|
from install import color_print
|
||||||
from jumpserver.api import get_mac_address
|
from jumpserver.api import get_mac_address, bash
|
||||||
|
|
||||||
socket.setdefaulttimeout(2)
|
socket.setdefaulttimeout(2)
|
||||||
|
|
||||||
|
@ -81,9 +81,10 @@ class Setup(object):
|
||||||
os.system('id %s &> /dev/null || useradd %s' % (self.admin_user, self.admin_user))
|
os.system('id %s &> /dev/null || useradd %s' % (self.admin_user, self.admin_user))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _ensure_sh():
|
def _cp_zzsh():
|
||||||
jshell = os.path.join(jms_dir, 'connect.py')
|
os.chdir(os.path.join(jms_dir, 'install'))
|
||||||
os.chmod(jshell, 0755)
|
shutil.copy('zzjumpserver.sh', '/etc/profile.d/')
|
||||||
|
bash("sed -i 's#/opt/jumpserver#%s#g' /etc/profile.d/zzjumpserver.sh" % jms_dir)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _run_service():
|
def _run_service():
|
||||||
|
@ -97,7 +98,7 @@ class Setup(object):
|
||||||
self._sync_db()
|
self._sync_db()
|
||||||
self._input_admin()
|
self._input_admin()
|
||||||
self._create_admin()
|
self._create_admin()
|
||||||
self._ensure_sh()
|
self._cp_zzsh()
|
||||||
self._run_service()
|
self._run_service()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -311,13 +311,12 @@ def excel_to_db(excel_file):
|
||||||
|
|
||||||
|
|
||||||
def get_ansible_asset_info(asset_ip, setup_info):
|
def get_ansible_asset_info(asset_ip, setup_info):
|
||||||
print asset_ip
|
print setup_info, '***'
|
||||||
disk_need = {}
|
disk_need = {}
|
||||||
disk_all = setup_info.get("ansible_devices")
|
disk_all = setup_info.get("ansible_devices")
|
||||||
if disk_all:
|
if disk_all:
|
||||||
for disk_name, disk_info in disk_all.iteritems():
|
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') or disk_name.startswith('xvd'):
|
||||||
if disk_name.startswith('sd') or disk_name.startswith('hd') or disk_name.startswith('vd'):
|
|
||||||
disk_size = disk_info.get("size", '')
|
disk_size = disk_info.get("size", '')
|
||||||
if 'M' in disk_size:
|
if 'M' in disk_size:
|
||||||
disk_format = round(float(disk_size[:-2]) / 1000, 0)
|
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")
|
mac = setup_info.get("ansible_default_ipv4").get("macaddress")
|
||||||
brand = setup_info.get("ansible_product_name")
|
brand = setup_info.get("ansible_product_name")
|
||||||
cpu_type = setup_info.get("ansible_processor")[1]
|
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)
|
cpu = cpu_type + ' * ' + unicode(cpu_cores)
|
||||||
memory = setup_info.get("ansible_memtotal_mb")
|
memory = setup_info.get("ansible_memtotal_mb")
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -75,7 +75,7 @@ class Asset(models.Model):
|
||||||
brand = models.CharField(max_length=64, blank=True, null=True, verbose_name=u'硬件厂商型号')
|
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')
|
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'内存')
|
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_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_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"系统平台")
|
system_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=u"系统平台")
|
||||||
|
|
|
@ -227,7 +227,7 @@ def asset_edit(request):
|
||||||
if use_default_auth:
|
if use_default_auth:
|
||||||
af_save.username = ''
|
af_save.username = ''
|
||||||
af_save.password = ''
|
af_save.password = ''
|
||||||
af_save.port = None
|
# af_save.port = None
|
||||||
else:
|
else:
|
||||||
if password:
|
if password:
|
||||||
password_encode = CRYPTOR.encrypt(password)
|
password_encode = CRYPTOR.encrypt(password)
|
||||||
|
|
|
@ -6,7 +6,10 @@ from contextlib import closing
|
||||||
from io import open as copen
|
from io import open as copen
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
from os.path import basename, dirname, exists, join
|
from os.path import basename, dirname, exists, join
|
||||||
from struct import unpack
|
from struct import unpack
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
|
@ -17,6 +20,7 @@ from jinja2 import FileSystemLoader, Template
|
||||||
from jinja2.environment import Environment
|
from jinja2.environment import Environment
|
||||||
|
|
||||||
from jumpserver.api import BASE_DIR
|
from jumpserver.api import BASE_DIR
|
||||||
|
from jlog.models import Log
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_TEMPLATE = join(BASE_DIR, 'templates', 'jlog', 'static.jinja2')
|
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
|
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()
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ def log_list(request, offset):
|
||||||
|
|
||||||
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
|
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
|
||||||
|
|
||||||
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
|
web_kill_uri = 'http://%s/kill' % WEB_SOCKET_HOST
|
||||||
session_id = request.session.session_key
|
session_id = request.session.session_key
|
||||||
return render_to_response('jlog/log_%s.html' % offset, locals(), context_instance=RequestContext(request))
|
return render_to_response('jlog/log_%s.html' % offset, locals(), context_instance=RequestContext(request))
|
||||||
|
|
|
@ -365,6 +365,16 @@ class MyTask(MyRunner):
|
||||||
self.run("user", module_args, become=True)
|
self.run("user", module_args, become=True)
|
||||||
return self.results
|
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
|
@staticmethod
|
||||||
def gen_sudo_script(role_list, sudo_list):
|
def gen_sudo_script(role_list, sudo_list):
|
||||||
# receive role_list = [role1, role2] sudo_list = [sudo1, sudo2]
|
# receive role_list = [role1, role2] sudo_list = [sudo1, sudo2]
|
||||||
|
|
109
jperm/views.py
109
jperm/views.py
|
@ -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
|
from jumpserver.api import my_render, get_object, CRYPTOR
|
||||||
|
|
||||||
# 设置PERM APP Log
|
# 设置PERM APP Log
|
||||||
from jumpserver.settings import LOG_LEVEL
|
from jumpserver.api import logger
|
||||||
logger = set_log(LOG_LEVEL, filename='jumpserver_perm.log')
|
#logger = set_log(LOG_LEVEL, filename='jumpserver_perm.log')
|
||||||
|
|
||||||
|
|
||||||
@require_role('admin')
|
@require_role('admin')
|
||||||
|
@ -277,7 +277,7 @@ def perm_role_add(request):
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
# 获取参数: name, comment
|
# 获取参数: name, comment
|
||||||
name = request.POST.get("role_name", "")
|
name = request.POST.get("role_name", "").strip()
|
||||||
comment = request.POST.get("role_comment", "")
|
comment = request.POST.get("role_comment", "")
|
||||||
password = request.POST.get("role_password", "")
|
password = request.POST.get("role_password", "")
|
||||||
key_content = request.POST.get("role_key", "")
|
key_content = request.POST.get("role_key", "")
|
||||||
|
@ -286,6 +286,8 @@ def perm_role_add(request):
|
||||||
try:
|
try:
|
||||||
if get_object(PermRole, name=name):
|
if get_object(PermRole, name=name):
|
||||||
raise ServerError(u'已经存在该用户 %s' % name)
|
raise ServerError(u'已经存在该用户 %s' % name)
|
||||||
|
if name == "root":
|
||||||
|
raise ServerError(u'禁止使用root用户作为系统用户,这样非常危险!')
|
||||||
default = get_object(Setting, name='default')
|
default = get_object(Setting, name='default')
|
||||||
|
|
||||||
if password:
|
if password:
|
||||||
|
@ -317,14 +319,37 @@ def perm_role_delete(request):
|
||||||
"""
|
"""
|
||||||
delete role page
|
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":
|
if request.method == "POST":
|
||||||
try:
|
try:
|
||||||
# 获取参数删除的role对象
|
# 获取参数删除的role对象
|
||||||
role_id = request.POST.get("id")
|
role_id = request.POST.get("id")
|
||||||
role = get_object(PermRole, id=role_id)
|
role = get_object(PermRole, id=role_id)
|
||||||
if not role:
|
if not role:
|
||||||
logger.warning(u"Delete Role: %s not exist" % role.name)
|
logger.warning(u"Delete Role: role_id %s not exist" % role_id)
|
||||||
raise ServerError(u"%s 无数据记录" % role.name)
|
raise ServerError(u"role_id %s 无数据记录" % role_id)
|
||||||
role_key = role.key_path
|
role_key = role.key_path
|
||||||
# 删除推送到主机上的role
|
# 删除推送到主机上的role
|
||||||
recycle_assets = [push.asset for push in role.perm_push.all() if push.success]
|
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)
|
recycle_resource = gen_resource(recycle_assets)
|
||||||
task = MyTask(recycle_resource)
|
task = MyTask(recycle_resource)
|
||||||
try:
|
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:
|
except Exception, e:
|
||||||
logger.warning(u"Recycle Role failed: %s" % e)
|
logger.warning(u"Recycle Role failed: %s" % e)
|
||||||
raise ServerError(u"回收已推送的系统用户失败: %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: 判断返回结果,处理异常
|
# TODO: 判断返回结果,处理异常
|
||||||
# 删除存储的秘钥,以及目录
|
# 删除存储的秘钥,以及目录
|
||||||
try:
|
try:
|
||||||
|
@ -423,6 +450,9 @@ def perm_role_edit(request):
|
||||||
if not role:
|
if not role:
|
||||||
raise ServerError('该系统用户不能存在')
|
raise ServerError('该系统用户不能存在')
|
||||||
|
|
||||||
|
if role_name == "root":
|
||||||
|
raise ServerError(u'禁止使用root用户作为系统用户,这样非常危险!')
|
||||||
|
|
||||||
if role_password:
|
if role_password:
|
||||||
encrypt_pass = CRYPTOR.encrypt(role_password)
|
encrypt_pass = CRYPTOR.encrypt(role_password)
|
||||||
role.password = encrypt_pass
|
role.password = encrypt_pass
|
||||||
|
@ -473,6 +503,7 @@ def perm_role_push(request):
|
||||||
for asset_group in asset_groups_obj:
|
for asset_group in asset_groups_obj:
|
||||||
group_assets_obj.extend(asset_group.asset_set.all())
|
group_assets_obj.extend(asset_group.asset_set.all())
|
||||||
calc_assets = list(set(assets_obj) | set(group_assets_obj))
|
calc_assets = list(set(assets_obj) | set(group_assets_obj))
|
||||||
|
|
||||||
push_resource = gen_resource(calc_assets)
|
push_resource = gen_resource(calc_assets)
|
||||||
|
|
||||||
# 调用Ansible API 进行推送
|
# 调用Ansible API 进行推送
|
||||||
|
@ -577,25 +608,28 @@ def perm_sudo_add(request):
|
||||||
"""
|
"""
|
||||||
# 渲染数据
|
# 渲染数据
|
||||||
header_title, path1, path2 = "Sudo命令", "别名管理", "添加别名"
|
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":
|
if not name or not commands:
|
||||||
# 获取参数: name, comment
|
raise ServerError(u"sudo name 和 commands是必填项!")
|
||||||
name = request.POST.get("sudo_name").strip().upper()
|
|
||||||
comment = request.POST.get("sudo_comment").strip()
|
|
||||||
commands = request.POST.get("sudo_commands").strip()
|
|
||||||
|
|
||||||
pattern = re.compile(r'[ \n,\r]')
|
pattern = re.compile(r'[\n,\r]')
|
||||||
commands = ', '.join(list_drop_str(pattern.split(commands), u''))
|
commands = ', '.join(list_drop_str(pattern.split(commands), u''))
|
||||||
logger.debug(u'添加sudo %s: %s' % (name, commands))
|
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
|
|
||||||
# 渲染数据
|
|
||||||
|
|
||||||
|
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)
|
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_id = request.GET.get("id")
|
||||||
sudo = PermSudo.objects.get(id=sudo_id)
|
sudo = PermSudo.objects.get(id=sudo_id)
|
||||||
|
|
||||||
if request.method == "POST":
|
try:
|
||||||
name = request.POST.get("sudo_name").upper()
|
if request.method == "POST":
|
||||||
commands = request.POST.get("sudo_commands")
|
name = request.POST.get("sudo_name").upper()
|
||||||
comment = request.POST.get("sudo_comment")
|
commands = request.POST.get("sudo_commands")
|
||||||
|
comment = request.POST.get("sudo_comment")
|
||||||
|
|
||||||
pattern = re.compile(r'[ \n,\r]')
|
if not name or not commands:
|
||||||
commands = ', '.join(list_drop_str(pattern.split(commands), u'')).strip()
|
raise ServerError(u"sudo name 和 commands是必填项!")
|
||||||
logger.debug(u'添加sudo %s: %s' % (name, commands))
|
|
||||||
|
|
||||||
sudo.name = name.strip()
|
pattern = re.compile(r'[\n,\r]')
|
||||||
sudo.commands = commands
|
commands = ', '.join(list_drop_str(pattern.split(commands), u'')).strip()
|
||||||
sudo.comment = comment
|
logger.debug(u'添加sudo %s: %s' % (name, commands))
|
||||||
sudo.save()
|
|
||||||
|
|
||||||
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)
|
return my_render('jperm/perm_sudo_edit.html', locals(), request)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,6 @@ def get_asset_info(asset):
|
||||||
info = {'hostname': asset.hostname, 'ip': asset.ip}
|
info = {'hostname': asset.hostname, 'ip': asset.ip}
|
||||||
if asset.use_default_auth:
|
if asset.use_default_auth:
|
||||||
if default:
|
if default:
|
||||||
info['port'] = int(default.field2)
|
|
||||||
info['username'] = default.field1
|
info['username'] = default.field1
|
||||||
try:
|
try:
|
||||||
info['password'] = CRYPTOR.decrypt(default.field3)
|
info['password'] = CRYPTOR.decrypt(default.field3)
|
||||||
|
@ -73,9 +72,12 @@ def get_asset_info(asset):
|
||||||
if os.path.isfile(default.field4):
|
if os.path.isfile(default.field4):
|
||||||
info['ssh_key'] = default.field4
|
info['ssh_key'] = default.field4
|
||||||
else:
|
else:
|
||||||
info['port'] = int(asset.port)
|
|
||||||
info['username'] = asset.username
|
info['username'] = asset.username
|
||||||
info['password'] = CRYPTOR.decrypt(asset.password)
|
info['password'] = CRYPTOR.decrypt(asset.password)
|
||||||
|
try:
|
||||||
|
info['port'] = int(asset.port)
|
||||||
|
except TypeError:
|
||||||
|
info['port'] = int(default.field2)
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
|
@ -152,5 +152,6 @@ STATIC_URL = '/static/'
|
||||||
BOOTSTRAP_COLUMN_COUNT = 10
|
BOOTSTRAP_COLUMN_COUNT = 10
|
||||||
|
|
||||||
CRONJOBS = [
|
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'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -344,7 +344,7 @@ def download(request):
|
||||||
def exec_cmd(request):
|
def exec_cmd(request):
|
||||||
role = request.GET.get('role')
|
role = request.GET.get('role')
|
||||||
check_assets = request.GET.get('check_assets', '')
|
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)
|
return my_render('exec_cmd.html', locals(), request)
|
||||||
|
|
||||||
|
|
||||||
|
@ -356,7 +356,7 @@ def web_terminal(request):
|
||||||
if asset:
|
if asset:
|
||||||
print asset
|
print asset
|
||||||
hostname = asset.hostname
|
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())
|
return render_to_response('jlog/web_terminal.html', locals())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -151,8 +151,8 @@ def server_add_user(username, password, ssh_key_pwd='', ssh_key_login_need=True)
|
||||||
add a system user in jumpserver
|
add a system user in jumpserver
|
||||||
在jumpserver服务器上添加一个用户
|
在jumpserver服务器上添加一个用户
|
||||||
"""
|
"""
|
||||||
bash("useradd -s %s/connect.py '%s'; echo '%s'; echo '%s' | passwd --stdin '%s'" %
|
bash("useradd '%s'; echo '%s'; echo '%s:%s' | chpasswd " %
|
||||||
(BASE_DIR, username, password, password, username))
|
(username, password, username, password))
|
||||||
if ssh_key_login_need:
|
if ssh_key_login_need:
|
||||||
gen_ssh_key(username, ssh_key_pwd)
|
gen_ssh_key(username, ssh_key_pwd)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
# from Crypto.PublicKey import RSA
|
# from Crypto.PublicKey import RSA
|
||||||
import uuid
|
import uuid
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from juser.user_api import *
|
from juser.user_api import *
|
||||||
from jperm.perm_api import get_group_user_perm
|
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:
|
if len(UserGroup.objects.filter(name=group_name)) > 1:
|
||||||
raise ServerError(u'%s 用户组已存在' % group_name)
|
raise ServerError(u'%s 用户组已存在' % group_name)
|
||||||
# add user group
|
# 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):
|
for user in User.objects.filter(id__in=users_selected):
|
||||||
user.group.add(UserGroup.objects.get(id=group_id))
|
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.name = group_name
|
||||||
user_group.comment = comment
|
user_group.comment = comment
|
||||||
user_group.save()
|
user_group.save()
|
||||||
except ServerError, e:
|
except ServerError, e:
|
||||||
error = e
|
error = e
|
||||||
|
|
||||||
if not error:
|
if not error:
|
||||||
return HttpResponseRedirect(reverse('user_group_list'))
|
return HttpResponseRedirect(reverse('user_group_list'))
|
||||||
else:
|
else:
|
||||||
|
@ -313,6 +309,12 @@ def reset_password(request):
|
||||||
hash_encode = request.GET.get('hash', '')
|
hash_encode = request.GET.get('hash', '')
|
||||||
action = '/juser/password/reset/?uuid=%s×tamp=%s&hash=%s' % (uuid_r, timestamp, hash_encode)
|
action = '/juser/password/reset/?uuid=%s×tamp=%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':
|
if request.method == 'POST':
|
||||||
password = request.POST.get('password')
|
password = request.POST.get('password')
|
||||||
password_confirm = request.POST.get('password_confirm')
|
password_confirm = request.POST.get('password_confirm')
|
||||||
|
@ -328,11 +330,8 @@ def reset_password(request):
|
||||||
else:
|
else:
|
||||||
return HttpResponse('用户不存在')
|
return HttpResponse('用户不存在')
|
||||||
|
|
||||||
if hash_encode == PyCrypt.md5_crypt(uuid_r + timestamp + KEY):
|
else:
|
||||||
if int(time.time()) - int(timestamp) > 600:
|
return render_to_response('juser/reset_password.html', locals())
|
||||||
return http_error(request, u'链接已超时')
|
|
||||||
else:
|
|
||||||
return render_to_response('juser/reset_password.html', locals())
|
|
||||||
|
|
||||||
return http_error(request, u'错误请求')
|
return http_error(request, u'错误请求')
|
||||||
|
|
||||||
|
|
|
@ -230,15 +230,14 @@ class ExecHandler(tornado.websocket.WebSocketHandler):
|
||||||
def on_message(self, message):
|
def on_message(self, message):
|
||||||
data = json.loads(message)
|
data = json.loads(message)
|
||||||
pattern = data.get('pattern', '')
|
pattern = data.get('pattern', '')
|
||||||
command = data.get('command', '')
|
self.command = data.get('command', '')
|
||||||
asset_name_str = ''
|
self.asset_name_str = ''
|
||||||
if pattern and command:
|
if pattern and self.command:
|
||||||
for inv in self.runner.inventory.get_hosts(pattern=pattern):
|
for inv in self.runner.inventory.get_hosts(pattern=pattern):
|
||||||
asset_name_str += '%s ' % inv.name
|
self.asset_name_str += '%s ' % inv.name
|
||||||
self.write_message('匹配主机: ' + asset_name_str)
|
self.write_message('匹配主机: ' + self.asset_name_str)
|
||||||
self.write_message('<span style="color: yellow">Ansible> %s</span>\n\n' % command)
|
self.write_message('<span style="color: yellow">Ansible> %s</span>\n\n' % self.command)
|
||||||
self.__class__.tasks.append(MyThread(target=self.run_cmd, args=(command, pattern)))
|
self.__class__.tasks.append(MyThread(target=self.run_cmd, args=(self.command, pattern)))
|
||||||
ExecLog(host=asset_name_str, cmd=command, user=self.user.username, remote_ip=self.remote_ip).save()
|
|
||||||
|
|
||||||
for t in self.__class__.tasks:
|
for t in self.__class__.tasks:
|
||||||
if t.is_alive():
|
if t.is_alive():
|
||||||
|
@ -251,11 +250,12 @@ class ExecHandler(tornado.websocket.WebSocketHandler):
|
||||||
|
|
||||||
def run_cmd(self, command, pattern):
|
def run_cmd(self, command, pattern):
|
||||||
self.runner.run('shell', command, pattern=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')
|
newline_pattern = re.compile(r'\n')
|
||||||
for k, v in self.runner.results.items():
|
for k, v in self.runner.results.items():
|
||||||
for host, output in v.items():
|
for host, output in v.items():
|
||||||
output = newline_pattern.sub('<br />', output)
|
output = newline_pattern.sub('<br />', output)
|
||||||
logger.debug(output)
|
|
||||||
if k == 'ok':
|
if k == 'ok':
|
||||||
header = "<span style='color: green'>[ %s => %s]</span>\n" % (host, 'Ok')
|
header = "<span style='color: green'>[ %s => %s]</span>\n" % (host, 'Ok')
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -23,7 +23,13 @@
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<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);
|
var ws = new WebSocket(wsUri);
|
||||||
|
|
||||||
function createSystemMessage(message) {
|
function createSystemMessage(message) {
|
||||||
|
|
|
@ -224,6 +224,7 @@
|
||||||
}
|
}
|
||||||
else if (dataArray.length == 1 && data != 'error' && navigator.platform == 'Win32'){
|
else if (dataArray.length == 1 && data != 'error' && navigator.platform == 'Win32'){
|
||||||
var title = 'Jumpserver Web Terminal' + '<span class="text-info"> '+ hostname +'</span>';
|
var title = 'Jumpserver Web Terminal' + '<span class="text-info"> '+ hostname +'</span>';
|
||||||
|
/*
|
||||||
layer.open({
|
layer.open({
|
||||||
type: 2,
|
type: 2,
|
||||||
title: title,
|
title: title,
|
||||||
|
@ -232,8 +233,11 @@
|
||||||
area: ['628px', '420px'],
|
area: ['628px', '420px'],
|
||||||
content: new_url+data
|
content: new_url+data
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
window.open(new_url+data, '', 'width=628px, height=380px');
|
||||||
} else if (dataArray.length == 1 && data != 'error'){
|
} else if (dataArray.length == 1 && data != 'error'){
|
||||||
layer.open({
|
/*
|
||||||
|
layer.open({
|
||||||
type: 2,
|
type: 2,
|
||||||
title: title,
|
title: title,
|
||||||
maxmin: true,
|
maxmin: true,
|
||||||
|
@ -241,6 +245,8 @@
|
||||||
area: ['628px', '452px'],
|
area: ['628px', '452px'],
|
||||||
content: new_url+data
|
content: new_url+data
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
window.open(new_url+data, '', 'width=628px, height=452px')
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
aUrl = '';
|
aUrl = '';
|
||||||
|
@ -266,6 +272,7 @@
|
||||||
var hostname = $(a).attr('value');
|
var hostname = $(a).attr('value');
|
||||||
var title = 'Jumpserver Web Terminal - ' + '<span class="text-info"> '+ hostname +'</span>';
|
var title = 'Jumpserver Web Terminal - ' + '<span class="text-info"> '+ hostname +'</span>';
|
||||||
if (navigator.platform == 'Win32'){
|
if (navigator.platform == 'Win32'){
|
||||||
|
/*
|
||||||
layer.open({
|
layer.open({
|
||||||
type: 2,
|
type: 2,
|
||||||
title: title,
|
title: title,
|
||||||
|
@ -274,8 +281,11 @@
|
||||||
shade: false,
|
shade: false,
|
||||||
content: new_url
|
content: new_url
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
window.open(new_url+data, '', 'width=628px, height=380px');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
/*
|
||||||
layer.open({
|
layer.open({
|
||||||
type: 2,
|
type: 2,
|
||||||
title: title,
|
title: title,
|
||||||
|
@ -284,6 +294,8 @@
|
||||||
shade: false,
|
shade: false,
|
||||||
content: new_url
|
content: new_url
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
window.open(new_url+data, '', 'width=628px, height=452px');
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -351,4 +363,4 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -243,8 +243,9 @@
|
||||||
area: ['628px', '420px'],
|
area: ['628px', '420px'],
|
||||||
content: new_url+data
|
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'){
|
} else if (dataArray.length == 1 && data != 'error'){
|
||||||
/*layer.open({
|
/*layer.open({
|
||||||
type: 2,
|
type: 2,
|
||||||
|
@ -255,7 +256,7 @@
|
||||||
content: new_url+data
|
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 {
|
else {
|
||||||
|
@ -292,7 +293,7 @@
|
||||||
content: new_url
|
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 {
|
} else {
|
||||||
/*
|
/*
|
||||||
|
@ -305,7 +306,7 @@
|
||||||
content: new_url
|
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
|
return false
|
||||||
|
@ -429,4 +430,4 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -136,14 +136,20 @@
|
||||||
{# })#}
|
{# })#}
|
||||||
{# });#}
|
{# });#}
|
||||||
function init(obj){
|
function init(obj){
|
||||||
|
var protocol = "ws://";
|
||||||
|
if (window.location.protocol == 'https:') {
|
||||||
|
protocol = 'wss://';
|
||||||
|
}
|
||||||
|
|
||||||
var file_path = obj.attr('file_path');
|
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 socket = new WebSocket(wsUri + '?file_path=' + file_path);
|
||||||
|
|
||||||
var term = new Terminal({
|
var term = new Terminal({
|
||||||
cols: 80,
|
cols: 80,
|
||||||
rows: 24,
|
rows: 24,
|
||||||
screenKeys: false
|
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>');
|
var tag = $('<div id="term" style="height:500px; overflow: auto;background-color: rgba(0, 0, 0, 0);border: none"></div>');
|
||||||
|
|
|
@ -43,7 +43,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
WSSHClient.prototype.connect = function(options) {
|
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) {
|
if (window.WebSocket) {
|
||||||
this._connection = new WebSocket(endpoint);
|
this._connection = new WebSocket(endpoint);
|
||||||
|
@ -112,7 +117,7 @@
|
||||||
$('.terminal').css('width', window.innerWidth-25);
|
$('.terminal').css('width', window.innerWidth-25);
|
||||||
console.log(window.innerWidth);
|
console.log(window.innerWidth);
|
||||||
console.log(window.innerWidth-10);
|
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;
|
var cols = Math.floor(window.innerWidth/colWidth) - 1;
|
||||||
|
|
||||||
return {rows: rows, cols: cols};
|
return {rows: rows, cols: cols};
|
||||||
|
|
|
@ -92,11 +92,9 @@ $('#roleForm').validator({
|
||||||
timely: 2,
|
timely: 2,
|
||||||
theme: "yellow_right_effect",
|
theme: "yellow_right_effect",
|
||||||
rules: {
|
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填写有误,请检查'],
|
check_begin: [/^[\-]+BEGIN R|DSA PRIVATE KEY[\-]+/gm, 'RSA|DSA Key填写有误,请检查']
|
||||||
{# either: function(){#}
|
|
||||||
{# return $('#role_password').val() == ''#}
|
|
||||||
{# }#}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -110,13 +108,8 @@ $('#roleForm').validator({
|
||||||
rule: "check_begin",
|
rule: "check_begin",
|
||||||
ok: "",
|
ok: "",
|
||||||
empty: true
|
empty: true
|
||||||
},
|
}
|
||||||
{# "role_key": {#}
|
|
||||||
{# rule: "required(either)",#}
|
|
||||||
{# tip: "输入密钥",#}
|
|
||||||
{# ok: "",#}
|
|
||||||
{# msg: {required: "密码和密钥必填一个!"}#}
|
|
||||||
{# }#}
|
|
||||||
},
|
},
|
||||||
valid: function(form) {
|
valid: function(form) {
|
||||||
form.submit();
|
form.submit();
|
||||||
|
|
|
@ -94,16 +94,24 @@ $('#roleForm').validator({
|
||||||
timely: 2,
|
timely: 2,
|
||||||
theme: "yellow_right_effect",
|
theme: "yellow_right_effect",
|
||||||
rules: {
|
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: {
|
fields: {
|
||||||
"role_name": {
|
"role_name": {
|
||||||
rule: "required;check_name",
|
rule: "required;check_name;check_name_root",
|
||||||
tip: "输入系统用户名称",
|
tip: "输入系统用户名称",
|
||||||
ok: "",
|
ok: "",
|
||||||
msg: {required: "系统用户名称必填"}
|
msg: {required: "系统用户名称必填"}
|
||||||
}
|
},
|
||||||
|
"role_key": {
|
||||||
|
rule: "check_begin",
|
||||||
|
ok: "",
|
||||||
|
empty: true
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
valid: function(form) {
|
valid: function(form) {
|
||||||
form.submit();
|
form.submit();
|
||||||
|
|
|
@ -84,22 +84,43 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function remove_role(role_id){
|
function remove_role(role_id){
|
||||||
if (confirm("确认删除")) {
|
$.ajax({
|
||||||
$.ajax({
|
type: "GET",
|
||||||
type: "POST",
|
url: "{% url 'role_del' %}",
|
||||||
url: "{% url 'role_del' %}",
|
data: {id: role_id, filter_type: 'recycle_assets'},
|
||||||
data: "id=" + role_id,
|
success: function(data) {
|
||||||
success: function(msg){
|
console.log(data)
|
||||||
alert( "成功: " + msg );
|
if (data) {
|
||||||
var del_row = $('tbody#edittbody>tr#' + role_id);
|
msg = data + "的系统用户会被删除,包括其家目录,请谨慎操作!"
|
||||||
del_row.remove()
|
}
|
||||||
},
|
else {
|
||||||
error: function (msg) {
|
msg = "该角色无已推送的系统用户, 可以安全删除"
|
||||||
console.log(msg);
|
}
|
||||||
alert("失败: " + msg.responseText)
|
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>
|
</script>
|
||||||
|
|
|
@ -112,7 +112,28 @@ var config = {
|
||||||
for (var selector in config) {
|
for (var selector in config) {
|
||||||
$(selector).chosen(config[selector]);
|
$(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>
|
||||||
<script src="/static/js/cropper/cropper.min.js"></script>
|
<script src="/static/js/cropper/cropper.min.js"></script>
|
||||||
<script src="/static/js/datapicker/bootstrap-datepicker.js"></script>
|
<script src="/static/js/datapicker/bootstrap-datepicker.js"></script>
|
||||||
|
|
|
@ -46,7 +46,7 @@ add_role_chosen() {
|
||||||
|
|
||||||
|
|
||||||
check_syntax(){
|
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
|
cp $real_file $tmp_file && add_cmd_alias $tmp_file && add_role_chosen $tmp_file || exit 1
|
||||||
|
|
|
@ -131,7 +131,7 @@ $('#userForm').validator({
|
||||||
timely: 2,
|
timely: 2,
|
||||||
theme: "yellow_right_effect",
|
theme: "yellow_right_effect",
|
||||||
rules: {
|
rules: {
|
||||||
check_username: [/^\w{3,20}$/, '大小写字母数字和下划线'],
|
check_username: [/^[\w.]{3,20}$/, '大小写字母数字和下划线小数点'],
|
||||||
type_m: function(element){
|
type_m: function(element){
|
||||||
return $("#M").is(":checked");
|
return $("#M").is(":checked");
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,6 +99,7 @@
|
||||||
autoProcessQueue: false,
|
autoProcessQueue: false,
|
||||||
uploadMultiple: true,
|
uploadMultiple: true,
|
||||||
parallelUploads: 100,
|
parallelUploads: 100,
|
||||||
|
maxFilesize: 2048,
|
||||||
maxFiles: 100,
|
maxFiles: 100,
|
||||||
url: '/file/upload/',
|
url: '/file/upload/',
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue