diff --git a/pkg/config/config.go b/pkg/config/config.go
index 40fc1d02768e62fbd8208bee4b1eb39f9be62f7d..46135c66a2220f99854f865eaad5c1e6b8b268a8 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -25,20 +25,23 @@ type Config struct {
 	SessionKeepDuration int               `json:"TERMINAL_SESSION_KEEP_DURATION"`
 	TelnetRegex         string            `json:"TERMINAL_TELNET_REGEX"`
 	MaxIdleTime         time.Duration     `json:"SECURITY_MAX_IDLE_TIME"`
+	SftpRoot            string            `json:"TERMINAL_SFTP_ROOT" yaml:"SFTP_ROOT"`
 	Name                string            `yaml:"NAME"`
+	SecretKey           string            `yaml:"SECRET_KEY"`
 	HostKeyFile         string            `yaml:"HOST_KEY_FILE"`
 	CoreHost            string            `yaml:"CORE_HOST"`
 	BootstrapToken      string            `yaml:"BOOTSTRAP_TOKEN"`
 	BindHost            string            `yaml:"BIND_HOST"`
 	SSHPort             int               `yaml:"SSHD_PORT"`
 	HTTPPort            int               `yaml:"HTTPD_PORT"`
+	SSHTimeout          int               `yaml:"SSH_TIMEOUT"`
 	AccessKey           string            `yaml:"ACCESS_KEY"`
 	AccessKeyFile       string            `yaml:"ACCESS_KEY_FILE"`
 	LogLevel            string            `yaml:"LOG_LEVEL"`
 	HeartbeatDuration   time.Duration     `yaml:"HEARTBEAT_INTERVAL"`
-	RootPath            string
-	Comment             string
-	Language            string
+	RootPath            string            `yaml:"ROOT_PATH"`
+	Comment             string            `yaml:"COMMENT"`
+	Language            string            `yaml:"LANG"`
 
 	mux sync.RWMutex
 }
