Skip to content

Commit d57cbf2

Browse files
committed
feat: Support script library management
1 parent 7a6e537 commit d57cbf2

File tree

56 files changed

+1468
-525
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1468
-525
lines changed

agent/init/migration/migrations/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
)
2020

2121
var AddTable = &gormigrate.Migration{
22-
ID: "20240108-add-table",
22+
ID: "20250108-add-table",
2323
Migrate: func(tx *gorm.DB) error {
2424
return tx.AutoMigrate(
2525
&model.AppDetail{},

agent/utils/ssh/ssh.go

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package ssh
22

33
import (
4-
"bytes"
54
"fmt"
6-
"io"
75
"strings"
8-
"sync"
96
"time"
107

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

85-
type SshConn struct {
86-
StdinPipe io.WriteCloser
87-
ComboOutput *wsBufferWriter
88-
Session *gossh.Session
89-
}
90-
91-
func (c *ConnInfo) NewSshConn(cols, rows int) (*SshConn, error) {
92-
sshSession, err := c.Client.NewSession()
93-
if err != nil {
94-
return nil, err
95-
}
96-
97-
stdinP, err := sshSession.StdinPipe()
98-
if err != nil {
99-
return nil, err
100-
}
101-
102-
comboWriter := new(wsBufferWriter)
103-
sshSession.Stdout = comboWriter
104-
sshSession.Stderr = comboWriter
105-
106-
modes := gossh.TerminalModes{
107-
gossh.ECHO: 1,
108-
gossh.TTY_OP_ISPEED: 14400,
109-
gossh.TTY_OP_OSPEED: 14400,
110-
}
111-
if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil {
112-
return nil, err
113-
}
114-
if err := sshSession.Shell(); err != nil {
115-
return nil, err
116-
}
117-
return &SshConn{StdinPipe: stdinP, ComboOutput: comboWriter, Session: sshSession}, nil
118-
}
119-
120-
func (s *SshConn) Close() {
121-
if s.Session != nil {
122-
s.Session.Close()
123-
}
124-
}
125-
126-
type wsBufferWriter struct {
127-
buffer bytes.Buffer
128-
mu sync.Mutex
129-
}
130-
131-
func (w *wsBufferWriter) Write(p []byte) (int, error) {
132-
w.mu.Lock()
133-
defer w.mu.Unlock()
134-
return w.buffer.Write(p)
135-
}
136-
13782
func makePrivateKeySigner(privateKey []byte, passPhrase []byte) (gossh.Signer, error) {
13883
if len(passPhrase) != 0 {
13984
return gossh.ParsePrivateKeyWithPassphrase(privateKey, passPhrase)

core/app/api/v2/entry.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ var (
1818
groupService = service.NewIGroupService()
1919
commandService = service.NewICommandService()
2020
appLauncherService = service.NewIAppLauncher()
21+
scriptService = service.NewIScriptService()
2122
)

core/app/api/v2/host.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@ func (b *BaseApi) HostTree(c *gin.Context) {
111111
// @Tags Host
112112
// @Summary Page host
113113
// @Accept json
114-
// @Param request body dto.SearchHostWithPage true "request"
114+
// @Param request body dto.SearchPageWithGroup true "request"
115115
// @Success 200 {object} dto.PageResult
116116
// @Security ApiKeyAuth
117117
// @Security Timestamp
118118
// @Router /core/hosts/search [post]
119119
func (b *BaseApi) SearchHost(c *gin.Context) {
120-
var req dto.SearchHostWithPage
120+
var req dto.SearchPageWithGroup
121121
if err := helper.CheckBindAndValidate(&req, c); err != nil {
122122
return
123123
}
@@ -304,7 +304,7 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
304304
return
305305
}
306306
defer client.Close()
307-
sws, err := terminal.NewLogicSshWsSession(cols, rows, true, client.Client, wsConn)
307+
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, "")
308308
if wshandleError(wsConn, err) {
309309
return
310310
}

core/app/api/v2/script_library.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package v2
2+
3+
import (
4+
"fmt"
5+
"path"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
10+
"github.com/1Panel-dev/1Panel/core/app/dto"
11+
"github.com/1Panel-dev/1Panel/core/app/service"
12+
"github.com/1Panel-dev/1Panel/core/global"
13+
"github.com/1Panel-dev/1Panel/core/utils/ssh"
14+
"github.com/1Panel-dev/1Panel/core/utils/terminal"
15+
"github.com/1Panel-dev/1Panel/core/utils/xpack"
16+
"github.com/gin-gonic/gin"
17+
"github.com/pkg/errors"
18+
)
19+
20+
// @Tags ScriptLibrary
21+
// @Summary Add script
22+
// @Accept json
23+
// @Param request body dto.ScriptOperate true "request"
24+
// @Success 200
25+
// @Security ApiKeyAuth
26+
// @Security Timestamp
27+
// @Router /script [post]
28+
// @x-panel-log {"bodyKeys":["tag","name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加脚本库脚本 [tag][name]","formatEN":"add script [tag][name]"}
29+
func (b *BaseApi) CreateScript(c *gin.Context) {
30+
var req dto.ScriptOperate
31+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
32+
return
33+
}
34+
35+
if err := scriptService.Create(req); err != nil {
36+
helper.InternalServer(c, err)
37+
return
38+
}
39+
helper.SuccessWithOutData(c)
40+
}
41+
42+
// @Tags ScriptLibrary
43+
// @Summary Page script
44+
// @Accept json
45+
// @Param request body dto.SearchPageWithGroup true "request"
46+
// @Success 200 {object} dto.PageResult
47+
// @Security ApiKeyAuth
48+
// @Security Timestamp
49+
// @Router /script/search [post]
50+
func (b *BaseApi) SearchScript(c *gin.Context) {
51+
var req dto.SearchPageWithGroup
52+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
53+
return
54+
}
55+
56+
total, list, err := scriptService.Search(req)
57+
if err != nil {
58+
helper.InternalServer(c, err)
59+
return
60+
}
61+
62+
helper.SuccessWithData(c, dto.PageResult{
63+
Items: list,
64+
Total: total,
65+
})
66+
}
67+
68+
// @Tags ScriptLibrary
69+
// @Summary Delete script
70+
// @Accept json
71+
// @Param request body dto.BatchDeleteReq true "request"
72+
// @Success 200
73+
// @Security ApiKeyAuth
74+
// @Security Timestamp
75+
// @Router /script/del [post]
76+
// @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]"}
77+
func (b *BaseApi) DeleteScript(c *gin.Context) {
78+
var req dto.OperateByIDs
79+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
80+
return
81+
}
82+
83+
if err := scriptService.Delete(req); err != nil {
84+
helper.InternalServer(c, err)
85+
return
86+
}
87+
helper.SuccessWithOutData(c)
88+
}
89+
90+
// @Tags ScriptLibrary
91+
// @Summary Update script
92+
// @Accept json
93+
// @Param request body dto.ScriptOperate true "request"
94+
// @Success 200
95+
// @Security ApiKeyAuth
96+
// @Security Timestamp
97+
// @Router /script/update [post]
98+
// @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]"}
99+
func (b *BaseApi) UpdateScript(c *gin.Context) {
100+
var req dto.ScriptOperate
101+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
102+
return
103+
}
104+
105+
if err := scriptService.Update(req); err != nil {
106+
helper.InternalServer(c, err)
107+
return
108+
}
109+
helper.SuccessWithOutData(c)
110+
}
111+
112+
func (b *BaseApi) RunScript(c *gin.Context) {
113+
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
114+
if err != nil {
115+
global.LOG.Errorf("gin context http handler failed, err: %v", err)
116+
return
117+
}
118+
defer wsConn.Close()
119+
120+
if global.CONF.Base.IsDemo {
121+
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
122+
return
123+
}
124+
}
125+
126+
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
127+
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
128+
return
129+
}
130+
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
131+
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
132+
return
133+
}
134+
scriptID := c.Query("script_id")
135+
currentNode := c.Query("current_node")
136+
intNum, _ := strconv.Atoi(scriptID)
137+
if intNum == 0 {
138+
if wshandleError(wsConn, fmt.Errorf(" no such script %v in library, please check and try again!", scriptID)) {
139+
return
140+
}
141+
}
142+
scriptItem, err := service.LoadScriptInfo(uint(intNum))
143+
if wshandleError(wsConn, err) {
144+
return
145+
}
146+
147+
fileName := strings.ReplaceAll(scriptItem.Name, " ", "_")
148+
quitChan := make(chan bool, 3)
149+
if currentNode == "local" {
150+
tmpFile := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/script")
151+
initCmd := fmt.Sprintf("d=%s && mkdir -p $d && echo %s > $d/%s && clear && bash $d/%s", tmpFile, scriptItem.Script, fileName, fileName)
152+
slave, err := terminal.NewCommand(initCmd)
153+
if wshandleError(wsConn, err) {
154+
return
155+
}
156+
defer slave.Close()
157+
158+
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true)
159+
if wshandleError(wsConn, err) {
160+
return
161+
}
162+
163+
quitChan := make(chan bool, 3)
164+
tty.Start(quitChan)
165+
go slave.Wait(quitChan)
166+
} else {
167+
connInfo, installDir, err := xpack.LoadNodeInfo(currentNode)
168+
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
169+
return
170+
}
171+
tmpFile := path.Join(installDir, "1panel/tmp/script")
172+
initCmd := fmt.Sprintf("d=%s && mkdir -p $d && echo %s > $d/%s && clear && bash $d/%s", tmpFile, scriptItem.Script, fileName, fileName)
173+
client, err := ssh.NewClient(*connInfo)
174+
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
175+
return
176+
}
177+
defer client.Close()
178+
179+
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, initCmd)
180+
if wshandleError(wsConn, err) {
181+
return
182+
}
183+
defer sws.Close()
184+
sws.Start(quitChan)
185+
go sws.Wait(quitChan)
186+
}
187+
188+
<-quitChan
189+
190+
global.LOG.Info("websocket finished")
191+
if wshandleError(wsConn, err) {
192+
return
193+
}
194+
}

