Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/init/migration/migrations/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
)

var AddTable = &gormigrate.Migration{
ID: "20240108-add-table",
ID: "20250108-add-table",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.AppDetail{},
Expand Down
55 changes: 0 additions & 55 deletions agent/utils/ssh/ssh.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package ssh

import (
"bytes"
"fmt"
"io"
"strings"
"sync"
"time"

gossh "golang.org/x/crypto/ssh"
Expand Down Expand Up @@ -82,58 +79,6 @@ func (c *ConnInfo) Close() {
_ = c.Client.Close()
}

type SshConn struct {
StdinPipe io.WriteCloser
ComboOutput *wsBufferWriter
Session *gossh.Session
}

func (c *ConnInfo) NewSshConn(cols, rows int) (*SshConn, error) {
sshSession, err := c.Client.NewSession()
if err != nil {
return nil, err
}

stdinP, err := sshSession.StdinPipe()
if err != nil {
return nil, err
}

comboWriter := new(wsBufferWriter)
sshSession.Stdout = comboWriter
sshSession.Stderr = comboWriter

modes := gossh.TerminalModes{
gossh.ECHO: 1,
gossh.TTY_OP_ISPEED: 14400,
gossh.TTY_OP_OSPEED: 14400,
}
if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil {
return nil, err
}
if err := sshSession.Shell(); err != nil {
return nil, err
}
return &SshConn{StdinPipe: stdinP, ComboOutput: comboWriter, Session: sshSession}, nil
}

func (s *SshConn) Close() {
if s.Session != nil {
s.Session.Close()
}
}

type wsBufferWriter struct {
buffer bytes.Buffer
mu sync.Mutex
}

func (w *wsBufferWriter) Write(p []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
return w.buffer.Write(p)
}

func makePrivateKeySigner(privateKey []byte, passPhrase []byte) (gossh.Signer, error) {
if len(passPhrase) != 0 {
return gossh.ParsePrivateKeyWithPassphrase(privateKey, passPhrase)
Expand Down
1 change: 1 addition & 0 deletions core/app/api/v2/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ var (
groupService = service.NewIGroupService()
commandService = service.NewICommandService()
appLauncherService = service.NewIAppLauncher()
scriptService = service.NewIScriptService()
)
6 changes: 3 additions & 3 deletions core/app/api/v2/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ func (b *BaseApi) HostTree(c *gin.Context) {
// @Tags Host
// @Summary Page host
// @Accept json
// @Param request body dto.SearchHostWithPage true "request"
// @Param request body dto.SearchPageWithGroup true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/hosts/search [post]
func (b *BaseApi) SearchHost(c *gin.Context) {
var req dto.SearchHostWithPage
var req dto.SearchPageWithGroup
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
Expand Down Expand Up @@ -304,7 +304,7 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
return
}
defer client.Close()
sws, err := terminal.NewLogicSshWsSession(cols, rows, true, client.Client, wsConn)
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, "")
if wshandleError(wsConn, err) {
return
}
Expand Down
194 changes: 194 additions & 0 deletions core/app/api/v2/script_library.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package v2

import (
"fmt"
"path"
"strconv"
"strings"

"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/service"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/ssh"
"github.com/1Panel-dev/1Panel/core/utils/terminal"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
)

// @Tags ScriptLibrary
// @Summary Add script
// @Accept json
// @Param request body dto.ScriptOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /script [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加脚本库脚本 [name]","formatEN":"add script [name]"}
func (b *BaseApi) CreateScript(c *gin.Context) {
var req dto.ScriptOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}

if err := scriptService.Create(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithOutData(c)
}

// @Tags ScriptLibrary
// @Summary Page script
// @Accept json
// @Param request body dto.SearchPageWithGroup true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /script/search [post]
func (b *BaseApi) SearchScript(c *gin.Context) {
var req dto.SearchPageWithGroup
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}

total, list, err := scriptService.Search(req)
if err != nil {
helper.InternalServer(c, err)
return
}

helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}

// @Tags ScriptLibrary
// @Summary Delete script
// @Accept json
// @Param request body dto.BatchDeleteReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /script/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"script_librarys","output_column":"name","output_value":"names"}],"formatZH":"删除脚本库脚本 [names]","formatEN":"delete script [names]"}
func (b *BaseApi) DeleteScript(c *gin.Context) {
var req dto.OperateByIDs
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}

if err := scriptService.Delete(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithOutData(c)
}

// @Tags ScriptLibrary
// @Summary Update script
// @Accept json
// @Param request body dto.ScriptOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /script/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"更新脚本库脚本 [name]","formatEN":"update script [name]"}
func (b *BaseApi) UpdateScript(c *gin.Context) {
var req dto.ScriptOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}

if err := scriptService.Update(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithOutData(c)
}

func (b *BaseApi) RunScript(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()

if global.CONF.Base.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
return
}
}

cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
scriptID := c.Query("script_id")
currentNode := c.Query("current_node")
intNum, _ := strconv.Atoi(scriptID)
if intNum == 0 {
if wshandleError(wsConn, fmt.Errorf(" no such script %v in library, please check and try again!", scriptID)) {
return
}
}
scriptItem, err := service.LoadScriptInfo(uint(intNum))
if wshandleError(wsConn, err) {
return
}

fileName := strings.ReplaceAll(scriptItem.Name, " ", "_")
quitChan := make(chan bool, 3)
if currentNode == "local" {
tmpFile := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/script")
initCmd := fmt.Sprintf("d=%s && mkdir -p $d && echo %s > $d/%s && clear && bash $d/%s", tmpFile, scriptItem.Script, fileName, fileName)
slave, err := terminal.NewCommand(initCmd)
if wshandleError(wsConn, err) {
return
}
defer slave.Close()

tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true)
if wshandleError(wsConn, err) {
return
}

quitChan := make(chan bool, 3)
tty.Start(quitChan)
go slave.Wait(quitChan)
} else {
connInfo, installDir, err := xpack.LoadNodeInfo(currentNode)
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
tmpFile := path.Join(installDir, "1panel/tmp/script")
initCmd := fmt.Sprintf("d=%s && mkdir -p $d && echo %s > $d/%s && clear && bash $d/%s", tmpFile, scriptItem.Script, fileName, fileName)
client, err := ssh.NewClient(*connInfo)
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
return
}
defer client.Close()

sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, initCmd)
if wshandleError(wsConn, err) {
return
}
defer sws.Close()
sws.Start(quitChan)
go sws.Wait(quitChan)
}

<-quitChan

global.LOG.Info("websocket finished")
if wshandleError(wsConn, err) {
return
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided code is from the 1Panel project and contains several parts of its logic related to various APIs in the script library.

Changes Needed Summary

There are multiple changes that can be suggested based on the specific requirements and constraints of your application:

BaseApi package

  • CreateScript: You might want to ensure consistent return values. Instead of using helper.SuccessWithOutData(c) which doesn't seem to have an action parameter like it should handle data back to clients properly.
  • To improve error handling with Gin, you might reconsider what's being returned when errors occur; consider returning more descriptive error messages if possible. A better option here would probably use the Go standard library's built-in types instead of manually checking whether errors occurred during processing.

General Code Modifications

  • Implement proper input validation at the beginning of each function before attempting to perform operations. This helps prevent unnecessary runtime errors or crashes due to invalid inputs.
  • Ensure all functions follow RESTful guidelines by naming their methods correctly in a way that corresponds to their HTTP requests (e.g., SearchScript rather than just /script/search).
  • Consider moving common utility functions into separate utility packages for improved modularity and maintainability.
  • Use standardized conventions and styles across different modules within the same application where applicable. Consistency in variable names, function signatures, etc. could further help in readability and maintenance.

To provide detailed and useful feedback, let’s address points specifically addressed below:

  1. The following section seems to contain some sample code snippets related to interacting with external systems via SSH:
    connInfo, installDir, err := xpack.LoadNodeInfo(currentNode)
    
    //...
This snippet uses `xpack`, but there isn’t much explanation about how it interacts with external systems directly or any context around why this approach was chosen. If `xpack` represents an internal or custom module, ensure it has been properly documented with its intended functionality. Otherwise, explore alternative means to interact or log interactions with external resources.

2. There seem two sections that reference `ssh`: one at the end for logging completion of websocket, while another appears earlier referencing ssh commands (`initCmd`) and sessions.
    - In both cases, clarify the role of these elements in terms of system interaction between `1Panel-dev/1Panel/core/app/service`. Are they part of the infrastructure for running scripts locally? Is the intent behind them clearer?
    - Given the need for proper control over session initiation details, it may be helpful to include a method that accepts configuration parameters for connections similar to existing ones available in the SSH package for making sure these instances run smoothly.

In conclusion, improving coding best practices requires integrating good habits such as thorough testing through automated checks and regular refactoring efforts aimed at maintaining quality codebases even under dynamic environments and evolving feature implementations. It also involves adopting patterns and strategies that facilitate efficient communication among teams working closely together. With thoughtful implementation decisions guided by comprehensive documentation, future contributions to projects will flourish.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided code appears to be from the script library API of a backend application written in Gin framework using Go programming language. Given that it's quite old with an outdated format and design elements, there is nothing inherently irregular about it at this point; however, some potential areas could include:

  • Code structure and organization might need review
  • Data structures like error handling should be updated for better clarity and efficiency
  • Performance considerations due to older technologies may require refactoring

In particular, here are some points which can guide improvements or optimizations based on modern practices while considering legacy nature of these functions:

Function Definitions: Ensure each function follows proper naming conventions without redundant comments. The dto package imports might not align well.

//@Package v2

Input Validation: Validate parameters in more robust ways instead of simple checks, especially when they represent data types beyond basic validation.

var req dto.ScriptOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
        return
    }