@@ -107,6 +110,7 @@ var Conf = &Config{
 	BootstrapToken: "",
 	BindHost:       "0.0.0.0",
 	SSHPort:        2222,
+	SSHTimeout:     60,
 	HTTPPort:       5000,
 	AccessKey:      "",
 	AccessKeyFile:  "access_key",
@@ -115,7 +119,7 @@ var Conf = &Config{
 	HostKey:        "",
 	RootPath:       rootPath,
 	Comment:        "Coco",
-	Language:       "zh_CN",
+	Language:       "zh",
 	ReplayStorage:  map[string]string{},
 	CommandStorage: map[string]string{},
 }
diff --git a/pkg/handler/session.go b/pkg/handler/session.go
index 3c73f8783fcfa271ab5607fb14e245df38300fe4..55a8a36ec93fa10762d4917f00006ef2f8df15e8 100644
--- a/pkg/handler/session.go
+++ b/pkg/handler/session.go
@@ -323,8 +323,11 @@ func (i *InteractiveHandler) searchNodeAssets(num int) (assets []model.Asset) {
 }
 
 func (i *InteractiveHandler) Proxy(ctx context.Context) {
+	i.assetSelect = &model.Asset{Hostname: "centos", Port: 22, Ip: "192.168.244.185"}
+	i.systemUserSelect = &model.SystemUser{Name: "web", UserName: "web", Password: "redhat"}
 	p := proxy.ProxyServer{
 		Session:    i.sess,
+		User:       i.user,
 		Asset:      i.assetSelect,
 		SystemUser: i.systemUserSelect,
 	}
diff --git a/pkg/proxy/parser.go b/pkg/proxy/parser.go
index 75a4813a8e19631570703ca08eb9aaa0d4cd27b6..021e660a910155410b3c28fbac07baf392cb20d0 100644
--- a/pkg/proxy/parser.go
+++ b/pkg/proxy/parser.go
@@ -171,3 +171,11 @@ func (p *Parser) ParseServerOutput(b []byte) []byte {
 func (p *Parser) SetCMDFilterRules(rules []model.SystemUserFilterRule) {
 	p.cmdFilterRules = rules
 }
+
+func (p *Parser) SetReplayRecorder() {
+
+}
+
+func (p *Parser) SetCommandRecorder() {
+
+}
diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go
index 0feedad97d47149f0729f5e24aad897be29d38bd..0a4f6adb12f77e38495efca9678d5881f356fc93 100644
--- a/pkg/proxy/proxy.go
+++ b/pkg/proxy/proxy.go
@@ -1,8 +1,15 @@
 package proxy
 
 import (
+	"fmt"
+	"io"
+	"strings"
+	"time"
+
 	"github.com/ibuler/ssh"
 
+	"cocogo/pkg/config"
+	"cocogo/pkg/i18n"
 	"cocogo/pkg/logger"
 	"cocogo/pkg/model"
 	"cocogo/pkg/service"
@@ -37,39 +44,63 @@ func (p *ProxyServer) validatePermission() bool {
 	return true
 }
 
-func (p *ProxyServer) getServerConn() {
-
+func (p *ProxyServer) getServerConn() (srvConn ServerConnection, err error) {
+	srvConn = &ServerSSHConnection{
+		host:     "192.168.244.145",
+		port:     "22",
+		user:     "root",
+		password: "redhat",
+	}
+	pty, _, ok := p.Session.Pty()
+	if !ok {
+		logger.Error("User not request Pty")
+		return
+	}
+	done := make(chan struct{})
+	go p.sendConnectingMsg(done)
+	err = srvConn.Connect(pty.Window.Height, pty.Window.Width, pty.Term)
+	_, _ = io.WriteString(p.Session, "\r\n")
+	done <- struct{}{}
+	return
 }
 
-func (p *ProxyServer) sendConnectingMsg() {
-
+func (p *ProxyServer) sendConnectingMsg(done chan struct{}) {
+	delay := 0.0
+	msg := fmt.Sprintf(i18n.T("Connecting to %s@%s  %.1f"), p.SystemUser.UserName, p.Asset.Ip, delay)
+	_, _ = io.WriteString(p.Session, msg)
+	for int(delay) < config.Conf.SSHTimeout {
+		select {
+		case <-done:
+			return
+		default:
+			delayS := fmt.Sprintf("%.1f", delay)
+			data := strings.Repeat("\x08", len(delayS)) + delayS
+			_, _ = io.WriteString(p.Session, data)
+			time.Sleep(100 * time.Millisecond)
+			delay += 0.1
+		}
+	}
 }
 
 func (p *ProxyServer) Proxy() {
 	if !p.checkProtocol() {
 		return
 	}
-	conn := ServerSSHConnection{
-		host:     "192.168.244.185",
-		port:     "22",
-		user:     "root",
-		password: "redhat",
-	}
-	ptyReq, _, ok := p.Session.Pty()
-	if !ok {
-		logger.Error("Pty not ok")
-		return
-	}
-	err := conn.Connect(ptyReq.Window.Height, ptyReq.Window.Width, ptyReq.Term)
+	srvConn, err := p.getServerConn()
 	if err != nil {
+		logger.Errorf("Connect host error: %s\n", err)
 		return
 	}
 
-	sw := Switch{
-		userConn:   p.Session,
-		serverConn: &conn,
-		parser:     parser,
+	userConn := &UserSSHConnection{Session: p.Session, winch: make(chan ssh.Window)}
+	sw := NewSwitch(userConn, srvConn)
+	cmdRules, err := service.GetSystemUserFilterRules(p.SystemUser.Id)
+	if err != nil {
+		logger.Error("Get system user filter rule error: ", err)
 	}
+	sw.parser.SetCMDFilterRules(cmdRules)
+	sw.parser.SetReplayRecorder()
+	sw.parser.SetCommandRecorder()
 	_ = sw.Bridge()
-	_ = conn.Close()
+	_ = srvConn.Close()
 }
diff --git a/pkg/proxy/switch.go b/pkg/proxy/switch.go
index 31fd85b2197d2567142ceec83d4dd8829b63589c..ba2af50aa8a8fdd8b05f6be59b71475083c2f3b4 100644
--- a/pkg/proxy/switch.go
+++ b/pkg/proxy/switch.go
@@ -2,7 +2,6 @@ package proxy
 
 import (
 	"cocogo/pkg/logger"
-	"cocogo/pkg/service"
 	"context"
 	"github.com/ibuler/ssh"
 	"github.com/satori/go.uuid"
@@ -10,13 +9,7 @@ import (
 )
 
 func NewSwitch(userConn UserConnection, serverConn ServerConnection) (sw *Switch) {
-	rules, err := service.GetSystemUserFilterRules("")
-	if err != nil {
-		logger.Error("Get system user filter rule error: ", err)
-	}
-	parser := &Parser{
-		cmdFilterRules: rules,
-	}
+	parser := new(Parser)
 	parser.Initial()
 	sw = &Switch{userConn: userConn, serverConn: serverConn, parser: parser}
 	return sw
@@ -98,7 +91,6 @@ func (s *Switch) readUserToServer(ctx context.Context) {
 				s.cancelFunc()
 			}
 			buf2 := s.parser.ParseUserInput(p)
-			logger.Debug("Send to server: ", string(buf2))
 			_, err := s.serverTran.Write(buf2)
 			if err != nil {
 				return
diff --git a/pkg/proxy/userconn.go b/pkg/proxy/userconn.go
index 4960b295a5ee608d46e2b03ae0df52a2c668ac16..541ba2672afda9b54226cc3cef2987860ee0c37c 100644
--- a/pkg/proxy/userconn.go
+++ b/pkg/proxy/userconn.go
@@ -12,25 +12,24 @@ type UserConnection interface {
 	Protocol() string
 	WinCh() <-chan ssh.Window
 	User() string
-	Name() string
 	LoginFrom() string
 	RemoteAddr() string
 }
 
-type SSHUserConnection struct {
+type UserSSHConnection struct {
 	ssh.Session
 	winch <-chan ssh.Window
 }
 
-func (uc *SSHUserConnection) Protocol() string {
+func (uc *UserSSHConnection) Protocol() string {
 	return "ssh"
 }
 
-func (uc *SSHUserConnection) User() string {
+func (uc *UserSSHConnection) User() string {
 	return uc.Session.User()
 }
 
-func (uc *SSHUserConnection) WinCh() (winch <-chan ssh.Window) {
+func (uc *UserSSHConnection) WinCh() (winch <-chan ssh.Window) {
 	_, winch, ok := uc.Pty()
 	if ok {
 		return
@@ -38,10 +37,10 @@ func (uc *SSHUserConnection) WinCh() (winch <-chan ssh.Window) {
 	return nil
 }
 
-func (uc *SSHUserConnection) LoginFrom() string {
+func (uc *UserSSHConnection) LoginFrom() string {
 	return "T"
 }
 
-func (uc *SSHUserConnection) RemoteAddr() string {
+func (uc *UserSSHConnection) RemoteAddr() string {
 	return strings.Split(uc.Session.RemoteAddr().String(), ":")[0]
 }
diff --git a/pkg/record/cmd.go b/pkg/record/cmd.go
deleted file mode 100644
index f17b821795514cd17f32efa3d75b384846a7b154..0000000000000000000000000000000000000000
--- a/pkg/record/cmd.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package record
-
-import (
-	"time"
-)
-
-type Command struct {
-	SessionID string
-	StartTime time.Time
-}
diff --git a/pkg/record/interface.go b/pkg/record/interface.go
deleted file mode 100644
index a9a310abf8b52f8d9dd280c4cbeeb47aa82b21c1..0000000000000000000000000000000000000000
--- a/pkg/record/interface.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package record
-
-type Storage interface {
-	Upload(gZipFile, target string)
-}
-
-func NewStorageServer() Storage {
-	//conf := config.GetGlobalConfig()
-	//
-	//switch conf.TermConfig.RePlayStorage["TYPE"] {
-	//case "server":
-	//	return NewJmsStorage()
-	//}
-	return nil
-}
diff --git a/pkg/recorder/cmd.go b/pkg/recorder/cmd.go
new file mode 100644
index 0000000000000000000000000000000000000000..12040431241cac880c2064ceae035b3ab5a95c33
--- /dev/null
+++ b/pkg/recorder/cmd.go
@@ -0,0 +1,25 @@
+package recorder
+
+import (
+	"time"
+)
+
+type CommandRecorder struct {
+	SessionID string
+	StartTime time.Time
+}
+
+type Command struct {
+	SessionId  string    `json:"session"`
+	OrgId      string    `json:"org_id"`
+	Input      string    `json:"input"`
+	Output     string    `json:"output"`
+	User       string    `json:"user"`
+	Server     string    `json:"asset"`
+	SystemUser string    `json:"system_user"`
+	Timestamp  time.Time `json:"timestamp"`
+}
+
+func (c *CommandRecorder) Record(cmd *Command) {
+
+}
diff --git a/pkg/record/replay.go b/pkg/recorder/replay.go
similarity index 81%
rename from pkg/record/replay.go
rename to pkg/recorder/replay.go
index c67c8734fd809fc6fc286743999ed83d4fb2c0fa..420458c83f67b0daeef147b22e7dd5e36206205b 100644
--- a/pkg/record/replay.go
+++ b/pkg/recorder/replay.go
@@ -1,4 +1,4 @@
-package record
+package recorder
 
 import (
 	"compress/gzip"
@@ -12,12 +12,12 @@ import (
 	"time"
 
 	"cocogo/pkg/config"
-	"cocogo/pkg/storage"
+	"cocogo/pkg/recorder/storage"
 )
 
 var conf = config.Conf
 
-func NewReplyRecord(sessionID string) *Reply {
+func NewReplyRecord(sessionID string) *ReplyRecorder {
 	rootPath := conf.RootPath
 	currentData := time.Now().UTC().Format("2006-01-02")
 	gzFileName := sessionID + ".replay.gz"
@@ -25,7 +25,7 @@ func NewReplyRecord(sessionID string) *Reply {
 	absGzFilePath := filepath.Join(rootPath, "data", "replays", currentData, gzFileName)
 
 	target := strings.Join([]string{currentData, gzFileName}, "/")
-	return &Reply{
+	return &ReplyRecorder{
 		SessionID:     sessionID,
 		FileName:      sessionID,
 		absFilePath:   absFilePath,
@@ -36,7 +36,7 @@ func NewReplyRecord(sessionID string) *Reply {
 	}
 }
 
-type Reply struct {
+type ReplyRecorder struct {
 	SessionID     string
 	FileName      string
 	gzFileName    string
@@ -47,19 +47,19 @@ type Reply struct {
 	StartTime     time.Time
 }
 
-func (r *Reply) Record(b []byte) {
+func (r *ReplyRecorder) Record(b []byte) {
 	interval := time.Now().UTC().Sub(r.StartTime).Seconds()
 	data, _ := json.Marshal(string(b))
 	_, _ = r.WriteF.WriteString(fmt.Sprintf("\"%0.6f\":%s,", interval, data))
 }
 
-func (r *Reply) StartRecord() {
+func (r *ReplyRecorder) Start() {
 	//auth.MakeSureDirExit(r.absFilePath)
 	//r.WriteF, _ = os.Create(r.absFilePath)
 	//_, _ = r.WriteF.Write([]byte("{"))
 }
 
-func (r *Reply) EndRecord(ctx context.Context) {
+func (r *ReplyRecorder) End(ctx context.Context) {
 	select {
 	case <-ctx.Done():
 		_, _ = r.WriteF.WriteString(`"0":""}`)
@@ -68,10 +68,10 @@ func (r *Reply) EndRecord(ctx context.Context) {
 	r.uploadReplay()
 }
 
-func (r *Reply) uploadReplay() {
+func (r *ReplyRecorder) uploadReplay() {
 	_ = GzipCompressFile(r.absFilePath, r.absGzFilePath)
-	if sto := storage.NewStorageServer(); sto != nil {
-		sto.Upload(r.absGzFilePath, r.target)
+	if store := storage.NewStorageServer(); store != nil {
+		store.Upload(r.absGzFilePath, r.target)
 	}
 	_ = os.Remove(r.absFilePath)
 	_ = os.Remove(r.absGzFilePath)
diff --git a/pkg/recorder/storage/interface.go b/pkg/recorder/storage/interface.go
new file mode 100644
index 0000000000000000000000000000000000000000..27ef6ef6899a0eb1fa007e83900cd537c1b2d5de
--- /dev/null
+++ b/pkg/recorder/storage/interface.go
@@ -0,0 +1,9 @@
+package storage
+
+type ReplayStorage interface {
+	Upload(gZipFile, target string)
+}
+
+func NewStorageServer() ReplayStorage {
+	return nil
+}
diff --git a/pkg/record/jms.go b/pkg/recorder/storage/jms.go
similarity index 88%
rename from pkg/record/jms.go
rename to pkg/recorder/storage/jms.go
index c7a21da79bd1476f17300b19dc55db5faa389b4c..e23e6f1ff2f04bb148cfc2b578c23e446e62d87a 100644
--- a/pkg/record/jms.go
+++ b/pkg/recorder/storage/jms.go
@@ -1,8 +1,8 @@
-package record
+package storage
 
 //var client = service.Client
 
-func NewJmsStorage() Storage {
+func NewJmsStorage() ReplayStorage {
 	//appService := auth.GetGlobalService()
 	//return &Server{
 	//	StorageType: "jms",