mirror of
https://github.com/veops/oneterm.git
synced 2025-09-27 03:36:02 +08:00
796 lines
18 KiB
Go
796 lines
18 KiB
Go
// Package handler
|
||
/**
|
||
Copyright (c) The Authors.
|
||
* @Author: feng.xiang
|
||
* @Date: 2023/12/13 09:50
|
||
* @Desc:
|
||
*/
|
||
package handler
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/BurntSushi/toml"
|
||
"github.com/c-bata/go-prompt"
|
||
"github.com/chzyer/readline"
|
||
gossh "github.com/gliderlabs/ssh"
|
||
"github.com/mattn/go-runewidth"
|
||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||
"github.com/olekukonko/tablewriter"
|
||
"github.com/patrickmn/go-cache"
|
||
"github.com/spf13/cast"
|
||
"github.com/veops/go-ansiterm"
|
||
"go.uber.org/zap"
|
||
"golang.org/x/crypto/ssh"
|
||
"golang.org/x/text/language"
|
||
|
||
myi18n "github.com/veops/oneterm/pkg/i18n"
|
||
"github.com/veops/oneterm/pkg/logger"
|
||
"github.com/veops/oneterm/pkg/proto/ssh/client"
|
||
"github.com/veops/oneterm/pkg/proto/ssh/config"
|
||
"github.com/veops/oneterm/pkg/server/model"
|
||
"github.com/veops/oneterm/pkg/util"
|
||
)
|
||
|
||
type InteractiveHandler struct {
|
||
Locker *sync.RWMutex
|
||
|
||
Session gossh.Session
|
||
//Term *term.Terminal
|
||
Term *readline.Instance
|
||
Prompt *prompt.Prompt
|
||
Localizer *i18n.Localizer
|
||
SshType int
|
||
pty *gossh.Pty
|
||
|
||
Sshd *sshdServer
|
||
Pty gossh.Pty
|
||
Language int
|
||
|
||
Assets []*model.Asset
|
||
Accounts map[int]*model.Account
|
||
Commands map[int]*model.Command
|
||
HistoryInput []string
|
||
|
||
SshClient *ssh.Client
|
||
SshSession map[string]*client.Connection
|
||
GatewayCloseChan chan struct{}
|
||
|
||
SelectedAsset *model.Asset
|
||
SessionReq *model.SshReq
|
||
|
||
AccountInfo *model.Account
|
||
NeedAccount bool
|
||
|
||
Parser *Parser
|
||
|
||
GatewayListener net.Listener
|
||
MessageChan chan string
|
||
AccountsForSelect []*model.Account
|
||
Cache *cache.Cache
|
||
}
|
||
|
||
type Parser struct {
|
||
Input *ansiterm.ByteStream
|
||
Output *ansiterm.ByteStream
|
||
InputData []byte
|
||
OutputData []byte
|
||
Ps1 string
|
||
Ps2 string
|
||
}
|
||
|
||
var (
|
||
Bundle = i18n.NewBundle(language.Chinese)
|
||
TotalSession = map[string]*client.Connection{}
|
||
)
|
||
|
||
func I18nInit(path string) {
|
||
Bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||
files, err := util.ListFiles(path)
|
||
if err != nil {
|
||
logger.L.Error(err.Error(), zap.String("module", "i18n"))
|
||
}
|
||
for _, f := range files {
|
||
_, err = Bundle.LoadMessageFile(f)
|
||
if err != nil {
|
||
logger.L.Warn(err.Error(), zap.String("module", "i18n"))
|
||
}
|
||
}
|
||
}
|
||
|
||
func NewInteractiveHandler(s gossh.Session, ss *sshdServer, pty gossh.Pty) *InteractiveHandler {
|
||
//t := term.NewTerminal(s, "> ")
|
||
|
||
t, err := readline.NewEx(&readline.Config{
|
||
Stdin: s,
|
||
Stdout: s,
|
||
Prompt: ">",
|
||
})
|
||
if err != nil {
|
||
logger.L.Error(err.Error())
|
||
}
|
||
|
||
ih := &InteractiveHandler{
|
||
Locker: new(sync.RWMutex),
|
||
Term: t,
|
||
Session: s,
|
||
Sshd: ss,
|
||
|
||
SessionReq: &model.SshReq{},
|
||
SshSession: map[string]*client.Connection{},
|
||
Pty: pty,
|
||
MessageChan: make(chan string, 128),
|
||
Cache: cache.New(time.Minute, time.Minute*5),
|
||
}
|
||
ih.Language = 1
|
||
ih.Localizer = i18n.NewLocalizer(Bundle)
|
||
width := 120
|
||
height := 40
|
||
if pty.Window.Width != 0 {
|
||
width = pty.Window.Width
|
||
}
|
||
if pty.Window.Height != 0 {
|
||
height = pty.Window.Height
|
||
}
|
||
ih.Parser = &Parser{
|
||
Input: NewParser(width, height),
|
||
Output: NewParser(width, height),
|
||
}
|
||
|
||
return ih
|
||
}
|
||
|
||
func completer(d prompt.Document) []prompt.Suggest {
|
||
// 这里可以根据用户的实时输入来动态生成建议
|
||
suggestions := []prompt.Suggest{
|
||
{Text: "users", Description: "Store the username"},
|
||
{Text: "articles", Description: "Store the article text posted by user"},
|
||
}
|
||
|
||
// 只有当用户输入为空,或者以 'u' 开始时,才显示建议
|
||
if d.TextBeforeCursor() == "" || d.GetWordBeforeCursorWithSpace() == "u" {
|
||
return prompt.FilterHasPrefix(suggestions, d.GetWordBeforeCursor(), true)
|
||
}
|
||
|
||
// 其他情况不显示任何建议
|
||
return []prompt.Suggest{}
|
||
}
|
||
|
||
func NewParser(width, height int) *ansiterm.ByteStream {
|
||
screen := ansiterm.NewScreen(width, height)
|
||
stream := ansiterm.InitByteStream(screen, false)
|
||
stream.Attach(screen)
|
||
return stream
|
||
}
|
||
|
||
func (i *InteractiveHandler) WatchWinSize(winChan <-chan gossh.Window) {
|
||
for {
|
||
select {
|
||
case <-i.Session.Context().Done():
|
||
return
|
||
case win, ok := <-winChan:
|
||
if !ok {
|
||
return
|
||
}
|
||
for _, v := range i.SshSession {
|
||
client.ResizeSshClient(v.Session, win.Height, win.Width)
|
||
_ = v.Record.Resize(win.Height, win.Width)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func (i *InteractiveHandler) SwitchLanguage(lang string) {
|
||
languages := []string{"zh", "en"}
|
||
|
||
switch len(lang) {
|
||
case 0:
|
||
length := len(languages)
|
||
if length <= 1 {
|
||
return
|
||
}
|
||
if i.Language >= length {
|
||
i.Language = 1
|
||
} else {
|
||
i.Language += 1
|
||
}
|
||
default:
|
||
for index, v := range languages {
|
||
if v == lang {
|
||
i.Language = index + 1
|
||
}
|
||
}
|
||
}
|
||
i.Localizer = i18n.NewLocalizer(Bundle, languages[i.Language-1])
|
||
|
||
}
|
||
|
||
func (i *InteractiveHandler) SwitchLang(lang string) {
|
||
languages := []string{"zh", "en"}
|
||
|
||
switch len(lang) {
|
||
case 0:
|
||
length := len(languages)
|
||
if length <= 1 {
|
||
return
|
||
}
|
||
if i.Language >= length {
|
||
i.Language = 1
|
||
} else {
|
||
i.Language += 1
|
||
}
|
||
default:
|
||
for index, v := range languages {
|
||
if v == lang {
|
||
i.Language = index + 1
|
||
}
|
||
}
|
||
}
|
||
i.Localizer = i18n.NewLocalizer(Bundle, languages[i.Language-1])
|
||
i.PrintMessage(myi18n.MsgSshWelcome, map[string]any{"User": i.Session.User()})
|
||
}
|
||
|
||
func (i *InteractiveHandler) output(msg string) {
|
||
_, _ = io.WriteString(i.Session, msg)
|
||
}
|
||
|
||
func (i *InteractiveHandler) HostInfo(id int) (asset *model.Asset, err error) {
|
||
if id < 0 {
|
||
return
|
||
}
|
||
|
||
cookie, ok := i.Session.Context().Value("cookie").(string)
|
||
if !ok {
|
||
err = fmt.Errorf("no cookie")
|
||
return
|
||
}
|
||
res, er := i.Sshd.Core.Asset.Lists(cookie, "", id)
|
||
if er != nil {
|
||
err = er
|
||
return
|
||
}
|
||
if res.Count != 1 {
|
||
er = fmt.Errorf("found %d hosts: not unique", res.Count)
|
||
return
|
||
}
|
||
bs, er := json.Marshal(res.List[0])
|
||
if er != nil {
|
||
err = er
|
||
return
|
||
}
|
||
err = json.Unmarshal(bs, &asset)
|
||
if err != nil {
|
||
return
|
||
}
|
||
return asset, nil
|
||
}
|
||
|
||
func (i *InteractiveHandler) Check(id int, host *model.Asset) (asset *model.Asset, state bool, err error) {
|
||
assets, er := i.AcquireAssets("", id)
|
||
if er != nil {
|
||
err = err
|
||
return
|
||
}
|
||
if len(assets) == 0 {
|
||
return
|
||
}
|
||
asset = assets[0]
|
||
state = i.Sshd.Core.Asset.HasPermission(asset.AccessAuth)
|
||
return
|
||
}
|
||
|
||
func (i *InteractiveHandler) generateSessionRecord(conn *client.Connection, status int) (res *model.Session, err error) {
|
||
res = &model.Session{
|
||
SessionType: cast.ToInt(i.Session.Context().Value("sshType")),
|
||
}
|
||
if i.SessionReq != nil && i.SessionReq.SessionId != "" {
|
||
err = util.DecodeStruct(&res, i.SessionReq)
|
||
if err != nil {
|
||
return
|
||
}
|
||
res.Uid = i.SessionReq.Uid
|
||
} else {
|
||
res.ClientIp = i.Session.RemoteAddr().String()
|
||
res.UserName = i.Session.Context().User()
|
||
res.AccountInfo = fmt.Sprintf("%s(%s)", i.AccountInfo.Name, i.AccountInfo.Account)
|
||
res.Protocol = i.SessionReq.Protocol
|
||
|
||
}
|
||
s, er := i.Sshd.Core.Auth.AclInfo(i.Session.Context().Value("cookie").(string))
|
||
if er != nil {
|
||
logger.L.Warn(er.Error(), zap.String("session", "add"))
|
||
} else if s != nil {
|
||
res.Uid = s.Uid
|
||
res.UserName = s.UserName
|
||
}
|
||
res.Status = status
|
||
res.AssetInfo = fmt.Sprintf("%s(%s)", i.SelectedAsset.Name, i.SelectedAsset.Ip)
|
||
res.SessionId = conn.SessionId
|
||
res.GatewayId = i.SelectedAsset.GatewayId
|
||
if conn.Gateway != nil {
|
||
res.GatewayInfo = fmt.Sprintf("%s:%d", conn.Gateway.Host, conn.Gateway.Port)
|
||
}
|
||
res.AssetId = i.SelectedAsset.Id
|
||
res.AccountId = i.AccountInfo.Id
|
||
if status == model.SESSIONSTATUS_OFFLINE {
|
||
t := time.Now()
|
||
res.ClosedAt = &t
|
||
}
|
||
return
|
||
}
|
||
func readLine(s gossh.Session) string {
|
||
buf := make([]byte, 1)
|
||
var in []byte
|
||
for {
|
||
_, _ = s.Read(buf)
|
||
switch buf[0] {
|
||
case []byte("\r")[0], []byte("\r\n")[0]:
|
||
return string(in)
|
||
default:
|
||
in = append(in, buf[0])
|
||
}
|
||
}
|
||
}
|
||
|
||
func (i *InteractiveHandler) Schedule(pty *gossh.Pty) {
|
||
i.pty = pty
|
||
var err error
|
||
var line string
|
||
if st, ok := i.Session.Context().Value("sshType").(int); ok && st == model.SESSIONTYPE_WEB {
|
||
//line, err = i.Term.ReadLine()
|
||
line = readLine(i.Session)
|
||
if err != nil {
|
||
logger.L.Debug("connection closed", zap.String("msg", err.Error()))
|
||
return
|
||
}
|
||
var r *model.SshReq
|
||
err = json.Unmarshal([]byte(line), &r)
|
||
if err != nil {
|
||
logger.L.Warn(err.Error())
|
||
return
|
||
}
|
||
// "Accept-Language")
|
||
//i.Localizer = i18n.NewLocalizer(conf.Bundle, lang, accept)
|
||
i.Session.Context().SetValue("cookie", r.Cookie)
|
||
i.SessionReq = r
|
||
|
||
// monitor
|
||
{
|
||
if i.SessionReq.SessionId != "" {
|
||
switch i.SessionReq.Action {
|
||
case model.SESSIONACTION_MONITOR:
|
||
i.wrapJsonResponse(i.SessionReq.SessionId, 0, "success")
|
||
RegisterMonitorSession(i.SessionReq.SessionId, i.Session)
|
||
return
|
||
case model.SESSIONACTION_CLOSE:
|
||
if v, ok := config.TotalHostSession.Load(i.SessionReq.SessionId); ok {
|
||
err = v.(*client.Connection).Session.Close()
|
||
if err != nil {
|
||
logger.L.Warn(err.Error())
|
||
i.wrapJsonResponse(i.SessionReq.SessionId, 1, "failed")
|
||
return
|
||
}
|
||
close(v.(*client.Connection).Exit)
|
||
}
|
||
i.wrapJsonResponse(i.SessionReq.SessionId, 0, "success")
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
host, ok, err := i.Check(r.AssetId, nil)
|
||
if err != nil {
|
||
logger.L.Warn(err.Error())
|
||
i.wrapJsonResponse("", 1, err.Error())
|
||
return
|
||
}
|
||
|
||
if !ok {
|
||
i.wrapJsonResponse("", 1, fmt.Sprintf("invalid status for %v", r.AssetId))
|
||
return
|
||
}
|
||
i.SelectedAsset = host
|
||
|
||
commands, er := i.AcquireCommands()
|
||
if er != nil {
|
||
return
|
||
}
|
||
i.Commands = commands
|
||
_, err = i.Proxy(host.Ip, r.AccountId)
|
||
if err != nil {
|
||
logger.L.Error(err.Error(), zap.String("module", "proxy"))
|
||
}
|
||
return
|
||
} else {
|
||
if config.SSHConfig.PlainMode {
|
||
i.SwitchLang("zh")
|
||
for {
|
||
//line, err = i.Term.ReadLine()
|
||
line, err = i.Term.Readline()
|
||
|
||
if err != nil {
|
||
logger.L.Debug("connection closed", zap.String("msg", err.Error()))
|
||
break
|
||
}
|
||
if strings.TrimSpace(line) == "" {
|
||
continue
|
||
}
|
||
if i.HandleInput(strings.TrimSpace(line)) {
|
||
break
|
||
}
|
||
}
|
||
} else {
|
||
tm := InitAndRunTerm(i)
|
||
_, err := tm.Run()
|
||
if err != nil {
|
||
logger.L.Error(err.Error(), zap.String("module", "schedule"))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func (i *InteractiveHandler) HandleInput(line string) (exit bool) {
|
||
|
||
switch strings.TrimSpace(line) {
|
||
case "/*":
|
||
i.SelectedAsset = nil
|
||
assets, er := i.AcquireAssets("", 0)
|
||
if er != nil {
|
||
return
|
||
}
|
||
accounts, er := i.AcquireAccounts()
|
||
if er != nil {
|
||
return
|
||
}
|
||
commands, er := i.AcquireCommands()
|
||
if er != nil {
|
||
return
|
||
}
|
||
i.Locker.Lock()
|
||
i.Assets = assets
|
||
i.Accounts = accounts
|
||
i.Commands = commands
|
||
i.Locker.Unlock()
|
||
|
||
i.showResult(assets)
|
||
return
|
||
case "/?", "/?":
|
||
i.PrintMessage(myi18n.MsgSshWelcome, map[string]any{"User": i.Session.User()})
|
||
return
|
||
case "/s":
|
||
i.SwitchLang("")
|
||
return
|
||
case "/q":
|
||
i.Session.Close()
|
||
return
|
||
default:
|
||
switch {
|
||
case line == "exit":
|
||
logger.L.Info("exit", zap.String("user", i.Session.User()), zap.String("input", line))
|
||
i.Session.Close()
|
||
return
|
||
}
|
||
}
|
||
_, er := i.Proxy(line, -1)
|
||
if er != nil {
|
||
logger.L.Info(er.Error())
|
||
}
|
||
if st, ok := i.Session.Context().Value("sshType").(int); ok && st == model.SESSIONTYPE_WEB {
|
||
exit = true
|
||
}
|
||
return
|
||
}
|
||
|
||
func (i *InteractiveHandler) AcquireAndStoreAssets(search string, id int) (selectedHosts, likeHosts []*model.Asset, err error) {
|
||
i.Locker.RLock()
|
||
count := len(i.Assets)
|
||
i.Locker.RUnlock()
|
||
var find = func(assets []*model.Asset) (selectedHosts, likeHosts []*model.Asset) {
|
||
if search == "" {
|
||
return
|
||
}
|
||
for _, v := range assets {
|
||
if v.Ip == search || v.Name == search {
|
||
selectedHosts = append(selectedHosts, v)
|
||
} else if strings.Contains(v.Ip, search) || strings.Contains(v.Name, search) {
|
||
likeHosts = append(likeHosts, v)
|
||
}
|
||
}
|
||
return
|
||
}
|
||
if count == 0 {
|
||
res, er := i.AcquireAssets(search, id)
|
||
if er != nil {
|
||
err = er
|
||
return
|
||
}
|
||
selectedHosts, likeHosts = find(res)
|
||
i.Locker.Lock()
|
||
i.Assets = res
|
||
i.Locker.Unlock()
|
||
return
|
||
} else {
|
||
i.Locker.Lock()
|
||
selectedHosts, likeHosts = find(i.Assets)
|
||
i.Locker.Unlock()
|
||
return
|
||
}
|
||
}
|
||
|
||
func (i *InteractiveHandler) AcquireAssets(search string, id int) (assets []*model.Asset, err error) {
|
||
if search == "" && id <= 0 {
|
||
if v, ok := i.Cache.Get("assets"); ok {
|
||
return v.([]*model.Asset), nil
|
||
} else {
|
||
defer func() {
|
||
if err == nil {
|
||
i.Cache.Set("assets", assets, 0)
|
||
}
|
||
}()
|
||
}
|
||
}
|
||
if totalAssets, ok := i.Cache.Get("assets"); ok {
|
||
for _, v := range totalAssets.([]*model.Asset) {
|
||
if id > 0 {
|
||
if id == v.Id {
|
||
assets = append(assets, v)
|
||
return
|
||
} else {
|
||
continue
|
||
}
|
||
} else {
|
||
if strings.Contains(v.Name, search) {
|
||
assets = append(assets, v)
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
cookie, ok := i.Session.Context().Value("cookie").(string)
|
||
if ok {
|
||
res, er := i.Sshd.Core.Asset.Lists(cookie, search, id)
|
||
if er != nil {
|
||
err = er
|
||
return
|
||
}
|
||
if res != nil {
|
||
for _, v := range res.List {
|
||
var v1 model.Asset
|
||
_ = util.DecodeStruct(&v1, v)
|
||
bs, _ := json.Marshal(v.(map[string]interface{})["authorization"])
|
||
er = json.Unmarshal(bs, &v1.Authorization)
|
||
if er != nil {
|
||
logger.L.Warn(er.Error())
|
||
}
|
||
assets = append(assets, &v1)
|
||
}
|
||
}
|
||
} else {
|
||
err = fmt.Errorf("no cookies")
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
func (i *InteractiveHandler) AcquireAccounts() (accounts map[int]*model.Account, err error) {
|
||
accounts = map[int]*model.Account{}
|
||
cookie, ok := i.Session.Context().Value("cookie").(string)
|
||
if ok {
|
||
res, er := i.Sshd.Core.Auth.Accounts(cookie)
|
||
if er != nil {
|
||
err = er
|
||
return
|
||
}
|
||
for _, v := range res {
|
||
var v1 model.Account
|
||
_ = util.DecodeStruct(&v1, v)
|
||
accounts[v1.Id] = &v1
|
||
}
|
||
} else {
|
||
err = fmt.Errorf("no cookies")
|
||
}
|
||
return
|
||
}
|
||
|
||
func (i *InteractiveHandler) AcquireAccountInfo(id int, name string) (res *model.Account, err error) {
|
||
cookie, ok := i.Session.Context().Value("cookie").(string)
|
||
if ok {
|
||
return i.Sshd.Core.Auth.AccountInfo(cookie, id, name)
|
||
} else {
|
||
err = fmt.Errorf("no cookies")
|
||
}
|
||
return
|
||
}
|
||
|
||
func (i *InteractiveHandler) AcquireCommands() (commands map[int]*model.Command, err error) {
|
||
commands = map[int]*model.Command{}
|
||
cookie, ok := i.Session.Context().Value("cookie").(string)
|
||
if ok {
|
||
res, er := i.Sshd.Core.Asset.Commands(cookie)
|
||
if er != nil {
|
||
err = er
|
||
return
|
||
}
|
||
for _, v := range res {
|
||
var v1 model.Command
|
||
_ = util.DecodeStruct(&v1, v)
|
||
commands[v1.Id] = &v1
|
||
}
|
||
} else {
|
||
err = fmt.Errorf("no cookies")
|
||
}
|
||
return
|
||
}
|
||
|
||
func (i *InteractiveHandler) AcquireConfig() (config *model.Config, err error) {
|
||
config = &model.Config{}
|
||
cookie, ok := i.Session.Context().Value("cookie").(string)
|
||
if ok {
|
||
res, er := i.Sshd.Core.Asset.Config(cookie)
|
||
if er != nil {
|
||
err = er
|
||
return
|
||
}
|
||
config = res
|
||
} else {
|
||
err = fmt.Errorf("no cookies")
|
||
}
|
||
return
|
||
}
|
||
|
||
func (i *InteractiveHandler) showResult(data []*model.Asset) {
|
||
i.Term.SetPrompt("host> ")
|
||
var hosts []string
|
||
for _, d := range data {
|
||
hosts = append(hosts, d.Name)
|
||
}
|
||
|
||
var templateData = map[string]interface{}{
|
||
"Count": len(data),
|
||
"Msg": "",
|
||
}
|
||
|
||
if data != nil {
|
||
templateData["Msg"] = i.tableData(hosts)
|
||
}
|
||
i.PrintMessage(myi18n.MsgSshShowAssetResults, templateData)
|
||
}
|
||
|
||
func (i *InteractiveHandler) tableData(data []string) string {
|
||
chunkData := i.chunkData(data)
|
||
buf := &bytes.Buffer{}
|
||
tw := tablewriter.NewWriter(buf)
|
||
tw.SetAutoWrapText(false)
|
||
tw.SetColumnSeparator(" ")
|
||
tw.SetNoWhiteSpace(false)
|
||
tw.SetBorder(false)
|
||
tw.SetAlignment(tablewriter.ALIGN_LEFT)
|
||
tw.AppendBulk(chunkData)
|
||
tw.Render()
|
||
return buf.String()
|
||
}
|
||
|
||
func (i *InteractiveHandler) chunkData(data []string) (res [][]string) {
|
||
width := 80
|
||
if i.pty != nil {
|
||
width = i.pty.Window.Width
|
||
}
|
||
n := len(data)
|
||
chunk := n
|
||
for ; chunk >= 1; chunk -= 1 {
|
||
ok := true
|
||
for i := 0; i < n && ok; i += chunk {
|
||
w := chunk*3 + 4
|
||
r := i + chunk
|
||
if r > n {
|
||
r = n
|
||
}
|
||
for _, s := range data[i:r] {
|
||
w += runewidth.StringWidth(s)
|
||
}
|
||
ok = ok && w <= width
|
||
}
|
||
if ok {
|
||
t := i.getChunk(data, chunk)
|
||
maxLen := make(map[int]int)
|
||
for _, c := range t {
|
||
for i, v := range c {
|
||
l := runewidth.StringWidth(v)
|
||
if l > maxLen[i] {
|
||
maxLen[i] = l
|
||
}
|
||
}
|
||
}
|
||
for _, row := range t {
|
||
w := chunk*3 + 4
|
||
for i := range row {
|
||
w += maxLen[i]
|
||
}
|
||
ok = ok && w <= width
|
||
}
|
||
|
||
}
|
||
if ok {
|
||
break
|
||
}
|
||
}
|
||
if chunk < 1 {
|
||
chunk = 1
|
||
}
|
||
res = i.getChunk(data, chunk)
|
||
|
||
return
|
||
}
|
||
|
||
func (i *InteractiveHandler) getChunk(data []string, chunk int) (res [][]string) {
|
||
n := len(data)
|
||
for i := 0; i < n; i += chunk {
|
||
r := i + chunk
|
||
if r > n {
|
||
r = n
|
||
}
|
||
res = append(res, data[i:r])
|
||
}
|
||
return
|
||
}
|
||
|
||
func (i *InteractiveHandler) wrapJsonResponse(sessionId string, code int, message string) {
|
||
if st, ok := i.Session.Context().Value("sshType").(int); ok && st != model.SESSIONTYPE_WEB {
|
||
return
|
||
}
|
||
res, er := json.Marshal(model.SshResp{
|
||
Code: code,
|
||
Message: message,
|
||
SessionId: sessionId,
|
||
Uid: i.SessionReq.Uid,
|
||
UserName: i.SessionReq.UserName,
|
||
})
|
||
|
||
if er != nil {
|
||
logger.L.Error(er.Error())
|
||
}
|
||
i.output(string(append(res, []byte("\r")...)))
|
||
}
|
||
|
||
func (i *InteractiveHandler) NewSession(account *model.Account, gateway *model.Gateway) (conn *client.Connection, err error) {
|
||
i.Locker.Lock()
|
||
defer i.Locker.Unlock()
|
||
if i.SshClient == nil {
|
||
con, ch, er := client.NewSShClient(strings.ReplaceAll(i.SessionReq.Protocol, "ssh", i.SelectedAsset.Ip), account, gateway)
|
||
if er != nil {
|
||
err = er
|
||
return
|
||
}
|
||
i.SshClient = con
|
||
i.GatewayCloseChan = ch
|
||
}
|
||
i.AccountInfo = account
|
||
|
||
conn, err = client.NewSShSession(i.SshClient, i.Pty, i.GatewayCloseChan)
|
||
|
||
if err != nil {
|
||
return
|
||
}
|
||
conn.AssetId = i.SelectedAsset.Id
|
||
conn.AccountId = account.Id
|
||
conn.Gateway = gateway
|
||
i.SshSession[conn.SessionId] = conn
|
||
|
||
return
|
||
}
|
||
|
||
func (i *InteractiveHandler) UpsertSession(conn *client.Connection, status int) error {
|
||
resp, err := i.generateSessionRecord(conn, status)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return i.Sshd.Core.Audit.NewSession(resp)
|
||
}
|