mirror of https://github.com/jumpserver/jumpserver
jlog
parent
72a18d6abf
commit
9366003f7b
71
connect.py
71
connect.py
|
@ -19,7 +19,6 @@ if django.get_version() != '1.6':
|
||||||
django.setup()
|
django.setup()
|
||||||
from jumpserver.api import ServerError, User, Asset, Jtty, get_object
|
from jumpserver.api import ServerError, User, Asset, Jtty, get_object
|
||||||
from jumpserver.api import logger
|
from jumpserver.api import logger
|
||||||
from jumpserver.api import BisGroup as AssetGroup
|
|
||||||
|
|
||||||
login_user = get_object(User, username=getpass.getuser())
|
login_user = get_object(User, username=getpass.getuser())
|
||||||
|
|
||||||
|
@ -98,76 +97,6 @@ def print_prompt():
|
||||||
print textwrap.dedent(msg)
|
print textwrap.dedent(msg)
|
||||||
|
|
||||||
|
|
||||||
# def remote_exec_cmd(ip, port, username, password, cmd):
|
|
||||||
# try:
|
|
||||||
# time.sleep(5)
|
|
||||||
# ssh = paramiko.SSHClient()
|
|
||||||
# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
# ssh.connect(ip, port, username, password, timeout=5)
|
|
||||||
# stdin, stdout, stderr = ssh.exec_command("bash -l -c '%s'" % cmd)
|
|
||||||
# out = stdout.readlines()
|
|
||||||
# err = stderr.readlines()
|
|
||||||
# color_print('%s:' % ip, 'blue')
|
|
||||||
# for i in out:
|
|
||||||
# color_print(" " * 4 + i.strip(), 'green')
|
|
||||||
# for j in err:
|
|
||||||
# color_print(" " * 4 + j.strip(), 'red')
|
|
||||||
# ssh.close()
|
|
||||||
# except Exception as e:
|
|
||||||
# color_print(ip + ':', 'blue')
|
|
||||||
# color_print(str(e), 'red')
|
|
||||||
|
|
||||||
|
|
||||||
# def multi_remote_exec_cmd(hosts, username, cmd):
|
|
||||||
# pool = Pool(processes=5)
|
|
||||||
# for host in hosts:
|
|
||||||
# username, password, ip, port = get_connect_item(username, host)
|
|
||||||
# pool.apply_async(remote_exec_cmd, (ip, port, username, password, cmd))
|
|
||||||
# pool.close()
|
|
||||||
# pool.join()
|
|
||||||
|
|
||||||
|
|
||||||
# def exec_cmd_servers(username):
|
|
||||||
# color_print("You can choose in the following IP(s), Use glob or ips split by comma. q/Q to PreLayer.", 'green')
|
|
||||||
# user.get_asset_info(printable=True)
|
|
||||||
# while True:
|
|
||||||
# hosts = []
|
|
||||||
# inputs = raw_input('\033[1;32mip(s)>: \033[0m')
|
|
||||||
# if inputs in ['q', 'Q']:
|
|
||||||
# break
|
|
||||||
# get_hosts = login_user.get_asset_info().keys()
|
|
||||||
#
|
|
||||||
# if ',' in inputs:
|
|
||||||
# ips_input = inputs.split(',')
|
|
||||||
# for host in ips_input:
|
|
||||||
# if host in get_hosts:
|
|
||||||
# hosts.append(host)
|
|
||||||
# else:
|
|
||||||
# for host in get_hosts:
|
|
||||||
# if fnmatch.fnmatch(host, inputs):
|
|
||||||
# hosts.append(host.strip())
|
|
||||||
#
|
|
||||||
# if len(hosts) == 0:
|
|
||||||
# color_print("Check again, Not matched any ip!", 'red')
|
|
||||||
# continue
|
|
||||||
# else:
|
|
||||||
# print "You matched ip: %s" % hosts
|
|
||||||
# color_print("Input the Command , The command will be Execute on servers, q/Q to quit.", 'green')
|
|
||||||
# while True:
|
|
||||||
# cmd = raw_input('\033[1;32mCmd(s): \033[0m')
|
|
||||||
# if cmd in ['q', 'Q']:
|
|
||||||
# break
|
|
||||||
# exec_log_dir = os.path.join(log_dir, 'exec_cmds')
|
|
||||||
# if not os.path.isdir(exec_log_dir):
|
|
||||||
# os.mkdir(exec_log_dir)
|
|
||||||
# os.chmod(exec_log_dir, 0777)
|
|
||||||
# filename = "%s/%s.log" % (exec_log_dir, time.strftime('%Y%m%d'))
|
|
||||||
# f = open(filename, 'a')
|
|
||||||
# f.write("DateTime: %s User: %s Host: %s Cmds: %s\n" %
|
|
||||||
# (time.strftime('%Y/%m/%d %H:%M:%S'), username, hosts, cmd))
|
|
||||||
# multi_remote_exec_cmd(hosts, username, cmd)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
"""
|
||||||
he he
|
he he
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
# coding: utf-8
|
||||||
|
|
||||||
|
|
||||||
|
from argparse import ArgumentParser, FileType
|
||||||
|
from contextlib import closing
|
||||||
|
from codecs import open as copen
|
||||||
|
from json import dumps
|
||||||
|
from math import ceil
|
||||||
|
from os.path import basename, dirname, exists, join
|
||||||
|
from struct import unpack
|
||||||
|
from subprocess import Popen
|
||||||
|
from sys import platform, prefix, stderr
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
|
from jinja2 import FileSystemLoader, Template
|
||||||
|
from jinja2.environment import Environment
|
||||||
|
|
||||||
|
from jumpserver.api import BASE_DIR
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_TEMPLATE = join(BASE_DIR, 'templates', 'jlog', 'static.jinja2')
|
||||||
|
|
||||||
|
|
||||||
|
def escapeString(string):
|
||||||
|
string = string.encode('unicode_escape').decode('utf-8')
|
||||||
|
string = string.replace("'", "\\'")
|
||||||
|
string = '\'' + string + '\''
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
def getTiming(timef):
|
||||||
|
timing = None
|
||||||
|
with closing(timef):
|
||||||
|
timing = [l.strip().split(' ') for l in timef]
|
||||||
|
timing = [(int(ceil(float(r[0]) * 1000)), int(r[1])) for r in timing]
|
||||||
|
return timing
|
||||||
|
|
||||||
|
|
||||||
|
def scriptToJSON(scriptf, timing=None):
|
||||||
|
ret = []
|
||||||
|
|
||||||
|
with closing(scriptf):
|
||||||
|
scriptf.readline() # ignore first header line from script file
|
||||||
|
offset = 0
|
||||||
|
for t in timing:
|
||||||
|
data = escapeString(scriptf.read(t[1]))
|
||||||
|
offset += t[0]
|
||||||
|
ret.append((data, offset))
|
||||||
|
return dumps(ret)
|
||||||
|
|
||||||
|
|
||||||
|
def renderTemplate(script_path, time_file_path, dimensions=(24, 60), templatename=DEFAULT_TEMPLATE):
|
||||||
|
with copen(script_path, encoding='utf-8', errors='replace') as scriptf:
|
||||||
|
with open(time_file_path) as timef:
|
||||||
|
timing = getTiming(timef)
|
||||||
|
json = scriptToJSON(scriptf, timing)
|
||||||
|
|
||||||
|
fsl = FileSystemLoader(dirname(templatename), 'utf-8')
|
||||||
|
e = Environment()
|
||||||
|
e.loader = fsl
|
||||||
|
|
||||||
|
templatename = basename(templatename)
|
||||||
|
rendered = e.get_template(templatename).render(json=json,
|
||||||
|
dimensions=dimensions)
|
||||||
|
|
||||||
|
return rendered
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,10 @@ class Log(models.Model):
|
||||||
user = models.CharField(max_length=20, null=True)
|
user = models.CharField(max_length=20, null=True)
|
||||||
host = models.CharField(max_length=20, null=True)
|
host = models.CharField(max_length=20, null=True)
|
||||||
remote_ip = models.CharField(max_length=100)
|
remote_ip = models.CharField(max_length=100)
|
||||||
dept_name = models.CharField(max_length=20)
|
|
||||||
log_path = models.CharField(max_length=100)
|
log_path = models.CharField(max_length=100)
|
||||||
start_time = models.DateTimeField(null=True)
|
start_time = models.DateTimeField(null=True)
|
||||||
pid = models.IntegerField(max_length=10)
|
pid = models.IntegerField(max_length=10)
|
||||||
is_finished = models.BooleanField(default=False)
|
is_finished = models.BooleanField(default=False)
|
||||||
handle_finished = models.BooleanField(default=False)
|
|
||||||
end_time = models.DateTimeField(null=True)
|
end_time = models.DateTimeField(null=True)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
|
|
|
@ -5,7 +5,7 @@ from jlog.views import *
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^$', log_list),
|
url(r'^$', log_list),
|
||||||
url(r'^log_list/(\w+)/$', log_list),
|
url(r'^log_list/(\w+)/$', log_list),
|
||||||
url(r'^log_kill/', log_kill),
|
# url(r'^log_kill/', log_kill),
|
||||||
url(r'^history/$', log_history),
|
# url(r'^history/$', log_history),
|
||||||
url(r'^search/$', log_search),
|
# url(r'^search/$', log_search),
|
||||||
)
|
)
|
184
jlog/views.py
184
jlog/views.py
|
@ -4,117 +4,103 @@ from django.template import RequestContext
|
||||||
from django.shortcuts import render_to_response
|
from django.shortcuts import render_to_response
|
||||||
|
|
||||||
from jumpserver.api import *
|
from jumpserver.api import *
|
||||||
from jasset.views import httperror
|
|
||||||
from django.http import HttpResponseNotFound
|
from django.http import HttpResponseNotFound
|
||||||
|
|
||||||
CONF = ConfigParser()
|
CONF = ConfigParser()
|
||||||
CONF.read('%s/jumpserver.conf' % BASE_DIR)
|
CONF.read('%s/jumpserver.conf' % BASE_DIR)
|
||||||
|
from jlog.models import Log
|
||||||
|
|
||||||
|
# def get_user_info(request, offset):
|
||||||
|
# """ 获取用户信息及环境 """
|
||||||
|
# env_dic = {'online': 0, 'offline': 1}
|
||||||
|
# env = env_dic[offset]
|
||||||
|
# keyword = request.GET.get('keyword', '')
|
||||||
|
# user_info = get_session_user_info(request)
|
||||||
|
# user_id, username = user_info[0:2]
|
||||||
|
# dept_id, dept_name = user_info[3:5]
|
||||||
|
# ret = [request, keyword, env, username, dept_name]
|
||||||
|
#
|
||||||
|
# return ret
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def get_user_log(ret_list):
|
||||||
|
# """ 获取不同类型用户日志记录 """
|
||||||
|
# request, keyword, env, username, dept_name = ret_list
|
||||||
|
# post_all = Log.objects.filter(is_finished=env).order_by('-start_time')
|
||||||
|
# post_keyword_all = Log.objects.filter(Q(user__contains=keyword) |
|
||||||
|
# Q(host__contains=keyword)) \
|
||||||
|
# .filter(is_finished=env).order_by('-start_time')
|
||||||
|
#
|
||||||
|
# if keyword:
|
||||||
|
# posts = post_keyword_all
|
||||||
|
# else:
|
||||||
|
# posts = post_all
|
||||||
|
#
|
||||||
|
# return posts
|
||||||
|
|
||||||
|
|
||||||
def get_user_info(request, offset):
|
|
||||||
""" 获取用户信息及环境 """
|
|
||||||
env_dic = {'online': 0, 'offline': 1}
|
|
||||||
env = env_dic[offset]
|
|
||||||
keyword = request.GET.get('keyword', '')
|
|
||||||
user_info = get_session_user_info(request)
|
|
||||||
user_id, username = user_info[0:2]
|
|
||||||
dept_id, dept_name = user_info[3:5]
|
|
||||||
ret = [request, keyword, env, username, dept_name]
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_log(ret_list):
|
|
||||||
""" 获取不同类型用户日志记录 """
|
|
||||||
request, keyword, env, username, dept_name = ret_list
|
|
||||||
post_all = Log.objects.filter(is_finished=env).order_by('-start_time')
|
|
||||||
post_keyword_all = Log.objects.filter(Q(user__contains=keyword) |
|
|
||||||
Q(host__contains=keyword)) \
|
|
||||||
.filter(is_finished=env).order_by('-start_time')
|
|
||||||
|
|
||||||
if is_super_user(request):
|
|
||||||
if keyword:
|
|
||||||
posts = post_keyword_all
|
|
||||||
else:
|
|
||||||
posts = post_all
|
|
||||||
|
|
||||||
elif is_group_admin(request):
|
|
||||||
if keyword:
|
|
||||||
posts = post_keyword_all.filter(dept_name=dept_name)
|
|
||||||
else:
|
|
||||||
posts = post_all.filter(dept_name=dept_name)
|
|
||||||
|
|
||||||
elif is_common_user(request):
|
|
||||||
if keyword:
|
|
||||||
posts = post_keyword_all.filter(user=username)
|
|
||||||
else:
|
|
||||||
posts = post_all.filter(user=username)
|
|
||||||
|
|
||||||
return posts
|
|
||||||
|
|
||||||
|
|
||||||
@require_login
|
|
||||||
def log_list(request, offset):
|
def log_list(request, offset):
|
||||||
""" 显示日志 """
|
""" 显示日志 """
|
||||||
header_title, path1, path2 = u'查看日志', u'查看日志', u'在线用户'
|
header_title, path1, path2 = u'查看日志', u'查看日志', u'在线用户'
|
||||||
keyword = request.GET.get('keyword', '')
|
keyword = request.GET.get('keyword', '')
|
||||||
web_socket_host = CONF.get('websocket', 'web_socket_host')
|
web_socket_host = CONF.get('websocket', 'web_socket_host')
|
||||||
posts = get_user_log(get_user_info(request, offset))
|
# posts = get_user_log(get_user_info(request, offset))
|
||||||
|
if offset == 'online':
|
||||||
|
posts = Log.objects.filter(is_finished=False).order_by('-start_time')
|
||||||
|
else:
|
||||||
|
posts = Log.objects.filter(is_finished=True).order_by('-start_time')
|
||||||
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)
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
|
#
|
||||||
@require_admin
|
# def log_kill(request):
|
||||||
def log_kill(request):
|
# """ 杀掉connect进程 """
|
||||||
""" 杀掉connect进程 """
|
# pid = request.GET.get('id', '')
|
||||||
pid = request.GET.get('id', '')
|
# log = Log.objects.filter(pid=pid)
|
||||||
log = Log.objects.filter(pid=pid)
|
# if log:
|
||||||
if log:
|
# log = log[0]
|
||||||
log = log[0]
|
# dept_name = log.dept_name
|
||||||
dept_name = log.dept_name
|
# deptname = get_session_user_info(request)[4]
|
||||||
deptname = get_session_user_info(request)[4]
|
# if is_group_admin(request) and dept_name != deptname:
|
||||||
if is_group_admin(request) and dept_name != deptname:
|
# return httperror(request, u'Kill失败, 您无权操作!')
|
||||||
return httperror(request, u'Kill失败, 您无权操作!')
|
# try:
|
||||||
try:
|
# os.kill(int(pid), 9)
|
||||||
os.kill(int(pid), 9)
|
# except OSError:
|
||||||
except OSError:
|
# pass
|
||||||
pass
|
# Log.objects.filter(pid=pid).update(is_finished=1, end_time=datetime.datetime.now())
|
||||||
Log.objects.filter(pid=pid).update(is_finished=1, end_time=datetime.datetime.now())
|
# return render_to_response('jlog/log_offline.html', locals(), context_instance=RequestContext(request))
|
||||||
return render_to_response('jlog/log_offline.html', locals(), context_instance=RequestContext(request))
|
# else:
|
||||||
else:
|
# return HttpResponseNotFound(u'没有此进程!')
|
||||||
return HttpResponseNotFound(u'没有此进程!')
|
#
|
||||||
|
#
|
||||||
|
# def log_history(request):
|
||||||
@require_login
|
# """ 命令历史记录 """
|
||||||
def log_history(request):
|
# log_id = request.GET.get('id', 0)
|
||||||
""" 命令历史记录 """
|
# log = Log.objects.filter(id=int(log_id))
|
||||||
log_id = request.GET.get('id', 0)
|
# if log:
|
||||||
log = Log.objects.filter(id=int(log_id))
|
# log = log[0]
|
||||||
if log:
|
# dept_name = log.dept_name
|
||||||
log = log[0]
|
# deptname = get_session_user_info(request)[4]
|
||||||
dept_name = log.dept_name
|
# if is_group_admin(request) and dept_name != deptname:
|
||||||
deptname = get_session_user_info(request)[4]
|
# return httperror(request, '查看失败, 您无权查看!')
|
||||||
if is_group_admin(request) and dept_name != deptname:
|
#
|
||||||
return httperror(request, '查看失败, 您无权查看!')
|
# elif is_common_user(request):
|
||||||
|
# return httperror(request, '查看失败, 您无权查看!')
|
||||||
elif is_common_user(request):
|
#
|
||||||
return httperror(request, '查看失败, 您无权查看!')
|
# log_his = "%s.his" % log.log_path
|
||||||
|
# if os.path.isfile(log_his):
|
||||||
log_his = "%s.his" % log.log_path
|
# f = open(log_his)
|
||||||
if os.path.isfile(log_his):
|
# content = f.read()
|
||||||
f = open(log_his)
|
# return HttpResponse(content)
|
||||||
content = f.read()
|
# else:
|
||||||
return HttpResponse(content)
|
# return httperror(request, '无日志记录, 请查看日志处理脚本是否开启!')
|
||||||
else:
|
#
|
||||||
return httperror(request, '无日志记录, 请查看日志处理脚本是否开启!')
|
#
|
||||||
|
# def log_search(request):
|
||||||
|
# """ 日志搜索 """
|
||||||
@require_login
|
# offset = request.GET.get('env', '')
|
||||||
def log_search(request):
|
# keyword = request.GET.get('keyword', '')
|
||||||
""" 日志搜索 """
|
# posts = get_user_log(get_user_info(request, offset))
|
||||||
offset = request.GET.get('env', '')
|
# contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
|
||||||
keyword = request.GET.get('keyword', '')
|
# return render_to_response('jlog/log_search.html', locals(), context_instance=RequestContext(request))
|
||||||
posts = get_user_log(get_user_info(request, offset))
|
|
||||||
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
|
|
||||||
return render_to_response('jlog/log_search.html', locals(), context_instance=RequestContext(request))
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import random
|
||||||
import subprocess
|
import subprocess
|
||||||
import paramiko
|
import paramiko
|
||||||
import struct, fcntl, signal,socket, select, fnmatch
|
import struct, fcntl, signal,socket, select, fnmatch
|
||||||
|
import re
|
||||||
|
|
||||||
from django.core.paginator import Paginator, EmptyPage, InvalidPage
|
from django.core.paginator import Paginator, EmptyPage, InvalidPage
|
||||||
from django.http import HttpResponse, Http404
|
from django.http import HttpResponse, Http404
|
||||||
|
@ -69,75 +70,6 @@ def set_log(level):
|
||||||
return logger_f
|
return logger_f
|
||||||
|
|
||||||
|
|
||||||
# class LDAPMgmt():
|
|
||||||
# """
|
|
||||||
# LDAP class for add, select, del, update
|
|
||||||
# LDAP 管理类,增删改查
|
|
||||||
# """
|
|
||||||
# def __init__(self,
|
|
||||||
# host_url,
|
|
||||||
# base_dn,
|
|
||||||
# root_cn,
|
|
||||||
# root_pw):
|
|
||||||
# self.ldap_host = host_url
|
|
||||||
# self.ldap_base_dn = base_dn
|
|
||||||
# self.conn = ldap.initialize(host_url)
|
|
||||||
# self.conn.set_option(ldap.OPT_REFERRALS, 0)
|
|
||||||
# self.conn.protocol_version = ldap.VERSION3
|
|
||||||
# self.conn.simple_bind_s(root_cn, root_pw)
|
|
||||||
#
|
|
||||||
# def list(self, filter, scope=ldap.SCOPE_SUBTREE, attr=None):
|
|
||||||
# """
|
|
||||||
# query
|
|
||||||
# 查询
|
|
||||||
# """
|
|
||||||
# result = {}
|
|
||||||
# try:
|
|
||||||
# ldap_result = self.conn.search_s(self.ldap_base_dn, scope, filter, attr)
|
|
||||||
# for entry in ldap_result:
|
|
||||||
# name, data = entry
|
|
||||||
# for k, v in data.items():
|
|
||||||
# print '%s: %s' % (k, v)
|
|
||||||
# result[k] = v
|
|
||||||
# return result
|
|
||||||
# except ldap.LDAPError, e:
|
|
||||||
# print e
|
|
||||||
#
|
|
||||||
# def add(self, dn, attrs):
|
|
||||||
# """
|
|
||||||
# add
|
|
||||||
# 添加
|
|
||||||
# """
|
|
||||||
# try:
|
|
||||||
# ldif = modlist.addModlist(attrs)
|
|
||||||
# self.conn.add_s(dn, ldif)
|
|
||||||
# except ldap.LDAPError, e:
|
|
||||||
# print e
|
|
||||||
#
|
|
||||||
# def modify(self, dn, attrs):
|
|
||||||
# """
|
|
||||||
# modify
|
|
||||||
# 更改
|
|
||||||
# """
|
|
||||||
# try:
|
|
||||||
# attr_s = []
|
|
||||||
# for k, v in attrs.items():
|
|
||||||
# attr_s.append((2, k, v))
|
|
||||||
# self.conn.modify_s(dn, attr_s)
|
|
||||||
# except ldap.LDAPError, e:
|
|
||||||
# print e
|
|
||||||
#
|
|
||||||
# def delete(self, dn):
|
|
||||||
# """
|
|
||||||
# delete
|
|
||||||
# 删除
|
|
||||||
# """
|
|
||||||
# try:
|
|
||||||
# self.conn.delete_s(dn)
|
|
||||||
# except ldap.LDAPError, e:
|
|
||||||
# print e
|
|
||||||
|
|
||||||
|
|
||||||
def page_list_return(total, current=1):
|
def page_list_return(total, current=1):
|
||||||
"""
|
"""
|
||||||
page
|
page
|
||||||
|
@ -181,17 +113,46 @@ def pages(post_objects, request):
|
||||||
return post_objects, paginator, page_objects, page_range, current_page, show_first, show_end
|
return post_objects, paginator, page_objects, page_range, current_page, show_first, show_end
|
||||||
|
|
||||||
|
|
||||||
|
def remove_control_char(str_r):
|
||||||
|
"""
|
||||||
|
处理日志特殊字符
|
||||||
|
"""
|
||||||
|
control_char = re.compile(r"""
|
||||||
|
\x1b[ #%()*+\-.\/]. |
|
||||||
|
\r | #匹配 回车符(CR)
|
||||||
|
(?:\x1b\[|\x9b) [ -?]* [@-~] | #匹配 控制顺序描述符(CSI)... Cmd
|
||||||
|
(?:\x1b\]|\x9d) .*? (?:\x1b\\|[\a\x9c]) | \x07 | #匹配 操作系统指令(OSC)...终止符或振铃符(ST|BEL)
|
||||||
|
(?:\x1b[P^_]|[\x90\x9e\x9f]) .*? (?:\x1b\\|\x9c) | #匹配 设备控制串或私讯或应用程序命令(DCS|PM|APC)...终止符(ST)
|
||||||
|
\x1b. #匹配 转义过后的字符
|
||||||
|
[\x80-\x9f] #匹配 所有控制字符
|
||||||
|
""", re.X)
|
||||||
|
backspace = re.compile(r"[^\b][\b]")
|
||||||
|
line_filtered = control_char.sub('', str_r.rstrip())
|
||||||
|
while backspace.search(line_filtered):
|
||||||
|
line_filtered = backspace.sub('', line_filtered)
|
||||||
|
|
||||||
|
return line_filtered
|
||||||
|
|
||||||
|
|
||||||
|
def newline_code_in(strings):
|
||||||
|
for i in ['\r', '\r\n', '\n']:
|
||||||
|
if i in strings:
|
||||||
|
#print "new line"
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Jtty(object):
|
class Jtty(object):
|
||||||
"""
|
"""
|
||||||
A virtual tty class
|
A virtual tty class
|
||||||
一个虚拟终端类,实现连接ssh和记录日志
|
一个虚拟终端类,实现连接ssh和记录日志
|
||||||
"""
|
"""
|
||||||
def __init__(self, user, asset):
|
def __init__(self, username, ip):
|
||||||
self.chan = None
|
self.chan = None
|
||||||
self.username = user.username
|
self.username = username
|
||||||
self.ip = asset.ip
|
self.ip = ip
|
||||||
self.user = user
|
# self.user = user
|
||||||
self.asset = asset
|
# self.asset = asset
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_win_size():
|
def get_win_size():
|
||||||
|
@ -227,11 +188,8 @@ class Jtty(object):
|
||||||
timestamp_start = int(time.time())
|
timestamp_start = int(time.time())
|
||||||
date_start = time.strftime('%Y%m%d', time.localtime(timestamp_start))
|
date_start = time.strftime('%Y%m%d', time.localtime(timestamp_start))
|
||||||
time_start = time.strftime('%H%M%S', time.localtime(timestamp_start))
|
time_start = time.strftime('%H%M%S', time.localtime(timestamp_start))
|
||||||
log_filename = '%s_%s_%s.log' % (self.username, self.ip, time_start)
|
|
||||||
today_connect_log_dir = os.path.join(tty_log_dir, date_start)
|
today_connect_log_dir = os.path.join(tty_log_dir, date_start)
|
||||||
log_file_path = os.path.join(today_connect_log_dir, log_filename)
|
log_file_path = os.path.join(today_connect_log_dir, '%s_%s_%s' % (self.username, self.ip, time_start))
|
||||||
dept_name = self.user.dept.name
|
|
||||||
|
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
pts = os.popen("ps axu | grep %s | grep -v grep | awk '{ print $7 }'" % pid).read().strip()
|
pts = os.popen("ps axu | grep %s | grep -v grep | awk '{ print $7 }'" % pid).read().strip()
|
||||||
ip_list = os.popen("who | grep %s | awk '{ print $5 }'" % pts).read().strip('()\n')
|
ip_list = os.popen("who | grep %s | awk '{ print $5 }'" % pts).read().strip('()\n')
|
||||||
|
@ -242,23 +200,29 @@ class Jtty(object):
|
||||||
raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, tty_log_dir))
|
raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, tty_log_dir))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log_file = open(log_file_path, 'a')
|
log_file_f = open(log_file_path + '.log', 'a')
|
||||||
|
log_time_f = open(log_file_path + '.time', 'a')
|
||||||
|
log_res_f = open(log_file_path + '.res', 'a')
|
||||||
except IOError:
|
except IOError:
|
||||||
raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir)
|
raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir)
|
||||||
|
|
||||||
log = Log(user=self.username, host=self.ip, remote_ip=ip_list, dept_name=dept_name,
|
log = Log(user=self.username, host=self.ip, remote_ip=ip_list,
|
||||||
log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid)
|
log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid)
|
||||||
log_file.write('Start time is %s\n' % datetime.datetime.now())
|
log_file_f.write('Start time is %s\n' % datetime.datetime.now())
|
||||||
log.save()
|
log.save()
|
||||||
return log_file, log
|
return log_file_f, log_time_f, log_res_f, log
|
||||||
|
|
||||||
def posix_shell(self):
|
def posix_shell(self):
|
||||||
"""
|
"""
|
||||||
Use paramiko channel connect server interactive.
|
Use paramiko channel connect server interactive.
|
||||||
使用paramiko模块的channel,连接后端,进入交互式
|
使用paramiko模块的channel,连接后端,进入交互式
|
||||||
"""
|
"""
|
||||||
log_file, log = self.log_record()
|
log_file_f, log_time_f, log_res_f, log = self.log_record()
|
||||||
old_tty = termios.tcgetattr(sys.stdin)
|
old_tty = termios.tcgetattr(sys.stdin)
|
||||||
|
pre_timestamp = time.time()
|
||||||
|
input_r = ''
|
||||||
|
input_mode = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tty.setraw(sys.stdin.fileno())
|
tty.setraw(sys.stdin.fileno())
|
||||||
tty.setcbreak(sys.stdin.fileno())
|
tty.setcbreak(sys.stdin.fileno())
|
||||||
|
@ -277,23 +241,40 @@ class Jtty(object):
|
||||||
break
|
break
|
||||||
sys.stdout.write(x)
|
sys.stdout.write(x)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
log_file.write(x)
|
log_file_f.write(x)
|
||||||
log_file.flush()
|
now_timestamp = time.time()
|
||||||
|
log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(x)))
|
||||||
|
pre_timestamp = now_timestamp
|
||||||
|
log_file_f.flush()
|
||||||
|
log_time_f.flush()
|
||||||
|
|
||||||
|
if input_mode and not newline_code_in(x):
|
||||||
|
input_r += x
|
||||||
|
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if sys.stdin in r:
|
if sys.stdin in r:
|
||||||
x = os.read(sys.stdin.fileno(), 1)
|
x = os.read(sys.stdin.fileno(), 1)
|
||||||
|
if not input_mode:
|
||||||
|
input_mode = True
|
||||||
|
|
||||||
|
if str(x) in ['\r', '\n', '\r\n']:
|
||||||
|
input_r = remove_control_char(input_r)
|
||||||
|
log_res_f.write('%s: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), input_r))
|
||||||
|
log_res_f.flush()
|
||||||
|
input_r = ''
|
||||||
|
input_mode = False
|
||||||
|
|
||||||
if len(x) == 0:
|
if len(x) == 0:
|
||||||
break
|
break
|
||||||
self.chan.send(x)
|
self.chan.send(x)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
|
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
|
||||||
log_file.write('End time is %s' % datetime.datetime.now())
|
log_file_f.write('End time is %s' % datetime.datetime.now())
|
||||||
log_file.close()
|
log_file_f.close()
|
||||||
log.is_finished = True
|
log.is_finished = True
|
||||||
log.handle_finished = False
|
|
||||||
log.end_time = datetime.datetime.now()
|
log.end_time = datetime.datetime.now()
|
||||||
log.save()
|
log.save()
|
||||||
|
|
||||||
|
@ -302,27 +283,15 @@ class Jtty(object):
|
||||||
get args for connect: ip, port, username, passwd
|
get args for connect: ip, port, username, passwd
|
||||||
获取连接需要的参数,也就是服务ip, 端口, 用户账号和密码
|
获取连接需要的参数,也就是服务ip, 端口, 用户账号和密码
|
||||||
"""
|
"""
|
||||||
if not self.asset.is_active:
|
# if not self.asset.is_active:
|
||||||
raise ServerError('该主机被禁用 Host %s is not active.' % self.ip)
|
# raise ServerError('该主机被禁用 Host %s is not active.' % self.ip)
|
||||||
|
#
|
||||||
|
# if not self.user.is_active:
|
||||||
|
# raise ServerError('该用户被禁用 User %s is not active.' % self.username)
|
||||||
|
|
||||||
if not self.user.is_active:
|
# password = CRYPTOR.decrypt(self.])
|
||||||
raise ServerError('该用户被禁用 User %s is not active.' % self.username)
|
# return self.username, password, self.ip, int(self.asset.port)
|
||||||
|
return 'root', 'redhat', '127.0.0.1', 22
|
||||||
login_type_dict = {
|
|
||||||
'L': self.user.ldap_pwd,
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.asset.login_type in login_type_dict:
|
|
||||||
password = CRYPTOR.decrypt(login_type_dict[self.asset.login_type])
|
|
||||||
return self.username, password, self.ip, int(self.asset.port)
|
|
||||||
|
|
||||||
elif self.asset.login_type == 'M':
|
|
||||||
username = self.asset.username
|
|
||||||
password = CRYPTOR.decrypt(self.asset.password)
|
|
||||||
return username, password, self.ip, int(self.asset.port)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ServerError('不支持的服务器登录方式 Login type is not in ["L", "M"]')
|
|
||||||
|
|
||||||
def get_connection(self):
|
def get_connection(self):
|
||||||
"""
|
"""
|
||||||
|
@ -337,7 +306,7 @@ class Jtty(object):
|
||||||
ssh.load_system_host_keys()
|
ssh.load_system_host_keys()
|
||||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
try:
|
try:
|
||||||
ssh.connect(ip, port=port, username=username, password=password, compress=True)
|
ssh.connect(ip, port=port, username=username, password=password)
|
||||||
except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException:
|
except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException:
|
||||||
raise ServerError('认证错误 Authentication Error.')
|
raise ServerError('认证错误 Authentication Error.')
|
||||||
except socket.error:
|
except socket.error:
|
||||||
|
@ -351,7 +320,7 @@ class Jtty(object):
|
||||||
连接服务器
|
连接服务器
|
||||||
"""
|
"""
|
||||||
ps1 = "PS1='[\u@%s \W]\$ '\n" % self.ip
|
ps1 = "PS1='[\u@%s \W]\$ '\n" % self.ip
|
||||||
login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % self.asset.ip
|
login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % self.ip
|
||||||
|
|
||||||
# 发起ssh连接请求 Make a ssh connection
|
# 发起ssh连接请求 Make a ssh connection
|
||||||
ssh = self.get_connection()
|
ssh = self.get_connection()
|
||||||
|
@ -706,14 +675,5 @@ def my_render(template, data, request):
|
||||||
|
|
||||||
CRYPTOR = PyCrypt(KEY)
|
CRYPTOR = PyCrypt(KEY)
|
||||||
|
|
||||||
# if LDAP_ENABLE:
|
|
||||||
# LDAP_HOST_URL = CONF.get('ldap', 'host_url')
|
|
||||||
# LDAP_BASE_DN = CONF.get('ldap', 'base_dn')
|
|
||||||
# LDAP_ROOT_DN = CONF.get('ldap', 'root_dn')
|
|
||||||
# LDAP_ROOT_PW = CONF.get('ldap', 'root_pw')
|
|
||||||
# ldap_conn = LDAPMgmt(LDAP_HOST_URL, LDAP_BASE_DN, LDAP_ROOT_DN, LDAP_ROOT_PW)
|
|
||||||
# else:
|
|
||||||
# ldap_conn = None
|
|
||||||
|
|
||||||
log_level = CONF.get('base', 'log')
|
log_level = CONF.get('base', 'log')
|
||||||
logger = set_log(log_level)
|
logger = set_log(log_level)
|
|
@ -62,7 +62,7 @@ INSTALLED_APPS = (
|
||||||
'juser',
|
'juser',
|
||||||
'jasset',
|
'jasset',
|
||||||
'jperm',
|
'jperm',
|
||||||
# 'jlog',
|
'jlog',
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
MIDDLEWARE_CLASSES = (
|
||||||
|
|
|
@ -16,7 +16,7 @@ urlpatterns = patterns('',
|
||||||
(r'^error/$', 'jumpserver.views.httperror'),
|
(r'^error/$', 'jumpserver.views.httperror'),
|
||||||
(r'^juser/', include('juser.urls')),
|
(r'^juser/', include('juser.urls')),
|
||||||
(r'^jasset/', include('jasset.urls')),
|
(r'^jasset/', include('jasset.urls')),
|
||||||
# (r'^jlog/', include('jlog.urls')),
|
(r'^jlog/', include('jlog.urls')),
|
||||||
(r'^jperm/', include('jperm.urls')),
|
(r'^jperm/', include('jperm.urls')),
|
||||||
(r'^node_auth/', 'jumpserver.views.node_auth'),
|
(r'^node_auth/', 'jumpserver.views.node_auth'),
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
<html>
|
||||||
|
<head>{% block head %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<input type="button" value="Play/Pause" onclick="pause(false);" />
|
||||||
|
<input type="button" value="Restart" onclick="restart(1);" />
|
||||||
|
<span id="beforeScrubberText"></span>
|
||||||
|
<input id="scrubber" type="range" value="0" min=0 max=100
|
||||||
|
onmousedown="pause(true);" onmouseup="scrub();" />
|
||||||
|
<span id="afterScrubberText"></span>
|
||||||
|
-5x <input id="speed" type="range" value="0" min=-5 max=5
|
||||||
|
onmouseup="setSpeed();" /> +5x
|
||||||
|
<script>
|
||||||
|
var data = {{ json }};
|
||||||
|
var toggle = true;
|
||||||
|
var totalTime = 0;
|
||||||
|
var TICK = 33;
|
||||||
|
var TIMESTEP = 33;
|
||||||
|
var time = 33;
|
||||||
|
var pos = 0;
|
||||||
|
var timer;
|
||||||
|
|
||||||
|
// Thanks http://stackoverflow.com/a/2998822
|
||||||
|
function zeroPad(num, size) {
|
||||||
|
var s = "0" + num;
|
||||||
|
return s.substr(s.length-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrub() {
|
||||||
|
setPercent = document.getElementById('scrubber').value;
|
||||||
|
time = (setPercent / 100) * totalTime;
|
||||||
|
restart(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTimeString(millis) {
|
||||||
|
hours = zeroPad(Math.floor(millis / (1000 * 60 * 60)), 2);
|
||||||
|
millis -= hours * (1000 * 60 * 60)
|
||||||
|
minutes = zeroPad(Math.floor(millis / (1000 * 60)), 2);
|
||||||
|
millis -= minutes * (1000 * 60);
|
||||||
|
seconds = zeroPad(Math.floor(millis / 1000), 2);
|
||||||
|
return hours + ':' + minutes + ':' + seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function advance() {
|
||||||
|
document.getElementById('scrubber').value =
|
||||||
|
Math.ceil((time / totalTime) * 100);
|
||||||
|
timestr = buildTimeString(time);
|
||||||
|
document.getElementById("beforeScrubberText").innerHTML =
|
||||||
|
timestr;
|
||||||
|
for (; pos < data.length; pos++) {
|
||||||
|
if (data[pos][1] <= time) {
|
||||||
|
term.write(eval(data[pos][0]));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos >= data.length) {
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
time += TIMESTEP;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pause(test) {
|
||||||
|
if (!toggle && test) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (toggle) {
|
||||||
|
clearInterval(timer);
|
||||||
|
toggle = !toggle;
|
||||||
|
} else {
|
||||||
|
timer = setInterval(advance, TICK);
|
||||||
|
toggle = !toggle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSpeed() {
|
||||||
|
speed = document.getElementById('speed').value;
|
||||||
|
if (speed == 0) {
|
||||||
|
TIMESTEP = TICK;
|
||||||
|
} else if (speed < 0) {
|
||||||
|
TIMESTEP = TICK / -speed;
|
||||||
|
} else {
|
||||||
|
TIMESTEP = TICK * speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restart(millis) {
|
||||||
|
clearInterval(timer);
|
||||||
|
term.reset();
|
||||||
|
time = millis;
|
||||||
|
pos = 0;
|
||||||
|
toggle = true;
|
||||||
|
timer = setInterval(advance, TICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
var term = new Terminal({
|
||||||
|
cols: {{ dimensions[1] }},
|
||||||
|
rows: {{ dimensions[0] }},
|
||||||
|
screenKeys: true
|
||||||
|
});
|
||||||
|
totalTime = data[data.length - 1][1];
|
||||||
|
timestr = buildTimeString(totalTime);
|
||||||
|
document.getElementById("afterScrubberText").innerHTML = timestr;
|
||||||
|
term.open(document.body);
|
||||||
|
timer = setInterval(advance, TICK);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "base.jinja2" %}
|
||||||
|
{% block head %}
|
||||||
|
<script src='term.js'></script>
|
||||||
|
<link href='http://fonts.googleapis.com/css?family=Ubuntu+Mono' rel='stylesheet' type='text/css'>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Ubuntu Mono', Courier, monospace;
|
||||||
|
font-size: 14pt;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
|
@ -39,12 +39,6 @@
|
||||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||||
<i class="fa fa-wrench"></i>
|
<i class="fa fa-wrench"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-user">
|
|
||||||
<li><a href="#">未启用 1</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="#">未启用 2</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<a class="close-link">
|
<a class="close-link">
|
||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -77,12 +71,12 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center"> 用户名 </th>
|
<th class="text-center"> 用户名 </th>
|
||||||
<th class="text-center"> 所属部门 </th>
|
|
||||||
<th class="text-center"> 登录主机 </th>
|
<th class="text-center"> 登录主机 </th>
|
||||||
<th class="text-center"> 来源IP </th>
|
<th class="text-center"> 来源IP </th>
|
||||||
{% ifnotequal session_role_id 0 %}
|
{% ifnotequal session_role_id 0 %}
|
||||||
<th class="text-center"> 命令统计 </th>
|
<th class="text-center"> 命令统计 </th>
|
||||||
{% endifnotequal %}
|
{% endifnotequal %}
|
||||||
|
<th class="text-center"> 回放录像 </th>
|
||||||
<th class="text-center"> 登录时间 </th>
|
<th class="text-center"> 登录时间 </th>
|
||||||
<th class="text-center"> 结束时间 </th>
|
<th class="text-center"> 结束时间 </th>
|
||||||
|
|
||||||
|
@ -92,12 +86,12 @@
|
||||||
{% for post in contacts.object_list %}
|
{% for post in contacts.object_list %}
|
||||||
<tr class="gradeX">
|
<tr class="gradeX">
|
||||||
<td class="text-center" id="username"> {{ post.user }} </td>
|
<td class="text-center" id="username"> {{ post.user }} </td>
|
||||||
<td class="text-center" id="dept"> {{ post.dept_name }} </td>
|
|
||||||
<td class="text-center" id="ip"> {{ post.host }} </td>
|
<td class="text-center" id="ip"> {{ post.host }} </td>
|
||||||
<td class="text-center" id="remote_ip"> {{ post.remote_ip }} </td>
|
<td class="text-center" id="remote_ip"> {{ post.remote_ip }} </td>
|
||||||
{% ifnotequal session_role_id 0 %}
|
{% ifnotequal session_role_id 0 %}
|
||||||
<td class="text-center"><a href="/jlog/history/?id={{ post.id }}" class="log_command"> 命令统计 </td>
|
<td class="text-center"><a href="/jlog/history/?id={{ post.id }}" class="log_command"> 命令统计 </td>
|
||||||
{% endifnotequal %}
|
{% endifnotequal %}
|
||||||
|
<td class="text-center"><a href="/jlog/record/?id={{ post.id }}" class="log_command"> 回放 </td>
|
||||||
<td class="text-center" id="start_time"> {{ post.start_time|date:"Y-m-d H:i:s"}} </td>
|
<td class="text-center" id="start_time"> {{ post.start_time|date:"Y-m-d H:i:s"}} </td>
|
||||||
<td class="text-center" id="end_time"> {{ post.end_time|date:"Y-m-d H:i:s" }} </td>
|
<td class="text-center" id="end_time"> {{ post.end_time|date:"Y-m-d H:i:s" }} </td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -39,12 +39,6 @@
|
||||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||||
<i class="fa fa-wrench"></i>
|
<i class="fa fa-wrench"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-user">
|
|
||||||
<li><a href="#">未启用 1</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="#">未启用 2</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<a class="close-link">
|
<a class="close-link">
|
||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -77,7 +71,6 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center"> 用户名 </th>
|
<th class="text-center"> 用户名 </th>
|
||||||
<th class="text-center"> 所属部门 </th>
|
|
||||||
<th class="text-center"> 登录主机 </th>
|
<th class="text-center"> 登录主机 </th>
|
||||||
<th class="text-center"> 来源IP </th>
|
<th class="text-center"> 来源IP </th>
|
||||||
{% ifnotequal session_role_id 0 %}
|
{% ifnotequal session_role_id 0 %}
|
||||||
|
@ -92,7 +85,6 @@
|
||||||
{% for post in contacts.object_list %}
|
{% for post in contacts.object_list %}
|
||||||
<tr class="gradeX">
|
<tr class="gradeX">
|
||||||
<td id="username" class="text-center"> {{ post.user }} </td>
|
<td id="username" class="text-center"> {{ post.user }} </td>
|
||||||
<td id="deptname" class="text-center"> {{ post.dept_name }} </td>
|
|
||||||
<td id="ip" class="text-center"> {{ post.host }} </td>
|
<td id="ip" class="text-center"> {{ post.host }} </td>
|
||||||
<td id="remote_ip" class="text-center"> {{ post.remote_ip }} </td>
|
<td id="remote_ip" class="text-center"> {{ post.remote_ip }} </td>
|
||||||
{% ifnotequal session_role_id 0 %}
|
{% ifnotequal session_role_id 0 %}
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue