Unverified Commit 1ecdcc2a authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #106 from jumpserver/dev

支持命令过滤
parents 021a70ac d7ba5b58
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import eventlet
from eventlet.debug import hub_prevent_multiple_readers
eventlet.monkey_patch()
hub_prevent_multiple_readers(False)
import datetime import datetime
import os import os
import time import time
...@@ -26,7 +21,7 @@ from .session import Session ...@@ -26,7 +21,7 @@ from .session import Session
from .models import Connection from .models import Connection
__version__ = '1.4.2' __version__ = '1.4.3'
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
logger = get_logger(__file__) logger = get_logger(__file__)
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
BACKSPACE_CHAR = {b'\x08': b'\x08\x1b[K', b'\x7f': b'\x08\x1b[K'} BACKSPACE_CHAR = {b'\x08': b'\x08\x1b[K', b'\x7f': b'\x08\x1b[K'}
ENTER_CHAR = [b'\r', b'\n', b'\r\n'] ENTER_CHAR = [b'\r', b'\n', b'\r\n']
ENTER_CHAR_ORDER = [ord(b'\r'), ord(b'\n')] ENTER_CHAR_ORDER = [ord(b'\r'), ord(b'\n')]
CLEAR_LINE_CHAR = b'\x15'
UNSUPPORTED_CHAR = {b'\x15': 'Ctrl-U', b'\x0c': 'Ctrl-L', b'\x05': 'Ctrl-E'} UNSUPPORTED_CHAR = {b'\x15': 'Ctrl-U', b'\x0c': 'Ctrl-L', b'\x05': 'Ctrl-E'}
CLEAR_CHAR = b'\x1b[H\x1b[2J' CLEAR_CHAR = b'\x1b[H\x1b[2J'
BELL_CHAR = b'\x07' BELL_CHAR = b'\x07'
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# #
from functools import wraps from functools import wraps
from flask import request, abort from flask import request, abort, redirect
from ..ctx import app_service from ..ctx import app_service
...@@ -23,7 +23,8 @@ def login_required(func): ...@@ -23,7 +23,8 @@ def login_required(func):
user = app_service.check_user_cookie(session_id, csrf_token) user = app_service.check_user_cookie(session_id, csrf_token)
request.current_user = user request.current_user = user
if not hasattr(request, 'current_user') or not request.current_user: if not hasattr(request, 'current_user') or not request.current_user:
return abort(403) url = '/users/login/?next={}'.format(request.path)
return redirect(url)
response = func(*args, **kwargs) response = func(*args, **kwargs)
return response return response
return wrapper return wrapper
...@@ -50,11 +50,13 @@ def sftp_host_connector_view(host): ...@@ -50,11 +50,13 @@ def sftp_host_connector_view(host):
@app.route('/coco/elfinder/sftp/<host>/') @app.route('/coco/elfinder/sftp/<host>/')
@login_required
def sftp_host_finder(host): def sftp_host_finder(host):
return render_template('elfinder/file_manager.html', host=host) return render_template('elfinder/file_manager.html', host=host)
@app.route('/coco/elfinder/sftp/') @app.route('/coco/elfinder/sftp/')
@login_required
def sftp_finder(): def sftp_finder():
return render_template('elfinder/file_manager.html', host='_') return render_template('elfinder/file_manager.html', host='_')
......
...@@ -4,7 +4,10 @@ import weakref ...@@ -4,7 +4,10 @@ import weakref
import uuid import uuid
import socket import socket
from .service import app_service
from .struct import SizedList, SelectEvent from .struct import SizedList, SelectEvent
from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning, \
ugettext as _
from . import char from . import char
from . import utils from . import utils
...@@ -140,6 +143,11 @@ class Client: ...@@ -140,6 +143,11 @@ class Client:
return "<%s from %s:%s>" % (self.user, self.addr[0], self.addr[1]) return "<%s from %s:%s>" % (self.user, self.addr[0], self.addr[1])
class ServerFilter:
def run(self, data):
pass
class BaseServer: class BaseServer:
""" """
Base Server Base Server
...@@ -147,18 +155,23 @@ class BaseServer: ...@@ -147,18 +155,23 @@ class BaseServer:
sub-class: Server, Telnet Server sub-class: Server, Telnet Server
""" """
def __init__(self): def __init__(self, chan=None):
self.chan = None self.chan = chan
self._session_ref = None
self.input_data = SizedList(maxsize=1024) self._pre_input_state = True
self.output_data = SizedList(maxsize=1024)
self._in_input_state = True self._in_input_state = True
self._input_initial = False self._input_initial = False
self._enter_vim_mark = b'\x1b[?25l\x1b[37;1H\x1b[1m'
self._exit_vim_mark = b'\x1b[37;1H\x1b[K\x1b'
self._in_vim_state = False self._in_vim_state = False
self.input_data = SizedList(maxsize=1024)
self.output_data = SizedList(maxsize=1024)
self._input = "" self._input = ""
self._output = "" self._output = ""
self._session_ref = None
self._zmodem_recv_start_mark = b'rz waiting to receive.**\x18B0100' self._zmodem_recv_start_mark = b'rz waiting to receive.**\x18B0100'
self._zmodem_send_start_mark = b'**\x18B00000000000000' self._zmodem_send_start_mark = b'**\x18B00000000000000'
self._zmodem_cancel_mark = b'\x18\x18\x18\x18\x18' self._zmodem_cancel_mark = b'\x18\x18\x18\x18\x18'
...@@ -167,6 +180,15 @@ class BaseServer: ...@@ -167,6 +180,15 @@ class BaseServer:
self._zmodem_state_recv = 'recv' self._zmodem_state_recv = 'recv'
self._zmodem_state = '' self._zmodem_state = ''
self._cmd_parser = utils.TtyIOParser()
self._cmd_filter_rules = self.get_system_user_cmd_filter_rules()
def get_system_user_cmd_filter_rules(self):
rules = app_service.get_system_user_cmd_filter_rules(
self.system_user.id
)
return rules
def set_session(self, session): def set_session(self, session):
self._session_ref = weakref.ref(session) self._session_ref = weakref.ref(session)
...@@ -177,51 +199,82 @@ class BaseServer: ...@@ -177,51 +199,82 @@ class BaseServer:
else: else:
return None return None
def initial_filter(self): def s_initial_filter(self, data):
if not self._input_initial: if not self._input_initial:
self._input_initial = True self._input_initial = True
return data
def parse_cmd_filter(self, data): def s_input_state_filter(self, data):
# 输入了回车键, 开始计算输入的内容 self._pre_input_state = self._in_input_state
if self._have_enter_char(data): if self._in_vim_state:
self._in_input_state = False self._in_input_state = False
self._input = self._parse_input() # 输入了回车键
elif self._have_enter_char(data):
self._in_input_state = False
else:
self._in_input_state = True
return data return data
# 用户输入了内容,但是如果没在输入状态,也就是用户刚开始输入了,结算上次输出内容
def s_parse_input_output_filter(self, data):
# 输入了回车键, 计算输入的命令
if not self._in_input_state: if not self._in_input_state:
self._input = self._parse_input()
# 用户输入了内容,但是上次没在输入状态,也就是用户刚开始输入了,结算上次输出内容
if not self._pre_input_state and self._in_input_state:
self._output = self._parse_output() self._output = self._parse_output()
logger.debug("\n{}\nInput: {}\nOutput: {}\n{}".format( # logger.debug("\n{}\nInput: {}\nOutput: {}\n{}".format(
"#" * 30 + " Command " + "#" * 30, # "#" * 30 + " Command " + "#" * 30,
self._input, self._output, # self._input, self._output,
"#" * 30 + " End " + "#" * 30, # "#" * 30 + " End " + "#" * 30,
)) # ))
if self._input: if self._input:
self.session.put_command(self._input, self._output) self.session.put_command(self._input, self._output)
self.input_data.clean() self.input_data.clean()
self.output_data.clean() self.output_data.clean()
self._in_input_state = True
return data return data
def send(self, data): def s_filter_cmd_filter(self, data):
self.initial_filter() if self._in_input_state:
self.parse_cmd_filter(data) return data
return self.chan.send(data) for rule in self._cmd_filter_rules:
action, cmd = rule.match(self._input)
if action == rule.ALLOW:
break
elif action == rule.DENY:
data = char.CLEAR_LINE_CHAR + b'\r\n'
msg = _("Command `{}` is forbidden ........").format(cmd)
msg = wr(warning(msg.encode()), before=1, after=1)
self.output_data.append(msg)
self.session.send_to_clients(msg)
self.session.put_replay(msg)
break
return data
def replay_filter(self, data): def r_replay_filter(self, data):
if not self._zmodem_state: if not self._zmodem_state:
self.session.put_replay(data) self.session.put_replay(data)
def input_output_filter(self, data): def r_vim_state_filter(self, data):
if self._zmodem_state:
return data
if self._in_vim_state and data[:11] == self._exit_vim_mark:
self._in_vim_state = False
elif not self._in_vim_state and data[:17] == self._enter_vim_mark:
self._in_vim_state = True
return data
def r_input_output_data_filter(self, data):
if not self._input_initial: if not self._input_initial:
return return data
if self._zmodem_state: if self._zmodem_state:
return return data
if self._in_input_state: if self._in_input_state:
self.input_data.append(data) self.input_data.append(data)
else: else:
self.output_data.append(data) self.output_data.append(data)
return data
def zmodem_state_filter(self, data): def r_zmodem_state_filter(self, data):
if not self._zmodem_state: if not self._zmodem_state:
if data[:50].find(self._zmodem_recv_start_mark) != -1: if data[:50].find(self._zmodem_recv_start_mark) != -1:
logger.debug("Zmodem state => recv") logger.debug("Zmodem state => recv")
...@@ -237,18 +290,29 @@ class BaseServer: ...@@ -237,18 +290,29 @@ class BaseServer:
logger.debug("Zmodem state => cancel") logger.debug("Zmodem state => cancel")
self._zmodem_state = '' self._zmodem_state = ''
def zmodem_cancel_filter(self): def r_zmodem_disable_filter(self, data=''):
if self._zmodem_state: if self._zmodem_state:
pass pass
# self.chan.send(self._zmodem_cancel_mark) # self.chan.send(self._zmodem_cancel_mark)
# self.chan.send("Zmodem disabled") # self.chan.send("Zmodem disabled")
def send(self, data):
self.s_initial_filter(data)
self.s_input_state_filter(data)
try:
self.s_parse_input_output_filter(data)
data = self.s_filter_cmd_filter(data)
except Exception as e:
logger.exception(e)
return self.chan.send(data)
def recv(self, size): def recv(self, size):
data = self.chan.recv(size) data = self.chan.recv(size)
self.zmodem_state_filter(data) self.r_zmodem_state_filter(data)
self.zmodem_cancel_filter() self.r_vim_state_filter(data)
self.replay_filter(data) self.r_zmodem_disable_filter(data)
self.input_output_filter(data) self.r_replay_filter(data)
self.r_input_output_data_filter(data)
return data return data
@staticmethod @staticmethod
...@@ -261,21 +325,19 @@ class BaseServer: ...@@ -261,21 +325,19 @@ class BaseServer:
def _parse_output(self): def _parse_output(self):
if not self.output_data: if not self.output_data:
return '' return ''
parser = utils.TtyIOParser() return self._cmd_parser.parse_output(self.output_data)
return parser.parse_output(self.output_data)
def _parse_input(self): def _parse_input(self):
if not self.input_data: if not self.input_data:
return return
parser = utils.TtyIOParser() return self._cmd_parser.parse_input(self.input_data)
return parser.parse_input(self.input_data)
def fileno(self): def fileno(self):
return self.chan.fileno() return self.chan.fileno()
def close(self): def close(self):
logger.info("Closed server {}".format(self)) logger.info("Closed server {}".format(self))
self.input_output_filter(b'') self.r_input_output_data_filter(b'')
self.chan.close() self.chan.close()
def __getattr__(self, item): def __getattr__(self, item):
...@@ -290,10 +352,9 @@ class TelnetServer(BaseServer): ...@@ -290,10 +352,9 @@ class TelnetServer(BaseServer):
Telnet server Telnet server
""" """
def __init__(self, sock, asset, system_user): def __init__(self, sock, asset, system_user):
super(TelnetServer, self).__init__()
self.chan = sock
self.asset = asset self.asset = asset
self.system_user = system_user self.system_user = system_user
super(TelnetServer, self).__init__(chan=sock)
class Server(BaseServer): class Server(BaseServer):
...@@ -306,11 +367,10 @@ class Server(BaseServer): ...@@ -306,11 +367,10 @@ class Server(BaseServer):
# Todo: Server name is not very suitable # Todo: Server name is not very suitable
def __init__(self, chan, sock, asset, system_user): def __init__(self, chan, sock, asset, system_user):
super(Server, self).__init__()
self.chan = chan
self.sock = sock self.sock = sock
self.asset = asset self.asset = asset
self.system_user = system_user self.system_user = system_user
super(Server, self).__init__(chan=chan)
def close(self): def close(self):
super().close() super().close()
......
...@@ -147,6 +147,10 @@ class Session: ...@@ -147,6 +147,10 @@ class Session:
pass pass
self.stop_evt.set() self.stop_evt.set()
def send_to_clients(self, data):
for watcher in [self.client] + self._watchers + self._sharers:
watcher.send(data)
def bridge(self): def bridge(self):
""" """
Bridge clients with server Bridge clients with server
......
...@@ -90,7 +90,7 @@ class SSHServer: ...@@ -90,7 +90,7 @@ class SSHServer:
continue continue
if not server.event.is_set(): if not server.event.is_set():
logger.warning("Client not request a valid request, exiting") logger.warning("Client not request invalid, exiting")
sock.close() sock.close()
return return
else: else:
......
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import eventlet
from eventlet.debug import hub_prevent_multiple_readers
eventlet.monkey_patch()
hub_prevent_multiple_readers(False)
import os import os
import sys import sys
import argparse import argparse
...@@ -16,12 +21,9 @@ except ImportError: ...@@ -16,12 +21,9 @@ except ImportError:
print("Please prepare config file `cp conf_example.py conf.py`") print("Please prepare config file `cp conf_example.py conf.py`")
sys.exit(1) sys.exit(1)
try: dirs = ('logs', 'keys')
os.mkdir("logs") for d in dirs:
os.mkdir("keys") os.makedirs(d, exist_ok=True)
os.mkdir("sessions")
except:
pass
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DAEMON = False DAEMON = False
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-03 10:39+0800\n" "POT-Creation-Date: 2018-10-10 15:22+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n" "PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n" "Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/en/LC\n" "Language-Team: Language locale/en/LC\n"
...@@ -16,7 +16,7 @@ msgstr "" ...@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:147 #: coco/app.py:141
msgid "Connect idle more than {} minutes, disconnect" msgid "Connect idle more than {} minutes, disconnect"
msgstr "" msgstr ""
...@@ -91,44 +91,44 @@ msgstr "" ...@@ -91,44 +91,44 @@ msgstr ""
msgid "Total: {}" msgid "Total: {}"
msgstr "" msgstr ""
#: coco/interactive.py:159 #: coco/interactive.py:161
msgid "There is no matched node, please re-enter" msgid "There is no matched node, please re-enter"
msgstr "" msgstr ""
#: coco/interactive.py:170 #: coco/interactive.py:172
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#: coco/interactive.py:170 #: coco/interactive.py:172
msgid "Hostname" msgid "Hostname"
msgstr "" msgstr ""
#: coco/interactive.py:170 #: coco/interactive.py:172
msgid "IP" msgid "IP"
msgstr "" msgstr ""
#: coco/interactive.py:170 #: coco/interactive.py:172
msgid "LoginAs" msgid "LoginAs"
msgstr "" msgstr ""
#: coco/interactive.py:184 #: coco/interactive.py:186
msgid "Comment" msgid "Comment"
msgstr "" msgstr ""
#: coco/interactive.py:192 #: coco/interactive.py:194
msgid "Total: {} Match: {}" msgid "Total: {} Match: {}"
msgstr "" msgstr ""
#: coco/interactive.py:235 #: coco/interactive.py:237
msgid "Select a login:: " msgid "Select a login:: "
msgstr "" msgstr ""
#: coco/interactive.py:258 #: coco/interactive.py:260
msgid "" msgid ""
"Terminal does not support login Windows, please use web terminal to access" "Terminal does not support login Windows, please use web terminal to access"
msgstr "" msgstr ""
#: coco/interactive.py:269 #: coco/interactive.py:271
msgid "No system user" msgid "No system user"
msgstr "" msgstr ""
......
# Language locale/en/LC translations for PACKAGE package.
# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# BaiJiangjie <bugatti_it@163.com>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-03 10:36+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/en/LC\n"
"Language: locale/en/LC_MESSAGES/coco\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:147
msgid "Connect idle more than {} minutes, disconnect"
msgstr ""
#: coco/interactive.py:61
#, python-brace-format
msgid ""
"\n"
"{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system "
"{end}{R}{R}"
msgstr ""
#: coco/interactive.py:63
#, python-brace-format
msgid ""
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, "
"Comment{end} to search login(if unique).{R}"
msgstr ""
#: coco/interactive.py:64
#, python-brace-format
msgid ""
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} "
"search, such as: /ip.{R}"
msgstr ""
#: coco/interactive.py:65
#, python-brace-format
msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}"
msgstr ""
#: coco/interactive.py:66
#, python-brace-format
msgid ""
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
msgstr ""
#: coco/interactive.py:67
#, python-brace-format
msgid ""
"{T}5) Enter {green}g{end} + {green}Group ID{end} to display the host under "
"the node, such as g1.{R}"
msgstr ""
#: coco/interactive.py:68
#, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr ""
#: coco/interactive.py:69
#, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr ""
#: coco/interactive.py:70
#, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr ""
#: coco/interactive.py:142
msgid "No"
msgstr ""
#: coco/interactive.py:149
msgid "Name"
msgstr ""
#: coco/interactive.py:149
msgid "Assets"
msgstr ""
#: coco/interactive.py:155
msgid "Total: {}"
msgstr ""
#: coco/interactive.py:159
msgid "There is no matched node, please re-enter"
msgstr ""
#: coco/interactive.py:170
msgid "ID"
msgstr ""
#: coco/interactive.py:170
msgid "Hostname"
msgstr ""
#: coco/interactive.py:170
msgid "IP"
msgstr ""
#: coco/interactive.py:170
msgid "LoginAs"
msgstr ""
#: coco/interactive.py:184
msgid "Comment"
msgstr ""
#: coco/interactive.py:192
msgid "Total: {} Match: {}"
msgstr ""
#: coco/interactive.py:235
msgid "Select a login:: "
msgstr ""
#: coco/interactive.py:258
msgid ""
"Terminal does not support login Windows, please use web terminal to access"
msgstr ""
#: coco/interactive.py:269
msgid "No system user"
msgstr ""
#: coco/session.py:143
msgid "Terminated by administrator"
msgstr ""
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-03 10:39+0800\n" "POT-Creation-Date: 2018-10-10 15:22+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n" "PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n" "Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/zh\n" "Language-Team: Language locale/zh\n"
...@@ -16,7 +16,7 @@ msgstr "" ...@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:147 #: coco/app.py:141
msgid "Connect idle more than {} minutes, disconnect" msgid "Connect idle more than {} minutes, disconnect"
msgstr "空闲时间超过 {} 分钟,断开连接" msgstr "空闲时间超过 {} 分钟,断开连接"
...@@ -77,9 +77,9 @@ msgid "{T}7) Enter {green}h{end} help.{R}" ...@@ -77,9 +77,9 @@ msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr "{T}7) 输入 {green}h{end} 帮助.{R}" msgstr "{T}7) 输入 {green}h{end} 帮助.{R}"
#: coco/interactive.py:70 #: coco/interactive.py:70
#, fuzzy, python-brace-format #, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}" msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr "{T}0) 输入 {green}q{end} 退出.{R}\n" msgstr "{T}0) 输入 {green}q{end} 退出.{R}"
#: coco/interactive.py:142 #: coco/interactive.py:142
msgid "No" msgid "No"
...@@ -97,44 +97,44 @@ msgstr "资产" ...@@ -97,44 +97,44 @@ msgstr "资产"
msgid "Total: {}" msgid "Total: {}"
msgstr "总共: {}" msgstr "总共: {}"
#: coco/interactive.py:159 #: coco/interactive.py:161
msgid "There is no matched node, please re-enter" msgid "There is no matched node, please re-enter"
msgstr "没有匹配分组,请重新输入" msgstr "没有匹配分组,请重新输入"
#: coco/interactive.py:170 #: coco/interactive.py:172
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#: coco/interactive.py:170 #: coco/interactive.py:172
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: coco/interactive.py:170 #: coco/interactive.py:172
msgid "IP" msgid "IP"
msgstr "" msgstr ""
#: coco/interactive.py:170 #: coco/interactive.py:172
msgid "LoginAs" msgid "LoginAs"
msgstr "" msgstr "登录用户"
#: coco/interactive.py:184 #: coco/interactive.py:186
msgid "Comment" msgid "Comment"
msgstr "备注" msgstr "备注"
#: coco/interactive.py:192 #: coco/interactive.py:194
msgid "Total: {} Match: {}" msgid "Total: {} Match: {}"
msgstr "总共: {} 匹配: {}" msgstr "总共: {} 匹配: {}"
#: coco/interactive.py:235 #: coco/interactive.py:237
msgid "Select a login:: " msgid "Select a login:: "
msgstr "选择一个登录:" msgstr "选择一个登录:"
#: coco/interactive.py:258 #: coco/interactive.py:260
msgid "" msgid ""
"Terminal does not support login Windows, please use web terminal to access" "Terminal does not support login Windows, please use web terminal to access"
msgstr "终端不支持登录windows, 请使用web terminal访问" msgstr "终端不支持登录windows, 请使用web terminal访问"
#: coco/interactive.py:269 #: coco/interactive.py:271
msgid "No system user" msgid "No system user"
msgstr "没有系统用户" msgstr "没有系统用户"
......
# Language locale/zh translations for PACKAGE package.
# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# BaiJiangjie <bugatti_it@163.com>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-03 10:36+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/zh\n"
"Language: locale/zh_CN/LC_MESSAGES/coco\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:147
msgid "Connect idle more than {} minutes, disconnect"
msgstr "空闲时间超过 {} 分钟,断开连接"
#: coco/interactive.py:61
#, python-brace-format
msgid ""
"\n"
"{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system "
"{end}{R}{R}"
msgstr ""
"\n"
"{T}{T}{title} {user}, 欢迎使用Jumpserver开源跳板机系统 {end}{R}{R}"
#: coco/interactive.py:63
#, python-brace-format
msgid ""
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, "
"Comment{end} to search login(if unique).{R}"
msgstr ""
"{T}1) 输入 {green}ID{end} 直接登录 或 输入{green}部分 IP,主机名,备注{end} 进"
"行搜索登录(如果唯一).{R}"
#: coco/interactive.py:64
#, python-brace-format
msgid ""
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} "
"search, such as: /ip.{R}"
msgstr ""
"{T}2) 输入 {green}/{end} + {green}IP, 主机名{end} or {green}备注 {end}搜索. "
"如: /ip{R}"
#: coco/interactive.py:65
#, python-brace-format
msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}"
msgstr "{T}3) 输入 {green}p{end} 显示您有权限的主机.{R}"
#: coco/interactive.py:66
#, python-brace-format
msgid ""
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
msgstr "{T}4) 输入 {green}g{end} 显示您有权限的节点.{R}"
#: coco/interactive.py:67
#, python-brace-format
msgid ""
"{T}5) Enter {green}g{end} + {green}Group ID{end} to display the host under "
"the node, such as g1.{R}"
msgstr "{T}5) 输入 {green}g{end} + {green}组ID{end} 显示节点下主机. 如: g1{R}"
#: coco/interactive.py:68
#, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr "{T}6) 输入 {green}s{end} 中/英文切换.{R}"
#: coco/interactive.py:69
#, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr "{T}7) 输入 {green}h{end} 帮助.{R}"
#: coco/interactive.py:70
#, fuzzy, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr "{T}0) 输入 {green}q{end} 退出.{R}\n"
#: coco/interactive.py:142
msgid "No"
msgstr "无"
#: coco/interactive.py:149
msgid "Name"
msgstr "名称"
#: coco/interactive.py:149
msgid "Assets"
msgstr "资产"
#: coco/interactive.py:155
msgid "Total: {}"
msgstr "总共: {}"
#: coco/interactive.py:159
msgid "There is no matched node, please re-enter"
msgstr "没有匹配分组,请重新输入"
#: coco/interactive.py:170
msgid "ID"
msgstr ""
#: coco/interactive.py:170
msgid "Hostname"
msgstr "主机名"
#: coco/interactive.py:170
msgid "IP"
msgstr ""
#: coco/interactive.py:170
msgid "LoginAs"
msgstr ""
#: coco/interactive.py:184
msgid "Comment"
msgstr "备注"
#: coco/interactive.py:192
msgid "Total: {} Match: {}"
msgstr "总共: {} 匹配: {}"
#: coco/interactive.py:235
msgid "Select a login:: "
msgstr "选择一个登录:"
#: coco/interactive.py:258
msgid ""
"Terminal does not support login Windows, please use web terminal to access"
msgstr "终端不支持登录windows, 请使用web terminal访问"
#: coco/interactive.py:269
msgid "No system user"
msgstr "没有系统用户"
#: coco/session.py:143
msgid "Terminated by administrator"
msgstr "被管理员中断"
...@@ -19,7 +19,7 @@ itsdangerous==0.24 ...@@ -19,7 +19,7 @@ itsdangerous==0.24
Jinja2==2.10 Jinja2==2.10
jmespath==0.9.3 jmespath==0.9.3
jms-storage==0.0.18 jms-storage==0.0.18
jumpserver-python-sdk==0.0.48 jumpserver-python-sdk==0.0.50
MarkupSafe==1.0 MarkupSafe==1.0
oss2==2.4.0 oss2==2.4.0
paramiko==2.4.1 paramiko==2.4.1
......
...@@ -2,15 +2,15 @@ ...@@ -2,15 +2,15 @@
# #
function init_message() { function init_message() {
xgettext -k_ -o pot/coco.pot --from-code=UTF-8 coco/*.py xgettext -k_ -o /tmp/coco.pot --from-code=UTF-8 coco/*.py
msginit -l locale/zh_CN/LC_MESSAGES/coco -i pot/coco.pot msginit -l locale/zh_CN/LC_MESSAGES/coco -i /tmp/coco.pot
msginit -l locale/en/LC_MESSAGES/coco -i pot/coco.pot msginit -l locale/en/LC_MESSAGES/coco -i /tmp/coco.pot
} }
function make_message() { function make_message() {
xgettext -k_ -o pot/coco.pot --from-code=UTF-8 coco/*.py xgettext -k_ -o /tmp/coco.pot --from-code=UTF-8 coco/*.py
msgmerge -U locale/zh_CN/LC_MESSAGES/coco.po pot/coco.pot msgmerge -U locale/zh_CN/LC_MESSAGES/coco.po /tmp/coco.pot
msgmerge -U locale/en/LC_MESSAGES/coco.po pot/coco.pot msgmerge -U locale/en/LC_MESSAGES/coco.po /tmp/coco.pot
} }
function compile_message() { function compile_message() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment