2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 12:40:25 +08:00

feat: update v2.0.10

This commit is contained in:
耗子
2023-07-22 16:44:41 +08:00
parent 1689ef4e4a
commit a5e1fbc232
34 changed files with 3116 additions and 231 deletions

View File

@@ -4,11 +4,10 @@ import (
"os"
"github.com/gookit/color"
"github.com/spf13/cast"
"github.com/goravel/framework/contracts/console"
"github.com/goravel/framework/contracts/console/command"
"github.com/goravel/framework/facades"
"github.com/spf13/cast"
"panel/app/models"
"panel/app/services"
@@ -73,12 +72,19 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Greenln("初始化成功")
case "update":
var task models.Task
err := facades.Orm().Query().Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).FirstOrFail(&task)
if err == nil {
color.Redln("当前有任务正在执行,禁止更新")
return nil
}
input := arg1
proxy := false
if input == "y" || input == "Y" || input == "yes" || input == "Yes" {
proxy = true
}
err := tools.UpdatePanel(cast.ToBool(proxy))
err = tools.UpdatePanel(cast.ToBool(proxy))
if err != nil {
color.Redln("更新失败: " + err.Error())
return nil

View File

@@ -53,7 +53,7 @@ func (r *CronController) Add(ctx http.Context) {
return
}
if validator.Fails() {
Error(ctx, http.StatusBadRequest, validator.Errors().All())
Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -129,7 +129,7 @@ func (r *CronController) Update(ctx http.Context) {
return
}
if validator.Fails() {
Error(ctx, http.StatusBadRequest, validator.Errors().All())
Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}

View File

@@ -163,8 +163,15 @@ func (c *InfoController) CheckUpdate(ctx http.Context) {
}
func (c *InfoController) Update(ctx http.Context) {
var task models.Task
err := facades.Orm().Query().Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).FirstOrFail(&task)
if err == nil {
Error(ctx, http.StatusInternalServerError, "当前有任务正在执行,禁止更新")
return
}
proxy := ctx.Request().InputBool("proxy")
err := tools.UpdatePanel(proxy)
err = tools.UpdatePanel(proxy)
if err != nil {
facades.Log().Error("[面板][InfoController] 更新面板失败 ", err.Error())
Error(ctx, http.StatusInternalServerError, "更新失败: "+err.Error())

View File

@@ -1,6 +1,7 @@
package mysql57
import (
"database/sql"
"fmt"
"os"
"path/filepath"
@@ -11,8 +12,6 @@ import (
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/carbon"
"github.com/spf13/cast"
"golang.org/x/exp/slices"
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
@@ -35,7 +34,7 @@ func (c *Mysql57Controller) Status(ctx http.Context) {
return
}
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -54,8 +53,8 @@ func (c *Mysql57Controller) Reload(ctx http.Context) {
return
}
tools.ExecShell("systemctl reload mysql")
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
tools.ExecShell("systemctl reload mysqld")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -74,8 +73,8 @@ func (c *Mysql57Controller) Restart(ctx http.Context) {
return
}
tools.ExecShell("systemctl restart mysql")
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
tools.ExecShell("systemctl restart mysqld")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -94,8 +93,8 @@ func (c *Mysql57Controller) Start(ctx http.Context) {
return
}
tools.ExecShell("systemctl start mysql")
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
tools.ExecShell("systemctl start mysqld")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -114,8 +113,8 @@ func (c *Mysql57Controller) Stop(ctx http.Context) {
return
}
tools.ExecShell("systemctl stop mysql")
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
tools.ExecShell("systemctl stop mysqld")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -135,7 +134,7 @@ func (c *Mysql57Controller) GetConfig(ctx http.Context) {
}
// 获取配置
config := tools.ReadFile("mysql57")
config := tools.ReadFile("/www/server/mysql/conf/my.cnf")
if len(config) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
return
@@ -156,7 +155,7 @@ func (c *Mysql57Controller) SaveConfig(ctx http.Context) {
return
}
if !tools.WriteFile("mysql57", config, 0644) {
if !tools.WriteFile("/www/server/mysql/conf/my.cnf", config, 0644) {
controllers.Error(ctx, http.StatusInternalServerError, "写入MySQL配置失败")
return
}
@@ -197,24 +196,24 @@ func (c *Mysql57Controller) Load(ctx http.Context) {
regex string
name string
}{
{`Uptime\s+\|\s+(\d+)\s+\|`, "总查询次数"},
{`Queries\s+\|\s+(\d+)\s+\|`, "总连接次数"},
{`Connections\s+\|\s+(\d+)\s+\|`, "每秒事务"},
{`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒回滚"},
{`Com_rollback\s+\|\s+(\d+)\s+\|`, "发送"},
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, "接收"},
{`Bytes_received\s+\|\s+(\d+)\s+\|`, "活动连接数"},
{`Threads_connected\s+\|\s+(\d+)\s+\|`, "峰值连接数"},
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, "索引命中率"},
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"},
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "创建临时表到磁盘"},
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"},
{`Open_tables\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"},
{`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"},
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"},
{`Select_range_check\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"},
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "锁表次数"},
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, ""},
{`Uptime\s+\|\s+(\d+)\s+\|`, "运行时间"},
{`Queries\s+\|\s+(\d+)\s+\|`, "总查询次数"},
{`Connections\s+\|\s+(\d+)\s+\|`, "总连接次数"},
{`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒事务"},
{`Com_rollback\s+\|\s+(\d+)\s+\|`, "每秒回滚"},
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, "发送"},
{`Bytes_received\s+\|\s+(\d+)\s+\|`, "接收"},
{`Threads_connected\s+\|\s+(\d+)\s+\|`, "活动连接数"},
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, "峰值连接数"},
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, "索引命中率"},
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"},
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "创建临时表到磁盘"},
{`Open_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"},
{`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"},
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"},
{`Select_range_check\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"},
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"},
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, "锁表次数"},
}
for i, expression := range expressions {
@@ -234,7 +233,7 @@ func (c *Mysql57Controller) Load(ctx http.Context) {
readRequests := cast.ToFloat64(data[9]["value"])
reads := cast.ToFloat64(data[10]["value"])
data[9]["value"] = fmt.Sprintf("%.2f%%", readRequests/(reads+readRequests)*100)
// Innodb索引命中率
// Innodb 索引命中率
bufferPoolReads := cast.ToFloat64(data[11]["value"])
bufferPoolReadRequests := cast.ToFloat64(data[12]["value"])
data[10]["value"] = fmt.Sprintf("%.2f%%", bufferPoolReadRequests/(bufferPoolReads+bufferPoolReadRequests)*100)
@@ -303,7 +302,7 @@ func (c *Mysql57Controller) SetRootPassword(ctx http.Context) {
return
}
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -341,29 +340,45 @@ func (c *Mysql57Controller) DatabaseList(ctx http.Context) {
return
}
out := tools.ExecShell("mysql -uroot -p" + c.setting.Get(models.SettingKeyMysqlRootPassword) + " -e \"show databases;\"")
databases := strings.Split(out, "\n")
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
type database struct {
Name string `json:"name"`
}
databases = databases[1 : len(databases)-1]
systemDatabases := []string{"information_schema", "mysql", "performance_schema", "sys"}
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
if err != nil {
facades.Log().Error("[MySQL57] 连接数据库失败" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "连接数据库失败")
return
}
defer db.Close()
var userDatabases []string
for _, db := range databases {
if !slices.Contains(systemDatabases, db) {
userDatabases = append(userDatabases, db)
rows, err := db.Query("SHOW DATABASES")
if err != nil {
facades.Log().Error("[MySQL57] 获取数据库列表失败" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
return
}
defer rows.Close()
var databases []database
for rows.Next() {
var d database
err := rows.Scan(&d.Name)
if err != nil {
continue
}
databases = append(databases, d)
}
type Database struct {
Name string
if err := rows.Err(); err != nil {
facades.Log().Error("[MySQL57] 获取数据库列表失败" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
return
}
var dbStructs []Database
for _, db := range userDatabases {
dbStructs = append(dbStructs, Database{Name: db})
}
controllers.Success(ctx, dbStructs)
controllers.Success(ctx, databases)
}
// AddDatabase 添加数据库
@@ -375,14 +390,14 @@ func (c *Mysql57Controller) AddDatabase(ctx http.Context) {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255|regex:^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*(_|[^\\w])).+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -413,7 +428,7 @@ func (c *Mysql57Controller) DeleteDatabase(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -458,6 +473,38 @@ func (c *Mysql57Controller) BackupList(ctx http.Context) {
controllers.Success(ctx, backupFiles)
}
// UploadBackup 上传备份
func (c *Mysql57Controller) UploadBackup(ctx http.Context) {
if !controllers.Check(ctx, "mysql57") {
return
}
// TODO 框架 Bug ? 下面会 panic
controllers.Error(ctx, http.StatusBadRequest, "暂不支持")
return
file, err := ctx.Request().File("file")
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, "上传文件失败")
return
}
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
name := file.GetClientOriginalName()
extension := file.GetClientOriginalExtension()
_, err = file.Store(backupPath + "/" + name + "." + extension)
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, "上传文件失败")
return
}
controllers.Success(ctx, "上传文件成功")
}
// CreateBackup 创建备份
func (c *Mysql57Controller) CreateBackup(ctx http.Context) {
if !controllers.Check(ctx, "mysql57") {
@@ -472,14 +519,14 @@ func (c *Mysql57Controller) CreateBackup(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql"
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
backupFile := backupPath + "/" + database + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
backupFile := database + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
@@ -490,9 +537,9 @@ func (c *Mysql57Controller) CreateBackup(ctx http.Context) {
return
}
tools.ExecShell("mysqldump -uroot " + database + " > " + backupFile)
tools.ExecShell("zip -c " + backupFile + ".zip " + backupFile)
tools.RemoveFile(backupFile)
tools.ExecShell("mysqldump -uroot " + database + " > " + backupPath + "/" + backupFile)
tools.ExecShell("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile)
tools.RemoveFile(backupPath + "/" + backupFile)
_ = os.Unsetenv("MYSQL_PWD")
controllers.Success(ctx, "备份成功")
@@ -512,7 +559,7 @@ func (c *Mysql57Controller) DeleteBackup(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -538,7 +585,7 @@ func (c *Mysql57Controller) RestoreBackup(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -602,36 +649,66 @@ func (c *Mysql57Controller) UserList(ctx http.Context) {
return
}
type User struct {
Username string `json:"username"`
Host string `json:"host"`
Privileges string `json:"privileges"`
type user struct {
User string `json:"user"`
Host string `json:"host"`
Grants []string `json:"grants"`
}
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
out := tools.ExecShell("mysql -uroot -p" + rootPassword + " -e 'select user,host from mysql.user'")
rawUsers := strings.Split(out, "\n")
users := make([]User, 0)
for _, rawUser := range rawUsers {
user := strings.Split(rawUser, "\t")
if user[0] == "root" || user[0] == "mysql.sys" || user[0] == "mysql.infoschema" || user[0] == "mysql.session" {
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
if err != nil {
facades.Log().Error("[MYSQL57] 连接数据库失败:" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "连接数据库失败")
}
defer db.Close()
rows, err := db.Query("SELECT user, host FROM mysql.user")
if err != nil {
facades.Log().Error("[MYSQL57] 查询数据库失败:" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "查询数据库失败")
}
defer rows.Close()
var userGrants []user
for rows.Next() {
var u user
err := rows.Scan(&u.User, &u.Host)
if err != nil {
continue
}
out := tools.ExecShell("mysql -uroot -p" + rootPassword + " -e 'show grants for " + user[0] + "@" + user[1] + "'")
rawPrivileges := strings.Split(out, "\n")
privileges := make([]string, 0)
for _, rawPrivilege := range rawPrivileges {
if rawPrivilege == "" {
// 查询用户权限
grantsRows, err := db.Query(fmt.Sprintf("SHOW GRANTS FOR '%s'@'%s'", u.User, u.Host))
if err != nil {
continue
}
defer grantsRows.Close()
for grantsRows.Next() {
var grant string
err := grantsRows.Scan(&grant)
if err != nil {
continue
}
privilege := rawPrivilege[6:strings.Index(rawPrivilege, " TO")]
privileges = append(privileges, privilege)
u.Grants = append(u.Grants, grant)
}
users = append(users, User{Username: user[0], Host: user[1], Privileges: strings.Join(privileges, " | ")})
if err := grantsRows.Err(); err != nil {
continue
}
userGrants = append(userGrants, u)
}
controllers.Success(ctx, users)
if err := rows.Err(); err != nil {
controllers.Error(ctx, http.StatusInternalServerError, "获取用户列表失败")
return
}
controllers.Success(ctx, userGrants)
}
// AddUser 添加用户
@@ -643,14 +720,14 @@ func (c *Mysql57Controller) AddUser(ctx http.Context) {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255|regex:^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*(_|[^\\w])).+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -679,7 +756,7 @@ func (c *Mysql57Controller) DeleteUser(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -698,14 +775,14 @@ func (c *Mysql57Controller) SetUserPassword(ctx http.Context) {
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255|regex:^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*(_|[^\\w])).+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -733,7 +810,7 @@ func (c *Mysql57Controller) SetUserPrivileges(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}

View File

@@ -1,6 +1,7 @@
package mysql80
import (
"database/sql"
"fmt"
"os"
"path/filepath"
@@ -11,8 +12,6 @@ import (
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/carbon"
"github.com/spf13/cast"
"golang.org/x/exp/slices"
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
@@ -35,7 +34,7 @@ func (c *Mysql80Controller) Status(ctx http.Context) {
return
}
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -54,8 +53,8 @@ func (c *Mysql80Controller) Reload(ctx http.Context) {
return
}
tools.ExecShell("systemctl reload mysql")
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
tools.ExecShell("systemctl reload mysqld")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -74,8 +73,8 @@ func (c *Mysql80Controller) Restart(ctx http.Context) {
return
}
tools.ExecShell("systemctl restart mysql")
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
tools.ExecShell("systemctl restart mysqld")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -94,8 +93,8 @@ func (c *Mysql80Controller) Start(ctx http.Context) {
return
}
tools.ExecShell("systemctl start mysql")
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
tools.ExecShell("systemctl start mysqld")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -114,8 +113,8 @@ func (c *Mysql80Controller) Stop(ctx http.Context) {
return
}
tools.ExecShell("systemctl stop mysql")
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
tools.ExecShell("systemctl stop mysqld")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -135,7 +134,7 @@ func (c *Mysql80Controller) GetConfig(ctx http.Context) {
}
// 获取配置
config := tools.ReadFile("mysql80")
config := tools.ReadFile("/www/server/mysql/conf/my.cnf")
if len(config) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
return
@@ -156,7 +155,7 @@ func (c *Mysql80Controller) SaveConfig(ctx http.Context) {
return
}
if !tools.WriteFile("mysql80", config, 0644) {
if !tools.WriteFile("/www/server/mysql/conf/my.cnf", config, 0644) {
controllers.Error(ctx, http.StatusInternalServerError, "写入MySQL配置失败")
return
}
@@ -197,24 +196,24 @@ func (c *Mysql80Controller) Load(ctx http.Context) {
regex string
name string
}{
{`Uptime\s+\|\s+(\d+)\s+\|`, "总查询次数"},
{`Queries\s+\|\s+(\d+)\s+\|`, "总连接次数"},
{`Connections\s+\|\s+(\d+)\s+\|`, "每秒事务"},
{`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒回滚"},
{`Com_rollback\s+\|\s+(\d+)\s+\|`, "发送"},
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, "接收"},
{`Bytes_received\s+\|\s+(\d+)\s+\|`, "活动连接数"},
{`Threads_connected\s+\|\s+(\d+)\s+\|`, "峰值连接数"},
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, "索引命中率"},
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"},
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "创建临时表到磁盘"},
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"},
{`Open_tables\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"},
{`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"},
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"},
{`Select_range_check\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"},
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "锁表次数"},
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, ""},
{`Uptime\s+\|\s+(\d+)\s+\|`, "运行时间"},
{`Queries\s+\|\s+(\d+)\s+\|`, "总查询次数"},
{`Connections\s+\|\s+(\d+)\s+\|`, "总连接次数"},
{`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒事务"},
{`Com_rollback\s+\|\s+(\d+)\s+\|`, "每秒回滚"},
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, "发送"},
{`Bytes_received\s+\|\s+(\d+)\s+\|`, "接收"},
{`Threads_connected\s+\|\s+(\d+)\s+\|`, "活动连接数"},
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, "峰值连接数"},
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, "索引命中率"},
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"},
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "创建临时表到磁盘"},
{`Open_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"},
{`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"},
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"},
{`Select_range_check\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"},
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"},
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, "锁表次数"},
}
for i, expression := range expressions {
@@ -234,7 +233,7 @@ func (c *Mysql80Controller) Load(ctx http.Context) {
readRequests := cast.ToFloat64(data[9]["value"])
reads := cast.ToFloat64(data[10]["value"])
data[9]["value"] = fmt.Sprintf("%.2f%%", readRequests/(reads+readRequests)*100)
// Innodb索引命中率
// Innodb 索引命中率
bufferPoolReads := cast.ToFloat64(data[11]["value"])
bufferPoolReadRequests := cast.ToFloat64(data[12]["value"])
data[10]["value"] = fmt.Sprintf("%.2f%%", bufferPoolReadRequests/(bufferPoolReads+bufferPoolReadRequests)*100)
@@ -303,7 +302,7 @@ func (c *Mysql80Controller) SetRootPassword(ctx http.Context) {
return
}
status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
return
@@ -341,29 +340,45 @@ func (c *Mysql80Controller) DatabaseList(ctx http.Context) {
return
}
out := tools.ExecShell("mysql -uroot -p" + c.setting.Get(models.SettingKeyMysqlRootPassword) + " -e \"show databases;\"")
databases := strings.Split(out, "\n")
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
type database struct {
Name string `json:"name"`
}
databases = databases[1 : len(databases)-1]
systemDatabases := []string{"information_schema", "mysql", "performance_schema", "sys"}
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
if err != nil {
facades.Log().Error("[MySQL80] 连接数据库失败" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "连接数据库失败")
return
}
defer db.Close()
var userDatabases []string
for _, db := range databases {
if !slices.Contains(systemDatabases, db) {
userDatabases = append(userDatabases, db)
rows, err := db.Query("SHOW DATABASES")
if err != nil {
facades.Log().Error("[MySQL80] 获取数据库列表失败" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
return
}
defer rows.Close()
var databases []database
for rows.Next() {
var d database
err := rows.Scan(&d.Name)
if err != nil {
continue
}
databases = append(databases, d)
}
type Database struct {
Name string
if err := rows.Err(); err != nil {
facades.Log().Error("[MySQL80] 获取数据库列表失败" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
return
}
var dbStructs []Database
for _, db := range userDatabases {
dbStructs = append(dbStructs, Database{Name: db})
}
controllers.Success(ctx, dbStructs)
controllers.Success(ctx, databases)
}
// AddDatabase 添加数据库
@@ -375,14 +390,14 @@ func (c *Mysql80Controller) AddDatabase(ctx http.Context) {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255|regex:^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*(_|[^\\w])).+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -413,7 +428,7 @@ func (c *Mysql80Controller) DeleteDatabase(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -458,6 +473,38 @@ func (c *Mysql80Controller) BackupList(ctx http.Context) {
controllers.Success(ctx, backupFiles)
}
// UploadBackup 上传备份
func (c *Mysql80Controller) UploadBackup(ctx http.Context) {
if !controllers.Check(ctx, "mysql80") {
return
}
// TODO 框架 Bug ? 下面会 panic
controllers.Error(ctx, http.StatusBadRequest, "暂不支持")
return
file, err := ctx.Request().File("file")
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, "上传文件失败")
return
}
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
name := file.GetClientOriginalName()
extension := file.GetClientOriginalExtension()
_, err = file.Store(backupPath + "/" + name + "." + extension)
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, "上传文件失败")
return
}
controllers.Success(ctx, "上传文件成功")
}
// CreateBackup 创建备份
func (c *Mysql80Controller) CreateBackup(ctx http.Context) {
if !controllers.Check(ctx, "mysql80") {
@@ -472,14 +519,14 @@ func (c *Mysql80Controller) CreateBackup(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql"
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
backupFile := backupPath + "/" + database + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
backupFile := database + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
@@ -490,9 +537,9 @@ func (c *Mysql80Controller) CreateBackup(ctx http.Context) {
return
}
tools.ExecShell("mysqldump -uroot " + database + " > " + backupFile)
tools.ExecShell("zip -c " + backupFile + ".zip " + backupFile)
tools.RemoveFile(backupFile)
tools.ExecShell("mysqldump -uroot " + database + " > " + backupPath + "/" + backupFile)
tools.ExecShell("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile)
tools.RemoveFile(backupPath + "/" + backupFile)
_ = os.Unsetenv("MYSQL_PWD")
controllers.Success(ctx, "备份成功")
@@ -512,7 +559,7 @@ func (c *Mysql80Controller) DeleteBackup(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -538,7 +585,7 @@ func (c *Mysql80Controller) RestoreBackup(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -553,7 +600,7 @@ func (c *Mysql80Controller) RestoreBackup(ctx http.Context) {
err = os.Setenv("MYSQL_PWD", rootPassword)
if err != nil {
facades.Log().Error("[MySQL80] 设置环境变量 MYSQL_PWD 失败:" + err.Error())
facades.Log().Error("[MYSQL80] 设置环境变量 MYSQL_PWD 失败:" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "还原失败")
return
}
@@ -602,36 +649,66 @@ func (c *Mysql80Controller) UserList(ctx http.Context) {
return
}
type User struct {
Username string `json:"username"`
Host string `json:"host"`
Privileges string `json:"privileges"`
type user struct {
User string `json:"user"`
Host string `json:"host"`
Grants []string `json:"grants"`
}
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
out := tools.ExecShell("mysql -uroot -p" + rootPassword + " -e 'select user,host from mysql.user'")
rawUsers := strings.Split(out, "\n")
users := make([]User, 0)
for _, rawUser := range rawUsers {
user := strings.Split(rawUser, "\t")
if user[0] == "root" || user[0] == "mysql.sys" || user[0] == "mysql.infoschema" || user[0] == "mysql.session" {
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
if err != nil {
facades.Log().Error("[MYSQL80] 连接数据库失败:" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "连接数据库失败")
}
defer db.Close()
rows, err := db.Query("SELECT user, host FROM mysql.user")
if err != nil {
facades.Log().Error("[MYSQL80] 查询数据库失败:" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "查询数据库失败")
}
defer rows.Close()
var userGrants []user
for rows.Next() {
var u user
err := rows.Scan(&u.User, &u.Host)
if err != nil {
continue
}
out := tools.ExecShell("mysql -uroot -p" + rootPassword + " -e 'show grants for " + user[0] + "@" + user[1] + "'")
rawPrivileges := strings.Split(out, "\n")
privileges := make([]string, 0)
for _, rawPrivilege := range rawPrivileges {
if rawPrivilege == "" {
// 查询用户权限
grantsRows, err := db.Query(fmt.Sprintf("SHOW GRANTS FOR '%s'@'%s'", u.User, u.Host))
if err != nil {
continue
}
defer grantsRows.Close()
for grantsRows.Next() {
var grant string
err := grantsRows.Scan(&grant)
if err != nil {
continue
}
privilege := rawPrivilege[6:strings.Index(rawPrivilege, " TO")]
privileges = append(privileges, privilege)
u.Grants = append(u.Grants, grant)
}
users = append(users, User{Username: user[0], Host: user[1], Privileges: strings.Join(privileges, " | ")})
if err := grantsRows.Err(); err != nil {
continue
}
userGrants = append(userGrants, u)
}
controllers.Success(ctx, users)
if err := rows.Err(); err != nil {
controllers.Error(ctx, http.StatusInternalServerError, "获取用户列表失败")
return
}
controllers.Success(ctx, userGrants)
}
// AddUser 添加用户
@@ -643,14 +720,14 @@ func (c *Mysql80Controller) AddUser(ctx http.Context) {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255|regex:^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*(_|[^\\w])).+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -679,7 +756,7 @@ func (c *Mysql80Controller) DeleteUser(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -698,14 +775,14 @@ func (c *Mysql80Controller) SetUserPassword(ctx http.Context) {
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255|regex:^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*(_|[^\\w])).+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -733,7 +810,7 @@ func (c *Mysql80Controller) SetUserPrivileges(ctx http.Context) {
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All())
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}

View File

@@ -154,7 +154,7 @@ func (c *Php74Controller) Load(ctx http.Context) {
}
client := req.C().SetTimeout(10 * time.Second)
resp, err := client.R().Get("http://127.0.0.1/phpfpm_" + c.version + "_status")
resp, err := client.R().Get("http://127.0.0.1/phpfpm_status/" + c.version)
if err != nil || !resp.IsSuccessState() {
facades.Log().Error("获取PHP-" + c.version + "运行状态失败")
controllers.Error(ctx, http.StatusInternalServerError, "[PHP-"+c.version+"] 获取运行状态失败")
@@ -258,7 +258,7 @@ func (c *Php74Controller) InstallExtension(ctx http.Context) {
var task models.Task
task.Name = "安装PHP-" + c.version + "扩展-" + item.Name
task.Status = models.TaskStatusWaiting
task.Shell = "bash /www/panel/scripts/php_extensions/" + item.Slug + ".sh install " + c.version + ">> /tmp/" + item.Slug + ".log 2>&1"
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' install ` + c.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + c.version + "] 创建安装拓展任务失败:" + err.Error())
@@ -298,7 +298,7 @@ func (c *Php74Controller) UninstallExtension(ctx http.Context) {
var task models.Task
task.Name = "卸载PHP-" + c.version + "扩展-" + item.Name
task.Status = models.TaskStatusWaiting
task.Shell = "bash /www/panel/scripts/php_extensions/" + item.Slug + ".sh uninstall " + c.version + ">> /tmp/" + item.Slug + ".log 2>&1"
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' uninstall ` + c.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + c.version + "] 创建卸载拓展任务失败:" + err.Error())
@@ -361,7 +361,7 @@ func (c *Php74Controller) GetExtensions() []Extension {
for _, item := range rawExtensionList {
if !strings.Contains(item, "[") && item != "" {
for i := range extensions {
if extensions[i].Name == item {
if extensions[i].Slug == item {
extensions[i].Installed = true
}
}

View File

@@ -154,7 +154,7 @@ func (c *Php80Controller) Load(ctx http.Context) {
}
client := req.C().SetTimeout(10 * time.Second)
resp, err := client.R().Get("http://127.0.0.1/phpfpm_" + c.version + "_status")
resp, err := client.R().Get("http://127.0.0.1/phpfpm_status/" + c.version)
if err != nil || !resp.IsSuccessState() {
facades.Log().Error("获取PHP-" + c.version + "运行状态失败")
controllers.Error(ctx, http.StatusInternalServerError, "[PHP-"+c.version+"] 获取运行状态失败")
@@ -258,7 +258,7 @@ func (c *Php80Controller) InstallExtension(ctx http.Context) {
var task models.Task
task.Name = "安装PHP-" + c.version + "扩展-" + item.Name
task.Status = models.TaskStatusWaiting
task.Shell = "bash /www/panel/scripts/php_extensions/" + item.Slug + ".sh install " + c.version + ">> /tmp/" + item.Slug + ".log 2>&1"
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' install ` + c.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + c.version + "] 创建安装拓展任务失败:" + err.Error())
@@ -298,7 +298,7 @@ func (c *Php80Controller) UninstallExtension(ctx http.Context) {
var task models.Task
task.Name = "卸载PHP-" + c.version + "扩展-" + item.Name
task.Status = models.TaskStatusWaiting
task.Shell = "bash /www/panel/scripts/php_extensions/" + item.Slug + ".sh uninstall " + c.version + ">> /tmp/" + item.Slug + ".log 2>&1"
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' uninstall ` + c.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + c.version + "] 创建卸载拓展任务失败:" + err.Error())
@@ -361,7 +361,7 @@ func (c *Php80Controller) GetExtensions() []Extension {
for _, item := range rawExtensionList {
if !strings.Contains(item, "[") && item != "" {
for i := range extensions {
if extensions[i].Name == item {
if extensions[i].Slug == item {
extensions[i].Installed = true
}
}

View File

@@ -64,7 +64,7 @@ func (c *WebsiteController) Add(ctx http.Context) {
return
}
if validator.Fails() {
Error(ctx, http.StatusBadRequest, validator.Errors().All())
Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
@@ -372,9 +372,8 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
website.Php = ctx.Request().InputInt("php")
phpConfigOld := tools.Cut(raw, "# php标记位开始", "# php标记位结束")
phpConfig := `
include enable-php` + strconv.Itoa(website.Php) + `.conf;
`
include enable-php-` + strconv.Itoa(website.Php) + `.conf;
`
if len(strings.TrimSpace(phpConfigOld)) != 0 {
raw = strings.Replace(raw, phpConfigOld, phpConfig, -1)
}

View File

@@ -24,7 +24,7 @@ func init() {
"disks": map[string]any{
"local": map[string]any{
"driver": "local",
"root": "/",
"root": "storage/app",
"url": "http://localhost/",
},
},

View File

@@ -8,6 +8,6 @@ func init() {
config := facades.Config()
config.Add("panel", map[string]any{
"name": "耗子面板",
"version": "v2.0.9",
"version": "v2.0.10",
})
}

4
go.mod
View File

@@ -5,8 +5,8 @@ go 1.18
require (
github.com/gertd/go-pluralize v0.2.1
github.com/gin-contrib/static v0.0.1
github.com/gookit/color v1.5.3
github.com/goravel/framework v1.12.1-0.20230717105343-6ce864794883
github.com/gookit/color v1.5.4
github.com/goravel/framework v1.12.1-0.20230721095426-6f24ecdaf6a7
github.com/iancoleman/strcase v0.2.0
github.com/imroc/req/v3 v3.37.2
github.com/mojocn/base64Captcha v1.3.5

13
go.sum
View File

@@ -85,6 +85,7 @@ github.com/aws/aws-sdk-go v1.37.16 h1:Q4YOP2s00NpB9wfmTDZArdcLRuG9ijbnoAwTW3ivle
github.com/aws/aws-sdk-go v1.37.16/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/brianvoe/gofakeit/v6 v6.23.0 h1:pgVhyWpYq4e0GEVCh2gdZnS/nBX+8SnyTBliHg5xjks=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
@@ -169,8 +170,6 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.8.0 h1:02X12E2I/4C1n+v90yTqrjRa8yuo7c3KeHI3FRznCvc=
github.com/glebarez/sqlite v1.8.0/go.mod h1:bpET16h1za2KOOMb8+jCp6UBP/iahDpfPQqSaYLTLx8=
github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs=
github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -345,8 +344,8 @@ github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK6
github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE=
github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gookit/filter v1.1.4 h1:SXd6PEumiP/0jtF2crQRaz1wmKwHbW9xg5Ds6/ZP16w=
github.com/gookit/filter v1.1.4/go.mod h1:0CEPQvudso375RitQf9X8HerUg9cz8N7c/yn6b1RMzM=
github.com/gookit/goutil v0.5.12/go.mod h1:6vhWm/bSYXGE8poqFbFz6IGM7jV2r6qVhyK567SX/AI=
@@ -358,10 +357,8 @@ github.com/goravel/file-rotatelogs v0.0.0-20211215053220-2ab31dd9575c h1:obhFK91
github.com/goravel/file-rotatelogs v0.0.0-20211215053220-2ab31dd9575c/go.mod h1:YSWsLXlG16u5CWFaXNZHhEQD10+NwF3xfgDV816OwLE=
github.com/goravel/file-rotatelogs/v2 v2.4.1 h1:ogkeIFcTHSBRUBpZYiyJbpul8hkVXxHPuDbOaP78O1M=
github.com/goravel/file-rotatelogs/v2 v2.4.1/go.mod h1:euk9qr52WrzM8ICs1hecFcR4CZ/ZZOPdacHfvHgbOf0=
github.com/goravel/framework v1.12.1-0.20230710102458-737cce0f687c h1:KPggCIxAZghopvPXC0g0jCmFNbC+r5EawjHCcbfBOhQ=
github.com/goravel/framework v1.12.1-0.20230710102458-737cce0f687c/go.mod h1:SBsBTY8KTqSDipGPEZXnRRC1mWzKmL55Eo1P5aHNDgY=
github.com/goravel/framework v1.12.1-0.20230717105343-6ce864794883 h1:8+87CCYqt5O6GqAYmGWz/W6SXGKp6xyn+McMbtWgyug=
github.com/goravel/framework v1.12.1-0.20230717105343-6ce864794883/go.mod h1:quBqUHAyZutEs/TFfm9b/caI+BtyF82njGhNIL1QJM0=
github.com/goravel/framework v1.12.1-0.20230721095426-6f24ecdaf6a7 h1:2rkC/6M7tLx/Ya1TulO2GjtVRIGXTmpp0XppzZRloYA=
github.com/goravel/framework v1.12.1-0.20230721095426-6f24ecdaf6a7/go.mod h1:1lxwtCXMkotSmt0YWRg/s7EnMUFfmne9L1a50X9J0fA=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=

View File

@@ -10,14 +10,12 @@ import (
"time"
"github.com/gookit/color"
"github.com/goravel/framework/facades"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/load"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/net"
"panel/app/models"
)
// MonitoringInfo 监控信息
@@ -119,12 +117,6 @@ func GetLatestPanelVersion() (PanelInfo, error) {
// UpdatePanel 更新面板
func UpdatePanel(proxy bool) error {
var task models.Task
err := facades.Orm().Query().Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).FirstOrFail(&task)
if err == nil {
return errors.New("面板有任务正在执行,禁止更新")
}
panelInfo, err := GetLatestPanelVersion()
if err != nil {
return err

View File

@@ -0,0 +1,558 @@
<!--
Name: MySQL管理器
Author: 耗子
Date: 2022-07-22
-->
<title>MySQL</title>
<div class="layui-fluid" id="component-tabs">
<div class="layui-row">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">MySQL管理</div>
<div class="layui-card-body">
<div class="layui-tab">
<ul class="layui-tab-title">
<li class="layui-this">基本信息</li>
<li>管理</li>
<li>配置修改</li>
<li>负载状态</li>
<li>错误日志</li>
<li>慢查询日志</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>运行状态</legend>
</fieldset>
<blockquote id="mysql-status" class="layui-elem-quote layui-quote-nm">当前状态:<span
class="layui-badge layui-bg-black">获取中</span></blockquote>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="mysql-start" class="layui-btn">启动</button>
<button id="mysql-stop" class="layui-btn layui-btn-danger">停止</button>
<button id="mysql-restart" class="layui-btn layui-btn-warm">重启</button>
<button id="mysql-reload" class="layui-btn layui-btn-normal">重载</button>
</div>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>基本设置</legend>
</fieldset>
<div class="layui-form" lay-filter="mysql_setting">
<div class="layui-form-item">
<label class="layui-form-label" style="font-size: 13px;">root 密码</label>
<div class="layui-input-inline">
<input type="text" name="mysql_root_password" value="获取中ing..."
class="layui-input" disabled>
</div>
<div class="layui-form-mid layui-word-aux">查看/修改MySQL的root密码</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-sm" lay-submit
lay-filter="mysql_setting_submit">确认修改
</button>
</div>
</div>
</div>
</div>
<div class="layui-tab-item">
<blockquote class="layui-elem-quote">面板仅集成了部分常用功能,如需更多功能,建议安装
phpMyAdmin 使用。
</blockquote>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>数据库列表</legend>
</fieldset>
<table class="layui-hide" id="mysql-database-list"
lay-filter="mysql-database-list"></table>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>用户列表</legend>
</fieldset>
<table class="layui-hide" id="mysql-user-list" lay-filter="mysql-user-list"></table>
<!-- 数据库顶部工具栏 -->
<script type="text/html" id="mysql-database-list-bar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add_database">新建数据库
</button>
</div>
</script>
<!-- 用户顶部工具栏 -->
<script type="text/html" id="mysql-user-list-bar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add_user">新建用户</button>
</div>
</script>
<!-- 数据库右侧管理 -->
<script type="text/html" id="mysql-database-list-control">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="backup">备份</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<!-- 用户右侧管理 -->
<script type="text/html" id="mysql-user-list-control">
<a class="layui-btn layui-btn-normal layui-btn-xs"
lay-event="change_password">改密</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
</div>
<div class="layui-tab-item">
<blockquote class="layui-elem-quote">此处修改的是MySQL主配置文件如果你不了解各参数的含义请不要随意修改<br>
提示Ctrl+F 搜索关键字Ctrl+S 保存Ctrl+H 查找替换!
</blockquote>
<div id="mysql-config-editor"
style="height: 600px;"></div>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="mysql-config-save" class="layui-btn">保存</button>
</div>
</div>
<div class="layui-tab-item">
<table class="layui-hide" id="mysql-load-status"></table>
</div>
<div class="layui-tab-item">
<div class="layui-btn-container">
<button id="mysql-clean-error-log" class="layui-btn">清空日志</button>
</div>
<pre id="mysql-error-log" class="layui-code">
获取中...
</pre>
</div>
<div class="layui-tab-item">
<div class="layui-btn-container">
<button id="mysql-clean-slow-log" class="layui-btn">清空日志</button>
</div>
<pre id="mysql-slow-log" class="layui-code">
获取中...
</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let mysql_config_editor;// 定义mysql配置编辑器的全局变量
layui.use(['index', 'code', 'table'], function () {
let $ = layui.$
, admin = layui.admin
, element = layui.element
, code = layui.code
, table = layui.table
, form = layui.form
, view = layui.view;
// 渲染表单
form.render();
admin.req({
url: "/api/plugins/mysql57/rootPassword"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板系统信息获取失败接口返回' + result);
return false;
}
form.val("mysql_setting", {
"mysql_root_password": result.data
});
$('input').attr('disabled', false);
}
});
// 提交修改
form.on('submit(mysql_setting_submit)', function (data) {
index = layer.msg('请稍候...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql57/rootPassword"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL设置保存失败接口返回' + result);
return false;
}
layer.msg('修改成功!')
}
});
return false;
});
// 获取mysql运行状态并渲染
admin.req({
url: "/api/plugins/mysql57/status"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板MySQL运行状态获取失败接口返回' + result);
return false;
}
if (result.data) {
$('#mysql-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
} else {
$('#mysql-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
}
}
});
// 获取数据库列表
table.render({
elem: '#mysql-database-list'
, url: '/api/plugins/mysql57/database'
, toolbar: '#mysql-database-list-bar'
, title: '数据库列表'
, cols: [[
{field: 'name', title: '库名', fixed: 'left', unresize: true, sort: true}
, {fixed: 'right', title: '操作', toolbar: '#mysql-database-list-control', width: 150}
]]
});
// 头工具栏事件
table.on('toolbar(mysql-database-list)', function (obj) {
if (obj.event === 'add_database') {
admin.popup({
title: '新建数据库'
, area: ['600px', '300px']
, id: 'LAY-popup-mysql-database-add'
, success: function (layer, index) {
view(this.id).render('plugins/mysql57/add_database', {}).done(function () {
form.render(null, 'LAY-popup-mysql-database-add');
});
}
});
}
});
// 行工具事件
table.on('tool(mysql-database-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('高风险操作,确定要删除数据库 <b style="color: red;">' + data.name + '</b> 吗?', function (index) {
index = layer.msg('请稍候...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/mysql57/deleteDatabase"
, type: 'post'
, data: {
database: data.name
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板数据库删除失败接口返回' + result);
return false;
}
obj.del();
layer.alert('数据库' + data.name + '删除成功!');
}
});
layer.close(index);
});
} else if (obj.event === 'backup') {
// 打开备份页面
admin.popup({
title: '备份管理 - ' + data.name
, area: ['70%', '80%']
, id: 'LAY-popup-mysql-backup'
, success: function (layero, index) {
view(this.id).render('plugins/mysql57/backup', {
data: data
}).done(function () {
form.render(null, 'LAY-popup-mysql-backup');
});
}
});
}
});
// 获取数据库用户列表
table.render({
elem: '#mysql-user-list'
, url: '/api/plugins/mysql57/user'
, toolbar: '#mysql-user-list-bar'
, title: '用户列表'
, cols: [[
{field: 'user', title: '用户名', fixed: 'left', width: 300, sort: true}
, {field: 'host', title: '主机', width: 250, sort: true}
, {field: 'grants', title: '权限'}
, {fixed: 'right', title: '操作', toolbar: '#mysql-user-list-control', width: 150}
]]
});
// 头工具栏事件
table.on('toolbar(mysql-user-list)', function (obj) {
if (obj.event === 'add_user') {
admin.popup({
title: '新建用户'
, area: ['600px', '300px']
, id: 'LAY-popup-mysql-user-add'
, success: function (layer, index) {
view(this.id).render('plugins/mysql57/add_user', {}).done(function () {
form.render(null, 'LAY-popup-mysql-user-add');
});
}
});
}
});
// 行工具事件
table.on('tool(mysql-user-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('高风险操作,确定要删除用户 <b style="color: red;">' + data.user + '</b> 吗?', function (index) {
index = layer.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/mysql57/deleteUser"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板用户删除失败接口返回' + result);
return false;
}
obj.del();
layer.alert('用户' + data.user + '删除成功!');
}
});
layer.close(index);
});
} else if (obj.event === 'change_password') {
// 弹出输入密码框
layer.prompt({
formType: 1
, title: '请输入新密码8位以上大小写数字特殊符号混合'
}, function (value, index) {
layer.close(index);
index = layer.msg('正在提交...', {icon: 16, time: 0});
// 发送请求
admin.req({
url: "/api/plugins/mysql57/userPassword"
, type: 'post'
, data: {
user: data.user,
password: value
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板密码修改失败接口返回' + result);
return false;
}
layer.alert('用户' + data.user + '密码修改成功!');
}
});
});
}
});
// 获取mysql错误日志并渲染
admin.req({
url: "/api/plugins/mysql57/errorLog"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板MySQL错误日志获取失败接口返回' + result);
$('#mysql-error-log').text('MySQL错误日志获取失败请刷新重试');
code({
elem: '#mysql-error-log'
, title: 'error.log'
, encode: true
, about: false
});
return false;
}
$('#mysql-error-log').text(result.data);
code({
elem: '#mysql-error-log'
, title: 'error.log'
, encode: true
, about: false
});
}
});
// 获取mysql慢查询日志并渲染
admin.req({
url: "/api/plugins/mysql57/slowLog"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板MySQL慢查询日志获取失败接口返回' + result);
$('#mysql-slow-log').text('MySQL慢查询日志获取失败请刷新重试');
code({
elem: '#mysql-slow-log'
, title: 'slow.log'
, encode: true
, about: false
});
return false;
}
$('#mysql-slow-log').text(result.data);
code({
elem: '#mysql-slow-log'
, title: 'slow.log'
, encode: true
, about: false
});
}
});
// 获取mysql配置并渲染
admin.req({
url: "/api/plugins/mysql57/config"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板MySQL主配置获取失败接口返回' + result);
return false;
}
$('#mysql-config-editor').text(result.data);
mysql_config_editor = ace.edit("mysql-config-editor", {
mode: "ace/mode/ini",
selectionStyle: "text"
});
}
});
// 获取mysql负载状态并渲染
table.render({
elem: '#mysql-load-status'
, url: '/api/plugins/mysql57/load'
, cols: [[
{field: 'name', width: '80%', title: '属性',}
, {field: 'value', width: '20%', title: '当前值'}
]]
});
element.render();
// 事件监听
$('#mysql-start').click(function () {
layer.confirm('确定要启动MySQL吗', {
btn: ['启动', '取消']
}, function () {
index = layer.msg('正在启动MySQL...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql57/start"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL启动失败接口返回' + result);
return false;
}
admin.events.refresh();
layer.alert('MySQL启动成功');
}
});
});
});
$('#mysql-stop').click(function () {
layer.confirm('停止MySQL将导致使用MySQL的网站无法访问是否继续停止', {
btn: ['停止', '取消']
}, function () {
index = layer.msg('正在停止MySQL...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql57/stop"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL停止失败接口返回' + result);
return false;
}
admin.events.refresh();
layer.alert('MySQL停止成功');
}
});
});
});
$('#mysql-restart').click(function () {
layer.confirm('重启MySQL将导致使用MySQL的网站短时间无法访问是否继续重启', {
btn: ['重启', '取消']
}, function () {
index = layer.msg('正在重启MySQL...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql57/restart"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL重启失败接口返回' + result);
return false;
}
admin.events.refresh();
layer.alert('MySQL重启成功');
}
});
});
});
$('#mysql-reload').click(function () {
index = layer.msg('正在重载MySQL...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql57/reload"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL重载失败接口返回' + result);
return false;
}
admin.events.refresh();
layer.alert('MySQL重载成功');
}
});
});
$('#mysql-config-save').click(function () {
index = layer.msg('正在保存配置...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql57/config"
, type: 'post'
, data: {
config: mysql_config_editor.getValue()
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL配置保存失败接口返回' + result);
return false;
}
layer.alert('MySQL配置保存成功');
}
});
});
$('#mysql-clean-error-log').click(function () {
index = layer.msg('正在清空错误日志...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql57/clearErrorLog"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL错误日志清空失败接口返回' + result);
return false;
}
layer.msg('MySQL错误日志已清空');
setTimeout(function () {
admin.events.refresh();
}, 1000);
}
});
});
$('#mysql-clean-slow-log').click(function () {
index = layer.msg('正在清空慢查询日志...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql57/clearSlowLog"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL慢查询日志清空失败接口返回' + result);
return false;
}
layer.msg('MySQL慢查询日志已清空');
setTimeout(function () {
admin.events.refresh();
}, 1000);
}
});
});
});
</script>

View File

@@ -0,0 +1,80 @@
<!--
Name: MySQL管理器 - 添加数据库
Author: 耗子
Date: 2023-07-22
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<form class="layui-form" action="" lay-filter="add-mysql-database-form">
<div class="layui-form-item">
<label class="layui-form-label">数据库名</label>
<div class="layui-input-block">
<input type="text" name="database" lay-verify="required" placeholder="请输入数据库名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="user" lay-verify="required" placeholder="请输入用户名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="text" name="password" lay-verify="required" placeholder="请输入密码8位以上大小写数字特殊符号混合"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<div class="layui-footer">
<button class="layui-btn" lay-submit="" lay-filter="add-mysql-database-submit">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</div>
</form>
</script>
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form'], function () {
var $ = layui.$
, admin = layui.admin
, layer = layui.layer
, form = layui.form
, table = layui.table
form.render();
// 提交
form.on('submit(add-mysql-database-submit)', function (data) {
index = layer.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/mysql57/addDatabase"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板数据库添加失败接口返回' + result);
layer.msg('数据库添加失败,请刷新重试!')
return false;
}
table.reload('mysql-database-list');
table.reload('mysql-user-list');
layer.alert('数据库添加成功!', {
icon: 1
, title: '提示'
, btn: ['确定']
, yes: function (index) {
layer.closeAll();
}
});
}
});
return false;
});
});
};
</script>

View File

@@ -0,0 +1,80 @@
<!--
Name: MySQL管理器 - 添加用户
Author: 耗子
Date: 2023-07-22
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<form class="layui-form" action="" lay-filter="add-mysql-user-form">
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="user" lay-verify="required" placeholder="请输入用户名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="text" name="password" lay-verify="required" placeholder="请输入密码8位以上大小写数字特殊符号混合"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数据库</label>
<div class="layui-input-block">
<input type="text" name="database" lay-verify="required" placeholder="输入授权给该用户的数据库名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<div class="layui-footer">
<button class="layui-btn" lay-submit="" lay-filter="add-mysql-user-submit">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</div>
</form>
</script>
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form'], function () {
var $ = layui.$
, admin = layui.admin
, layer = layui.layer
, form = layui.form
, table = layui.table
form.render();
// 提交
form.on('submit(add-mysql-user-submit)', function (data) {
index = layer.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/mysql57/addUser"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板用户添加失败接口返回' + result);
layer.msg('用户添加失败,请刷新重试!')
return false;
}
table.reload('mysql-database-list');
table.reload('mysql-user-list');
layer.alert('用户添加成功!', {
icon: 1
, title: '提示'
, btn: ['确定']
, yes: function (index) {
layer.closeAll();
}
});
}
});
return false;
});
});
};
</script>

View File

@@ -0,0 +1,145 @@
<!--
Name: MySQL管理器 - 数据库备份
Author: 耗子
Date: 2023-07-22
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<div class="layui-row">
<div class="layui-col-xs12 layui-col-sm12 layui-col-md12">
<table class="layui-hide" id="mysql-backup-list" lay-filter="mysql-backup-list"></table>
</div>
</div>
</script>
<!-- 备份顶部工具栏 -->
<script type="text/html" id="mysql-database-backup-bar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="backup_database">备份数据库</button>
<button class="layui-btn layui-btn-sm" id="upload_mysql_backup">上传备份</button>
</div>
</script>
<!-- 备份右侧管理 -->
<script type="text/html" id="mysql-database-backup-control">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="restore">恢复</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form', 'laydate', 'code'], function () {
var $ = layui.$
, admin = layui.admin
, layer = layui.layer
, table = layui.table
, upload = layui.upload;
// 渲染表格
table.render({
elem: '#mysql-backup-list'
, url: '/api/plugins/mysql57/backup'
, toolbar: '#mysql-database-backup-bar'
, title: '备份列表'
, cols: [[
{field: 'file', title: '备份名称', width: 500}
, {field: 'size', title: '文件大小'}
, {field: 'right', title: '操作', width: 150, toolbar: '#mysql-database-backup-control'}
]]
, text: {
none: '无备份数据'
}
, done: function (res, curr, count) {
upload.render({
elem: '#upload_mysql_backup'
, url: '/api/plugins/mysql57/uploadBackup'
, accept: 'file'
, ext: 'sql|zip|rar|tar|gz|bz2'
, before: function (obj) {
index = layer.msg('正在上传备份文件,可能需要较长时间,请勿操作...', {
icon: 16
, time: 0
});
}
, done: function (res) {
layer.close(index);
layer.msg('上传成功!', {icon: 1});
table.reload('mysql-backup-list');
}
});
}
});
// 头工具栏事件
table.on('toolbar(mysql-backup-list)', function (obj) {
if (obj.event === 'backup_database') {
index = layer.msg('正在备份数据库,请稍等...', {
icon: 16
, time: 0
});
admin.req({
url: '/api/plugins/mysql57/createBackup'
, type: 'post'
, data: {
database: params.data.name
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板备份数据库失败接口返回' + result);
layer.alert('备份失败!');
return false;
}
table.reload('mysql-backup-list');
layer.msg('备份成功!', {icon: 1});
}
});
}
});
// 行工具事件
table.on('tool(mysql-backup-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('确定要删除数据库备份 <b style="color: red;">' + data.file + '</b> 吗?', function (index) {
index = layer.msg('正在删除数据库备份,请稍等...', {
icon: 16
, time: 0
});
admin.req({
url: "/api/plugins/mysql57/deleteBackup"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板数据库备份删除失败接口返回' + result);
layer.msg('数据库备份删除失败,请刷新重试!')
return false;
}
obj.del();
layer.alert('数据库备份' + data.file + '删除成功!');
}
});
});
} else if (obj.event === 'restore') {
layer.confirm('高风险操作,确定要恢复数据库备份 <b style="color: red;">' + data.file + '</b> 吗?', function (index) {
index = layer.msg('正在恢复数据库备份,可能需要较长时间,请勿操作...', {
icon: 16
, time: 0
});
data.database = params.data.name;
admin.req({
url: "/api/plugins/mysql57/restoreBackup"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板数据库恢复失败接口返回' + result);
layer.msg('数据库备份恢复失败,请刷新重试!')
return false;
}
layer.alert('数据库备份' + data.file + '恢复成功!');
}
});
});
}
});
});
};
</script>

View File

@@ -0,0 +1,558 @@
<!--
Name: MySQL管理器
Author: 耗子
Date: 2022-07-22
-->
<title>MySQL</title>
<div class="layui-fluid" id="component-tabs">
<div class="layui-row">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">MySQL管理</div>
<div class="layui-card-body">
<div class="layui-tab">
<ul class="layui-tab-title">
<li class="layui-this">基本信息</li>
<li>管理</li>
<li>配置修改</li>
<li>负载状态</li>
<li>错误日志</li>
<li>慢查询日志</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>运行状态</legend>
</fieldset>
<blockquote id="mysql-status" class="layui-elem-quote layui-quote-nm">当前状态:<span
class="layui-badge layui-bg-black">获取中</span></blockquote>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="mysql-start" class="layui-btn">启动</button>
<button id="mysql-stop" class="layui-btn layui-btn-danger">停止</button>
<button id="mysql-restart" class="layui-btn layui-btn-warm">重启</button>
<button id="mysql-reload" class="layui-btn layui-btn-normal">重载</button>
</div>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>基本设置</legend>
</fieldset>
<div class="layui-form" lay-filter="mysql_setting">
<div class="layui-form-item">
<label class="layui-form-label" style="font-size: 13px;">root 密码</label>
<div class="layui-input-inline">
<input type="text" name="mysql_root_password" value="获取中ing..."
class="layui-input" disabled>
</div>
<div class="layui-form-mid layui-word-aux">查看/修改MySQL的root密码</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-sm" lay-submit
lay-filter="mysql_setting_submit">确认修改
</button>
</div>
</div>
</div>
</div>
<div class="layui-tab-item">
<blockquote class="layui-elem-quote">面板仅集成了部分常用功能,如需更多功能,建议安装
phpMyAdmin 使用。
</blockquote>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>数据库列表</legend>
</fieldset>
<table class="layui-hide" id="mysql-database-list"
lay-filter="mysql-database-list"></table>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>用户列表</legend>
</fieldset>
<table class="layui-hide" id="mysql-user-list" lay-filter="mysql-user-list"></table>
<!-- 数据库顶部工具栏 -->
<script type="text/html" id="mysql-database-list-bar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add_database">新建数据库
</button>
</div>
</script>
<!-- 用户顶部工具栏 -->
<script type="text/html" id="mysql-user-list-bar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add_user">新建用户</button>
</div>
</script>
<!-- 数据库右侧管理 -->
<script type="text/html" id="mysql-database-list-control">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="backup">备份</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<!-- 用户右侧管理 -->
<script type="text/html" id="mysql-user-list-control">
<a class="layui-btn layui-btn-normal layui-btn-xs"
lay-event="change_password">改密</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
</div>
<div class="layui-tab-item">
<blockquote class="layui-elem-quote">此处修改的是MySQL主配置文件如果你不了解各参数的含义请不要随意修改<br>
提示Ctrl+F 搜索关键字Ctrl+S 保存Ctrl+H 查找替换!
</blockquote>
<div id="mysql-config-editor"
style="height: 600px;"></div>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="mysql-config-save" class="layui-btn">保存</button>
</div>
</div>
<div class="layui-tab-item">
<table class="layui-hide" id="mysql-load-status"></table>
</div>
<div class="layui-tab-item">
<div class="layui-btn-container">
<button id="mysql-clean-error-log" class="layui-btn">清空日志</button>
</div>
<pre id="mysql-error-log" class="layui-code">
获取中...
</pre>
</div>
<div class="layui-tab-item">
<div class="layui-btn-container">
<button id="mysql-clean-slow-log" class="layui-btn">清空日志</button>
</div>
<pre id="mysql-slow-log" class="layui-code">
获取中...
</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let mysql_config_editor;// 定义mysql配置编辑器的全局变量
layui.use(['index', 'code', 'table'], function () {
let $ = layui.$
, admin = layui.admin
, element = layui.element
, code = layui.code
, table = layui.table
, form = layui.form
, view = layui.view;
// 渲染表单
form.render();
admin.req({
url: "/api/plugins/mysql80/rootPassword"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板系统信息获取失败接口返回' + result);
return false;
}
form.val("mysql_setting", {
"mysql_root_password": result.data
});
$('input').attr('disabled', false);
}
});
// 提交修改
form.on('submit(mysql_setting_submit)', function (data) {
index = layer.msg('请稍候...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql80/rootPassword"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL设置保存失败接口返回' + result);
return false;
}
layer.msg('修改成功!')
}
});
return false;
});
// 获取mysql运行状态并渲染
admin.req({
url: "/api/plugins/mysql80/status"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板MySQL运行状态获取失败接口返回' + result);
return false;
}
if (result.data) {
$('#mysql-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
} else {
$('#mysql-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
}
}
});
// 获取数据库列表
table.render({
elem: '#mysql-database-list'
, url: '/api/plugins/mysql80/database'
, toolbar: '#mysql-database-list-bar'
, title: '数据库列表'
, cols: [[
{field: 'name', title: '库名', fixed: 'left', unresize: true, sort: true}
, {fixed: 'right', title: '操作', toolbar: '#mysql-database-list-control', width: 150}
]]
});
// 头工具栏事件
table.on('toolbar(mysql-database-list)', function (obj) {
if (obj.event === 'add_database') {
admin.popup({
title: '新建数据库'
, area: ['600px', '300px']
, id: 'LAY-popup-mysql-database-add'
, success: function (layer, index) {
view(this.id).render('plugins/mysql80/add_database', {}).done(function () {
form.render(null, 'LAY-popup-mysql-database-add');
});
}
});
}
});
// 行工具事件
table.on('tool(mysql-database-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('高风险操作,确定要删除数据库 <b style="color: red;">' + data.name + '</b> 吗?', function (index) {
index = layer.msg('请稍候...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/mysql80/deleteDatabase"
, type: 'post'
, data: {
database: data.name
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板数据库删除失败接口返回' + result);
return false;
}
obj.del();
layer.alert('数据库' + data.name + '删除成功!');
}
});
layer.close(index);
});
} else if (obj.event === 'backup') {
// 打开备份页面
admin.popup({
title: '备份管理 - ' + data.name
, area: ['70%', '80%']
, id: 'LAY-popup-mysql-backup'
, success: function (layero, index) {
view(this.id).render('plugins/mysql80/backup', {
data: data
}).done(function () {
form.render(null, 'LAY-popup-mysql-backup');
});
}
});
}
});
// 获取数据库用户列表
table.render({
elem: '#mysql-user-list'
, url: '/api/plugins/mysql80/user'
, toolbar: '#mysql-user-list-bar'
, title: '用户列表'
, cols: [[
{field: 'user', title: '用户名', fixed: 'left', width: 300, sort: true}
, {field: 'host', title: '主机', width: 250, sort: true}
, {field: 'grants', title: '权限'}
, {fixed: 'right', title: '操作', toolbar: '#mysql-user-list-control', width: 150}
]]
});
// 头工具栏事件
table.on('toolbar(mysql-user-list)', function (obj) {
if (obj.event === 'add_user') {
admin.popup({
title: '新建用户'
, area: ['600px', '300px']
, id: 'LAY-popup-mysql-user-add'
, success: function (layer, index) {
view(this.id).render('plugins/mysql80/add_user', {}).done(function () {
form.render(null, 'LAY-popup-mysql-user-add');
});
}
});
}
});
// 行工具事件
table.on('tool(mysql-user-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('高风险操作,确定要删除用户 <b style="color: red;">' + data.user + '</b> 吗?', function (index) {
index = layer.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/mysql80/deleteUser"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板用户删除失败接口返回' + result);
return false;
}
obj.del();
layer.alert('用户' + data.user + '删除成功!');
}
});
layer.close(index);
});
} else if (obj.event === 'change_password') {
// 弹出输入密码框
layer.prompt({
formType: 1
, title: '请输入新密码8位以上大小写数字特殊符号混合'
}, function (value, index) {
layer.close(index);
index = layer.msg('正在提交...', {icon: 16, time: 0});
// 发送请求
admin.req({
url: "/api/plugins/mysql80/userPassword"
, type: 'post'
, data: {
user: data.user,
password: value
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板密码修改失败接口返回' + result);
return false;
}
layer.alert('用户' + data.user + '密码修改成功!');
}
});
});
}
});
// 获取mysql错误日志并渲染
admin.req({
url: "/api/plugins/mysql80/errorLog"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板MySQL错误日志获取失败接口返回' + result);
$('#mysql-error-log').text('MySQL错误日志获取失败请刷新重试');
code({
elem: '#mysql-error-log'
, title: 'error.log'
, encode: true
, about: false
});
return false;
}
$('#mysql-error-log').text(result.data);
code({
elem: '#mysql-error-log'
, title: 'error.log'
, encode: true
, about: false
});
}
});
// 获取mysql慢查询日志并渲染
admin.req({
url: "/api/plugins/mysql80/slowLog"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板MySQL慢查询日志获取失败接口返回' + result);
$('#mysql-slow-log').text('MySQL慢查询日志获取失败请刷新重试');
code({
elem: '#mysql-slow-log'
, title: 'slow.log'
, encode: true
, about: false
});
return false;
}
$('#mysql-slow-log').text(result.data);
code({
elem: '#mysql-slow-log'
, title: 'slow.log'
, encode: true
, about: false
});
}
});
// 获取mysql配置并渲染
admin.req({
url: "/api/plugins/mysql80/config"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板MySQL主配置获取失败接口返回' + result);
return false;
}
$('#mysql-config-editor').text(result.data);
mysql_config_editor = ace.edit("mysql-config-editor", {
mode: "ace/mode/ini",
selectionStyle: "text"
});
}
});
// 获取mysql负载状态并渲染
table.render({
elem: '#mysql-load-status'
, url: '/api/plugins/mysql80/load'
, cols: [[
{field: 'name', width: '80%', title: '属性',}
, {field: 'value', width: '20%', title: '当前值'}
]]
});
element.render();
// 事件监听
$('#mysql-start').click(function () {
layer.confirm('确定要启动MySQL吗', {
btn: ['启动', '取消']
}, function () {
index = layer.msg('正在启动MySQL...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql80/start"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL启动失败接口返回' + result);
return false;
}
admin.events.refresh();
layer.alert('MySQL启动成功');
}
});
});
});
$('#mysql-stop').click(function () {
layer.confirm('停止MySQL将导致使用MySQL的网站无法访问是否继续停止', {
btn: ['停止', '取消']
}, function () {
index = layer.msg('正在停止MySQL...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql80/stop"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL停止失败接口返回' + result);
return false;
}
admin.events.refresh();
layer.alert('MySQL停止成功');
}
});
});
});
$('#mysql-restart').click(function () {
layer.confirm('重启MySQL将导致使用MySQL的网站短时间无法访问是否继续重启', {
btn: ['重启', '取消']
}, function () {
index = layer.msg('正在重启MySQL...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql80/restart"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL重启失败接口返回' + result);
return false;
}
admin.events.refresh();
layer.alert('MySQL重启成功');
}
});
});
});
$('#mysql-reload').click(function () {
index = layer.msg('正在重载MySQL...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql80/reload"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL重载失败接口返回' + result);
return false;
}
admin.events.refresh();
layer.alert('MySQL重载成功');
}
});
});
$('#mysql-config-save').click(function () {
index = layer.msg('正在保存配置...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql80/config"
, type: 'post'
, data: {
config: mysql_config_editor.getValue()
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL配置保存失败接口返回' + result);
return false;
}
layer.alert('MySQL配置保存成功');
}
});
});
$('#mysql-clean-error-log').click(function () {
index = layer.msg('正在清空错误日志...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql80/clearErrorLog"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL错误日志清空失败接口返回' + result);
return false;
}
layer.msg('MySQL错误日志已清空');
setTimeout(function () {
admin.events.refresh();
}, 1000);
}
});
});
$('#mysql-clean-slow-log').click(function () {
index = layer.msg('正在清空慢查询日志...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/mysql80/clearSlowLog"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板MySQL慢查询日志清空失败接口返回' + result);
return false;
}
layer.msg('MySQL慢查询日志已清空');
setTimeout(function () {
admin.events.refresh();
}, 1000);
}
});
});
});
</script>

View File

@@ -0,0 +1,80 @@
<!--
Name: MySQL管理器 - 添加数据库
Author: 耗子
Date: 2023-07-22
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<form class="layui-form" action="" lay-filter="add-mysql-database-form">
<div class="layui-form-item">
<label class="layui-form-label">数据库名</label>
<div class="layui-input-block">
<input type="text" name="database" lay-verify="required" placeholder="请输入数据库名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="user" lay-verify="required" placeholder="请输入用户名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="text" name="password" lay-verify="required" placeholder="请输入密码8位以上大小写数字特殊符号混合"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<div class="layui-footer">
<button class="layui-btn" lay-submit="" lay-filter="add-mysql-database-submit">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</div>
</form>
</script>
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form'], function () {
var $ = layui.$
, admin = layui.admin
, layer = layui.layer
, form = layui.form
, table = layui.table
form.render();
// 提交
form.on('submit(add-mysql-database-submit)', function (data) {
index = layer.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/mysql80/addDatabase"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板数据库添加失败接口返回' + result);
layer.msg('数据库添加失败,请刷新重试!')
return false;
}
table.reload('mysql-database-list');
table.reload('mysql-user-list');
layer.alert('数据库添加成功!', {
icon: 1
, title: '提示'
, btn: ['确定']
, yes: function (index) {
layer.closeAll();
}
});
}
});
return false;
});
});
};
</script>

View File

@@ -0,0 +1,80 @@
<!--
Name: MySQL管理器 - 添加用户
Author: 耗子
Date: 2023-07-22
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<form class="layui-form" action="" lay-filter="add-mysql-user-form">
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="user" lay-verify="required" placeholder="请输入用户名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="text" name="password" lay-verify="required" placeholder="请输入密码8位以上大小写数字特殊符号混合"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数据库</label>
<div class="layui-input-block">
<input type="text" name="database" lay-verify="required" placeholder="输入授权给该用户的数据库名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<div class="layui-footer">
<button class="layui-btn" lay-submit="" lay-filter="add-mysql-user-submit">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</div>
</form>
</script>
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form'], function () {
var $ = layui.$
, admin = layui.admin
, layer = layui.layer
, form = layui.form
, table = layui.table
form.render();
// 提交
form.on('submit(add-mysql-user-submit)', function (data) {
index = layer.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/mysql80/addUser"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板用户添加失败接口返回' + result);
layer.msg('用户添加失败,请刷新重试!')
return false;
}
table.reload('mysql-database-list');
table.reload('mysql-user-list');
layer.alert('用户添加成功!', {
icon: 1
, title: '提示'
, btn: ['确定']
, yes: function (index) {
layer.closeAll();
}
});
}
});
return false;
});
});
};
</script>

View File

@@ -0,0 +1,145 @@
<!--
Name: MySQL管理器 - 数据库备份
Author: 耗子
Date: 2023-07-22
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<div class="layui-row">
<div class="layui-col-xs12 layui-col-sm12 layui-col-md12">
<table class="layui-hide" id="mysql-backup-list" lay-filter="mysql-backup-list"></table>
</div>
</div>
</script>
<!-- 备份顶部工具栏 -->
<script type="text/html" id="mysql-database-backup-bar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="backup_database">备份数据库</button>
<button class="layui-btn layui-btn-sm" id="upload_mysql_backup">上传备份</button>
</div>
</script>
<!-- 备份右侧管理 -->
<script type="text/html" id="mysql-database-backup-control">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="restore">恢复</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form', 'laydate', 'code'], function () {
var $ = layui.$
, admin = layui.admin
, layer = layui.layer
, table = layui.table
, upload = layui.upload;
// 渲染表格
table.render({
elem: '#mysql-backup-list'
, url: '/api/plugins/mysql80/backup'
, toolbar: '#mysql-database-backup-bar'
, title: '备份列表'
, cols: [[
{field: 'file', title: '备份名称', width: 500}
, {field: 'size', title: '文件大小'}
, {field: 'right', title: '操作', width: 150, toolbar: '#mysql-database-backup-control'}
]]
, text: {
none: '无备份数据'
}
, done: function (res, curr, count) {
upload.render({
elem: '#upload_mysql_backup'
, url: '/api/plugins/mysql80/uploadBackup'
, accept: 'file'
, ext: 'sql|zip|rar|tar|gz|bz2'
, before: function (obj) {
index = layer.msg('正在上传备份文件,可能需要较长时间,请勿操作...', {
icon: 16
, time: 0
});
}
, done: function (res) {
layer.close(index);
layer.msg('上传成功!', {icon: 1});
table.reload('mysql-backup-list');
}
});
}
});
// 头工具栏事件
table.on('toolbar(mysql-backup-list)', function (obj) {
if (obj.event === 'backup_database') {
index = layer.msg('正在备份数据库,请稍等...', {
icon: 16
, time: 0
});
admin.req({
url: '/api/plugins/mysql80/createBackup'
, type: 'post'
, data: {
database: params.data.name
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板备份数据库失败接口返回' + result);
layer.alert('备份失败!');
return false;
}
table.reload('mysql-backup-list');
layer.msg('备份成功!', {icon: 1});
}
});
}
});
// 行工具事件
table.on('tool(mysql-backup-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('确定要删除数据库备份 <b style="color: red;">' + data.file + '</b> 吗?', function (index) {
index = layer.msg('正在删除数据库备份,请稍等...', {
icon: 16
, time: 0
});
admin.req({
url: "/api/plugins/mysql80/deleteBackup"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板数据库备份删除失败接口返回' + result);
layer.msg('数据库备份删除失败,请刷新重试!')
return false;
}
obj.del();
layer.alert('数据库备份' + data.file + '删除成功!');
}
});
});
} else if (obj.event === 'restore') {
layer.confirm('高风险操作,确定要恢复数据库备份 <b style="color: red;">' + data.file + '</b> 吗?', function (index) {
index = layer.msg('正在恢复数据库备份,可能需要较长时间,请勿操作...', {
icon: 16
, time: 0
});
data.database = params.data.name;
admin.req({
url: "/api/plugins/mysql80/restoreBackup"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板数据库恢复失败接口返回' + result);
layer.msg('数据库备份恢复失败,请刷新重试!')
return false;
}
layer.alert('数据库备份' + data.file + '恢复成功!');
}
});
});
}
});
});
};
</script>

View File

@@ -0,0 +1,381 @@
<!--
Name: PHP管理器
Author: 耗子
Date: 2023-07-22
-->
<title>PHP-7.4</title>
<div class="layui-fluid" id="component-tabs">
<div class="layui-row">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">PHP-7.4管理</div>
<div class="layui-card-body">
<div class="layui-tab">
<ul class="layui-tab-title">
<li class="layui-this">运行状态</li>
<li>拓展管理</li>
<li>配置修改</li>
<li>负载状态</li>
<li>运行日志</li>
<li>慢日志</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<blockquote id="php74-status" class="layui-elem-quote layui-quote-nm">当前状态:<span
class="layui-badge layui-bg-black">获取中</span></blockquote>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="php74-start" class="layui-btn">启动</button>
<button id="php74-stop" class="layui-btn layui-btn-danger">停止</button>
<button id="php74-restart" class="layui-btn layui-btn-warm">重启</button>
<button id="php74-reload" class="layui-btn layui-btn-normal">重载</button>
</div>
</div>
<div class="layui-tab-item">
<table id="php74-extension" lay-filter="php74-extension"></table>
<!-- 操作按钮模板 -->
<script type="text/html" id="php74-extension-control">
{{# if(d.installed == true){ }}
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="uninstall">卸载</a>
{{# } else{ }}
<a class="layui-btn layui-btn-xs" lay-event="install">安装</a>
{{# } }}
</script>
</div>
<div class="layui-tab-item">
<blockquote class="layui-elem-quote">此处修改的是PHP主配置文件如果你不了解各参数的含义请不要随意修改<br>
提示Ctrl+F 搜索关键字Ctrl+S 保存Ctrl+H 查找替换!
</blockquote>
<div id="php74-config-editor"
style="height: 600px;"></div>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="php74-config-save" class="layui-btn">保存</button>
</div>
</div>
<div class="layui-tab-item">
<table class="layui-hide" id="php74-load-status"></table>
</div>
<div class="layui-tab-item">
<div class="layui-btn-container">
<button id="php74-clean-error-log" class="layui-btn">清空日志</button>
</div>
<pre id="php74-error-log" class="layui-code">
获取中...
</pre>
</div>
<div class="layui-tab-item">
<div class="layui-btn-container">
<button id="php74-clean-slow-log" class="layui-btn">清空日志</button>
</div>
<pre id="php74-slow-log" class="layui-code">
获取中...
</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let php74_config_editor;
layui.use(['index', 'code', 'table'], function () {
let $ = layui.$
, admin = layui.admin
, element = layui.element
, code = layui.code
, table = layui.table;
// 获取php74运行状态并渲染
admin.req({
url: "/api/plugins/php74/status"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板PHP运行状态获取失败接口返回' + result);
return false;
}
if (result.data) {
$('#php74-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
} else {
$('#php74-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
}
}
});
// 获取php74错误日志并渲染
admin.req({
url: "/api/plugins/php74/errorLog"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
$('#php74-error-log').text('PHP日志获取失败请刷新重试');
code({
elem: '#php74-error-log'
, title: 'php-fpm.log'
, encode: true
, about: false
});
return false;
}
$('#php74-error-log').text(result.data);
code({
elem: '#php74-error-log'
, title: 'php-fpm.log'
, encode: true
, about: false
});
}
});
// 获取php74慢日志并渲染
admin.req({
url: "/api/plugins/php74/slowLog"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
$('#php74-slow-log').text('PHP慢日志获取失败请刷新重试');
code({
elem: '#php74-slow-log'
, title: 'slow.log'
, encode: true
, about: false
});
return false;
}
$('#php74-slow-log').text(result.data);
code({
elem: '#php74-slow-log'
, title: 'slow.log'
, encode: true
, about: false
});
}
});
// 获取php74配置并渲染
admin.req({
url: "/api/plugins/php74/config"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板PHP主配置获取失败接口返回' + result);
return false;
}
$('#php74-config-editor').text(result.data);
php74_config_editor = ace.edit("php74-config-editor", {
mode: "ace/mode/ini",
selectionStyle: "text"
});
}
});
// 获取php74负载状态并渲染
table.render({
elem: '#php74-load-status'
, url: '/api/plugins/php74/load'
, cols: [[
{field: 'name', width: '80%', title: '属性',}
, {field: 'value', width: '20%', title: '当前值'}
]]
});
element.render();
// 获取php74扩展并渲染
table.render({
elem: '#php74-extension'
, url: '/api/plugins/php74/extensions'
, cols: [[
{field: 'slug', hide: true, title: 'Slug', sort: true}
, {field: 'name', width: '20%', title: '拓展名'}
, {field: 'description', width: '70%', title: '描述'}
, {
field: 'control',
title: '操作',
templet: '#php74-extension-control',
fixed: 'right',
align: 'left'
}
]]
, page: false
, text: {
none: '暂无拓展'
}
});
// 工具条
table.on('tool(php74-extension)', function (obj) {
let data = obj.data;
if (obj.event === 'install') {
layer.confirm('确定安装该拓展吗?', function (index) {
layer.close(index);
index = layer.msg('请稍后...', {icon: 16, time: 0});
admin.req({
url: '/api/plugins/php74/installExtension',
type: 'POST',
data: {
slug: data.slug
}
, success: function (res) {
if (res.code === 0) {
layer.close(index);
table.reload('php74-extension');
layer.msg('安装:' + data.name + ' 成功加入任务队列', {
icon: 1,
time: 1000
});
} else {
layer.msg(res.msg, {icon: 2, time: 1000});
}
}
});
});
} else if (obj.event === 'uninstall') {
layer.confirm('确定卸载该拓展吗?', function (index) {
layer.close(index);
index = layer.msg('请稍后...', {icon: 16, time: 0});
admin.req({
url: '/api/plugins/php74/uninstallExtension',
type: 'POST',
data: {
slug: data.slug
}
, success: function (res) {
layer.close(index);
if (res.code === 0) {
table.reload('php74-extension');
layer.msg('卸载:' + data.name + ' 成功加入任务队列', {icon: 1, time: 1000});
} else {
layer.msg(res.msg, {icon: 2, time: 1000});
}
}
});
});
}
});
// 事件监听
$('#php74-start').click(function () {
index = layer.msg('正在启动PHP请稍后...', {icon: 16, time: 0});
layer.confirm('确定要启动PHP吗', {
btn: ['启动', '取消']
}, function () {
admin.req({
url: "/api/plugins/php74/start"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('PHP启动成功');
}
});
});
});
$('#php74-stop').click(function () {
layer.confirm('停止PHP将导致使用PHP的网站无法访问是否继续停止', {
btn: ['停止', '取消']
}, function () {
index = layer.msg('正在停止PHP请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php74/stop"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('PHP停止成功');
}
});
});
});
$('#php74-restart').click(function () {
layer.confirm('重启PHP将导致使用PHP的网站短时间无法访问是否继续重启', {
btn: ['重启', '取消']
}, function () {
index = layer.msg('正在重启PHP请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php74/restart"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('PHP重启成功');
}
});
});
});
$('#php74-reload').click(function () {
index = layer.msg('正在重载PHP请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php74/reload"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
layer.alert('PHP重载成功');
}
});
});
$('#php74-config-save').click(function () {
index = layer.msg('正在保存配置,请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php74/config"
, type: 'post'
, data: {
config: php74_config_editor.getValue()
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
layer.alert('PHP配置保存成功');
}
});
});
$('#php74-clean-error-log').click(function () {
index = layer.msg('正在清空错误日志,请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php74/clearErrorLog"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.msg('PHP日志已清空');
}
});
});
$('#php74-clean-slow-log').click(function () {
index = layer.msg('正在清空慢日志,请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php74/clearSlowLog"
, type: 'post'
, success: function (result) {
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.msg('PHP慢日志已清空');
}
});
});
});
</script>

View File

@@ -0,0 +1,381 @@
<!--
Name: PHP管理器
Author: 耗子
Date: 2023-07-22
-->
<title>PHP-8.0</title>
<div class="layui-fluid" id="component-tabs">
<div class="layui-row">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">PHP-8.0管理</div>
<div class="layui-card-body">
<div class="layui-tab">
<ul class="layui-tab-title">
<li class="layui-this">运行状态</li>
<li>拓展管理</li>
<li>配置修改</li>
<li>负载状态</li>
<li>运行日志</li>
<li>慢日志</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<blockquote id="php80-status" class="layui-elem-quote layui-quote-nm">当前状态:<span
class="layui-badge layui-bg-black">获取中</span></blockquote>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="php80-start" class="layui-btn">启动</button>
<button id="php80-stop" class="layui-btn layui-btn-danger">停止</button>
<button id="php80-restart" class="layui-btn layui-btn-warm">重启</button>
<button id="php80-reload" class="layui-btn layui-btn-normal">重载</button>
</div>
</div>
<div class="layui-tab-item">
<table id="php80-extension" lay-filter="php80-extension"></table>
<!-- 操作按钮模板 -->
<script type="text/html" id="php80-extension-control">
{{# if(d.installed == true){ }}
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="uninstall">卸载</a>
{{# } else{ }}
<a class="layui-btn layui-btn-xs" lay-event="install">安装</a>
{{# } }}
</script>
</div>
<div class="layui-tab-item">
<blockquote class="layui-elem-quote">此处修改的是PHP主配置文件如果你不了解各参数的含义请不要随意修改<br>
提示Ctrl+F 搜索关键字Ctrl+S 保存Ctrl+H 查找替换!
</blockquote>
<div id="php80-config-editor"
style="height: 600px;"></div>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="php80-config-save" class="layui-btn">保存</button>
</div>
</div>
<div class="layui-tab-item">
<table class="layui-hide" id="php80-load-status"></table>
</div>
<div class="layui-tab-item">
<div class="layui-btn-container">
<button id="php80-clean-error-log" class="layui-btn">清空日志</button>
</div>
<pre id="php80-error-log" class="layui-code">
获取中...
</pre>
</div>
<div class="layui-tab-item">
<div class="layui-btn-container">
<button id="php80-clean-slow-log" class="layui-btn">清空日志</button>
</div>
<pre id="php80-slow-log" class="layui-code">
获取中...
</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let php80_config_editor;
layui.use(['index', 'code', 'table'], function () {
let $ = layui.$
, admin = layui.admin
, element = layui.element
, code = layui.code
, table = layui.table;
// 获取php80运行状态并渲染
admin.req({
url: "/api/plugins/php80/status"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板PHP运行状态获取失败接口返回' + result);
return false;
}
if (result.data) {
$('#php80-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
} else {
$('#php80-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
}
}
});
// 获取php80错误日志并渲染
admin.req({
url: "/api/plugins/php80/errorLog"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
$('#php80-error-log').text('PHP日志获取失败请刷新重试');
code({
elem: '#php80-error-log'
, title: 'php-fpm.log'
, encode: true
, about: false
});
return false;
}
$('#php80-error-log').text(result.data);
code({
elem: '#php80-error-log'
, title: 'php-fpm.log'
, encode: true
, about: false
});
}
});
// 获取php80慢日志并渲染
admin.req({
url: "/api/plugins/php80/slowLog"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
$('#php80-slow-log').text('PHP慢日志获取失败请刷新重试');
code({
elem: '#php80-slow-log'
, title: 'slow.log'
, encode: true
, about: false
});
return false;
}
$('#php80-slow-log').text(result.data);
code({
elem: '#php80-slow-log'
, title: 'slow.log'
, encode: true
, about: false
});
}
});
// 获取php80配置并渲染
admin.req({
url: "/api/plugins/php80/config"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
console.log('耗子Linux面板PHP主配置获取失败接口返回' + result);
return false;
}
$('#php80-config-editor').text(result.data);
php80_config_editor = ace.edit("php80-config-editor", {
mode: "ace/mode/ini",
selectionStyle: "text"
});
}
});
// 获取php80负载状态并渲染
table.render({
elem: '#php80-load-status'
, url: '/api/plugins/php80/load'
, cols: [[
{field: 'name', width: '80%', title: '属性',}
, {field: 'value', width: '20%', title: '当前值'}
]]
});
element.render();
// 获取php80扩展并渲染
table.render({
elem: '#php80-extension'
, url: '/api/plugins/php80/extensions'
, cols: [[
{field: 'slug', hide: true, title: 'Slug', sort: true}
, {field: 'name', width: '20%', title: '拓展名'}
, {field: 'description', width: '70%', title: '描述'}
, {
field: 'control',
title: '操作',
templet: '#php80-extension-control',
fixed: 'right',
align: 'left'
}
]]
, page: false
, text: {
none: '暂无拓展'
}
});
// 工具条
table.on('tool(php80-extension)', function (obj) {
let data = obj.data;
if (obj.event === 'install') {
layer.confirm('确定安装该拓展吗?', function (index) {
layer.close(index);
index = layer.msg('请稍后...', {icon: 16, time: 0});
admin.req({
url: '/api/plugins/php80/installExtension',
type: 'POST',
data: {
slug: data.slug
}
, success: function (res) {
if (res.code === 0) {
layer.close(index);
table.reload('php80-extension');
layer.msg('安装:' + data.name + ' 成功加入任务队列', {
icon: 1,
time: 1000
});
} else {
layer.msg(res.msg, {icon: 2, time: 1000});
}
}
});
});
} else if (obj.event === 'uninstall') {
layer.confirm('确定卸载该拓展吗?', function (index) {
layer.close(index);
index = layer.msg('请稍后...', {icon: 16, time: 0});
admin.req({
url: '/api/plugins/php80/uninstallExtension',
type: 'POST',
data: {
slug: data.slug
}
, success: function (res) {
layer.close(index);
if (res.code === 0) {
table.reload('php80-extension');
layer.msg('卸载:' + data.name + ' 成功加入任务队列', {icon: 1, time: 1000});
} else {
layer.msg(res.msg, {icon: 2, time: 1000});
}
}
});
});
}
});
// 事件监听
$('#php80-start').click(function () {
index = layer.msg('正在启动PHP请稍后...', {icon: 16, time: 0});
layer.confirm('确定要启动PHP吗', {
btn: ['启动', '取消']
}, function () {
admin.req({
url: "/api/plugins/php80/start"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('PHP启动成功');
}
});
});
});
$('#php80-stop').click(function () {
layer.confirm('停止PHP将导致使用PHP的网站无法访问是否继续停止', {
btn: ['停止', '取消']
}, function () {
index = layer.msg('正在停止PHP请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php80/stop"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('PHP停止成功');
}
});
});
});
$('#php80-restart').click(function () {
layer.confirm('重启PHP将导致使用PHP的网站短时间无法访问是否继续重启', {
btn: ['重启', '取消']
}, function () {
index = layer.msg('正在重启PHP请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php80/restart"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('PHP重启成功');
}
});
});
});
$('#php80-reload').click(function () {
index = layer.msg('正在重载PHP请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php80/reload"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
layer.alert('PHP重载成功');
}
});
});
$('#php80-config-save').click(function () {
index = layer.msg('正在保存配置,请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php80/config"
, type: 'post'
, data: {
config: php80_config_editor.getValue()
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
layer.alert('PHP配置保存成功');
}
});
});
$('#php80-clean-error-log').click(function () {
index = layer.msg('正在清空错误日志,请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php80/clearErrorLog"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.msg('PHP日志已清空');
}
});
});
$('#php80-clean-slow-log').click(function () {
index = layer.msg('正在清空慢日志,请稍后...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/php80/clearSlowLog"
, type: 'post'
, success: function (result) {
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.msg('PHP慢日志已清空');
}
});
});
});
</script>

View File

@@ -0,0 +1,158 @@
<!--
Name: 网站 - 备份
Author: 耗子
Date: 2023-07-21
-->
<h1>这里正在装修,下个版本再来看看吧!</h1>
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<div class="layui-row">
<div class="layui-col-xs12 layui-col-sm12 layui-col-md12">
<table class="layui-hide" id="website-backup-list" lay-filter="website-backup-list"></table>
</div>
</div>
</script>
<!-- 备份顶部工具栏 -->
<script type="text/html" id="website-backup-bar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="backup_website">备份网站</button>
<button class="layui-btn layui-btn-sm" id="upload_website_backup">上传备份</button>
</div>
</script>
<!-- 备份右侧管理 -->
<script type="text/html" id="website-backup-control">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="restore">恢复</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form', 'laydate', 'code'], function () {
var $ = layui.$
, admin = layui.admin
, layer = layui.layer
, table = layui.table
, upload = layui.upload;
// 渲染表格
table.render({
elem: '#website-backup-list'
, url: '/api/panel/website/backupList'
, toolbar: '#website-backup-bar'
, title: '备份列表'
, cols: [[
{field: 'backup', title: '备份名称', width: 500}
, {field: 'size', title: '文件大小'}
, {field: 'right', title: '操作', width: 150, toolbar: '#website-backup-control'}
]]
, text: {
none: '无备份数据'
}
, done: function (res, curr, count) {
upload.render({
elem: '#upload_website_backup'
, url: '/api/panel/website/uploadBackup'
, accept: 'file'
, exts: 'zip'
, before: function (obj) {
index = layer.msg('正在上传备份文件,可能需要较长时间,请勿操作...', {
icon: 16
, time: 0
});
}
, done: function (res) {
layer.close(index);
layer.msg('上传成功!', {icon: 1});
table.reload('website-backup-list');
}
, error: function (res) {
layer.msg('上传失败:' + res.msg, {icon: 2});
}
});
}
});
// 头工具栏事件
table.on('toolbar(website-backup-list)', function (obj) {
if (obj.event === 'backup_website') {
index = layer.msg('正在备份网站,请稍等...', {
icon: 16
, time: 0
});
admin.req({
url: '/api/panel/website/createBackup'
, type: 'post'
, data: {
name: params.data.name
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板网站失败接口返回' + result);
layer.alert('备份失败!');
return false;
}
table.reload('website-backup-list');
layer.msg('备份成功!', {icon: 1});
}
, error: function (xhr, status, error) {
console.log('耗子Linux面板ajax请求出错错误' + error);
}
});
}
});
// 行工具事件
table.on('tool(website-backup-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('确定要删除网站备份 <b style="color: red;">' + data.backup + '</b> 吗?', function (index) {
index = layer.msg('正在删除网站备份,请稍等...', {
icon: 16
, time: 0
});
admin.req({
url: "/api/panel/website/deleteBackup"
, method: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板网站备份删除失败接口返回' + result);
layer.msg('网站备份删除失败,请刷新重试!')
return false;
}
obj.del();
layer.alert('网站备份' + data.backup + '删除成功!');
}
, error: function (xhr, status, error) {
console.log('耗子Linux面板ajax请求出错错误' + error);
}
});
});
} else if (obj.event === 'restore') {
layer.confirm('高风险操作,确定要恢复网站备份 <b style="color: red;">' + data.backup + '</b> 吗?', function (index) {
index = layer.msg('正在恢复网站备份,可能需要较长时间,请勿操作...', {
icon: 16
, time: 0
});
data.name = params.data.name;
admin.req({
url: "/api/panel/website/restoreBackup"
, method: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板网站备份恢复失败接口返回' + result);
layer.msg('网站备份恢复失败,请刷新重试!')
return false;
}
layer.alert('网站备份' + data.backup + '恢复成功!');
}
, error: function (xhr, status, error) {
console.log('耗子Linux面板ajax请求出错错误' + error);
}
});
});
}
});
});
};
</script>

View File

@@ -0,0 +1,76 @@
<!--
Name: 网站 - 全局设置
Author: 耗子
Date: 2023-07-21
-->
<script type="text/html" template lay-url="/api/panel/website/defaultConfig"
lay-done="layui.data.sendParams(d.params)">
<div class="layui-tab">
<ul class="layui-tab-title">
<li class="layui-this">默认页</li>
<li>停止页</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<!-- 默认页 -->
<blockquote class="layui-elem-quote layui-quote-nm">
设置站点未找到时的提示页面
</blockquote>
<div id="index-editor" style="height: 400px;">{{ d.data.index }}</div>
</div>
<div class="layui-tab-item">
<!-- 停止页 -->
<blockquote class="layui-elem-quote layui-quote-nm">
设置站点停止时的提示页面设置后需重新开关网站方可生效
</blockquote>
<div id="stop-editor" style="height: 400px;">{{ d.data.stop }}</div>
</div>
</div>
</div>
<div class="layui-footer">
<button id="save-website-default-settings" class="layui-btn">保存设置</button>
</div>
</script>
<script>
let indexEditor = '';
let stopEditor = '';
layui.data.sendParams = function (params) {
layui.use(['admin', 'form', 'laydate', 'code'], function () {
var $ = layui.$
, admin = layui.admin
, layer = layui.layer;
indexEditor = ace.edit("index-editor", {
mode: "ace/mode/html",
selectionStyle: "text"
});
stopEditor = ace.edit("stop-editor", {
mode: "ace/mode/html",
selectionStyle: "text"
});
$('#save-website-default-settings').click(function () {
layer.load();
admin.req({
url: '/api/panel/website/defaultConfig'
, type: 'post'
, data: {
index: indexEditor.getValue(),
stop: stopEditor.getValue()
}
, success: function (res) {
layer.closeAll('loading');
if (res.code === 0) {
layer.msg('保存成功', {icon: 1, shade: 0.3});
setTimeout(function () {
admin.render();
}, 1000);
} else {
layer.msg(res.message, {icon: 2, shade: 0.3});
}
}
});
});
});
};
</script>

View File

@@ -107,7 +107,7 @@ Date: 2023-07-21
<div class="layui-input-block">
<select name="php" lay-filter="website-php">
{{# layui.each(d.params.php, function(index, item){ }}
{{# if(item == d.params.config.php){ }}
{{# if(item.slug == d.params.config.php){ }}
<option value="{{ item.slug }}" selected="">{{ item.name }}</option>
{{# }else{ }}
<option value="{{ item.slug }}">{{ item.name }}</option>

View File

@@ -38,9 +38,9 @@ func Plugin() {
route.Get("config", mysql57Controller.GetConfig)
route.Post("config", mysql57Controller.SaveConfig)
route.Get("errorLog", mysql57Controller.ErrorLog)
route.Get("clearErrorLog", mysql57Controller.ClearErrorLog)
route.Post("clearErrorLog", mysql57Controller.ClearErrorLog)
route.Get("slowLog", mysql57Controller.SlowLog)
route.Get("clearSlowLog", mysql57Controller.ClearSlowLog)
route.Post("clearSlowLog", mysql57Controller.ClearSlowLog)
route.Get("rootPassword", mysql57Controller.GetRootPassword)
route.Post("rootPassword", mysql57Controller.SetRootPassword)
route.Get("database", mysql57Controller.DatabaseList)
@@ -48,13 +48,14 @@ func Plugin() {
route.Post("deleteDatabase", mysql57Controller.DeleteDatabase)
route.Get("backup", mysql57Controller.BackupList)
route.Post("createBackup", mysql57Controller.CreateBackup)
route.Post("uploadBackup", mysql57Controller.UploadBackup)
route.Post("deleteBackup", mysql57Controller.DeleteBackup)
route.Post("restoreBackup", mysql57Controller.RestoreBackup)
route.Get("user", mysql57Controller.UserList)
route.Post("addUser", mysql57Controller.AddUser)
route.Post("deleteUser", mysql57Controller.DeleteUser)
route.Post("setUserPassword", mysql57Controller.SetUserPassword)
route.Post("setUserPrivileges", mysql57Controller.SetUserPrivileges)
route.Post("userPassword", mysql57Controller.SetUserPassword)
route.Post("userPrivileges", mysql57Controller.SetUserPrivileges)
})
facades.Route().Prefix("api/plugins/mysql80").Middleware(middleware.Jwt()).Group(func(route route.Route) {
mysql80Controller := mysql80.NewMysql80Controller()
@@ -67,9 +68,9 @@ func Plugin() {
route.Get("config", mysql80Controller.GetConfig)
route.Post("config", mysql80Controller.SaveConfig)
route.Get("errorLog", mysql80Controller.ErrorLog)
route.Get("clearErrorLog", mysql80Controller.ClearErrorLog)
route.Post("clearErrorLog", mysql80Controller.ClearErrorLog)
route.Get("slowLog", mysql80Controller.SlowLog)
route.Get("clearSlowLog", mysql80Controller.ClearSlowLog)
route.Post("clearSlowLog", mysql80Controller.ClearSlowLog)
route.Get("rootPassword", mysql80Controller.GetRootPassword)
route.Post("rootPassword", mysql80Controller.SetRootPassword)
route.Get("database", mysql80Controller.DatabaseList)
@@ -77,13 +78,14 @@ func Plugin() {
route.Post("deleteDatabase", mysql80Controller.DeleteDatabase)
route.Get("backup", mysql80Controller.BackupList)
route.Post("createBackup", mysql80Controller.CreateBackup)
route.Post("uploadBackup", mysql80Controller.UploadBackup)
route.Post("deleteBackup", mysql80Controller.DeleteBackup)
route.Post("restoreBackup", mysql80Controller.RestoreBackup)
route.Get("user", mysql80Controller.UserList)
route.Post("addUser", mysql80Controller.AddUser)
route.Post("deleteUser", mysql80Controller.DeleteUser)
route.Post("setUserPassword", mysql80Controller.SetUserPassword)
route.Post("setUserPrivileges", mysql80Controller.SetUserPrivileges)
route.Post("userPassword", mysql80Controller.SetUserPassword)
route.Post("userPrivileges", mysql80Controller.SetUserPrivileges)
})
facades.Route().Prefix("api/plugins/php74").Middleware(middleware.Jwt()).Group(func(route route.Route) {
php74Controller := php74.NewPhp74Controller()
@@ -97,8 +99,8 @@ func Plugin() {
route.Post("config", php74Controller.SaveConfig)
route.Get("errorLog", php74Controller.ErrorLog)
route.Get("slowLog", php74Controller.SlowLog)
route.Get("clearErrorLog", php74Controller.ClearErrorLog)
route.Get("clearSlowLog", php74Controller.ClearSlowLog)
route.Post("clearErrorLog", php74Controller.ClearErrorLog)
route.Post("clearSlowLog", php74Controller.ClearSlowLog)
route.Get("extensions", php74Controller.GetExtensionList)
route.Post("installExtension", php74Controller.InstallExtension)
route.Post("uninstallExtension", php74Controller.UninstallExtension)
@@ -115,8 +117,8 @@ func Plugin() {
route.Post("config", php80Controller.SaveConfig)
route.Get("errorLog", php80Controller.ErrorLog)
route.Get("slowLog", php80Controller.SlowLog)
route.Get("clearErrorLog", php80Controller.ClearErrorLog)
route.Get("clearSlowLog", php80Controller.ClearSlowLog)
route.Post("clearErrorLog", php80Controller.ClearErrorLog)
route.Post("clearSlowLog", php80Controller.ClearSlowLog)
route.Get("extensions", php80Controller.GetExtensionList)
route.Post("installExtension", php80Controller.InstallExtension)
route.Post("uninstallExtension", php80Controller.UninstallExtension)

View File

@@ -165,7 +165,7 @@ Init_Panel() {
mkdir ${setup_Path}/server
mkdir ${setup_Path}/server/cron
mkdir ${setup_Path}/server/cron/logs
chmod -R 644 ${setup_Path}/server
chmod -R 755 ${setup_Path}/server
mkdir ${setup_Path}/panel
rm -rf ${setup_Path}/panel/*
# 下载面板zip包并解压

View File

@@ -266,16 +266,13 @@ elif [[ ${memTotal} -ge 32768 ]]; then
sed -i "s#^innodb_log_buffer_size.*#innodb_log_buffer_size = 512M#" ${mysqlPath}/conf/my.cnf
fi
chmod 644 ${mysqlPath}/conf/my.cnf
chown -R mysql:mysql ${mysqlPath}
chmod -R 755 ${mysqlPath}
# 初始化
rm -rf ${mysqlPath}/src
rm -rf ${mysqlPath}/data
mkdir -p ${mysqlPath}/data
chown -R mysql:mysql ${mysqlPath}
chmod -R 755 ${mysqlPath}
chmod 644 ${mysqlPath}/conf/my.cnf
${mysqlPath}/bin/mysqld --initialize-insecure --user=mysql --basedir=${mysqlPath} --datadir=${mysqlPath}/data
@@ -284,7 +281,7 @@ source /etc/profile
# 启动
cp ${mysqlPath}/lib/systemd/system/mysqld.service /etc/systemd/system/mysqld.service
sed -i "/ExecStartPre/d" /etc/systemd/system/mysqld.service
sed -i '/ExecStartPre/d' /etc/systemd/system/mysqld.service
systemctl daemon-reload
systemctl enable mysqld
@@ -292,7 +289,7 @@ systemctl start mysqld
${mysqlPath}/bin/mysqladmin -u root password ${mysqlPassword}
panel writePlugin mysqsl${1} ${mysqlVersion}
panel writePlugin mysql${1} ${mysqlVersion}
panel writeMysqlPassword ${mysqlPassword}
echo -e "${HR}\nMySQL-${1} 安装完成\n${HR}"

View File

@@ -39,10 +39,10 @@ if [ "${OS}" == "centos" ]; then
/usr/bin/crb enable
dnf makecache
dnf groupinstall "Development Tools" -y
dnf install tar unzip gd gd-devel git-core flex perl perl-CPAN oniguruma oniguruma-devel libsodium-devel libxml2-devel libxslt-devel GeoIP-devel bison yajl yajl-devel curl curl-devel libtermcap-devel ncurses-devel libevent-devel readline-devel libuuid-devel brotli-devel icu libicu libicu-devel openssl openssl-devel -y
dnf install tar unzip gd gd-devel git-core flex perl oniguruma oniguruma-devel libsodium-devel libxml2-devel libxslt-devel GeoIP-devel bison yajl yajl-devel curl curl-devel libtermcap-devel ncurses-devel libevent-devel readline-devel libuuid-devel brotli-devel icu libicu libicu-devel openssl openssl-devel -y
elif [ "${OS}" == "debian" ]; then
apt update
apt install build-essential tar unzip libgd3 libgd-dev git flex perl perl-modules libonig-dev libsodium-dev libxml2-dev libxslt1-dev libgeoip-dev bison libyajl-dev curl libcurl4-openssl-dev libncurses5-dev libevent-dev libreadline-dev uuid-dev libbrotli-dev icu-devtools libicu-dev openssl libssl-dev -y
apt install build-essential tar unzip libgd3 libgd-dev git flex perl libonig-dev libsodium-dev libxml2-dev libxslt1-dev libgeoip-dev bison libyajl-dev curl libcurl4-openssl-dev libncurses5-dev libevent-dev libreadline-dev uuid-dev libbrotli-dev icu-devtools libicu-dev openssl libssl-dev -y
else
echo -e $HR
echo "错误耗子Linux面板不支持该系统"

View File

@@ -174,7 +174,7 @@ pm.min_spare_servers = 5
pm.max_spare_servers = 10
request_terminate_timeout = 100
request_slowlog_timeout = 30
pm.status_path = /phpfpm_${phpVersion}_status
pm.status_path = /phpfpm_status/${phpVersion}
slowlog = var/log/slow.log
EOF

View File

@@ -18,6 +18,7 @@ limitations under the License.
'
HR="+----------------------------------------------------"
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://dl.cdn.haozi.net/panel/php_extensions"
action="$1"
@@ -34,7 +35,15 @@ Install() {
fi
# 安装依赖
dnf install ImageMagick ImageMagick-devel -y
if [ "${OS}" == "centos" ]; then
dnf install ImageMagick ImageMagick-devel -y
elif [ "${OS}" == "debian" ]; then
apt install imagemagick libmagickwand-dev
else
echo -e $HR
echo "错误耗子Linux面板不支持该系统"
exit 1
fi
cd /www/server/php/${phpVersion}/src/ext
rm -rf imagick