Ensure all requests have validations for required fields before processing them.

For example:

if len(req.name) < 1 || len(req.description) < 1 { // Adding custom validator function
    errors.New("Invalid name or description length.")
}

Use more meaningful names for constants rather than hard-coded variable names where possible.

Consider replacing magic number 67598371 used with arbitrary numeric values with actual variables as needed.

To enhance readability, organize the source file into namespaces/slices/tuples wherever appropriate for modularity.

Finally, maintain backward compatibility for new functionality added later on, but ensure backwards compatability across existing functionality remains intact.

Regarding performance and optimization, consider adding import statements for more recent packages that would improve runtime and memory efficiency.

As these examples reflect changes related to best practices in modern engineering development, they are intended solely as guidance towards cleaner coding approaches.

4 changes: 2 additions & 2 deletions core/app/dto/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ type SearchCommandWithPage struct {
OrderBy string `json:"orderBy" validate:"required,oneof=name command createdAt"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
GroupID uint `json:"groupID"`
Type string `josn:"type" validate:"required,oneof=redis command"`
Type string `json:"type" validate:"required,oneof=redis command"`
Info string `json:"info"`
}

type CommandOperate struct {
ID uint `json:"id"`
Type string `josn:"type"`
Type string `json:"type"`
GroupID uint `json:"groupID"`
GroupBelong string `json:"groupBelong"`
Name string `json:"name" validate:"required"`
Expand Down
6 changes: 6 additions & 0 deletions core/app/dto/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ type SearchPageWithType struct {
Info string `json:"info"`
}

type SearchPageWithGroup struct {
PageInfo
GroupID uint `json:"groupID"`
Info string `json:"info"`
}

type PageInfo struct {
Page int `json:"page" validate:"required,number"`
PageSize int `json:"pageSize" validate:"required,number"`
Expand Down
6 changes: 0 additions & 6 deletions core/app/dto/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ type HostConnTest struct {
PassPhrase string `json:"passPhrase"`
}

type SearchHostWithPage struct {
PageInfo
GroupID uint `json:"groupID"`
Info string `json:"info"`
}

type SearchForTree struct {
Info string `json:"info"`
}
Expand Down
23 changes: 23 additions & 0 deletions core/app/dto/script_library.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dto

import "time"

type ScriptInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Lable string `json:"lable"`
Script string `json:"script"`
GroupList []uint `json:"groupList"`
GroupBelong []string `json:"groupBelong"`
IsSystem bool `json:"isSystem"`
Description string `json:"description"`
CreatedAt time.Time `json:"createdAt"`
}

type ScriptOperate struct {
ID uint `json:"id"`
Name string `json:"name"`
Script string `json:"script"`
Groups string `json:"groups"`
Description string `json:"description"`
}
11 changes: 11 additions & 0 deletions core/app/model/script_library.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package model

type ScriptLibrary struct {
BaseModel
Name string `json:"name" gorm:"not null;"`
Lable string `json:"lable"`
Script string `json:"script" gorm:"not null;"`
Groups string `json:"groups"`
IsSystem bool `json:"isSystem"`
Description string `json:"description"`
}
Loading
Loading