Commit 026713de authored by BaiJiangJie's avatar BaiJiangJie Committed by 老广

[Update] 使用分页展示用户授权资产 (#118)

* [Update] 分页显示用户被授权的资产

* [Update] 修改分页展示资产列表为通用的分页展示结果列表

* [Update] 修改在显示资产时,输入id直接登录

* [Update] 修改分页显示page_size随窗口动态变化

* [Update] 删除之前异步获取用户资产的代码

* [Update] 支持用户授权资产搜索

* [Update] 用户授权资产分页展示-删除注释,添加翻译内容
parent 4f53020d
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
import socket import socket
import threading import threading
import os import os
import math
import time
from . import char from . import char
from .config import config from .config import config
...@@ -17,19 +19,34 @@ from .proxy import ProxyServer ...@@ -17,19 +19,34 @@ from .proxy import ProxyServer
logger = get_logger(__file__) logger = get_logger(__file__)
PAGE_DOWN = 'down'
PAGE_UP = 'up'
BACK = 'back'
PROXY = 'proxy'
class InteractiveServer: class InteractiveServer:
_sentinel = object() _sentinel = object()
def __init__(self, client): def __init__(self, client):
self.client = client self.client = client
self.assets = None
self.closed = False self.closed = False
self._search_result = None self._search_result = None
self.nodes = None self.nodes = None
self.get_user_assets_async() self.offset = 0
self.limit = 100
self.assets_list = []
self.finish = False
self.page = 1
self.total_assets = 0
self.total_count = 0 # 分页展示中用来存放数目总条数
self.get_user_assets_paging_async()
self.get_user_nodes_async() self.get_user_nodes_async()
@property
def page_size(self):
return self.client.request.meta['height'] - 8
@property @property
def search_result(self): def search_result(self):
if self._search_result: if self._search_result:
...@@ -101,38 +118,36 @@ class InteractiveServer: ...@@ -101,38 +118,36 @@ class InteractiveServer:
self.search_and_proxy(opt) self.search_and_proxy(opt)
def search_assets(self, q): def search_assets(self, q):
if self.assets is None: if not self.finish:
self.get_user_assets() assets = app_service.get_search_user_granted_assets(self.client.user, q)
return assets
assets = self.assets_list
result = [] result = []
# 所有的 # 所有的
if q in ('', None): if q in ('', None):
result = self.assets result = assets
# 用户输入的是数字,可能想使用id唯一键搜索
elif q.isdigit() and self.search_result and \
len(self.search_result) >= int(q):
result = [self.search_result[int(q) - 1]]
# 全匹配到则直接返回全匹配的 # 全匹配到则直接返回全匹配的
if len(result) == 0: if len(result) == 0:
_result = [asset for asset in self.assets _result = [asset for asset in assets
if is_obj_attr_eq(asset, q)] if is_obj_attr_eq(asset, q)]
if len(_result) == 1: if len(_result) == 1:
result = _result result = _result
# 最后模糊匹配 # 最后模糊匹配
if len(result) == 0: if len(result) == 0:
result = [asset for asset in self.assets result = [asset for asset in assets
if is_obj_attr_has(asset, q)] if is_obj_attr_has(asset, q)]
self.search_result = result return result
def display_assets(self): def display_assets(self):
""" """
Display user all assets Display user all assets
:return: :return:
""" """
self.search_and_display('') self.display_result_paging(self.assets_list)
def display_nodes(self): def display_nodes(self):
if self.nodes is None: if self.nodes is None:
...@@ -163,8 +178,8 @@ class InteractiveServer: ...@@ -163,8 +178,8 @@ class InteractiveServer:
self.display_nodes() self.display_nodes()
return return
self.search_result = self.nodes[_id - 1].assets_granted assets = self.nodes[_id - 1].assets_granted
self.display_search_result() self.display_result_paging(assets)
def display_search_result(self): def display_search_result(self):
sort_by = config["ASSET_LIST_SORT_BY"] sort_by = config["ASSET_LIST_SORT_BY"]
...@@ -191,13 +206,15 @@ class InteractiveServer: ...@@ -191,13 +206,15 @@ class InteractiveServer:
asset.system_users_name_list, asset.comment asset.system_users_name_list, asset.comment
] ]
self.client.send(wr(format_with_zh(size_list, *data))) self.client.send(wr(format_with_zh(size_list, *data)))
self.client.send(wr(_("Total: {} Match: {}").format(
len(self.assets), len(self.search_result)), before=1) total_page = math.ceil(self.total_count/self.page_size)
self.client.send(wr(title(_("Page: {}, Count: {}, Total Page: {}, Total Count: {}").format(
self.page, len(self.search_result), total_page, self.total_count)), before=1)
) )
def search_and_display(self, q): def search_and_display(self, q):
self.search_assets(q) assets = self.search_assets(q)
self.display_search_result() self.display_result_paging(assets)
def get_user_nodes(self): def get_user_nodes(self):
self.nodes = app_service.get_user_asset_groups(self.client.user) self.nodes = app_service.get_user_asset_groups(self.client.user)
...@@ -217,14 +234,24 @@ class InteractiveServer: ...@@ -217,14 +234,24 @@ class InteractiveServer:
asset.system_users_granted = system_users_cleaned asset.system_users_granted = system_users_cleaned
return assets return assets
def get_user_assets(self): def get_user_assets_paging(self):
self.assets = app_service.get_user_assets(self.client.user) while not self.closed:
logger.debug("Get user {} assets total: {}".format( assets, total = app_service.get_user_assets_paging(
self.client.user, len(self.assets)) self.client.user, offset=self.offset, limit=self.limit
) )
logger.info('Get user assets paging async: {}'.format(len(assets)))
def get_user_assets_async(self): if not assets:
thread = threading.Thread(target=self.get_user_assets) logger.info('Get user assets paging async finished.')
self.finish = True
return
if not self.total_assets:
self.total_assets = total
self.total_count = total
self.assets_list.extend(assets)
self.offset += self.limit
def get_user_assets_paging_async(self):
thread = threading.Thread(target=self.get_user_assets_paging)
thread.start() thread.start()
def choose_system_user(self, system_users): def choose_system_user(self, system_users):
...@@ -251,9 +278,9 @@ class InteractiveServer: ...@@ -251,9 +278,9 @@ class InteractiveServer:
self.client.send(wr("{} {}".format(index, system_user.name))) self.client.send(wr("{} {}".format(index, system_user.name)))
def search_and_proxy(self, opt): def search_and_proxy(self, opt):
self.search_assets(opt) assets = self.search_assets(opt)
if self.search_result and len(self.search_result) == 1: if assets and len(assets) == 1:
asset = self.search_result[0] asset = assets[0]
self.search_result = None self.search_result = None
if asset.platform == "Windows": if asset.platform == "Windows":
self.client.send(warning( self.client.send(warning(
...@@ -263,7 +290,102 @@ class InteractiveServer: ...@@ -263,7 +290,102 @@ class InteractiveServer:
return return
self.proxy(asset) self.proxy(asset)
else: else:
self.display_search_result() self.display_result_paging(assets)
def display_result_paging(self, result_list):
if result_list is self.assets_list:
self.total_count = self.total_assets
else:
if len(result_list) == 0:
return
self.total_count = len(result_list)
action = PAGE_DOWN
gen_result = self.get_result_page_down_or_up(result_list)
while True:
try:
page, result = gen_result.send(action)
except TypeError as e:
page, result = next(gen_result)
logger.info(e)
except StopIteration:
logger.info('StopIteration')
self.display_banner()
return None
self.display_result_of_page(page, result)
action = self.get_user_action()
def get_result_page_down_or_up(self, result_list):
left = 0
page = 1
page_up_size = 0 # 记录上一页大小
while True:
right = left + self.page_size
result = result_list[left:right]
if not result and (result_list is self.assets_list) and self.finish:
# 上一页已经是最后一页, 还是展示最后一页(展示上一页)
left -= page_up_size
page -= 1
continue
elif not result and (result_list is not self.assets_list):
# 上一页已经是最后一页, 还是展示最后一页(展示上一页)
left -= page_up_size
page -= 1
continue
elif not result and (result_list is self.assets_list) and not self.finish:
# 还有下一页(暂时没有加载完),需要等待
time.sleep(1)
continue
else:
# 其他4中情况,返回assets
action = yield (page, result)
if action == BACK:
return None, None
elif action == PAGE_UP:
if page <= 1:
# 已经是第一页了
page = 1
left = 0
else:
page -= 1
left -= self.page_size
else:
# PAGE_DOWN
page += 1
left += len(result)
page_up_size = len(result)
def display_result_of_page(self, page, result):
self.client.send(char.CLEAR_CHAR)
self.page = page
self.search_result = result
self.display_search_result()
self.display_prompt_of_page()
def display_prompt_of_page(self):
self.client.send(wr(_('Tips: Enter the asset ID and log directly into the asset.'), before=1))
prompt_page_up = _("Page up: P/p")
prompt_page_down = _("Page down: Enter|N/n")
prompt_exit = _("BACK: B/b")
prompts = [prompt_page_up, prompt_page_down, prompt_exit]
prompt = '\t'.join(prompts)
self.client.send(wr(prompt, before=1))
def get_user_action(self):
opt = net_input(self.client, prompt=':')
if opt in ('p', 'P'):
return PAGE_UP
elif opt in ('B', 'b'):
return BACK
elif opt.isdigit() and self.search_result and 0 < int(opt) <= len(self.search_result):
self.proxy(self.search_result[int(opt)-1])
return BACK
else:
# PAGE_DOWN
return PAGE_DOWN
def proxy(self, asset): def proxy(self, asset):
system_user = self.choose_system_user(asset.system_users_granted) system_user = self.choose_system_user(asset.system_users_granted)
......
...@@ -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-10-10 15:22+0800\n" "POT-Creation-Date: 2018-10-30 10:37+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,11 +16,11 @@ msgstr "" ...@@ -16,11 +16,11 @@ 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:141 #: coco/app.py:135
msgid "Connect idle more than {} minutes, disconnect" msgid "Connect idle more than {} minutes, disconnect"
msgstr "" msgstr ""
#: coco/interactive.py:61 #: coco/interactive.py:78
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"\n" "\n"
...@@ -28,115 +28,135 @@ msgid "" ...@@ -28,115 +28,135 @@ msgid ""
"{end}{R}{R}" "{end}{R}{R}"
msgstr "" msgstr ""
#: coco/interactive.py:63 #: coco/interactive.py:80
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, " "{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, "
"Comment{end} to search login(if unique).{R}" "Comment{end} to search login(if unique).{R}"
msgstr "" msgstr ""
#: coco/interactive.py:64 #: coco/interactive.py:81
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} " "{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} "
"search, such as: /ip.{R}" "search, such as: /ip.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:65 #: coco/interactive.py:82
#, python-brace-format #, python-brace-format
msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}" msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:66 #: coco/interactive.py:83
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}" "{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:67 #: coco/interactive.py:84
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}5) Enter {green}g{end} + {green}Group ID{end} to display the host under " "{T}5) Enter {green}g{end} + {green}Group ID{end} to display the host under "
"the node, such as g1.{R}" "the node, such as g1.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:68 #: coco/interactive.py:85
#, python-brace-format #, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}" msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:69 #: coco/interactive.py:86
#, python-brace-format #, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}" msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:70 #: coco/interactive.py:87
#, 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 "" msgstr ""
#: coco/interactive.py:142 #: coco/interactive.py:157
msgid "No" msgid "No"
msgstr "" msgstr ""
#: coco/interactive.py:149 #: coco/interactive.py:164
msgid "Name" msgid "Name"
msgstr "" msgstr ""
#: coco/interactive.py:149 #: coco/interactive.py:164
msgid "Assets" msgid "Assets"
msgstr "" msgstr ""
#: coco/interactive.py:155 #: coco/interactive.py:170
msgid "Total: {}" msgid "Total: {}"
msgstr "" msgstr ""
#: coco/interactive.py:161 #: coco/interactive.py:176
msgid "There is no matched node, please re-enter" msgid "There is no matched node, please re-enter"
msgstr "" msgstr ""
#: coco/interactive.py:172 #: coco/interactive.py:187
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#: coco/interactive.py:172 #: coco/interactive.py:187
msgid "Hostname" msgid "Hostname"
msgstr "" msgstr ""
#: coco/interactive.py:172 #: coco/interactive.py:187
msgid "IP" msgid "IP"
msgstr "" msgstr ""
#: coco/interactive.py:172 #: coco/interactive.py:187
msgid "LoginAs" msgid "LoginAs"
msgstr "" msgstr ""
#: coco/interactive.py:186 #: coco/interactive.py:201
msgid "Comment" msgid "Comment"
msgstr "" msgstr ""
#: coco/interactive.py:194 #: coco/interactive.py:211
msgid "Total: {} Match: {}" msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr "" msgstr ""
#: coco/interactive.py:237 #: coco/interactive.py:264
msgid "Select a login:: " msgid "Select a login:: "
msgstr "" msgstr ""
#: coco/interactive.py:260 #: coco/interactive.py:287
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:271 #: coco/interactive.py:369
msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr ""
#: coco/interactive.py:370
msgid "Page up: P/p"
msgstr ""
#: coco/interactive.py:371
msgid "Page down: Enter|N/n"
msgstr ""
#: coco/interactive.py:372
msgid "BACK: B/b"
msgstr ""
#: coco/interactive.py:393
msgid "No system user" msgid "No system user"
msgstr "" msgstr ""
#: coco/proxy.py:88 #: coco/models.py:247
msgid "Command `{}` is forbidden ........"
msgstr ""
#: coco/proxy.py:89
msgid "No permission" msgid "No permission"
msgstr "" msgstr ""
#: coco/proxy.py:130 #: coco/proxy.py:131
msgid "Connecting to {}@{} {:.1f}" msgid "Connecting to {}@{} {:.1f}"
msgstr "" 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-10-10 15:22+0800\n" "POT-Creation-Date: 2018-10-30 10:37+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,11 +16,11 @@ msgstr "" ...@@ -16,11 +16,11 @@ 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:141 #: coco/app.py:135
msgid "Connect idle more than {} minutes, disconnect" msgid "Connect idle more than {} minutes, disconnect"
msgstr "空闲时间超过 {} 分钟,断开连接" msgstr "空闲时间超过 {} 分钟,断开连接"
#: coco/interactive.py:61 #: coco/interactive.py:78
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"\n" "\n"
...@@ -30,7 +30,7 @@ msgstr "" ...@@ -30,7 +30,7 @@ msgstr ""
"\n" "\n"
"{T}{T}{title} {user}, 欢迎使用Jumpserver开源跳板机系统 {end}{R}{R}" "{T}{T}{title} {user}, 欢迎使用Jumpserver开源跳板机系统 {end}{R}{R}"
#: coco/interactive.py:63 #: coco/interactive.py:80
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, " "{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, "
...@@ -39,7 +39,7 @@ msgstr "" ...@@ -39,7 +39,7 @@ msgstr ""
"{T}1) 输入 {green}ID{end} 直接登录 或 输入{green}部分 IP,主机名,备注{end} 进" "{T}1) 输入 {green}ID{end} 直接登录 或 输入{green}部分 IP,主机名,备注{end} 进"
"行搜索登录(如果唯一).{R}" "行搜索登录(如果唯一).{R}"
#: coco/interactive.py:64 #: coco/interactive.py:81
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} " "{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} "
...@@ -48,104 +48,127 @@ msgstr "" ...@@ -48,104 +48,127 @@ msgstr ""
"{T}2) 输入 {green}/{end} + {green}IP, 主机名{end} or {green}备注 {end}搜索. " "{T}2) 输入 {green}/{end} + {green}IP, 主机名{end} or {green}备注 {end}搜索. "
"如: /ip{R}" "如: /ip{R}"
#: coco/interactive.py:65 #: coco/interactive.py:82
#, python-brace-format #, python-brace-format
msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}" msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}"
msgstr "{T}3) 输入 {green}p{end} 显示您有权限的主机.{R}" msgstr "{T}3) 输入 {green}p{end} 显示您有权限的主机.{R}"
#: coco/interactive.py:66 #: coco/interactive.py:83
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}" "{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
msgstr "{T}4) 输入 {green}g{end} 显示您有权限的节点.{R}" msgstr "{T}4) 输入 {green}g{end} 显示您有权限的节点.{R}"
#: coco/interactive.py:67 #: coco/interactive.py:84
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}5) Enter {green}g{end} + {green}Group ID{end} to display the host under " "{T}5) Enter {green}g{end} + {green}Group ID{end} to display the host under "
"the node, such as g1.{R}" "the node, such as g1.{R}"
msgstr "{T}5) 输入 {green}g{end} + {green}组ID{end} 显示节点下主机. 如: g1{R}" msgstr "{T}5) 输入 {green}g{end} + {green}组ID{end} 显示节点下主机. 如: g1{R}"
#: coco/interactive.py:68 #: coco/interactive.py:85
#, python-brace-format #, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}" msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr "{T}6) 输入 {green}s{end} 中/英文切换.{R}" msgstr "{T}6) 输入 {green}s{end} 中/英文切换.{R}"
#: coco/interactive.py:69 #: coco/interactive.py:86
#, python-brace-format #, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}" 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:87
#, 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}" msgstr "{T}0) 输入 {green}q{end} 退出.{R}"
#: coco/interactive.py:142 #: coco/interactive.py:157
msgid "No" msgid "No"
msgstr "无" msgstr "无"
#: coco/interactive.py:149 #: coco/interactive.py:164
msgid "Name" msgid "Name"
msgstr "名称" msgstr "名称"
#: coco/interactive.py:149 #: coco/interactive.py:164
msgid "Assets" msgid "Assets"
msgstr "资产" msgstr "资产"
#: coco/interactive.py:155 #: coco/interactive.py:170
msgid "Total: {}" msgid "Total: {}"
msgstr "总共: {}" msgstr "总共: {}"
#: coco/interactive.py:161 #: coco/interactive.py:176
msgid "There is no matched node, please re-enter" msgid "There is no matched node, please re-enter"
msgstr "没有匹配分组,请重新输入" msgstr "没有匹配分组,请重新输入"
#: coco/interactive.py:172 #: coco/interactive.py:187
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#: coco/interactive.py:172 #: coco/interactive.py:187
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: coco/interactive.py:172 #: coco/interactive.py:187
msgid "IP" msgid "IP"
msgstr "" msgstr ""
#: coco/interactive.py:172 #: coco/interactive.py:187
msgid "LoginAs" msgid "LoginAs"
msgstr "登录用户" msgstr "登录用户"
#: coco/interactive.py:186 #: coco/interactive.py:201
msgid "Comment" msgid "Comment"
msgstr "备注" msgstr "备注"
#: coco/interactive.py:194 #: coco/interactive.py:211
msgid "Total: {} Match: {}" msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr "总共: {} 匹配: {}" msgstr "页码: {}, 数量: {}, 总页数: {}, 总数量: {}"
#: coco/interactive.py:237 #: coco/interactive.py:264
msgid "Select a login:: " msgid "Select a login:: "
msgstr "选择一个登录:" msgstr "选择一个登录:"
#: coco/interactive.py:260 #: coco/interactive.py:287
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:271 #: coco/interactive.py:369
msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr "提示: 输入资产ID,直接登录资产."
#: coco/interactive.py:370
msgid "Page up: P/p"
msgstr "上一页: P/p"
#: coco/interactive.py:371
msgid "Page down: Enter|N/n"
msgstr "下一页: Enter|N/n"
#: coco/interactive.py:372
msgid "BACK: B/b"
msgstr "返回: B/b"
#: coco/interactive.py:393
msgid "No system user" msgid "No system user"
msgstr "没有系统用户" msgstr "没有系统用户"
#: coco/proxy.py:88 #: coco/models.py:247
msgid "Command `{}` is forbidden ........"
msgstr ""
#: coco/proxy.py:89
msgid "No permission" msgid "No permission"
msgstr "没有权限" msgstr "没有权限"
#: coco/proxy.py:130 #: coco/proxy.py:131
msgid "Connecting to {}@{} {:.1f}" msgid "Connecting to {}@{} {:.1f}"
msgstr "开始连接到 {}@{} {:.1f}" msgstr "开始连接到 {}@{} {:.1f}"
#: coco/session.py:143 #: coco/session.py:143
msgid "Terminated by administrator" msgid "Terminated by administrator"
msgstr "被管理员中断" msgstr "被管理员中断"
#~ msgid "Total: {} Match: {}"
#~ msgstr "总共: {} 匹配: {}"
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