feat: Sprint 5 — xterm.js terminal with WebSocket mock shell, Alpine tab management
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func Terminal(c *fiber.Ctx) error {
|
||||
return c.Render("terminal", fiber.Map{
|
||||
"View": "terminal",
|
||||
"Title": "Terminal",
|
||||
"NavItems": navItems,
|
||||
})
|
||||
}
|
||||
|
||||
var hostList = []struct {
|
||||
ID string
|
||||
Name string
|
||||
IP string
|
||||
}{
|
||||
{"h1", "api-prod-01", "10.0.1.15"},
|
||||
{"h2", "db-primary-01", "10.0.2.5"},
|
||||
{"h6", "monitor-01", "10.0.0.5"},
|
||||
{"h7", "worker-pool-01", "10.0.4.50"},
|
||||
}
|
||||
|
||||
func TerminalWS(c *websocket.Conn) {
|
||||
host := c.Params("host", "unknown")
|
||||
log.Printf("WS connected: host=%s", host)
|
||||
|
||||
welcome := fmt.Sprintf("\x1b[1;32mWelcome to %s\x1b[0m\r\n\x1b[2mConnected at %s\x1b[0m\r\n\r\n", host, time.Now().Format(time.RFC822))
|
||||
c.WriteMessage(websocket.TextMessage, []byte(welcome))
|
||||
|
||||
prompt := fmt.Sprintf("\x1b[1;34m%s:~$\x1b[0m ", host)
|
||||
cwd := "~"
|
||||
|
||||
buf := ""
|
||||
for {
|
||||
_, msg, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
input := string(msg)
|
||||
|
||||
switch {
|
||||
case input == "\r":
|
||||
output := handleCommand(buf, host, &cwd)
|
||||
c.WriteMessage(websocket.TextMessage, []byte(output+prompt))
|
||||
buf = ""
|
||||
case input == "\x7f":
|
||||
if len(buf) > 0 {
|
||||
buf = buf[:len(buf)-1]
|
||||
c.WriteMessage(websocket.TextMessage, []byte("\b \b"))
|
||||
}
|
||||
case input == "\x03":
|
||||
buf = ""
|
||||
c.WriteMessage(websocket.TextMessage, []byte("^C\r\n"+prompt))
|
||||
default:
|
||||
if input >= " " && input <= "~" {
|
||||
buf += input
|
||||
c.WriteMessage(websocket.TextMessage, []byte(input))
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf("WS disconnected: host=%s", host)
|
||||
}
|
||||
|
||||
func handleCommand(cmd, host string, cwd *string) string {
|
||||
cmd = strings.TrimSpace(cmd)
|
||||
if cmd == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
parts := strings.Fields(cmd)
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
case "clear":
|
||||
return "\x1b[2J\x1b[H"
|
||||
case "exit", "logout":
|
||||
return "logout\r\n\x1b[2J\x1b[H\x1b[1;31mConnection closed.\x1b[0m\r\n"
|
||||
case "pwd":
|
||||
return *cwd + "\r\n"
|
||||
case "whoami":
|
||||
return "deploy\r\n"
|
||||
case "hostname":
|
||||
return host + "\r\n"
|
||||
case "date":
|
||||
return time.Now().Format("Mon Jan 2 15:04:05 MST 2006") + "\r\n"
|
||||
case "uptime":
|
||||
return fmt.Sprintf(" 13:42:17 up %d day, 1 user, load average: 0.08, 0.12, 0.10\r\n", 1+time.Now().Day()%30)
|
||||
case "uname":
|
||||
if len(parts) > 1 && parts[1] == "-a" {
|
||||
return fmt.Sprintf("Linux %s 6.8.0-45-generic #47-Ubuntu SMP PREEMPT x86_64 x86_64 x86_64 GNU/Linux\r\n", host)
|
||||
}
|
||||
return "Linux\r\n"
|
||||
case "ls":
|
||||
if *cwd == "~" || *cwd == "/home/deploy" {
|
||||
return "Documents Downloads projects config.yml README.md .env\r\n"
|
||||
}
|
||||
return "total 0\r\n"
|
||||
case "cd":
|
||||
if len(parts) > 1 {
|
||||
*cwd = parts[1]
|
||||
} else {
|
||||
*cwd = "~"
|
||||
}
|
||||
return ""
|
||||
case "cat":
|
||||
if len(parts) > 1 {
|
||||
return fmt.Sprintf("\x1b[1;37m# Content of %s\x1b[0m\r\n(not implemented in mock shell)\r\n", parts[1])
|
||||
}
|
||||
return ""
|
||||
case "echo":
|
||||
return strings.Join(parts[1:], " ") + "\r\n"
|
||||
case "ssh":
|
||||
return "\x1b[1;33mSSH forwarding not available in mock mode\x1b[0m\r\n"
|
||||
case "help":
|
||||
return "Available commands: clear, exit, pwd, whoami, hostname, date, uptime, uname, ls, cd, cat, echo, ssh, help\r\n"
|
||||
default:
|
||||
return fmt.Sprintf("\x1b[1;31m%s: command not found\x1b[0m\r\n", parts[0])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user