diff --git a/.gitignore b/.gitignore index 11fbb8a3..d3740e75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ tmp -.env -.history -.air.toml -panel.conf +/.air.toml +/panel.conf # Golang # # `go test -c` 生成的二进制文件 @@ -23,7 +21,7 @@ _cgo_export.* *.exe *.o *.so -panel +/panel # 压缩包 # # Git 自带压缩,如果这些压缩包里有代码,建议解压后 commit diff --git a/app/http/controllers/plugins/mysql57/mysql57_controller.go b/app/http/controllers/plugins/mysql57/mysql57_controller.go index f6db4653..11fd370a 100644 --- a/app/http/controllers/plugins/mysql57/mysql57_controller.go +++ b/app/http/controllers/plugins/mysql57/mysql57_controller.go @@ -378,7 +378,26 @@ func (c *Mysql57Controller) DatabaseList(ctx http.Context) { return } - controllers.Success(ctx, databases) + page := ctx.Request().QueryInt("page", 1) + limit := ctx.Request().QueryInt("limit", 10) + startIndex := (page - 1) * limit + endIndex := page * limit + if startIndex > len(databases) { + controllers.Success(ctx, http.Json{ + "total": 0, + "items": []database{}, + }) + return + } + if endIndex > len(databases) { + endIndex = len(databases) + } + pagedDatabases := databases[startIndex:endIndex] + + controllers.Success(ctx, http.Json{ + "total": len(databases), + "items": pagedDatabases, + }) } // AddDatabase 添加数据库 @@ -626,7 +645,26 @@ func (c *Mysql57Controller) UserList(ctx http.Context) { return } - controllers.Success(ctx, userGrants) + page := ctx.Request().QueryInt("page", 1) + limit := ctx.Request().QueryInt("limit", 10) + startIndex := (page - 1) * limit + endIndex := page * limit + if startIndex > len(userGrants) { + controllers.Success(ctx, http.Json{ + "total": 0, + "items": []user{}, + }) + return + } + if endIndex > len(userGrants) { + endIndex = len(userGrants) + } + pagedUserGrants := userGrants[startIndex:endIndex] + + controllers.Success(ctx, http.Json{ + "total": len(userGrants), + "items": pagedUserGrants, + }) } // AddUser 添加用户 diff --git a/app/http/controllers/plugins/mysql80/mysql80_controller.go b/app/http/controllers/plugins/mysql80/mysql80_controller.go index 3d3e1f2f..a44c5fe3 100644 --- a/app/http/controllers/plugins/mysql80/mysql80_controller.go +++ b/app/http/controllers/plugins/mysql80/mysql80_controller.go @@ -378,7 +378,26 @@ func (c *Mysql80Controller) DatabaseList(ctx http.Context) { return } - controllers.Success(ctx, databases) + page := ctx.Request().QueryInt("page", 1) + limit := ctx.Request().QueryInt("limit", 10) + startIndex := (page - 1) * limit + endIndex := page * limit + if startIndex > len(databases) { + controllers.Success(ctx, http.Json{ + "total": 0, + "items": []database{}, + }) + return + } + if endIndex > len(databases) { + endIndex = len(databases) + } + pagedDatabases := databases[startIndex:endIndex] + + controllers.Success(ctx, http.Json{ + "total": len(databases), + "items": pagedDatabases, + }) } // AddDatabase 添加数据库 @@ -626,7 +645,26 @@ func (c *Mysql80Controller) UserList(ctx http.Context) { return } - controllers.Success(ctx, userGrants) + page := ctx.Request().QueryInt("page", 1) + limit := ctx.Request().QueryInt("limit", 10) + startIndex := (page - 1) * limit + endIndex := page * limit + if startIndex > len(userGrants) { + controllers.Success(ctx, http.Json{ + "total": 0, + "items": []user{}, + }) + return + } + if endIndex > len(userGrants) { + endIndex = len(userGrants) + } + pagedUserGrants := userGrants[startIndex:endIndex] + + controllers.Success(ctx, http.Json{ + "total": len(userGrants), + "items": pagedUserGrants, + }) } // AddUser 添加用户 diff --git a/app/http/controllers/plugins/pureftpd/pureftpd_controller.go b/app/http/controllers/plugins/pureftpd/pureftpd_controller.go new file mode 100644 index 00000000..d4027287 --- /dev/null +++ b/app/http/controllers/plugins/pureftpd/pureftpd_controller.go @@ -0,0 +1,313 @@ +package pureftpd + +import ( + "regexp" + "strings" + + "github.com/goravel/framework/contracts/http" + "panel/app/http/controllers" + "panel/pkg/tools" +) + +type PureFtpdController struct { +} + +type User struct { + Username string `json:"username"` + Path string `json:"path"` +} + +func NewPureFtpdController() *PureFtpdController { + return &PureFtpdController{} +} + +// Status 获取运行状态 +func (c *PureFtpdController) Status(ctx http.Context) { + if !controllers.Check(ctx, "pureftpd") { + return + } + + status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Reload 重载配置 +func (c *PureFtpdController) Reload(ctx http.Context) { + if !controllers.Check(ctx, "pureftpd") { + return + } + + tools.ExecShell("systemctl reload pure-ftpd") + status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Restart 重启服务 +func (c *PureFtpdController) Restart(ctx http.Context) { + if !controllers.Check(ctx, "pureftpd") { + return + } + + tools.ExecShell("systemctl restart pure-ftpd") + status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Start 启动服务 +func (c *PureFtpdController) Start(ctx http.Context) { + if !controllers.Check(ctx, "pureftpd") { + return + } + + tools.ExecShell("systemctl start pure-ftpd") + status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Stop 停止服务 +func (c *PureFtpdController) Stop(ctx http.Context) { + if !controllers.Check(ctx, "pureftpd") { + return + } + + tools.ExecShell("systemctl stop pure-ftpd") + status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败") + return + } + + if status != "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// List 获取用户列表 +func (c *PureFtpdController) List(ctx http.Context) { + if !controllers.Check(ctx, "pureftpd") { + return + } + + listRaw := tools.ExecShell("pure-pw list") + if len(listRaw) == 0 { + controllers.Success(ctx, http.Json{ + "total": 0, + "items": []User{}, + }) + return + } + + listArr := strings.Split(listRaw, "\n") + var users []User + for _, v := range listArr { + if len(v) == 0 { + continue + } + + match := regexp.MustCompile(`(\S+)\s+(\S+)`).FindStringSubmatch(v) + users = append(users, User{ + Username: match[1], + Path: strings.Replace(match[2], "/./", "/", 1), + }) + } + + page := ctx.Request().QueryInt("page", 1) + limit := ctx.Request().QueryInt("limit", 10) + startIndex := (page - 1) * limit + endIndex := page * limit + if startIndex > len(users) { + controllers.Success(ctx, http.Json{ + "total": 0, + "items": []User{}, + }) + return + } + if endIndex > len(users) { + endIndex = len(users) + } + pagedUsers := users[startIndex:endIndex] + + controllers.Success(ctx, http.Json{ + "total": len(users), + "items": pagedUsers, + }) +} + +// Add 添加用户 +func (c *PureFtpdController) Add(ctx http.Context) { + if !controllers.Check(ctx, "pureftpd") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "username": "required", + "password": "required|min_len:6", + "path": "required", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + return + } + + username := ctx.Request().Input("username") + password := ctx.Request().Input("password") + path := ctx.Request().Input("path") + + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + if !tools.Exists(path) { + controllers.Error(ctx, http.StatusBadRequest, "目录不存在") + return + } + + tools.Chmod(path, 755) + tools.Chown(path, "www", "www") + tools.ExecShell(`yes '` + password + `' | pure-pw useradd ` + username + ` -u www -g www -d ` + path) + tools.ExecShell("pure-pw mkdb") + + controllers.Success(ctx, nil) +} + +// Delete 删除用户 +func (c *PureFtpdController) Delete(ctx http.Context) { + if !controllers.Check(ctx, "pureftpd") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "username": "required", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + return + } + + username := ctx.Request().Input("username") + + tools.ExecShell("pure-pw userdel " + username + " -m") + tools.ExecShell("pure-pw mkdb") + + controllers.Success(ctx, nil) +} + +// ChangePassword 修改密码 +func (c *PureFtpdController) ChangePassword(ctx http.Context) { + if !controllers.Check(ctx, "pureftpd") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "username": "required", + "password": "required|min_len:6", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + return + } + + username := ctx.Request().Input("username") + password := ctx.Request().Input("password") + + tools.ExecShell(`yes '` + password + `' | pure-pw passwd ` + username + ` -m`) + tools.ExecShell("pure-pw mkdb") + + controllers.Success(ctx, nil) +} + +// GetPort 获取端口 +func (c *PureFtpdController) GetPort(ctx http.Context) { + if !controllers.Check(ctx, "pureftpd") { + return + } + + port := tools.ExecShell(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`) + if len(port) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd端口失败") + return + } + + controllers.Success(ctx, port) +} + +// SetPort 设置端口 +func (c *PureFtpdController) SetPort(ctx http.Context) { + if !controllers.Check(ctx, "pureftpd") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "port": "required", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + return + } + + port := ctx.Request().Input("port") + tools.ExecShell(`sed -i "s/Bind.*/Bind 0.0.0.0,` + port + `/g" /www/server/pure-ftpd/etc/pure-ftpd.conf`) + if tools.IsRHEL() { + tools.ExecShell("firewall-cmd --zone=public --add-port=" + port + "/tcp --permanent") + tools.ExecShell("firewall-cmd --reload") + } else { + tools.ExecShell("ufw allow " + port + "/tcp") + tools.ExecShell("ufw reload") + } + tools.ExecShell("systemctl restart pure-ftpd") + + controllers.Success(ctx, nil) +} diff --git a/app/http/controllers/safe_controller.go b/app/http/controllers/safe_controller.go index 4a772e85..c11c8d52 100644 --- a/app/http/controllers/safe_controller.go +++ b/app/http/controllers/safe_controller.go @@ -48,6 +48,8 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) { Success(ctx, nil) return } + page := ctx.Request().QueryInt("page", 1) + limit := ctx.Request().QueryInt("limit", 10) if tools.IsRHEL() { out := tools.ExecShell("firewall-cmd --list-all 2>&1") @@ -66,7 +68,24 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) { }) } - Success(ctx, rules) + startIndex := (page - 1) * limit + endIndex := page * limit + if startIndex > len(rules) { + Success(ctx, http.Json{ + "total": 0, + "items": []map[string]string{}, + }) + return + } + if endIndex > len(rules) { + endIndex = len(rules) + } + pagedRules := rules[startIndex:endIndex] + + Success(ctx, http.Json{ + "total": len(rules), + "items": pagedRules, + }) } else { out := tools.ExecShell("ufw status | grep -v '(v6)' | grep ALLOW | awk '{print $1}'") if len(out) == 0 { @@ -82,7 +101,24 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) { }) } - Success(ctx, rules) + startIndex := (page - 1) * limit + endIndex := page * limit + if startIndex > len(rules) { + Success(ctx, http.Json{ + "total": 0, + "items": []map[string]string{}, + }) + return + } + if endIndex > len(rules) { + endIndex = len(rules) + } + pagedRules := rules[startIndex:endIndex] + + Success(ctx, http.Json{ + "total": len(rules), + "items": pagedRules, + }) } } diff --git a/app/plugins/pureftpd/pureftpd.go b/app/plugins/pureftpd/pureftpd.go new file mode 100644 index 00000000..614e59c6 --- /dev/null +++ b/app/plugins/pureftpd/pureftpd.go @@ -0,0 +1,13 @@ +package pureftpd + +var ( + Name = "Pure-FTPd" + Description = "Pure-Ftpd 是一个快速、高效、轻便、安全的 FTP 服务器,它以安全和配置简单为设计目标,支持虚拟主机,IPV6,PAM 等功能。" + Slug = "pureftpd" + Version = "1.0.50" + Requires = []string{} + Excludes = []string{} + Install = `bash /www/panel/scripts/pureftpd/install.sh` + Uninstall = `bash /www/panel/scripts/pureftpd/uninstall.sh` + Update = `bash /www/panel/scripts/pureftpd/update.sh` +) diff --git a/app/services/plugin.go b/app/services/plugin.go index d88f634b..7e2522a2 100644 --- a/app/services/plugin.go +++ b/app/services/plugin.go @@ -14,6 +14,7 @@ import ( "panel/app/plugins/php81" "panel/app/plugins/php82" "panel/app/plugins/phpmyadmin" + "panel/app/plugins/pureftpd" "panel/app/plugins/s3fs" "panel/app/plugins/supervisor" ) @@ -147,6 +148,17 @@ func (r *PluginImpl) All() []PanelPlugin { Uninstall: phpmyadmin.Uninstall, Update: phpmyadmin.Update, }) + p = append(p, PanelPlugin{ + Name: pureftpd.Name, + Description: pureftpd.Description, + Slug: pureftpd.Slug, + Version: pureftpd.Version, + Requires: pureftpd.Requires, + Excludes: pureftpd.Excludes, + Install: pureftpd.Install, + Uninstall: pureftpd.Uninstall, + Update: pureftpd.Update, + }) p = append(p, PanelPlugin{ Name: s3fs.Name, Description: s3fs.Description, diff --git a/config/app.go b/config/app.go index 7cbe5bbe..44f60890 100644 --- a/config/app.go +++ b/config/app.go @@ -51,7 +51,7 @@ func init() { // Here you may specify the default timezone for your application. // Example: UTC, Asia/Shanghai // More: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - "timezone": carbon.UTC, + "timezone": carbon.PRC, // Encryption Key // diff --git a/public/panel/views/plugins/mysql57.html b/public/panel/views/plugins/mysql57.html index 29d377d9..7a9f0d9d 100644 --- a/public/panel/views/plugins/mysql57.html +++ b/public/panel/views/plugins/mysql57.html @@ -203,6 +203,15 @@ Date: 2022-07-22 {field: 'name', title: '库名', fixed: 'left', unresize: true, sort: true} , {fixed: 'right', title: '操作', toolbar: '#mysql-database-list-control', width: 150} ]] + , parseData: function (res) { + return { + "code": res.code, + "msg": res.message, + "count": res.data.total, + "data": res.data.items + }; + } + , page: true }); // 头工具栏事件 table.on('toolbar(mysql-database-list)', function (obj) { @@ -272,6 +281,15 @@ Date: 2022-07-22 , {field: 'grants', title: '权限'} , {fixed: 'right', title: '操作', toolbar: '#mysql-user-list-control', width: 150} ]] + , parseData: function (res) { + return { + "code": res.code, + "msg": res.message, + "count": res.data.total, + "data": res.data.items + }; + } + , page: true }); // 头工具栏事件 table.on('toolbar(mysql-user-list)', function (obj) { diff --git a/public/panel/views/plugins/mysql80.html b/public/panel/views/plugins/mysql80.html index d76b39ee..2c1134ce 100644 --- a/public/panel/views/plugins/mysql80.html +++ b/public/panel/views/plugins/mysql80.html @@ -203,6 +203,15 @@ Date: 2022-07-22 {field: 'name', title: '库名', fixed: 'left', unresize: true, sort: true} , {fixed: 'right', title: '操作', toolbar: '#mysql-database-list-control', width: 150} ]] + , parseData: function (res) { + return { + "code": res.code, + "msg": res.message, + "count": res.data.total, + "data": res.data.items + }; + } + , page: true }); // 头工具栏事件 table.on('toolbar(mysql-database-list)', function (obj) { @@ -272,6 +281,15 @@ Date: 2022-07-22 , {field: 'grants', title: '权限'} , {fixed: 'right', title: '操作', toolbar: '#mysql-user-list-control', width: 150} ]] + , parseData: function (res) { + return { + "code": res.code, + "msg": res.message, + "count": res.data.total, + "data": res.data.items + }; + } + , page: true }); // 头工具栏事件 table.on('toolbar(mysql-user-list)', function (obj) { diff --git a/public/panel/views/plugins/pureftpd.html b/public/panel/views/plugins/pureftpd.html new file mode 100644 index 00000000..1b61f092 --- /dev/null +++ b/public/panel/views/plugins/pureftpd.html @@ -0,0 +1,305 @@ + +
当前状态:获取中+