Unverified Commit 50194e95 authored by Eric_Lee's avatar Eric_Lee Committed by GitHub

Merge pull request #120 from jumpserver/dev

Dev
parents 3271c65b 981f283e
...@@ -8,171 +8,181 @@ msgstr "" ...@@ -8,171 +8,181 @@ msgstr ""
"X-Generator: xgotext\n" "X-Generator: xgotext\n"
#. i18n.T #. i18n.T
#: pkg/auth/server.go:17 #: pkg/handler/banner.go:31
msgid "Please enter 6 digits."
msgstr ""
#. i18n.T
#: pkg/auth/server.go:18
msgid "[MFA auth]: "
msgstr ""
#. i18n.T
#: pkg/handler/banner.go:32
msgid "\t%d) Enter {{.GreenBoldColor}}%s{{.ColorEnd}} to %s.%s" msgid "\t%d) Enter {{.GreenBoldColor}}%s{{.ColorEnd}} to %s.%s"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:47 #: pkg/handler/banner.go:46
msgid "Welcome to use Jumpserver open source fortress system" msgid "Welcome to use Jumpserver open source fortress system"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:49 #: pkg/handler/banner.go:48
msgid "directly login" msgid "directly login"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:50 #: pkg/handler/banner.go:49
msgid "part IP, Hostname, Comment" msgid "part IP, Hostname, Comment"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:50 #: pkg/handler/banner.go:49
msgid "to search login if unique" msgid "to search login if unique"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:51 #: pkg/handler/banner.go:50
msgid "/ + IP, Hostname, Comment" msgid "/ + IP, Hostname, Comment"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:51 #: pkg/handler/banner.go:50
msgid "to search, such as: /192.168" msgid "to search, such as: /192.168"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:52 #: pkg/handler/banner.go:51
msgid "display the host you have permission" msgid "display the host you have permission"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:53 #: pkg/handler/banner.go:52
msgid "display the node that you have permission" msgid "display the node that you have permission"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:54 #: pkg/handler/banner.go:53
msgid "refresh your assets and nodes" msgid "refresh your assets and nodes"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:55 #: pkg/handler/banner.go:54
msgid "print help" msgid "print help"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:56 #: pkg/handler/banner.go:55
msgid "exit" msgid "exit"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:133 #: pkg/handler/banner.go:90
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:133 #: pkg/handler/banner.go:91
msgid "hostname" msgid "hostname"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:133 #: pkg/handler/banner.go:92
msgid "IP" msgid "IP"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:133 #: pkg/handler/banner.go:93
msgid "systemUsers" msgid "comment"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:133 #: pkg/handler/banner.go:94
msgid "comment" msgid "Page: %d, Count: %d, Total Page: %d, Total Count: %d"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:152 #: pkg/handler/banner.go:95
msgid "Page: %d, Count: %d, Total Page: %d, Total Count: %d" msgid "No Assets"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:179 #: pkg/handler/banner.go:96
msgid "" msgid ""
"\n" "\n"
"Tips: Enter the asset ID and log directly into the asset.\n" "Tips: Enter the asset ID and directly login the asset.\n"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:180 #: pkg/handler/banner.go:97
msgid "" msgid ""
"\n" "\n"
"Page up: P/p\tPage down: Enter|N/n\tBACK: b.\n" "Page up: P/p\tPage down: Enter|N/n\tBACK: b.\n"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/session.go:267 #: pkg/handler/banner.go:98
msgid "No Assets"
msgstr ""
#. i18n.T
#: pkg/handler/session.go:289
msgid "Node: [ ID.Name(Asset amount) ]" msgid "Node: [ ID.Name(Asset amount) ]"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/session.go:290 #: pkg/handler/banner.go:99
msgid "Tips: Enter g+NodeID to display the host under the node, such as g1" msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/handler/session.go:304 #: pkg/handler/banner.go:100
msgid "Refresh done" msgid "Refresh done"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/proxy/parser.go:124 #: pkg/handler/banner.go:101
msgid "Tips: Enter system user ID and directly login the asset [ %s(%s) ]"
msgstr ""
#. i18n.T
#: pkg/handler/banner.go:102
msgid "Back: B/b"
msgstr ""
#. i18n.T
#: pkg/handler/banner.go:103
msgid "Name"
msgstr ""
#. i18n.T
#: pkg/handler/banner.go:104
msgid "Username"
msgstr ""
#. i18n.T
#: pkg/proxy/parser.go:131
msgid "Command `%s` is forbidden" msgid "Command `%s` is forbidden"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/proxy/proxy.go:152 #: pkg/proxy/proxy.go:143
msgid "Reuse SSH connections (%s@%s) [Number of connections: %d]"
msgstr ""
#. i18n.T
#: pkg/proxy/proxy.go:161
msgid "Connecting to %s@%s %.1f" msgid "Connecting to %s@%s %.1f"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/proxy/proxy.go:171 #: pkg/proxy/proxy.go:180
msgid "System user <%s> and asset <%s> protocol are inconsistent." msgid "System user <%s> and asset <%s> protocol are inconsistent."
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/proxy/proxy.go:177 #: pkg/proxy/proxy.go:186
msgid "" msgid ""
"Terminal only support protocol ssh/telnet, please use web terminal to access" "Terminal only support protocol ssh/telnet, please use web terminal to access"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/proxy/sessmanager.go:63 #: pkg/proxy/sessmanager.go:67
msgid "Connect with api server failed" msgid "Connect with api server failed"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/proxy/switch.go:142 #: pkg/proxy/switch.go:159
msgid "Connect idle more than %d minutes, disconnect" msgid "Connect idle more than %d minutes, disconnect"
msgstr "" msgstr ""
#. i18n.T #. i18n.T
#: pkg/proxy/switch.go:148 #: pkg/proxy/switch.go:166
msgid "Terminated by administrator" msgid "Terminated by administrator"
msgstr "" msgstr ""
...@@ -8,175 +8,190 @@ msgstr "" ...@@ -8,175 +8,190 @@ msgstr ""
"X-Generator: xgotext\n" "X-Generator: xgotext\n"
#. i18n.T #. i18n.T
#: pkg/auth/server.go:17 #: pkg/handler/banner.go:31
msgid "Please enter 6 digits."
msgstr "请输入六位数字"
#. i18n.T
#: pkg/auth/server.go:18
msgid "[MFA auth]: "
msgstr "[MFA认证]"
#. i18n.T
#: pkg/handler/banner.go:32
msgid "\t%d) Enter {{.GreenBoldColor}}%s{{.ColorEnd}} to %s.%s" msgid "\t%d) Enter {{.GreenBoldColor}}%s{{.ColorEnd}} to %s.%s"
msgstr "\t%d) 输入 {{.GreenBoldColor}}%s{{.ColorEnd}} 进行%s.%s" msgstr "\t%d) 输入 {{.GreenBoldColor}}%s{{.ColorEnd}} 进行%s.%s"
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:47 #: pkg/handler/banner.go:46
msgid "Welcome to use Jumpserver open source fortress system" msgid "Welcome to use Jumpserver open source fortress system"
msgstr "欢迎使用Jumpserver开源堡垒机系统" msgstr "欢迎使用Jumpserver开源堡垒机系统"
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:49 #: pkg/handler/banner.go:48
msgid "directly login" msgid "directly login"
msgstr "直接登" msgstr "直接登"
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:50 #: pkg/handler/banner.go:49
msgid "part IP, Hostname, Comment" msgid "part IP, Hostname, Comment"
msgstr "部分IP、主机名、备注" msgstr "部分IP、主机名、备注"
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:50 #: pkg/handler/banner.go:49
msgid "to search login if unique" msgid "to search login if unique"
msgstr "进行搜索登录(如果唯一)" msgstr "进行搜索登录(如果唯一)"
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:51 #: pkg/handler/banner.go:50
msgid "/ + IP, Hostname, Comment" msgid "/ + IP, Hostname, Comment"
msgstr "/ + IP, 主机名 or 备注" msgstr "/ + IP 主机名 or 备注"
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:51 #: pkg/handler/banner.go:50
msgid "to search, such as: /192.168" msgid "to search, such as: /192.168"
msgstr "搜索, 如: /192.168" msgstr "搜索, 如/192.168"
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:52 #: pkg/handler/banner.go:51
msgid "display the host you have permission" msgid "display the host you have permission"
msgstr "显示您有权限的主机" msgstr "显示您有权限的主机"
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:53 #: pkg/handler/banner.go:52
msgid "display the node that you have permission" msgid "display the node that you have permission"
msgstr "显示您有权限的节点" msgstr "显示您有权限的节点"
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:54 #: pkg/handler/banner.go:53
msgid "refresh your assets and nodes" msgid "refresh your assets and nodes"
msgstr "刷新最新的机器和节点信息" msgstr "刷新最新的机器和节点信息"
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:55 #: pkg/handler/banner.go:54
msgid "print help" msgid "print help"
msgstr "显示帮助" msgstr "显示帮助"
#. i18n.T #. i18n.T
#: pkg/handler/banner.go:56 #: pkg/handler/banner.go:55
msgid "exit" msgid "exit"
msgstr "退出" msgstr "退出"
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:133 #: pkg/handler/banner.go:90
msgid "ID" msgid "ID"
msgstr "ID" msgstr "ID"
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:133 #: pkg/handler/banner.go:91
msgid "hostname" msgid "hostname"
msgstr "主机名" msgstr "主机名"
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:133 #: pkg/handler/banner.go:92
msgid "IP" msgid "IP"
msgstr "IP" msgstr "IP"
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:133 #: pkg/handler/banner.go:93
msgid "systemUsers"
msgstr "登录用户"
#. i18n.T
#: pkg/handler/pagination.go:133
msgid "comment" msgid "comment"
msgstr "备注" msgstr "备注"
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:152 #: pkg/handler/banner.go:94
msgid "Page: %d, Count: %d, Total Page: %d, Total Count: %d" msgid "Page: %d, Count: %d, Total Page: %d, Total Count: %d"
msgstr "页码: %d, 每页行数: %d, 总页数: %d, 总数量: %d" msgstr "页码:%d,每页行数:%d,总页数:%d,总数量:%d"
#. i18n.T
#: pkg/handler/banner.go:95
msgid "No Assets"
msgstr "没有资产"
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:179 #: pkg/handler/banner.go:96
#, fuzzy
msgid "" msgid ""
"\n" "\n"
"Tips: Enter the asset ID and log directly into the asset.\n" "Tips: Enter the asset ID and directly login the asset.\n"
msgstr "" msgstr ""
"\n" "\n"
"提示: 输入资产ID,直接登录资产.\n" "提示:输入资产ID,登录资产\n"
#. i18n.T #. i18n.T
#: pkg/handler/pagination.go:180 #: pkg/handler/banner.go:97
msgid "" msgid ""
"\n" "\n"
"Page up: P/p\tPage down: Enter|N/n\tBACK: b.\n" "Page up: P/p\tPage down: Enter|N/n\tBACK: b.\n"
msgstr "" msgstr ""
"\n" "\n"
"上一页: P/p 下一页: Enter|N/n 返回: B/b\n" "上一页:P/p 下一页:Enter|N/n 返回:B/b\n"
#. i18n.T #. i18n.T
#: pkg/handler/session.go:267 #: pkg/handler/banner.go:98
msgid "No Assets"
msgstr "没有资产"
#. i18n.T
#: pkg/handler/session.go:289
msgid "Node: [ ID.Name(Asset amount) ]" msgid "Node: [ ID.Name(Asset amount) ]"
msgstr "节点: [ ID.名称(资产数量) ]" msgstr "节点[ ID.名称(资产数量) ]"
#. i18n.T #. i18n.T
#: pkg/handler/session.go:290 #: pkg/handler/banner.go:99
msgid "Tips: Enter g+NodeID to display the host under the node, such as g1" msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr "提示: 输入 g+节点ID 显示节点下主机. 如: g1" msgstr "提示:输入 g+节点ID 显示节点下主机,如: g1"
#. i18n.T #. i18n.T
#: pkg/handler/session.go:304 #: pkg/handler/banner.go:100
msgid "Refresh done" msgid "Refresh done"
msgstr "刷新完成" msgstr "刷新完成"
#. i18n.T #. i18n.T
#: pkg/proxy/parser.go:124 #: pkg/handler/banner.go:101
#, fuzzy
msgid "Tips: Enter system user ID and directly login the asset [ %s(%s) ]"
msgstr ""
"\n"
"提示:输入系统用户ID,登录资产[ %s(%s) ]\n"
#. i18n.T
#: pkg/handler/banner.go:102
msgid "Back: B/b"
msgstr "返回:B/b"
#. i18n.T
#: pkg/handler/banner.go:103
msgid "Name"
msgstr "名称"
#. i18n.T
#: pkg/handler/banner.go:104
#, fuzzy
msgid "Username"
msgstr "用户名"
#. i18n.T
#: pkg/proxy/parser.go:131
msgid "Command `%s` is forbidden" msgid "Command `%s` is forbidden"
msgstr "命令 `%s` 是被禁止的 ..." msgstr "命令 `%s` 是被禁止的 ..."
#. i18n.T #. i18n.T
#: pkg/proxy/proxy.go:152 #: pkg/proxy/proxy.go:143
msgid "Reuse SSH connections (%s@%s) [Number of connections: %d]"
msgstr "复用SSH连接(%s@%s)[连接数量: %d]"
#. i18n.T
#: pkg/proxy/proxy.go:161
msgid "Connecting to %s@%s %.1f" msgid "Connecting to %s@%s %.1f"
msgstr "开始连接到 %s@%s %.1f" msgstr "开始连接到 %s@%s %.1f"
#. i18n.T #. i18n.T
#: pkg/proxy/proxy.go:171 #: pkg/proxy/proxy.go:180
msgid "System user <%s> and asset <%s> protocol are inconsistent." msgid "System user <%s> and asset <%s> protocol are inconsistent."
msgstr "系统用户<%s>和资产<%s>协议不一致" msgstr "系统用户<%s>和资产<%s>协议不一致"
#. i18n.T #. i18n.T
#: pkg/proxy/proxy.go:177 #: pkg/proxy/proxy.go:186
msgid "" msgid ""
"Terminal only support protocol ssh/telnet, please use web terminal to access" "Terminal only support protocol ssh/telnet, please use web terminal to access"
msgstr "终端仅支持ssh/telnet协议,请使用web终端登录" msgstr "终端仅支持ssh/telnet协议,请使用web终端登录"
#. i18n.T #. i18n.T
#: pkg/proxy/sessmanager.go:63 #: pkg/proxy/sessmanager.go:67
msgid "Connect with api server failed" msgid "Connect with api server failed"
msgstr "连接API服务失败" msgstr "连接API服务失败"
#. i18n.T #. i18n.T
#: pkg/proxy/switch.go:142 #: pkg/proxy/switch.go:159
msgid "Connect idle more than %d minutes, disconnect" msgid "Connect idle more than %d minutes, disconnect"
msgstr "空闲时间超过%d分钟,断开连接" msgstr "空闲时间超过%d分钟,断开连接"
#. i18n.T #. i18n.T
#: pkg/proxy/switch.go:148 #: pkg/proxy/switch.go:166
msgid "Terminated by administrator" msgid "Terminated by administrator"
msgstr "管理员中断连接" msgstr "管理员中断连接"
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"sync"
"text/template" "text/template"
"github.com/jumpserver/koko/pkg/config" "github.com/jumpserver/koko/pkg/config"
...@@ -79,3 +80,29 @@ func displayBanner(sess io.ReadWriter, user string) { ...@@ -79,3 +80,29 @@ func displayBanner(sess io.ReadWriter, user string) {
utils.IgnoreErrWriteString(sess, v.Text()) utils.IgnoreErrWriteString(sess, v.Text())
} }
} }
var i18nMap map[string]string
var i18nOnce sync.Once
func getI18nFromMap(name string) string {
i18nOnce.Do(func() {
i18nMap = map[string]string{
"ID": i18n.T("ID"),
"Hostname": i18n.T("hostname"),
"IP": i18n.T("IP"),
"Comment": i18n.T("comment"),
"AssetTableCaption": i18n.T("Page: %d, Count: %d, Total Page: %d, Total Count: %d"),
"NoAssets": i18n.T("No Assets"),
"LoginTip": i18n.T("\nTips: Enter the asset ID and directly login the asset.\n"),
"PageActionTip": i18n.T("\nPage up: P/p Page down: Enter|N/n BACK: b.\n"),
"NodeHeaderTip": i18n.T("Node: [ ID.Name(Asset amount) ]"),
"NodeEndTip": i18n.T("Tips: Enter g+NodeID to display the host under the node, such as g1"),
"RefreshDone": i18n.T("Refresh done"),
"SelectUserTip": i18n.T("Tips: Enter system user ID and directly login the asset [ %s(%s) ]"),
"BackTip": i18n.T("Back: B/b"),
"Name": i18n.T("Name"),
"Username": i18n.T("Username"),
}
})
return i18nMap[name]
}
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"github.com/jumpserver/koko/pkg/common" "github.com/jumpserver/koko/pkg/common"
"github.com/jumpserver/koko/pkg/config" "github.com/jumpserver/koko/pkg/config"
"github.com/jumpserver/koko/pkg/i18n"
"github.com/jumpserver/koko/pkg/model" "github.com/jumpserver/koko/pkg/model"
"github.com/jumpserver/koko/pkg/service" "github.com/jumpserver/koko/pkg/service"
"github.com/jumpserver/koko/pkg/utils" "github.com/jumpserver/koko/pkg/utils"
...@@ -132,7 +131,8 @@ func (p *AssetPagination) Start() []model.Asset { ...@@ -132,7 +131,8 @@ func (p *AssetPagination) Start() []model.Asset {
} }
func (p *AssetPagination) displayPageAssets() { func (p *AssetPagination) displayPageAssets() {
Labels := []string{i18n.T("ID"), i18n.T("hostname"), i18n.T("IP"), i18n.T("comment")} Labels := []string{getI18nFromMap("ID"), getI18nFromMap("Hostname"),
getI18nFromMap("IP"), getI18nFromMap("Comment")}
fields := []string{"ID", "hostname", "IP", "comment"} fields := []string{"ID", "hostname", "IP", "comment"}
data := make([]map[string]string, len(p.currentData)) data := make([]map[string]string, len(p.currentData))
for i, j := range p.currentData { for i, j := range p.currentData {
...@@ -152,7 +152,7 @@ func (p *AssetPagination) displayPageAssets() { ...@@ -152,7 +152,7 @@ func (p *AssetPagination) displayPageAssets() {
data[i] = row data[i] = row
} }
w, _ := p.term.GetSize() w, _ := p.term.GetSize()
caption := fmt.Sprintf(i18n.T("Page: %d, Count: %d, Total Page: %d, Total Count: %d"), caption := fmt.Sprintf(getI18nFromMap("AssetTableCaption"),
p.page.CurrentPage(), p.page.PageSize(), p.page.TotalPage(), p.page.TotalCount(), p.page.CurrentPage(), p.page.PageSize(), p.page.TotalPage(), p.page.TotalCount(),
) )
caption = utils.WrapperString(caption, utils.Green) caption = utils.WrapperString(caption, utils.Green)
...@@ -269,12 +269,12 @@ func (p *UserAssetPagination) Start() []model.Asset { ...@@ -269,12 +269,12 @@ func (p *UserAssetPagination) Start() []model.Asset {
func (p *UserAssetPagination) displayPageAssets() { func (p *UserAssetPagination) displayPageAssets() {
if len(p.Data.Data) == 0 { if len(p.Data.Data) == 0 {
_, _ = p.term.Write([]byte(i18n.T("No Assets"))) _, _ = p.term.Write([]byte(getI18nFromMap("NoAssets")+"\n\r"))
_, _ = p.term.Write([]byte("\n\r"))
return return
} }
Labels := []string{i18n.T("ID"), i18n.T("hostname"), i18n.T("IP"), i18n.T("comment")} Labels := []string{getI18nFromMap("ID"), getI18nFromMap("Hostname"),
getI18nFromMap("IP"), getI18nFromMap("Comment")}
fields := []string{"ID", "hostname", "IP", "comment"} fields := []string{"ID", "hostname", "IP", "comment"}
p.currentData = model.AssetList(p.Data.Data).SortBy(config.GetConf().AssetListSortBy) p.currentData = model.AssetList(p.Data.Data).SortBy(config.GetConf().AssetListSortBy)
data := make([]map[string]string, len(p.currentData)) data := make([]map[string]string, len(p.currentData))
...@@ -324,9 +324,9 @@ func (p *UserAssetPagination) displayPageAssets() { ...@@ -324,9 +324,9 @@ func (p *UserAssetPagination) displayPageAssets() {
currentPage = (currentOffset / pageSize) + 1 currentPage = (currentOffset / pageSize) + 1
} }
} }
caption := fmt.Sprintf(i18n.T("Page: %d, Count: %d, Total Page: %d, Total Count: %d"), caption := fmt.Sprintf(getI18nFromMap("AssetTableCaption"),
currentPage, pageSize, totalPage, totalCount, currentPage, pageSize, totalPage, totalCount)
)
caption = utils.WrapperString(caption, utils.Green) caption = utils.WrapperString(caption, utils.Green)
table := common.WrapperTable{ table := common.WrapperTable{
Fields: fields, Fields: fields,
...@@ -387,11 +387,6 @@ func getPageSize(term *utils.Terminal) int { ...@@ -387,11 +387,6 @@ func getPageSize(term *utils.Terminal) int {
} }
func displayAssetPaginationTipsInfo(w io.Writer) { func displayAssetPaginationTipsInfo(w io.Writer) {
tips := []string{ utils.IgnoreErrWriteString(w, getI18nFromMap("LoginTip"))
i18n.T("\nTips: Enter the asset ID and log directly into the asset.\n"), utils.IgnoreErrWriteString(w, getI18nFromMap("PageActionTip"))
i18n.T("\nPage up: P/p Page down: Enter|N/n BACK: b.\n"),
}
for _, tip := range tips {
_, _ = w.Write([]byte(tip))
}
} }
...@@ -8,12 +8,11 @@ import ( ...@@ -8,12 +8,11 @@ import (
"strings" "strings"
"github.com/gliderlabs/ssh" "github.com/gliderlabs/ssh"
"github.com/olekukonko/tablewriter"
"github.com/xlab/treeprint" "github.com/xlab/treeprint"
"github.com/jumpserver/koko/pkg/cctx" "github.com/jumpserver/koko/pkg/cctx"
"github.com/jumpserver/koko/pkg/common"
"github.com/jumpserver/koko/pkg/config" "github.com/jumpserver/koko/pkg/config"
"github.com/jumpserver/koko/pkg/i18n"
"github.com/jumpserver/koko/pkg/logger" "github.com/jumpserver/koko/pkg/logger"
"github.com/jumpserver/koko/pkg/model" "github.com/jumpserver/koko/pkg/model"
"github.com/jumpserver/koko/pkg/proxy" "github.com/jumpserver/koko/pkg/proxy"
...@@ -196,57 +195,86 @@ func (h *interactiveHandler) displayAllAssets() { ...@@ -196,57 +195,86 @@ func (h *interactiveHandler) displayAllAssets() {
} }
} }
func (h *interactiveHandler) chooseSystemUser(systemUsers []model.SystemUser) model.SystemUser { func (h *interactiveHandler) chooseSystemUser(asset model.Asset,
systemUsers []model.SystemUser) (systemUser model.SystemUser, ok bool) {
length := len(systemUsers) length := len(systemUsers)
switch length { switch length {
case 0: case 0:
return model.SystemUser{} return model.SystemUser{}, false
case 1: case 1:
return systemUsers[0] return systemUsers[0], true
default: default:
} }
displaySystemUsers := selectHighestPrioritySystemUsers(systemUsers) displaySystemUsers := selectHighestPrioritySystemUsers(systemUsers)
if len(displaySystemUsers) == 1 { if len(displaySystemUsers) == 1 {
return displaySystemUsers[0] return displaySystemUsers[0], true
} }
table := tablewriter.NewWriter(h.term) Labels := []string{getI18nFromMap("ID"), getI18nFromMap("Name"), getI18nFromMap("Username")}
table.SetHeader([]string{"ID", "Name"}) fields := []string{"ID", "Name", "Username"}
for i := 0; i < len(displaySystemUsers); i++ {
table.Append([]string{strconv.Itoa(i + 1), displaySystemUsers[i].Name}) data := make([]map[string]string, len(displaySystemUsers))
} for i, j := range displaySystemUsers {
table.SetBorder(false) row := make(map[string]string)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) row["ID"] = strconv.Itoa(i + 1)
table.SetAlignment(tablewriter.ALIGN_LEFT) row["Name"] = j.Name
row["Username"] = j.Username
data[i] = row
}
w, _ := h.term.GetSize()
table := common.WrapperTable{
Fields: fields,
Labels: Labels,
FieldsSize: map[string][3]int{
"ID": {0, 0, 5},
"Name": {0, 8, 0},
"Username": {0, 10, 0},
},
Data: data,
TotalSize: w,
TruncPolicy: common.TruncMiddle,
}
table.Initial()
h.term.SetPrompt("ID> ") h.term.SetPrompt("ID> ")
defer h.term.SetPrompt("Opt> ") defer h.term.SetPrompt("Opt> ")
for count := 0; count < 3; count++ { selectUserTip := fmt.Sprintf(getI18nFromMap("SelectUserTip"), asset.Hostname, asset.IP)
table.Render() for {
utils.IgnoreErrWriteString(h.term, table.Display())
utils.IgnoreErrWriteString(h.term, selectUserTip)
utils.IgnoreErrWriteString(h.term, getI18nFromMap("BackTip"))
utils.IgnoreErrWriteString(h.term, "\r\n")
line, err := h.term.ReadLine() line, err := h.term.ReadLine()
if err != nil { if err != nil {
break return
} }
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
switch strings.ToLower(line) {
case "q", "b", "quit", "exit", "back":
return
}
if num, err := strconv.Atoi(line); err == nil { if num, err := strconv.Atoi(line); err == nil {
if num > 0 && num <= len(displaySystemUsers) { if num > 0 && num <= len(displaySystemUsers) {
return displaySystemUsers[num-1] return displaySystemUsers[num-1], true
} }
} }
} }
return displaySystemUsers[0]
} }
func (h *interactiveHandler) displayAssets(assets model.AssetList) { func (h *interactiveHandler) displayAssets(assets model.AssetList) {
if len(assets) == 0 { if len(assets) == 0 {
_, _ = io.WriteString(h.term, i18n.T("No Assets")+"\n\r") _, _ = io.WriteString(h.term, getI18nFromMap("NoAssets")+"\n\r")
} else { } else {
sortedAssets := assets.SortBy(config.GetConf().AssetListSortBy) sortedAssets := assets.SortBy(config.GetConf().AssetListSortBy)
pag := NewAssetPagination(h.term, sortedAssets) pag := NewAssetPagination(h.term, sortedAssets)
selectOneAssets := pag.Start() selectOneAssets := pag.Start()
if len(selectOneAssets) == 1 { if len(selectOneAssets) == 1 {
systemUsers := service.GetUserAssetSystemUsers(h.user.ID, selectOneAssets[0].ID) systemUsers := service.GetUserAssetSystemUsers(h.user.ID, selectOneAssets[0].ID)
systemUser := h.chooseSystemUser(systemUsers) systemUser, ok := h.chooseSystemUser(selectOneAssets[0], systemUsers)
if !ok {
return
}
h.assetSelect = &selectOneAssets[0] h.assetSelect = &selectOneAssets[0]
h.systemUserSelect = &systemUser h.systemUserSelect = &systemUser
h.Proxy(context.TODO()) h.Proxy(context.TODO())
...@@ -259,12 +287,9 @@ func (h *interactiveHandler) displayAssets(assets model.AssetList) { ...@@ -259,12 +287,9 @@ func (h *interactiveHandler) displayAssets(assets model.AssetList) {
func (h *interactiveHandler) displayNodes(nodes []model.Node) { func (h *interactiveHandler) displayNodes(nodes []model.Node) {
tree := ConstructAssetNodeTree(nodes) tree := ConstructAssetNodeTree(nodes)
tipHeaderMsg := i18n.T("Node: [ ID.Name(Asset amount) ]") _, err := io.WriteString(h.term, "\n\r"+getI18nFromMap("NodeHeaderTip"))
tipEndMsg := i18n.T("Tips: Enter g+NodeID to display the host under the node, such as g1")
_, err := io.WriteString(h.term, "\n\r"+tipHeaderMsg)
_, err = io.WriteString(h.term, tree.String()) _, err = io.WriteString(h.term, tree.String())
_, err = io.WriteString(h.term, tipEndMsg+"\n\r") _, err = io.WriteString(h.term, getI18nFromMap("NodeEndTip")+"\n\r")
if err != nil { if err != nil {
logger.Info("displayAssetNodes err:", err) logger.Info("displayAssetNodes err:", err)
} }
...@@ -277,7 +302,7 @@ func (h *interactiveHandler) refreshAssetsAndNodesData() { ...@@ -277,7 +302,7 @@ func (h *interactiveHandler) refreshAssetsAndNodesData() {
h.loadAllAssets() h.loadAllAssets()
} }
h.loadUserNodes("2") h.loadUserNodes("2")
_, err := io.WriteString(h.term, i18n.T("Refresh done")+"\n\r") _, err := io.WriteString(h.term, getI18nFromMap("RefreshDone")+"\n\r")
if err != nil { if err != nil {
logger.Error("refresh Assets Nodes err:", err) logger.Error("refresh Assets Nodes err:", err)
} }
...@@ -363,7 +388,10 @@ func (h *interactiveHandler) searchNodeAssets(num int) (assets model.AssetList) ...@@ -363,7 +388,10 @@ func (h *interactiveHandler) searchNodeAssets(num int) (assets model.AssetList)
func (h *interactiveHandler) ProxyAsset(assetSelect model.Asset) { func (h *interactiveHandler) ProxyAsset(assetSelect model.Asset) {
systemUsers := service.GetUserAssetSystemUsers(h.user.ID, assetSelect.ID) systemUsers := service.GetUserAssetSystemUsers(h.user.ID, assetSelect.ID)
systemUserSelect := h.chooseSystemUser(systemUsers) systemUserSelect, ok := h.chooseSystemUser(assetSelect, systemUsers)
if !ok {
return
}
h.systemUserSelect = &systemUserSelect h.systemUserSelect = &systemUserSelect
h.assetSelect = &assetSelect h.assetSelect = &assetSelect
h.Proxy(context.Background()) h.Proxy(context.Background())
......
...@@ -21,6 +21,8 @@ type ProxyServer struct { ...@@ -21,6 +21,8 @@ type ProxyServer struct {
User *model.User User *model.User
Asset *model.Asset Asset *model.Asset
SystemUser *model.SystemUser SystemUser *model.SystemUser
cacheSSHClient *srvconn.SSHClient
} }
// getSystemUserAuthOrManualSet 获取系统用户的认证信息或手动设置 // getSystemUserAuthOrManualSet 获取系统用户的认证信息或手动设置
...@@ -105,6 +107,7 @@ func (p *ProxyServer) getSSHConn() (srvConn *srvconn.ServerSSHConnection, err er ...@@ -105,6 +107,7 @@ func (p *ProxyServer) getSSHConn() (srvConn *srvconn.ServerSSHConnection, err er
ReuseConnection: conf.ReuseConnection, ReuseConnection: conf.ReuseConnection,
CloseOnce: new(sync.Once), CloseOnce: new(sync.Once),
} }
srvConn.SetSSHClient(p.cacheSSHClient)
err = srvConn.Connect(pty.Window.Height, pty.Window.Width, pty.Term) err = srvConn.Connect(pty.Window.Height, pty.Window.Width, pty.Term)
return return
} }
...@@ -129,12 +132,22 @@ func (p *ProxyServer) getTelnetConn() (srvConn *srvconn.ServerTelnetConnection, ...@@ -129,12 +132,22 @@ func (p *ProxyServer) getTelnetConn() (srvConn *srvconn.ServerTelnetConnection,
// getServerConn 获取获取server连接 // getServerConn 获取获取server连接
func (p *ProxyServer) getServerConn() (srvConn srvconn.ServerConnection, err error) { func (p *ProxyServer) getServerConn() (srvConn srvconn.ServerConnection, err error) {
if p.cacheSSHClient == nil {
done := make(chan struct{}) done := make(chan struct{})
defer func() { defer func() {
utils.IgnoreErrWriteString(p.UserConn, "\r\n") utils.IgnoreErrWriteString(p.UserConn, "\r\n")
close(done) close(done)
}() }()
go p.sendConnectingMsg(done, config.GetConf().SSHTimeout*time.Second) go p.sendConnectingMsg(done, config.GetConf().SSHTimeout*time.Second)
} else {
reuseMsg := fmt.Sprintf(i18n.T("Reuse SSH connections (%s@%s) [Number of connections: %d]"),
p.SystemUser.Username, p.Asset.Hostname, p.cacheSSHClient.RefCount())
utils.IgnoreErrWriteString(p.UserConn, reuseMsg+"\r\n")
logger.Infof("Request %s: Reuse connection for SSH. SSH client %p current ref: %d", p.UserConn.ID(),
p.cacheSSHClient, p.cacheSSHClient.RefCount())
}
if p.SystemUser.Protocol == "telnet" { if p.SystemUser.Protocol == "telnet" {
return p.getTelnetConn() return p.getTelnetConn()
} else { } else {
...@@ -193,6 +206,17 @@ func (p *ProxyServer) checkRequiredSystemUserInfo() error { ...@@ -193,6 +206,17 @@ func (p *ProxyServer) checkRequiredSystemUserInfo() error {
logger.Errorf("Get asset %s systemuser username err: %s", p.Asset.Hostname, err) logger.Errorf("Get asset %s systemuser username err: %s", p.Asset.Hostname, err)
return err return err
} }
if config.GetConf().ReuseConnection {
key := srvconn.MakeReuseSSHClientKey(p.User, p.Asset, p.SystemUser)
cacheSSHClient, ok := srvconn.GetClientFromCache(key)
if ok {
p.cacheSSHClient = cacheSSHClient
logger.Infof("Reuse connection for SFTP: %s->%s@%s. SSH client %p current ref: %d",
p.User.Username, p.SystemUser.Username, p.Asset.IP, cacheSSHClient, cacheSSHClient.RefCount())
return nil
}
}
if err := p.getSystemUserAuthOrManualSet(); err != nil { if err := p.getSystemUserAuthOrManualSet(); err != nil {
logger.Errorf("Get asset %s systemuser password/PrivateKey err: %s", p.Asset.Hostname, err) logger.Errorf("Get asset %s systemuser password/PrivateKey err: %s", p.Asset.Hostname, err)
return err return err
......
...@@ -11,7 +11,7 @@ func GetSystemUserAssetAuthInfo(systemUserID, assetID string) (info model.System ...@@ -11,7 +11,7 @@ func GetSystemUserAssetAuthInfo(systemUserID, assetID string) (info model.System
Url := fmt.Sprintf(SystemUserAssetAuthURL, systemUserID, assetID) Url := fmt.Sprintf(SystemUserAssetAuthURL, systemUserID, assetID)
_, err := authClient.Get(Url, &info) _, err := authClient.Get(Url, &info)
if err != nil { if err != nil {
logger.Error("Get system user %s asset %s auth info failed", systemUserID, assetID) logger.Errorf("Get system user %s asset %s auth info failed", systemUserID, assetID)
} }
return return
} }
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
...@@ -42,7 +43,7 @@ type SSHClient struct { ...@@ -42,7 +43,7 @@ type SSHClient struct {
closed chan struct{} closed chan struct{}
} }
func (s *SSHClient) refCount() int { func (s *SSHClient) RefCount() int {
if s.isClosed() { if s.isClosed() {
return 0 return 0
} }
...@@ -273,34 +274,33 @@ func newClient(asset *model.Asset, systemUser *model.SystemUser, timeout time.Du ...@@ -273,34 +274,33 @@ func newClient(asset *model.Asset, systemUser *model.SystemUser, timeout time.Du
func NewClient(user *model.User, asset *model.Asset, systemUser *model.SystemUser, timeout time.Duration, func NewClient(user *model.User, asset *model.Asset, systemUser *model.SystemUser, timeout time.Duration,
useCache bool) (client *SSHClient, err error) { useCache bool) (client *SSHClient, err error) {
key := fmt.Sprintf("%s_%s_%s", user.ID, asset.ID, systemUser.ID)
switch {
case useCache:
client = getClientFromCache(key)
if client != nil {
if systemUser.Username == "" {
systemUser.Username = client.username
}
logger.Infof("Reuse connection: %s->%s@%s. SSH client %p current ref: %d",
user.Username, client.username, asset.IP, client, client.refCount())
return client, nil
}
}
client, err = newClient(asset, systemUser, timeout) client, err = newClient(asset, systemUser, timeout)
if err == nil && useCache { if err == nil && useCache {
key := MakeReuseSSHClientKey(user, asset, systemUser)
setClientCache(key, client) setClientCache(key, client)
} }
return return
} }
func getClientFromCache(key string) (client *SSHClient) { func searchSSHClientFromCache(prefixKey string) (client *SSHClient, ok bool) {
clientLock.Lock() clientLock.Lock()
defer clientLock.Unlock() defer clientLock.Unlock()
client, ok := sshClients[key] for key, cacheClient := range sshClients {
if !ok { if strings.HasPrefix(key, prefixKey) {
return nil cacheClient.increaseRef()
return cacheClient, true
} }
}
return
}
func GetClientFromCache(key string) (client *SSHClient, ok bool) {
clientLock.Lock()
defer clientLock.Unlock()
client, ok = sshClients[key]
if ok {
client.increaseRef() client.increaseRef()
}
return return
} }
...@@ -317,7 +317,7 @@ func RecycleClient(client *SSHClient) { ...@@ -317,7 +317,7 @@ func RecycleClient(client *SSHClient) {
return return
} }
client.decreaseRef() client.decreaseRef()
if client.refCount() == 0 { if client.RefCount() == 0 {
clientLock.Lock() clientLock.Lock()
delete(sshClients, client.key) delete(sshClients, client.key)
clientLock.Unlock() clientLock.Unlock()
...@@ -328,6 +328,10 @@ func RecycleClient(client *SSHClient) { ...@@ -328,6 +328,10 @@ func RecycleClient(client *SSHClient) {
logger.Infof("Success to close SSH client %p", client) logger.Infof("Success to close SSH client %p", client)
} }
} else { } else {
logger.Debugf("SSH client %p ref -1. current ref: %d", client, client.refCount()) logger.Debugf("SSH client %p ref -1. current ref: %d", client, client.RefCount())
} }
} }
func MakeReuseSSHClientKey(user *model.User, asset *model.Asset, systemUser *model.SystemUser) string {
return fmt.Sprintf("%s_%s_%s_%s", user.ID, asset.ID, systemUser.ID, systemUser.Username)
}
...@@ -589,10 +589,42 @@ func (u *UserSftp) SendFTPLog(dataChan <-chan *model.FTPLog) { ...@@ -589,10 +589,42 @@ func (u *UserSftp) SendFTPLog(dataChan <-chan *model.FTPLog) {
} }
func (u *UserSftp) GetSftpClient(asset *model.Asset, sysUser *model.SystemUser) (conn *SftpConn, err error) { func (u *UserSftp) GetSftpClient(asset *model.Asset, sysUser *model.SystemUser) (conn *SftpConn, err error) {
sshClient, err := NewClient(u.User, asset, sysUser, u.Overtime, u.ReuseConnection) var (
sshClient *SSHClient
ok bool
)
if u.ReuseConnection {
key := MakeReuseSSHClientKey(u.User, asset, sysUser)
switch sysUser.Username {
case "":
sshClient, ok = searchSSHClientFromCache(key)
if ok {
sysUser.Username = sshClient.username
}
default:
sshClient, ok = GetClientFromCache(key)
}
if !ok {
sshClient, err = NewClient(u.User, asset, sysUser, u.Overtime, u.ReuseConnection)
if err != nil {
logger.Errorf("Get new SSH client err: %s", err)
return
}
} else {
logger.Infof("Reuse connection for SFTP: %s->%s@%s. SSH client %p current ref: %d",
u.User.Username, sshClient.username, asset.IP, sshClient, sshClient.RefCount())
}
} else {
sshClient, err = NewClient(u.User, asset, sysUser, u.Overtime, u.ReuseConnection)
if err != nil { if err != nil {
logger.Errorf("Get new SSH client err: %s", err)
return return
} }
}
sftpClient, err := sftp.NewClient(sshClient.client) sftpClient, err := sftp.NewClient(sshClient.client)
if err != nil { if err != nil {
logger.Errorf("SSH client %p start sftp client session err %s", sshClient, err) logger.Errorf("SSH client %p start sftp client session err %s", sshClient, err)
......
...@@ -25,6 +25,12 @@ type ServerSSHConnection struct { ...@@ -25,6 +25,12 @@ type ServerSSHConnection struct {
stdout io.Reader stdout io.Reader
} }
func (sc *ServerSSHConnection) SetSSHClient(client *SSHClient) {
if client != nil {
sc.client = client
}
}
func (sc *ServerSSHConnection) Protocol() string { func (sc *ServerSSHConnection) Protocol() string {
return "ssh" return "ssh"
} }
...@@ -57,11 +63,13 @@ func (sc *ServerSSHConnection) invokeShell(h, w int, term string) (err error) { ...@@ -57,11 +63,13 @@ func (sc *ServerSSHConnection) invokeShell(h, w int, term string) (err error) {
} }
func (sc *ServerSSHConnection) Connect(h, w int, term string) (err error) { func (sc *ServerSSHConnection) Connect(h, w int, term string) (err error) {
if sc.client == nil {
sc.client, err = NewClient(sc.User, sc.Asset, sc.SystemUser, sc.Timeout(), sc.ReuseConnection) sc.client, err = NewClient(sc.User, sc.Asset, sc.SystemUser, sc.Timeout(), sc.ReuseConnection)
if err != nil { if err != nil {
logger.Errorf("New SSH client err: %s", err) logger.Errorf("New SSH client err: %s", err)
return return
} }
}
err = sc.invokeShell(h, w, term) err = sc.invokeShell(h, w, term)
if err != nil { if err != nil {
logger.Errorf("SSH client %p start ssh shell session err %s", sc.client, err) logger.Errorf("SSH client %p start ssh shell session err %s", sc.client, err)
......
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