mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
terminal working, not prod ready, but it works
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package blockcontroller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/wavetermdev/thenextwave/pkg/shellexec"
|
||||
)
|
||||
|
||||
const CommandKey = "command"
|
||||
|
||||
const (
|
||||
BlockCommand_Message = "message"
|
||||
BlockCommand_Run = "run"
|
||||
BlockCommand_Input = "input"
|
||||
BlockCommand_RunShell = "runshell"
|
||||
)
|
||||
|
||||
var CommandToTypeMap = map[string]reflect.Type{
|
||||
BlockCommand_Message: reflect.TypeOf(MessageCommand{}),
|
||||
BlockCommand_Run: reflect.TypeOf(RunCommand{}),
|
||||
BlockCommand_Input: reflect.TypeOf(InputCommand{}),
|
||||
BlockCommand_RunShell: reflect.TypeOf(RunShellCommand{}),
|
||||
}
|
||||
|
||||
type BlockCommand interface {
|
||||
GetCommand() string
|
||||
}
|
||||
|
||||
func ParseCmdMap(cmdMap map[string]any) (BlockCommand, error) {
|
||||
cmdType, ok := cmdMap[CommandKey].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no %s field in command map", CommandKey)
|
||||
}
|
||||
mapJson, err := json.Marshal(cmdMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshalling command map: %w", err)
|
||||
}
|
||||
rtype := CommandToTypeMap[cmdType]
|
||||
if rtype == nil {
|
||||
return nil, fmt.Errorf("unknown command type %q", cmdType)
|
||||
}
|
||||
cmd := reflect.New(rtype).Interface()
|
||||
err = json.Unmarshal(mapJson, cmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling command: %w", err)
|
||||
}
|
||||
return cmd.(BlockCommand), nil
|
||||
}
|
||||
|
||||
type MessageCommand struct {
|
||||
Command string `json:"command"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (mc *MessageCommand) GetCommand() string {
|
||||
return BlockCommand_Message
|
||||
}
|
||||
|
||||
type RunCommand struct {
|
||||
Command string `json:"command"`
|
||||
CmdStr string `json:"cmdstr"`
|
||||
TermSize shellexec.TermSize `json:"termsize"`
|
||||
}
|
||||
|
||||
func (rc *RunCommand) GetCommand() string {
|
||||
return BlockCommand_Run
|
||||
}
|
||||
|
||||
type InputCommand struct {
|
||||
Command string `json:"command"`
|
||||
InputData64 string `json:"inputdata64"`
|
||||
SigName string `json:"signame,omitempty"`
|
||||
TermSize *shellexec.TermSize `json:"termsize,omitempty"`
|
||||
}
|
||||
|
||||
func (ic *InputCommand) GetCommand() string {
|
||||
return BlockCommand_Input
|
||||
}
|
||||
|
||||
type RunShellCommand struct {
|
||||
Command string `json:"command"`
|
||||
TermSize shellexec.TermSize `json:"termsize"`
|
||||
}
|
||||
|
||||
func (rsc *RunShellCommand) GetCommand() string {
|
||||
return BlockCommand_RunShell
|
||||
}
|
||||
@@ -5,12 +5,13 @@ package blockcontroller
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wavetermdev/thenextwave/pkg/eventbus"
|
||||
"github.com/wavetermdev/thenextwave/pkg/shellexec"
|
||||
@@ -20,64 +21,31 @@ import (
|
||||
var globalLock = &sync.Mutex{}
|
||||
var blockControllerMap = make(map[string]*BlockController)
|
||||
|
||||
type BlockCommand interface {
|
||||
GetCommand() string
|
||||
}
|
||||
|
||||
type MessageCommand struct {
|
||||
Command string `json:"command"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (mc *MessageCommand) GetCommand() string {
|
||||
return "message"
|
||||
}
|
||||
|
||||
type RunCommand struct {
|
||||
Command string `json:"command"`
|
||||
CmdStr string `json:"cmdstr"`
|
||||
TermSize shellexec.TermSize `json:"termsize"`
|
||||
}
|
||||
|
||||
func (rc *RunCommand) GetCommand() string {
|
||||
return "run"
|
||||
}
|
||||
|
||||
type BlockController struct {
|
||||
BlockId string
|
||||
InputCh chan BlockCommand
|
||||
Lock *sync.Mutex
|
||||
BlockId string
|
||||
InputCh chan BlockCommand
|
||||
ShellProc *shellexec.ShellProc
|
||||
ShellInputCh chan *InputCommand
|
||||
}
|
||||
|
||||
func ParseCmdMap(cmdMap map[string]any) (BlockCommand, error) {
|
||||
cmdType, ok := cmdMap["command"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no command field in command map")
|
||||
}
|
||||
mapJson, err := json.Marshal(cmdMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshalling command map: %w", err)
|
||||
}
|
||||
switch cmdType {
|
||||
case "message":
|
||||
var cmd MessageCommand
|
||||
err := json.Unmarshal(mapJson, &cmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling message command: %w", err)
|
||||
}
|
||||
return &cmd, nil
|
||||
case "run":
|
||||
var cmd RunCommand
|
||||
err := json.Unmarshal(mapJson, &cmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling run command: %w", err)
|
||||
}
|
||||
return &cmd, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown command type %q", cmdType)
|
||||
func (bc *BlockController) setShellProc(shellProc *shellexec.ShellProc) error {
|
||||
bc.Lock.Lock()
|
||||
defer bc.Lock.Unlock()
|
||||
if bc.ShellProc != nil {
|
||||
return fmt.Errorf("shell process already running")
|
||||
}
|
||||
bc.ShellProc = shellProc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *BlockController) StartShellCommand(rc *RunCommand) error {
|
||||
func (bc *BlockController) getShellProc() *shellexec.ShellProc {
|
||||
bc.Lock.Lock()
|
||||
defer bc.Lock.Unlock()
|
||||
return bc.ShellProc
|
||||
}
|
||||
|
||||
func (bc *BlockController) DoRunCommand(rc *RunCommand) error {
|
||||
cmdStr := rc.CmdStr
|
||||
shellPath := shellutil.DetectLocalShellPath()
|
||||
ecmd := exec.Command(shellPath, "-c", cmdStr)
|
||||
@@ -104,6 +72,71 @@ func (bc *BlockController) StartShellCommand(rc *RunCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *BlockController) DoRunShellCommand(rc *RunShellCommand) error {
|
||||
if bc.getShellProc() != nil {
|
||||
return nil
|
||||
}
|
||||
shellProc, err := shellexec.StartShellProc(rc.TermSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bc.setShellProc(shellProc)
|
||||
if err != nil {
|
||||
bc.ShellProc.Close()
|
||||
return err
|
||||
}
|
||||
shellInputCh := make(chan *InputCommand)
|
||||
bc.ShellInputCh = shellInputCh
|
||||
go func() {
|
||||
defer func() {
|
||||
// needs synchronization
|
||||
bc.ShellProc.Close()
|
||||
close(bc.ShellInputCh)
|
||||
bc.ShellProc = nil
|
||||
bc.ShellInputCh = nil
|
||||
}()
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
nr, err := bc.ShellProc.Pty.Read(buf)
|
||||
eventbus.SendEvent(application.WailsEvent{
|
||||
Name: "block:ptydata",
|
||||
Data: map[string]any{
|
||||
"blockid": bc.BlockId,
|
||||
"blockfile": "main",
|
||||
"ptydata": base64.StdEncoding.EncodeToString(buf[:nr]),
|
||||
},
|
||||
})
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("error reading from shell: %v\n", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for ic := range shellInputCh {
|
||||
if ic.InputData64 != "" {
|
||||
inputBuf := make([]byte, base64.StdEncoding.DecodedLen(len(ic.InputData64)))
|
||||
nw, err := base64.StdEncoding.Decode(inputBuf, []byte(ic.InputData64))
|
||||
if err != nil {
|
||||
log.Printf("error decoding input data: %v\n", err)
|
||||
continue
|
||||
}
|
||||
bc.ShellProc.Pty.Write(inputBuf[:nw])
|
||||
}
|
||||
if ic.TermSize != nil {
|
||||
err := pty.Setsize(bc.ShellProc.Pty, &pty.Winsize{Rows: uint16(ic.TermSize.Rows), Cols: uint16(ic.TermSize.Cols)})
|
||||
if err != nil {
|
||||
log.Printf("error setting term size: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *BlockController) Run() {
|
||||
defer func() {
|
||||
eventbus.SendEvent(application.WailsEvent{
|
||||
@@ -132,12 +165,28 @@ func (bc *BlockController) Run() {
|
||||
case *RunCommand:
|
||||
fmt.Printf("RUN: %s | %q\n", bc.BlockId, cmd.CmdStr)
|
||||
go func() {
|
||||
err := bc.StartShellCommand(cmd)
|
||||
err := bc.DoRunCommand(cmd)
|
||||
if err != nil {
|
||||
log.Printf("error running shell command: %v\n", err)
|
||||
}
|
||||
}()
|
||||
case *InputCommand:
|
||||
fmt.Printf("INPUT: %s | %q\n", bc.BlockId, cmd.InputData64)
|
||||
if bc.ShellInputCh != nil {
|
||||
bc.ShellInputCh <- cmd
|
||||
}
|
||||
|
||||
case *RunShellCommand:
|
||||
fmt.Printf("RUNSHELL: %s\n", bc.BlockId)
|
||||
if bc.ShellProc != nil {
|
||||
continue
|
||||
}
|
||||
go func() {
|
||||
err := bc.DoRunShellCommand(cmd)
|
||||
if err != nil {
|
||||
log.Printf("error running shell: %v\n", err)
|
||||
}
|
||||
}()
|
||||
default:
|
||||
fmt.Printf("unknown command type %T\n", cmd)
|
||||
}
|
||||
@@ -151,6 +200,7 @@ func StartBlockController(blockId string) *BlockController {
|
||||
return existingBC
|
||||
}
|
||||
bc := &BlockController{
|
||||
Lock: &sync.Mutex{},
|
||||
BlockId: blockId,
|
||||
InputCh: make(chan BlockCommand),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user