core/app/dto/command.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ type SearchCommandWithPage struct {
55
OrderBy string `json:"orderBy" validate:"required,oneof=name command createdAt"`
66
Order string `json:"order" validate:"required,oneof=null ascending descending"`
77
GroupID uint `json:"groupID"`
8-
Type string `josn:"type" validate:"required,oneof=redis command"`
8+
Type string `json:"type" validate:"required,oneof=redis command"`
99
Info string `json:"info"`
1010
}
1111

1212
type CommandOperate struct {
1313
ID uint `json:"id"`
14-
Type string `josn:"type"`
14+
Type string `json:"type"`
1515
GroupID uint `json:"groupID"`
1616
GroupBelong string `json:"groupBelong"`
1717
Name string `json:"name" validate:"required"`

core/app/dto/common.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ type SearchPageWithType struct {
1111
Info string `json:"info"`
1212
}
1313

14+
type SearchPageWithGroup struct {
15+
PageInfo
16+
GroupID uint `json:"groupID"`
17+
Info string `json:"info"`
18+
}
19+
1420
type PageInfo struct {
1521
Page int `json:"page" validate:"required,number"`
1622
PageSize int `json:"pageSize" validate:"required,number"`

core/app/dto/host.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,6 @@ type HostConnTest struct {
3030
PassPhrase string `json:"passPhrase"`
3131
}
3232

33-
type SearchHostWithPage struct {
34-
PageInfo
35-
GroupID uint `json:"groupID"`
36-
Info string `json:"info"`
37-
}
38-
3933
type SearchForTree struct {
4034
Info string `json:"info"`
4135
}

core/app/dto/script_library.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dto
2+
3+
import "time"
4+
5+
type ScriptInfo struct {
6+
ID uint `json:"id"`
7+
Name string `json:"name"`
8+
Lable string `json:"lable"`
9+
Script string `json:"script"`
10+
GroupList []uint `json:"groupList"`
11+
GroupBelong []string `json:"groupBelong"`
12+
IsSystem bool `json:"isSystem"`
13+
Description string `json:"description"`
14+
CreatedAt time.Time `json:"createdAt"`
15+
}
16+
17+
type ScriptOperate struct {
18+
ID uint `json:"id"`
19+
Name string `json:"name"`
20+
Script string `json:"script"`
21+
Groups string `json:"groups"`
22+
Description string `json:"description"`
23+
}

core/app/model/script_library.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package model
2+
3+
type ScriptLibrary struct {
4+
BaseModel
5+
Name string `json:"name" gorm:"not null;"`
6+
Lable string `json:"lable"`
7+
Script string `json:"script" gorm:"not null;"`
8+
Groups string `json:"groups"`
9+
IsSystem bool `json:"isSystem"`
10+
Description string `json:"description"`
11+
}

0 commit comments

Comments
 (0)