Files
terminal/server/api/filesystem_api.go
T

434 lines
11 KiB
Go

package api
import (
"fmt"
"next-terminal/server/common/maps"
"next-terminal/server/global/session"
"next-terminal/server/log"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/labstack/echo/v4"
"github.com/pkg/sftp"
)
type FileSystemApi struct{}
func (api FileSystemApi) LsEndpoint(c echo.Context) error {
sessionId := c.Param("id")
dir := c.QueryParam("dir")
hiddenFileVisible := c.QueryParam("hiddenFileVisible") == "true"
log.Debug("FileSystem Ls: sessionId=" + sessionId + " dir=" + dir)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
log.Debug("FileSystem Ls: failed to create SFTP client: " + err.Error())
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
files, err := sftpClient.ReadDir(dir)
if err != nil {
log.Debug("FileSystem Ls: failed to read directory: " + err.Error())
return Fail(c, -1, "failed to read directory: "+err.Error())
}
var result []maps.Map
for _, f := range files {
name := f.Name()
if !hiddenFileVisible && strings.HasPrefix(name, ".") {
continue
}
result = append(result, maps.Map{
"name": name,
"size": f.Size(),
"modTime": f.ModTime().UnixMilli(),
"path": path.Join(dir, name),
"mode": f.Mode().String(),
"isDir": f.IsDir(),
"isLink": f.Mode()&os.ModeSymlink != 0,
})
}
return Success(c, result)
}
func (api FileSystemApi) MkdirEndpoint(c echo.Context) error {
sessionId := c.Param("id")
dir := c.QueryParam("dir")
log.Debug("FileSystem Mkdir: sessionId=" + sessionId + " dir=" + dir)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
err = sftpClient.MkdirAll(dir)
if err != nil {
return Fail(c, -1, "failed to create directory: "+err.Error())
}
return Success(c, nil)
}
func (api FileSystemApi) TouchEndpoint(c echo.Context) error {
sessionId := c.Param("id")
filename := c.QueryParam("filename")
log.Debug("FileSystem Touch: sessionId=" + sessionId + " filename=" + filename)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
f, err := sftpClient.Create(filename)
if err != nil {
return Fail(c, -1, "failed to create file: "+err.Error())
}
f.Close()
return Success(c, nil)
}
func (api FileSystemApi) RmEndpoint(c echo.Context) error {
sessionId := c.Param("id")
filename := c.QueryParam("filename")
log.Debug("FileSystem Rm: sessionId=" + sessionId + " filename=" + filename)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
f, err := sftpClient.Stat(filename)
if err != nil {
return Fail(c, -1, "file not found: "+err.Error())
}
if f.IsDir() {
err = sftpClient.RemoveDirectory(filename)
if err != nil {
err = api.removeRecursive(sftpClient, filename)
if err != nil {
return Fail(c, -1, "failed to remove directory: "+err.Error())
}
}
} else {
err = sftpClient.Remove(filename)
if err != nil {
return Fail(c, -1, "failed to remove file: "+err.Error())
}
}
return Success(c, nil)
}
func (api FileSystemApi) removeRecursive(sftpClient *sftp.Client, dirPath string) error {
files, err := sftpClient.ReadDir(dirPath)
if err != nil {
return err
}
for _, f := range files {
fullPath := path.Join(dirPath, f.Name())
if f.IsDir() {
err = api.removeRecursive(sftpClient, fullPath)
if err != nil {
return err
}
} else {
err = sftpClient.Remove(fullPath)
if err != nil {
return err
}
}
}
return sftpClient.RemoveDirectory(dirPath)
}
func (api FileSystemApi) RenameEndpoint(c echo.Context) error {
sessionId := c.Param("id")
oldName := c.QueryParam("oldName")
newName := c.QueryParam("newName")
log.Debug("FileSystem Rename: sessionId=" + sessionId + " oldName=" + oldName + " newName=" + newName)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
err = sftpClient.Rename(oldName, newName)
if err != nil {
return Fail(c, -1, "failed to rename: "+err.Error())
}
return Success(c, nil)
}
func (api FileSystemApi) EditEndpoint(c echo.Context) error {
sessionId := c.Param("id")
var req struct {
Filename string `json:"filename"`
FileContent string `json:"fileContent"`
}
if err := c.Bind(&req); err != nil {
return err
}
log.Debug("FileSystem Edit: sessionId=" + sessionId + " filename=" + req.Filename)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
f, err := sftpClient.OpenFile(req.Filename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE)
if err != nil {
return Fail(c, -1, "failed to open file: "+err.Error())
}
defer f.Close()
_, err = f.Write([]byte(req.FileContent))
if err != nil {
return Fail(c, -1, "failed to write file: "+err.Error())
}
return Success(c, nil)
}
func (api FileSystemApi) ReadEndpoint(c echo.Context) error {
sessionId := c.Param("id")
filename := c.QueryParam("filename")
log.Debug("FileSystem Read: sessionId=" + sessionId + " filename=" + filename)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
f, err := sftpClient.Open(filename)
if err != nil {
return Fail(c, -1, "failed to open file: "+err.Error())
}
defer f.Close()
content, err := readFileContent(f, 10*1024*1024)
if err != nil {
return Fail(c, -1, "failed to read file: "+err.Error())
}
return Success(c, maps.Map{
"content": content,
"path": filename,
})
}
func readFileContent(f *sftp.File, maxSize int64) (string, error) {
stat, err := f.Stat()
if err != nil {
return "", err
}
if stat.Size() > maxSize {
return "", fmt.Errorf("file too large (max %d bytes)", maxSize)
}
buf := make([]byte, stat.Size())
_, err = f.Read(buf)
if err != nil {
return "", err
}
return string(buf), nil
}
func (api FileSystemApi) ChmodEndpoint(c echo.Context) error {
sessionId := c.Param("id")
filename := c.QueryParam("filename")
modeStr := c.QueryParam("mode")
log.Debug("FileSystem Chmod: sessionId=" + sessionId + " filename=" + filename + " mode=" + modeStr)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
mode, err := strconv.ParseUint(modeStr, 10, 32)
if err != nil {
return Fail(c, -1, "invalid mode: "+err.Error())
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
err = sftpClient.Chmod(filename, os.FileMode(mode))
if err != nil {
return Fail(c, -1, "failed to chmod: "+err.Error())
}
return Success(c, nil)
}
func (api FileSystemApi) UploadProgressEndpoint(c echo.Context) error {
sessionId := c.Param("id")
id := c.QueryParam("id")
log.Debug("FileSystem UploadProgress: sessionId=" + sessionId + " id=" + id)
return Success(c, maps.Map{
"total": 0,
"written": 0,
"percent": 100,
"speed": 0,
"elapsedTime": 0,
"isCompleted": true,
})
}
func (api FileSystemApi) DownloadEndpoint(c echo.Context) error {
sessionId := c.Param("id")
filename := c.QueryParam("filename")
log.Debug("FileSystem Download: sessionId=" + sessionId + " filename=" + filename)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
f, err := sftpClient.Open(filename)
if err != nil {
return Fail(c, -1, "failed to open file: "+err.Error())
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
return Fail(c, -1, "failed to stat file: "+err.Error())
}
c.Response().Header().Set("Content-Disposition", "attachment; filename="+path.Base(filename))
c.Response().Header().Set("Content-Type", "application/octet-stream")
c.Response().Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10))
return c.Stream(200, "application/octet-stream", f)
}
func (api FileSystemApi) UploadEndpoint(c echo.Context) error {
sessionId := c.Param("id")
destDir := c.QueryParam("dir")
log.Debug("FileSystem Upload: sessionId=" + sessionId + " dir=" + destDir)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
file, err := c.FormFile("file")
if err != nil {
return Fail(c, -1, "failed to get upload file: "+err.Error())
}
src, err := file.Open()
if err != nil {
return Fail(c, -1, "failed to open upload file: "+err.Error())
}
defer src.Close()
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
destPath := path.Join(destDir, file.Filename)
dst, err := sftpClient.Create(destPath)
if err != nil {
return Fail(c, -1, "failed to create remote file: "+err.Error())
}
defer dst.Close()
buf := make([]byte, 32*1024)
for {
n, err := src.Read(buf)
if n > 0 {
_, writeErr := dst.Write(buf[:n])
if writeErr != nil {
return Fail(c, -1, "failed to write remote file: "+writeErr.Error())
}
}
if err != nil {
break
}
}
return Success(c, maps.Map{
"path": destPath,
"size": file.Size,
"timestamp": time.Now().UnixMilli(),
})
}