mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
fix(webconsole): record command input and ps1
This commit is contained in:
@@ -37,7 +37,7 @@ func NewCommandLogManager() *CommandLogManager {
|
||||
modulebase.ResourceManager{
|
||||
BaseManager: *modulebase.NewBaseManager("webconsole", "", "webconsole", []string{
|
||||
"id", "ops_time", "obj_id", "obj_type", "obj_name", "user", "user_id", "tenant", "tenant_id", "owner_tenant_id", "action", "notes",
|
||||
"session_id", "accessed_at", "type", "login_user", "start_time", "command",
|
||||
"session_id", "accessed_at", "type", "login_user", "start_time", "ps1", "command",
|
||||
}, nil),
|
||||
Keyword: "commandlog", KeywordPlural: "commandlogs",
|
||||
},
|
||||
|
||||
@@ -74,6 +74,7 @@ type SCommandLog struct {
|
||||
Type string `width:"32" charset:"utf8" nullable:"true" list:"user" create:"required"`
|
||||
LoginUser string `charset:"utf8" list:"user" create:"required"`
|
||||
StartTime time.Time `list:"user" create:"required"`
|
||||
Ps1 string `charset:"utf8" list:"user" create:"optional" json:"ps1"`
|
||||
Command CommandType `charset:"utf8" list:"user" create:"required"`
|
||||
}
|
||||
|
||||
@@ -96,6 +97,7 @@ type CommandLogCreateInput struct {
|
||||
Type CommandType
|
||||
LoginUser string
|
||||
StartTime time.Time
|
||||
Ps1 string `json:"ps1"`
|
||||
Command string
|
||||
Notes jsonutils.JSONObject
|
||||
}
|
||||
|
||||
@@ -16,8 +16,11 @@ package recorder
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/LeeEirc/terminalparser"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
@@ -28,7 +31,7 @@ import (
|
||||
|
||||
type Recoder interface {
|
||||
Start()
|
||||
Write(data string)
|
||||
Write(userInput string, ptyOutput string)
|
||||
}
|
||||
|
||||
type Object struct {
|
||||
@@ -50,44 +53,90 @@ func NewObject(id, name, oType, loginUser string, notes jsonutils.JSONObject) *O
|
||||
}
|
||||
|
||||
type cmdRecoder struct {
|
||||
cs *mcclient.ClientSession
|
||||
sessionId string
|
||||
accessedAt time.Time
|
||||
buffer []string
|
||||
curCmd string
|
||||
cmdCh chan string
|
||||
object *Object
|
||||
cs *mcclient.ClientSession
|
||||
sessionId string
|
||||
accessedAt time.Time
|
||||
userInputStart bool
|
||||
userInputBuff string
|
||||
ptyInitialOutput string
|
||||
ptyOutputBuff string
|
||||
ps1Parsed bool
|
||||
ps1 string
|
||||
cmdCh chan string
|
||||
object *Object
|
||||
wLock *sync.Mutex
|
||||
}
|
||||
|
||||
func NewCmdRecorder(s *mcclient.ClientSession, obj *Object, sessionId string, accessedAt time.Time) Recoder {
|
||||
return &cmdRecoder{
|
||||
cs: s,
|
||||
sessionId: sessionId,
|
||||
accessedAt: accessedAt,
|
||||
buffer: make([]string, 0),
|
||||
curCmd: "",
|
||||
cmdCh: make(chan string),
|
||||
object: obj,
|
||||
cs: s,
|
||||
sessionId: sessionId,
|
||||
accessedAt: accessedAt,
|
||||
userInputStart: false,
|
||||
userInputBuff: "",
|
||||
ptyOutputBuff: "",
|
||||
cmdCh: make(chan string),
|
||||
object: obj,
|
||||
wLock: new(sync.Mutex),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *cmdRecoder) Write(data string) {
|
||||
if data == "\n" || data == "\r" {
|
||||
r.sendMessage(r.curCmd)
|
||||
func (r *cmdRecoder) Write(userInput string, ptyOutput string) {
|
||||
r.wLock.Lock()
|
||||
defer r.wLock.Unlock()
|
||||
|
||||
if len(userInput) != 0 {
|
||||
r.userInputStart = true
|
||||
}
|
||||
|
||||
if !r.userInputStart && userInput == "" && len(ptyOutput) > 0 {
|
||||
r.ptyInitialOutput += ptyOutput
|
||||
}
|
||||
// try parse PS1
|
||||
if !r.ps1Parsed && r.userInputBuff != "" && userInput == "\r" && r.ptyInitialOutput != "" {
|
||||
outs := r.parsePtyOutputs(r.ptyInitialOutput)
|
||||
if len(outs) != 0 && len(outs) > 1 {
|
||||
r.ps1 = outs[len(outs)-1]
|
||||
}
|
||||
r.ps1Parsed = true
|
||||
}
|
||||
|
||||
// user enter command
|
||||
if userInput == "\r" && r.userInputBuff != "" {
|
||||
r.sendMessage(r.ptyOutputBuff)
|
||||
return
|
||||
}
|
||||
r.curCmd += data
|
||||
|
||||
r.userInputBuff += userInput
|
||||
r.ptyOutputBuff += ptyOutput
|
||||
}
|
||||
|
||||
func (r *cmdRecoder) cleanCmd() *cmdRecoder {
|
||||
r.curCmd = ""
|
||||
r.userInputBuff = ""
|
||||
r.ptyOutputBuff = ""
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *cmdRecoder) sendMessage(msg string) {
|
||||
r.cmdCh <- msg
|
||||
func (r *cmdRecoder) parsePtyOutputs(data string) []string {
|
||||
s := terminalparser.Screen{
|
||||
Rows: make([]*terminalparser.Row, 0, 1024),
|
||||
Cursor: &terminalparser.Cursor{},
|
||||
}
|
||||
return s.Parse([]byte(data))
|
||||
}
|
||||
|
||||
func (r *cmdRecoder) sendMessage(ptyOutputBuff string) {
|
||||
ptyOuts := r.parsePtyOutputs(ptyOutputBuff)
|
||||
if len(ptyOuts) == 0 {
|
||||
return
|
||||
}
|
||||
cmd := ptyOuts[len(ptyOuts)-1]
|
||||
if r.ps1 != "" {
|
||||
cmd = strings.TrimPrefix(cmd, r.ps1)
|
||||
}
|
||||
r.cleanCmd()
|
||||
log.Infof("Message %q sended", msg)
|
||||
r.cmdCh <- cmd
|
||||
log.Debugf("sendMessage ps1: %q, ptyOuts: %#v, cmd: %q", r.ps1, ptyOuts, cmd)
|
||||
}
|
||||
|
||||
func (r *cmdRecoder) Start() {
|
||||
@@ -105,6 +154,9 @@ func (r *cmdRecoder) save(command string) error {
|
||||
if r.object == nil {
|
||||
return nil
|
||||
}
|
||||
if command == "" {
|
||||
return nil
|
||||
}
|
||||
userCred := r.cs.GetToken()
|
||||
input := r.newModelInput(userCred, command)
|
||||
_, err := models.GetCommandLogManager().Create(r.cs.GetContext(), userCred, input)
|
||||
@@ -135,6 +187,7 @@ func (r *cmdRecoder) newModelInput(userCred mcclient.TokenCredential, command st
|
||||
LoginUser: r.object.LoginUser,
|
||||
Type: models.CommandTypeSSH,
|
||||
StartTime: time.Now(),
|
||||
Ps1: r.ps1,
|
||||
Command: command,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (server *TTYServer) initEventHandler(s *session.SSession) {
|
||||
}
|
||||
|
||||
func initSocketHandler(so socketio.Socket, p *session.Pty) {
|
||||
// handle read
|
||||
// handle command output
|
||||
go func() {
|
||||
for !p.Exit {
|
||||
if p.IsInShellMode() {
|
||||
@@ -82,7 +82,9 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) {
|
||||
*/
|
||||
cleanUp(so, p)
|
||||
} else {
|
||||
// log.Errorf("--p.Pty.output data: %q", data)
|
||||
so.Emit(OUTPUT_EVENT, string(data))
|
||||
go p.Session.GetRecorder().Write("", string(data))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -95,7 +97,7 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) {
|
||||
}
|
||||
}()
|
||||
|
||||
// handle write
|
||||
// handle user input write
|
||||
so.On(INPUT_EVENT, func(data string) {
|
||||
if !p.IsInShellMode() {
|
||||
for _, d := range []byte(data) {
|
||||
@@ -120,7 +122,7 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) {
|
||||
}
|
||||
} else {
|
||||
p.Pty.Write([]byte(data))
|
||||
go p.Session.GetRecorder().Write(data)
|
||||
go p.Session.GetRecorder().Write(data, "")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user