mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 01:12:17 +08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a5e1fbc232 | |||
| 1689ef4e4a | |||
| fb383f9f90 | |||
| 624fc9c77c | |||
| 793b2ee989 | |||
| e56cf991ae | |||
| 1daaab1e93 | |||
| b9f75534ef | |||
| 594dfea986 | |||
| 915403e3c0 | |||
| 0715a4e497 | |||
| 0ab4738e1d | |||
| 0d4f8bbea1 | |||
| ea59149175 | |||
| 87a7350f9a | |||
| 59243d5203 | |||
| 83e2526ce8 | |||
| 1f5718cd67 | |||
| 3d8f2948be | |||
| 6ef713ab3d | |||
| feb377a8f1 |
@@ -23,6 +23,7 @@ _cgo_export.*
|
||||
*.exe
|
||||
*.o
|
||||
*.so
|
||||
panel
|
||||
|
||||
# 压缩包 #
|
||||
# Git 自带压缩,如果这些压缩包里有代码,建议解压后 commit
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
|
||||
|
||||
```shell
|
||||
TODO
|
||||
bash <(curl -sSL https://dl.cdn.haozi.net/panel/install_panel.sh)
|
||||
```
|
||||
|
||||
## 日常维护
|
||||
|
||||
```shell
|
||||
TODO
|
||||
panel xxx
|
||||
```
|
||||
|
||||
## 问题反馈
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
"github.com/goravel/framework/contracts/console/command"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/app/services"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
@@ -36,10 +38,8 @@ func (receiver *Monitoring) Extend() command.Extend {
|
||||
// Handle Execute the console command.
|
||||
func (receiver *Monitoring) Handle(ctx console.Context) error {
|
||||
var setting models.Setting
|
||||
if err := facades.Orm().Query().Where("key", "monitor").First(&setting); err != nil {
|
||||
return nil
|
||||
}
|
||||
if setting.Value == "0" || len(setting.Value) == 0 {
|
||||
monitor := services.NewSettingImpl().Get(models.SettingKeyMonitor)
|
||||
if !cast.ToBool(monitor) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -50,7 +49,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
settings := []models.Setting{{Key: models.SettingKeyName, Value: "耗子Linux面板"}, {Key: models.SettingKeyMonitor, Value: "1"}, {Key: models.SettingKeyMonitorDays, Value: "30"}, {Key: models.SettingKeyBackupPath, Value: "/www/backup"}, {Key: models.SettingKeyWebsitePath, Value: "/www/wwwroot"}, {Key: models.SettingKeyPanelEntrance, Value: "/"}}
|
||||
settings := []models.Setting{{Key: models.SettingKeyName, Value: "耗子Linux面板"}, {Key: models.SettingKeyMonitor, Value: "1"}, {Key: models.SettingKeyMonitorDays, Value: "30"}, {Key: models.SettingKeyBackupPath, Value: "/www/backup"}, {Key: models.SettingKeyWebsitePath, Value: "/www/wwwroot"}, {Key: models.SettingKeyEntrance, Value: "/"}}
|
||||
err = facades.Orm().Query().Create(&settings)
|
||||
if err != nil {
|
||||
color.Redln("初始化失败")
|
||||
@@ -73,7 +72,19 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
color.Greenln("初始化成功")
|
||||
|
||||
case "update":
|
||||
err := tools.UpdatePanel()
|
||||
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))
|
||||
if err != nil {
|
||||
color.Redln("更新失败: " + err.Error())
|
||||
return nil
|
||||
@@ -105,19 +116,19 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
port := tools.ExecShell("cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}'")
|
||||
port := tools.ExecShell(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
|
||||
color.Greenln("用户名: " + user.Username)
|
||||
color.Greenln("密码: " + password)
|
||||
color.Greenln("面板端口: " + port)
|
||||
color.Greenln("面板入口: " + services.NewSettingImpl().Get(models.SettingKeyPanelEntrance, "/"))
|
||||
color.Greenln("面板入口: " + services.NewSettingImpl().Get(models.SettingKeyEntrance, "/"))
|
||||
|
||||
case "getPort":
|
||||
port := tools.ExecShell("cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}'")
|
||||
color.Greenln("面板端口: " + port)
|
||||
|
||||
case "getEntrance":
|
||||
color.Greenln("面板入口: " + services.NewSettingImpl().Get(models.SettingKeyPanelEntrance, "/"))
|
||||
color.Greenln("面板入口: " + services.NewSettingImpl().Get(models.SettingKeyEntrance, "/"))
|
||||
|
||||
case "writePlugin":
|
||||
slug := arg1
|
||||
@@ -177,14 +188,14 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
|
||||
color.Greenln("写入MySQL root密码成功")
|
||||
|
||||
case "cleanRunningTask":
|
||||
_, err := facades.Orm().Query().Model(&models.Task{}).Where("status", models.TaskStatusRunning).Update("status", models.TaskStatusFailed)
|
||||
case "cleanTask":
|
||||
_, err := facades.Orm().Query().Model(&models.Task{}).Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).Update("status", models.TaskStatusFailed)
|
||||
if err != nil {
|
||||
color.Redln("清理正在运行的任务失败")
|
||||
color.Redln("清理任务失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("清理正在运行的任务成功")
|
||||
color.Greenln("清理任务成功")
|
||||
|
||||
case "backup":
|
||||
|
||||
@@ -279,15 +290,15 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
default:
|
||||
color.Yellowln(facades.Config().GetString("panel.name") + "命令行工具 - " + facades.Config().GetString("panel.version"))
|
||||
color.Greenln("请使用以下命令:")
|
||||
color.Greenln("panel update 更新/修复面板到最新版本")
|
||||
color.Greenln("panel update {proxy} 更新/修复面板到最新版本")
|
||||
color.Greenln("panel getInfo 重新初始化面板账号信息")
|
||||
color.Greenln("panel getPort 获取面板访问端口")
|
||||
color.Greenln("panel getEntrance 获取面板访问入口")
|
||||
color.Greenln("panel cleanRunningTask 强制清理面板正在运行的任务")
|
||||
color.Greenln("panel cleanTask 清理面板运行中和等待中的任务")
|
||||
color.Greenln("panel backup {website/mysql/postgresql} {name} {path} 备份网站/MySQL数据库/PostgreSQL数据库到指定目录")
|
||||
color.Redln("以下命令请在开发者指导下使用:")
|
||||
color.Yellowln("panel init 初始化面板")
|
||||
color.Yellowln("panel writePlugin {slug} 写入插件安装状态")
|
||||
color.Yellowln("panel writePlugin {slug} {version} 写入插件安装状态")
|
||||
color.Yellowln("panel deletePlugin {slug} 移除插件安装状态")
|
||||
color.Yellowln("panel writeMysqlPassword {password} 写入MySQL root密码")
|
||||
color.Yellowln("panel writeSite {name} {status} {path} {php} {ssl} 写入网站数据到面板")
|
||||
|
||||
@@ -3,6 +3,7 @@ package console
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/console"
|
||||
"github.com/goravel/framework/contracts/schedule"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/console/commands"
|
||||
)
|
||||
@@ -11,7 +12,9 @@ type Kernel struct {
|
||||
}
|
||||
|
||||
func (kernel *Kernel) Schedule() []schedule.Event {
|
||||
return []schedule.Event{}
|
||||
return []schedule.Event{
|
||||
facades.Schedule().Command("panel:monitoring").EveryMinute().SkipIfStillRunning(),
|
||||
}
|
||||
}
|
||||
|
||||
func (kernel *Kernel) Commands() []console.Command {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
@@ -44,7 +45,7 @@ func (r *CronController) List(ctx http.Context) {
|
||||
func (r *CronController) Add(ctx http.Context) {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"name": "required|min_len:1|max_len:255",
|
||||
"time": "required|regex:^((\\*|\\d+|\\d+-\\d+|\\d+\\/\\d+|\\d+-\\d+\\/\\d+|\\*\\/\\d+)(\\,(\\*|\\d+|\\d+-\\d+|\\d+\\/\\d+|\\d+-\\d+\\/\\d+|\\*\\/\\d+))*\\s?){5}$",
|
||||
"time": "required",
|
||||
"script": "required",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -52,7 +53,13 @@ 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
|
||||
}
|
||||
|
||||
// 单独验证时间格式
|
||||
if !regexp.MustCompile(`^((\*|\d+|\d+-\d+|\d+/\d+|\d+-\d+/\d+|\*/\d+)(,(\*|\d+|\d+-\d+|\d+/\d+|\d+-\d+/\d+|\*/\d+))*\s?){5}$`).MatchString(ctx.Request().Input("time")) {
|
||||
Error(ctx, http.StatusBadRequest, "时间格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -70,7 +77,7 @@ func (r *CronController) Add(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
shellFile := strconv.Itoa(int(carbon.Now().Timestamp())) + tools.RandomString(16)
|
||||
if !tools.WriteFile(shellDir+shellFile+".sh", ctx.Request().Input("script"), 0644) {
|
||||
if !tools.WriteFile(shellDir+shellFile+".sh", ctx.Request().Input("script"), 0700) {
|
||||
facades.Log().Error("[面板][CronController] 创建计划任务脚本失败 ", err)
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return
|
||||
@@ -99,11 +106,22 @@ func (r *CronController) Add(ctx http.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// Script 获取脚本内容
|
||||
func (r *CronController) Script(ctx http.Context) {
|
||||
var cron models.Cron
|
||||
err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusBadRequest, "计划任务不存在")
|
||||
return
|
||||
}
|
||||
|
||||
Success(ctx, tools.ReadFile(cron.Shell))
|
||||
}
|
||||
|
||||
func (r *CronController) Update(ctx http.Context) {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"id": "required|int",
|
||||
"name": "required|min_len:1|max_len:255",
|
||||
"time": "required|regex:^((\\*|\\d+|\\d+-\\d+|\\d+\\/\\d+|\\d+-\\d+\\/\\d+|\\*\\/\\d+)(\\,(\\*|\\d+|\\d+-\\d+|\\d+\\/\\d+|\\d+-\\d+\\/\\d+|\\*\\/\\d+))*\\s?){5}$",
|
||||
"time": "required",
|
||||
"script": "required",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -111,7 +129,13 @@ 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
|
||||
}
|
||||
|
||||
// 单独验证时间格式
|
||||
if !regexp.MustCompile(`^((\*|\d+|\d+-\d+|\d+/\d+|\d+-\d+/\d+|\*/\d+)(,(\*|\d+|\d+-\d+|\d+/\d+|\d+-\d+/\d+|\*/\d+))*\s?){5}$`).MatchString(ctx.Request().Input("time")) {
|
||||
Error(ctx, http.StatusBadRequest, "时间格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -122,6 +146,20 @@ func (r *CronController) Update(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if !cron.Status {
|
||||
Error(ctx, http.StatusBadRequest, "计划任务已禁用")
|
||||
return
|
||||
}
|
||||
|
||||
cron.Time = ctx.Request().Input("time")
|
||||
cron.Name = ctx.Request().Input("name")
|
||||
err = facades.Orm().Query().Save(&cron)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][CronController] 更新计划任务失败 ", err)
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return
|
||||
}
|
||||
|
||||
if !tools.WriteFile(cron.Shell, ctx.Request().Input("script"), 0644) {
|
||||
facades.Log().Error("[面板][CronController] 更新计划任务脚本失败 ", err)
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
@@ -138,25 +176,16 @@ func (r *CronController) Update(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (r *CronController) Delete(ctx http.Context) {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"id": "required|int",
|
||||
})
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
Error(ctx, http.StatusBadRequest, validator.Errors().All())
|
||||
return
|
||||
}
|
||||
|
||||
var cron models.Cron
|
||||
err = facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
|
||||
err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusBadRequest, "计划任务不存在")
|
||||
return
|
||||
}
|
||||
|
||||
r.cron.DeleteFromSystem(cron)
|
||||
tools.RemoveFile(cron.Shell)
|
||||
|
||||
_, err = facades.Orm().Query().Delete(&cron)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][CronController] 删除计划任务失败 ", err)
|
||||
@@ -164,22 +193,19 @@ func (r *CronController) Delete(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
r.cron.DeleteFromSystem(cron)
|
||||
|
||||
Success(ctx, nil)
|
||||
}
|
||||
|
||||
func (r *CronController) Status(ctx http.Context) {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"id": "required|int",
|
||||
"status": "required|in:true,false",
|
||||
"status": "bool",
|
||||
})
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
Error(ctx, http.StatusBadRequest, validator.Errors().All())
|
||||
Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -191,7 +217,7 @@ func (r *CronController) Status(ctx http.Context) {
|
||||
}
|
||||
|
||||
cron.Status = ctx.Request().InputBool("status")
|
||||
_, err = facades.Orm().Query().Update(&cron)
|
||||
err = facades.Orm().Query().Save(&cron)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][CronController] 更新计划任务状态失败 ", err)
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
@@ -207,20 +233,8 @@ func (r *CronController) Status(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (r *CronController) Log(ctx http.Context) {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"id": "required|int",
|
||||
})
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
Error(ctx, http.StatusBadRequest, validator.Errors().All())
|
||||
return
|
||||
}
|
||||
|
||||
var cron models.Cron
|
||||
err = facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
|
||||
err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusBadRequest, "计划任务不存在")
|
||||
return
|
||||
@@ -231,7 +245,5 @@ func (r *CronController) Log(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
Success(ctx, http.Json{
|
||||
"log": tools.ReadFile(cron.Log),
|
||||
})
|
||||
Success(ctx, tools.ReadFile(cron.Log))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
package controllers
|
||||
|
||||
import "github.com/goravel/framework/contracts/http"
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/services"
|
||||
)
|
||||
|
||||
func Success(ctx http.Context, data any) {
|
||||
ctx.Response().Success().Json(http.Json{
|
||||
@@ -16,3 +23,51 @@ func Error(ctx http.Context, code int, message any) {
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
// Check 检查插件是否可用
|
||||
func Check(ctx http.Context, slug string) bool {
|
||||
plugin := services.NewPluginImpl().GetBySlug(slug)
|
||||
installedPlugin := services.NewPluginImpl().GetInstalledBySlug(slug)
|
||||
installedPlugins, err := services.NewPluginImpl().AllInstalled()
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][插件] 获取已安装插件失败")
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return false
|
||||
}
|
||||
|
||||
if installedPlugin.Version != plugin.Version || installedPlugin.Slug != plugin.Slug {
|
||||
Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要更新至 "+plugin.Version+" 版本")
|
||||
return false
|
||||
}
|
||||
|
||||
var lock sync.RWMutex
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
lock.Lock()
|
||||
pluginsMap[p.Slug] = true
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
lock.RLock()
|
||||
_, requireFound := pluginsMap[require]
|
||||
lock.RUnlock()
|
||||
if !requireFound {
|
||||
Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
lock.RLock()
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
lock.RUnlock()
|
||||
if excludeFound {
|
||||
Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
@@ -19,16 +20,16 @@ type MenuItem struct {
|
||||
}
|
||||
|
||||
type InfoController struct {
|
||||
// Dependent services
|
||||
plugin services.Plugin
|
||||
}
|
||||
|
||||
func NewInfoController() *InfoController {
|
||||
return &InfoController{
|
||||
// Inject services
|
||||
plugin: services.NewPluginImpl(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InfoController) Name(ctx http.Context) {
|
||||
func (c *InfoController) Name(ctx http.Context) {
|
||||
var setting models.Setting
|
||||
err := facades.Orm().Query().Where("key", "name").First(&setting)
|
||||
if err != nil {
|
||||
@@ -42,20 +43,20 @@ func (r *InfoController) Name(ctx http.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (r *InfoController) Menu(ctx http.Context) {
|
||||
func (c *InfoController) Menu(ctx http.Context) {
|
||||
Success(ctx, []MenuItem{
|
||||
{Name: "home", Title: "主页", Icon: "layui-icon-home", Jump: "/"},
|
||||
{Name: "website", Title: "网站管理", Icon: "layui-icon-website", Jump: "website/list"},
|
||||
{Name: "monitor", Title: "资源监控", Icon: "layui-icon-chart-screen", Jump: "monitor"},
|
||||
{Name: "safe", Title: "系统安全", Icon: "layui-icon-auz", Jump: "safe"},
|
||||
{Name: "file", Title: "文件管理", Icon: "layui-icon-file", Jump: "file"},
|
||||
/*{Name: "file", Title: "文件管理", Icon: "layui-icon-file", Jump: "file"},*/
|
||||
{Name: "cron", Title: "计划任务", Icon: "layui-icon-date", Jump: "cron"},
|
||||
{Name: "plugin", Title: "插件中心", Icon: "layui-icon-app", Jump: "plugin"},
|
||||
{Name: "setting", Title: "面板设置", Icon: "layui-icon-set", Jump: "setting"},
|
||||
})
|
||||
}
|
||||
|
||||
func (r *InfoController) HomePlugins(ctx http.Context) {
|
||||
func (c *InfoController) HomePlugins(ctx http.Context) {
|
||||
var plugins []models.Plugin
|
||||
err := facades.Orm().Query().Where("show", 1).Find(&plugins)
|
||||
if err != nil {
|
||||
@@ -80,11 +81,11 @@ func (r *InfoController) HomePlugins(ctx http.Context) {
|
||||
Success(ctx, pluginsJson)
|
||||
}
|
||||
|
||||
func (r *InfoController) NowMonitor(ctx http.Context) {
|
||||
func (c *InfoController) NowMonitor(ctx http.Context) {
|
||||
Success(ctx, tools.GetMonitoringInfo())
|
||||
}
|
||||
|
||||
func (r *InfoController) SystemInfo(ctx http.Context) {
|
||||
func (c *InfoController) SystemInfo(ctx http.Context) {
|
||||
monitorInfo := tools.GetMonitoringInfo()
|
||||
|
||||
Success(ctx, http.Json{
|
||||
@@ -94,7 +95,7 @@ func (r *InfoController) SystemInfo(ctx http.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (r *InfoController) InstalledDbAndPhp(ctx http.Context) {
|
||||
func (c *InfoController) InstalledDbAndPhp(ctx http.Context) {
|
||||
var php []models.Plugin
|
||||
err := facades.Orm().Query().Where("slug like ?", "php%").Find(&php)
|
||||
if err != nil {
|
||||
@@ -116,9 +117,66 @@ func (r *InfoController) InstalledDbAndPhp(ctx http.Context) {
|
||||
postgresqlInstalled = false
|
||||
}
|
||||
|
||||
type data struct {
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
var phpData []data
|
||||
phpData = append(phpData, data{Slug: "0", Name: "不使用"})
|
||||
for _, p := range php {
|
||||
phpData = append(phpData, data{Slug: strings.ReplaceAll(p.Slug, "php", ""), Name: c.plugin.GetBySlug(p.Slug).Name})
|
||||
}
|
||||
|
||||
Success(ctx, http.Json{
|
||||
"php": php,
|
||||
"php": phpData,
|
||||
"mysql": mysqlInstalled,
|
||||
"postgresql": postgresqlInstalled,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *InfoController) CheckUpdate(ctx http.Context) {
|
||||
version := facades.Config().GetString("panel.version")
|
||||
remote, err := tools.GetLatestPanelVersion()
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusInternalServerError, "获取最新版本失败")
|
||||
return
|
||||
}
|
||||
|
||||
if version == remote.Version {
|
||||
Success(ctx, http.Json{
|
||||
"update": false,
|
||||
"version": remote.Version,
|
||||
"name": remote.Name,
|
||||
"body": remote.Body,
|
||||
"date": remote.Date,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
Success(ctx, http.Json{
|
||||
"update": true,
|
||||
"version": remote.Version,
|
||||
"name": remote.Name,
|
||||
"body": remote.Body,
|
||||
"date": remote.Date,
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][InfoController] 更新面板失败 ", err.Error())
|
||||
Error(ctx, http.StatusInternalServerError, "更新失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
Success(ctx, nil)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/spf13/cast"
|
||||
"panel/app/models"
|
||||
|
||||
"panel/app/services"
|
||||
)
|
||||
|
||||
@@ -46,9 +47,20 @@ func (r *MonitorController) SaveDays(ctx http.Context) {
|
||||
Success(ctx, nil)
|
||||
}
|
||||
|
||||
// SwitchAndDays 监控开关和监控天数
|
||||
func (r *MonitorController) SwitchAndDays(ctx http.Context) {
|
||||
monitor := r.setting.Get(models.SettingKeyMonitor)
|
||||
monitorDays := r.setting.Get(models.SettingKeyMonitorDays)
|
||||
|
||||
Success(ctx, http.Json{
|
||||
"switch": cast.ToBool(monitor),
|
||||
"days": cast.ToInt(monitorDays),
|
||||
})
|
||||
}
|
||||
|
||||
// Clear 清空监控数据
|
||||
func (r *MonitorController) Clear(ctx http.Context) {
|
||||
_, err := facades.Orm().Query().Delete(&models.Monitor{})
|
||||
_, err := facades.Orm().Query().Where("1 = 1").Delete(&models.Monitor{})
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][MonitorController] 清空监控数据失败 ", err)
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
@@ -60,18 +72,97 @@ func (r *MonitorController) Clear(ctx http.Context) {
|
||||
|
||||
// List 监控数据列表
|
||||
func (r *MonitorController) List(ctx http.Context) {
|
||||
start := ctx.Request().Input("start")
|
||||
end := ctx.Request().Input("end")
|
||||
startTime := carbon.Parse(start)
|
||||
endTime := carbon.Parse(end)
|
||||
start := ctx.Request().InputInt64("start")
|
||||
end := ctx.Request().InputInt64("end")
|
||||
startTime := carbon.FromTimestampMilli(start)
|
||||
endTime := carbon.FromTimestampMilli(end)
|
||||
|
||||
var monitors []models.Monitor
|
||||
err := facades.Orm().Query().Where("created_at", ">=", startTime).Where("created_at", "<=", endTime).Get(&monitors)
|
||||
err := facades.Orm().Query().Where("created_at >= ?", startTime.ToDateTimeString()).Where("created_at <= ?", endTime.ToDateTimeString()).Get(&monitors)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][MonitorController] 查询监控数据失败 ", err)
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return
|
||||
}
|
||||
|
||||
Success(ctx, monitors)
|
||||
if len(monitors) == 0 {
|
||||
Error(ctx, http.StatusNotFound, "监控数据为空")
|
||||
return
|
||||
}
|
||||
|
||||
type load struct {
|
||||
Load1 []float64 `json:"load1"`
|
||||
Load5 []float64 `json:"load5"`
|
||||
Load15 []float64 `json:"load15"`
|
||||
}
|
||||
type cpu struct {
|
||||
Percent []string `json:"percent"`
|
||||
}
|
||||
type mem struct {
|
||||
Total string `json:"total"`
|
||||
Available []string `json:"available"`
|
||||
Used []string `json:"used"`
|
||||
}
|
||||
type swap struct {
|
||||
Total string `json:"total"`
|
||||
Used []string `json:"used"`
|
||||
Free []string `json:"free"`
|
||||
}
|
||||
type network struct {
|
||||
Sent []string `json:"sent"`
|
||||
Recv []string `json:"recv"`
|
||||
Tx []string `json:"tx"`
|
||||
Rx []string `json:"rx"`
|
||||
}
|
||||
type monitorData struct {
|
||||
Times []string `json:"times"`
|
||||
Load load `json:"load"`
|
||||
Cpu cpu `json:"cpu"`
|
||||
Mem mem `json:"mem"`
|
||||
Swap swap `json:"swap"`
|
||||
Net network `json:"net"`
|
||||
}
|
||||
|
||||
var data monitorData
|
||||
var bytesSent uint64
|
||||
var bytesRecv uint64
|
||||
var bytesSent2 uint64
|
||||
var bytesRecv2 uint64
|
||||
for _, net := range monitors[0].Info.Net {
|
||||
bytesSent += net.BytesSent
|
||||
bytesRecv += net.BytesRecv
|
||||
}
|
||||
for i, monitor := range monitors {
|
||||
// 跳过第一条数据,因为第一条数据的流量为 0
|
||||
if i == 0 {
|
||||
// MB
|
||||
data.Mem.Total = fmt.Sprintf("%.2f", float64(monitor.Info.Mem.Total)/1024/1024)
|
||||
data.Swap.Total = fmt.Sprintf("%.2f", float64(monitor.Info.Swap.Total)/1024/1024)
|
||||
continue
|
||||
}
|
||||
for _, net := range monitor.Info.Net {
|
||||
bytesSent2 += net.BytesSent
|
||||
bytesRecv2 += net.BytesRecv
|
||||
}
|
||||
data.Times = append(data.Times, monitor.CreatedAt.ToDateTimeString())
|
||||
data.Load.Load1 = append(data.Load.Load1, monitor.Info.Load.Load1)
|
||||
data.Load.Load5 = append(data.Load.Load5, monitor.Info.Load.Load5)
|
||||
data.Load.Load15 = append(data.Load.Load15, monitor.Info.Load.Load15)
|
||||
data.Cpu.Percent = append(data.Cpu.Percent, fmt.Sprintf("%.2f", monitor.Info.Percent[0]))
|
||||
data.Mem.Available = append(data.Mem.Available, fmt.Sprintf("%.2f", float64(monitor.Info.Mem.Available)/1024/1024))
|
||||
data.Mem.Used = append(data.Mem.Used, fmt.Sprintf("%.2f", float64(monitor.Info.Mem.Used)/1024/1024))
|
||||
data.Swap.Used = append(data.Swap.Used, fmt.Sprintf("%.2f", float64(monitor.Info.Swap.Used)/1024/1024))
|
||||
data.Swap.Free = append(data.Swap.Free, fmt.Sprintf("%.2f", float64(monitor.Info.Swap.Free)/1024/1024))
|
||||
data.Net.Sent = append(data.Net.Sent, fmt.Sprintf("%.2f", float64(bytesSent2/1024)))
|
||||
data.Net.Recv = append(data.Net.Recv, fmt.Sprintf("%.2f", float64(bytesRecv2/1024)))
|
||||
|
||||
// 监控频率为 1 分钟,所以这里除以 60 即可得到每秒的流量
|
||||
data.Net.Tx = append(data.Net.Tx, fmt.Sprintf("%.2f", float64(bytesSent2-bytesSent)/60/1024))
|
||||
data.Net.Rx = append(data.Net.Rx, fmt.Sprintf("%.2f", float64(bytesRecv2-bytesRecv)/60/1024))
|
||||
|
||||
bytesSent = bytesSent2
|
||||
bytesRecv = bytesRecv2
|
||||
}
|
||||
|
||||
Success(ctx, data)
|
||||
}
|
||||
|
||||
@@ -80,35 +80,53 @@ func (r *PluginController) List(ctx http.Context) {
|
||||
// Install 安装插件
|
||||
func (r *PluginController) Install(ctx http.Context) {
|
||||
slug := ctx.Request().Input("slug")
|
||||
plugins := r.plugin.All()
|
||||
|
||||
var plugin services.PanelPlugin
|
||||
check := false
|
||||
for _, item := range plugins {
|
||||
if item.Slug == slug {
|
||||
check = true
|
||||
plugin = item
|
||||
break
|
||||
}
|
||||
}
|
||||
if !check {
|
||||
Error(ctx, http.StatusBadRequest, "插件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var installedPlugin models.Plugin
|
||||
if err := facades.Orm().Query().Where("slug", slug).First(&installedPlugin); err != nil {
|
||||
plugin := r.plugin.GetBySlug(slug)
|
||||
installedPlugin := r.plugin.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.plugin.AllInstalled()
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][PluginController] 获取已安装插件失败")
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return
|
||||
}
|
||||
|
||||
if installedPlugin.ID != 0 {
|
||||
Error(ctx, http.StatusBadRequest, "插件已安装")
|
||||
return
|
||||
}
|
||||
|
||||
var lock sync.RWMutex
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
lock.Lock()
|
||||
pluginsMap[p.Slug] = true
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
lock.RLock()
|
||||
_, requireFound := pluginsMap[require]
|
||||
lock.RUnlock()
|
||||
if !requireFound {
|
||||
Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
lock.RLock()
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
lock.RUnlock()
|
||||
if excludeFound {
|
||||
Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "安装插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
task.Shell = "bash scripts/plugins/" + plugin.Slug + "/install.sh >> /tmp/" + plugin.Slug + ".log 2>&1"
|
||||
task.Shell = plugin.Install + " >> /tmp/" + plugin.Slug + ".log 2>&1"
|
||||
task.Log = "/tmp/" + plugin.Slug + ".log"
|
||||
if err := facades.Orm().Query().Create(&task); err != nil {
|
||||
facades.Log().Error("[面板][PluginController] 创建任务失败: " + err.Error())
|
||||
@@ -123,35 +141,53 @@ func (r *PluginController) Install(ctx http.Context) {
|
||||
// Uninstall 卸载插件
|
||||
func (r *PluginController) Uninstall(ctx http.Context) {
|
||||
slug := ctx.Request().Input("slug")
|
||||
plugins := r.plugin.All()
|
||||
|
||||
var plugin services.PanelPlugin
|
||||
check := false
|
||||
for _, item := range plugins {
|
||||
if item.Slug == slug {
|
||||
check = true
|
||||
plugin = item
|
||||
break
|
||||
}
|
||||
}
|
||||
if !check {
|
||||
Error(ctx, http.StatusBadRequest, "插件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var installedPlugin models.Plugin
|
||||
if err := facades.Orm().Query().Where("slug", slug).First(&installedPlugin); err != nil {
|
||||
plugin := r.plugin.GetBySlug(slug)
|
||||
installedPlugin := r.plugin.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.plugin.AllInstalled()
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][PluginController] 获取已安装插件失败")
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return
|
||||
}
|
||||
|
||||
if installedPlugin.ID == 0 {
|
||||
Error(ctx, http.StatusBadRequest, "插件未安装")
|
||||
return
|
||||
}
|
||||
|
||||
var lock sync.RWMutex
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
lock.Lock()
|
||||
pluginsMap[p.Slug] = true
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
lock.RLock()
|
||||
_, requireFound := pluginsMap[require]
|
||||
lock.RUnlock()
|
||||
if !requireFound {
|
||||
Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
lock.RLock()
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
lock.RUnlock()
|
||||
if excludeFound {
|
||||
Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "卸载插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
task.Shell = "bash scripts/plugins/" + plugin.Slug + "/uninstall.sh >> /tmp/" + plugin.Slug + ".log 2>&1"
|
||||
task.Shell = plugin.Uninstall + " >> /tmp/" + plugin.Slug + ".log 2>&1"
|
||||
task.Log = "/tmp/" + plugin.Slug + ".log"
|
||||
if err := facades.Orm().Query().Create(&task); err != nil {
|
||||
facades.Log().Error("[面板][PluginController] 创建任务失败: " + err.Error())
|
||||
@@ -166,35 +202,53 @@ func (r *PluginController) Uninstall(ctx http.Context) {
|
||||
// Update 更新插件
|
||||
func (r *PluginController) Update(ctx http.Context) {
|
||||
slug := ctx.Request().Input("slug")
|
||||
plugins := r.plugin.All()
|
||||
|
||||
var plugin services.PanelPlugin
|
||||
check := false
|
||||
for _, item := range plugins {
|
||||
if item.Slug == slug {
|
||||
check = true
|
||||
plugin = item
|
||||
break
|
||||
}
|
||||
}
|
||||
if !check {
|
||||
Error(ctx, http.StatusBadRequest, "插件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var installedPlugin models.Plugin
|
||||
if err := facades.Orm().Query().Where("slug", slug).First(&installedPlugin); err != nil {
|
||||
plugin := r.plugin.GetBySlug(slug)
|
||||
installedPlugin := r.plugin.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.plugin.AllInstalled()
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][PluginController] 获取已安装插件失败")
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return
|
||||
}
|
||||
|
||||
if installedPlugin.ID == 0 {
|
||||
Error(ctx, http.StatusBadRequest, "插件未安装")
|
||||
return
|
||||
}
|
||||
|
||||
var lock sync.RWMutex
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
lock.Lock()
|
||||
pluginsMap[p.Slug] = true
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
lock.RLock()
|
||||
_, requireFound := pluginsMap[require]
|
||||
lock.RUnlock()
|
||||
if !requireFound {
|
||||
Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
lock.RLock()
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
lock.RUnlock()
|
||||
if excludeFound {
|
||||
Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "更新插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
task.Shell = "bash scripts/plugins/" + plugin.Slug + "/update.sh >> /tmp/" + plugin.Slug + ".log 2>&1"
|
||||
task.Shell = plugin.Update + " >> /tmp/" + plugin.Slug + ".log 2>&1"
|
||||
task.Log = "/tmp/" + plugin.Slug + ".log"
|
||||
if err := facades.Orm().Query().Create(&task); err != nil {
|
||||
facades.Log().Error("[面板][PluginController] 创建任务失败: " + err.Error())
|
||||
|
||||
@@ -0,0 +1,825 @@
|
||||
package mysql57
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/spf13/cast"
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/models"
|
||||
"panel/app/services"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
type Mysql57Controller struct {
|
||||
setting services.Setting
|
||||
}
|
||||
|
||||
func NewMysql57Controller() *Mysql57Controller {
|
||||
return &Mysql57Controller{
|
||||
setting: services.NewSettingImpl(),
|
||||
}
|
||||
}
|
||||
|
||||
// Status 获取运行状态
|
||||
func (c *Mysql57Controller) Status(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Reload 重载配置
|
||||
func (c *Mysql57Controller) Reload(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Restart 重启服务
|
||||
func (c *Mysql57Controller) Restart(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动服务
|
||||
func (c *Mysql57Controller) Start(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop 停止服务
|
||||
func (c *Mysql57Controller) Stop(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if status != "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfig 获取配置
|
||||
func (c *Mysql57Controller) GetConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
config := tools.ReadFile("/www/server/mysql/conf/my.cnf")
|
||||
if len(config) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// SaveConfig 保存配置
|
||||
func (c *Mysql57Controller) SaveConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
config := ctx.Request().Input("config")
|
||||
if len(config) == 0 {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "配置不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if !tools.WriteFile("/www/server/mysql/conf/my.cnf", config, 0644) {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "写入MySQL配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, "保存MySQL配置成功")
|
||||
}
|
||||
|
||||
// Load 获取负载
|
||||
func (c *Mysql57Controller) Load(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if len(rootPassword) == 0 {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码为空")
|
||||
return
|
||||
}
|
||||
|
||||
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if status != "active" {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "MySQL 已停止运行")
|
||||
return
|
||||
}
|
||||
|
||||
raw := tools.ExecShell("mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
|
||||
if strings.Contains(raw, "Access denied for user") {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码错误")
|
||||
return
|
||||
}
|
||||
if !strings.Contains(raw, "Uptime") {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL负载失败")
|
||||
return
|
||||
}
|
||||
|
||||
data := make(map[int]map[string]string)
|
||||
expressions := []struct {
|
||||
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_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 {
|
||||
re := regexp.MustCompile(expression.regex)
|
||||
matches := re.FindStringSubmatch(raw)
|
||||
if len(matches) > 1 {
|
||||
data[i] = make(map[string]string)
|
||||
data[i] = map[string]string{"name": expression.name, "value": matches[1]}
|
||||
|
||||
if expression.name == "发送" || expression.name == "接收" {
|
||||
data[i]["value"] = tools.FormatBytes(cast.ToFloat64(matches[1]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 索引命中率
|
||||
readRequests := cast.ToFloat64(data[9]["value"])
|
||||
reads := cast.ToFloat64(data[10]["value"])
|
||||
data[9]["value"] = fmt.Sprintf("%.2f%%", readRequests/(reads+readRequests)*100)
|
||||
// Innodb 索引命中率
|
||||
bufferPoolReads := cast.ToFloat64(data[11]["value"])
|
||||
bufferPoolReadRequests := cast.ToFloat64(data[12]["value"])
|
||||
data[10]["value"] = fmt.Sprintf("%.2f%%", bufferPoolReadRequests/(bufferPoolReads+bufferPoolReadRequests)*100)
|
||||
|
||||
controllers.Success(ctx, data)
|
||||
}
|
||||
|
||||
// ErrorLog 获取错误日志
|
||||
func (c *Mysql57Controller) ErrorLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
log := tools.ExecShell("tail -n 100 /www/server/mysql/mysql-error.log")
|
||||
controllers.Success(ctx, log)
|
||||
}
|
||||
|
||||
// ClearErrorLog 清空错误日志
|
||||
func (c *Mysql57Controller) ClearErrorLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("echo '' > /www/server/mysql/mysql-error.log")
|
||||
controllers.Success(ctx, "清空错误日志成功")
|
||||
}
|
||||
|
||||
// SlowLog 获取慢查询日志
|
||||
func (c *Mysql57Controller) SlowLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
log := tools.ExecShell("tail -n 100 /www/server/mysql/mysql-slow.log")
|
||||
controllers.Success(ctx, log)
|
||||
}
|
||||
|
||||
// ClearSlowLog 清空慢查询日志
|
||||
func (c *Mysql57Controller) ClearSlowLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("echo '' > /www/server/mysql/mysql-slow.log")
|
||||
controllers.Success(ctx, "清空慢查询日志成功")
|
||||
}
|
||||
|
||||
// GetRootPassword 获取root密码
|
||||
func (c *Mysql57Controller) GetRootPassword(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if len(rootPassword) == 0 {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码为空")
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, rootPassword)
|
||||
}
|
||||
|
||||
// SetRootPassword 设置root密码
|
||||
func (c *Mysql57Controller) SetRootPassword(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if status != "active" {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行")
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := ctx.Request().Input(models.SettingKeyMysqlRootPassword)
|
||||
if len(rootPassword) == 0 {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
oldRootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if oldRootPassword != rootPassword {
|
||||
tools.ExecShell("mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + oldRootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
err := c.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword)
|
||||
if err != nil {
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
controllers.Success(ctx, "设置root密码成功")
|
||||
}
|
||||
|
||||
// DatabaseList 获取数据库列表
|
||||
func (c *Mysql57Controller) DatabaseList(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
type database struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
facades.Log().Error("[MySQL57] 获取数据库列表失败" + err.Error())
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, databases)
|
||||
}
|
||||
|
||||
// AddDatabase 添加数据库
|
||||
func (c *Mysql57Controller) AddDatabase(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
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",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
database := ctx.Request().Input("database")
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + database + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "添加数据库成功")
|
||||
}
|
||||
|
||||
// DeleteDatabase 删除数据库
|
||||
func (c *Mysql57Controller) DeleteDatabase(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
database := ctx.Request().Input("database")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\"")
|
||||
|
||||
controllers.Success(ctx, "删除数据库成功")
|
||||
}
|
||||
|
||||
// BackupList 获取备份列表
|
||||
func (c *Mysql57Controller) BackupList(ctx http.Context) {
|
||||
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql"
|
||||
|
||||
if !tools.Exists(backupPath) {
|
||||
tools.Mkdir(backupPath, 0644)
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(backupPath)
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var backupFiles []map[string]string
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := file.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
backupFiles = append(backupFiles, map[string]string{
|
||||
"file": file.Name(),
|
||||
"size": tools.FormatBytes(float64(info.Size())),
|
||||
})
|
||||
}
|
||||
|
||||
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") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
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 := database + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
|
||||
if !tools.Exists(backupPath) {
|
||||
tools.Mkdir(backupPath, 0644)
|
||||
}
|
||||
err = os.Setenv("MYSQL_PWD", rootPassword)
|
||||
if err != nil {
|
||||
facades.Log().Error("[MySQL57] 设置环境变量 MYSQL_PWD 失败:" + err.Error())
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "备份失败")
|
||||
return
|
||||
}
|
||||
|
||||
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, "备份成功")
|
||||
}
|
||||
|
||||
// DeleteBackup 删除备份
|
||||
func (c *Mysql57Controller) DeleteBackup(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"file": "required|min_len:1|max_len:255",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql"
|
||||
file := ctx.Request().Input("file")
|
||||
tools.RemoveFile(backupPath + "/" + file)
|
||||
|
||||
controllers.Success(ctx, "删除备份成功")
|
||||
}
|
||||
|
||||
// RestoreBackup 还原备份
|
||||
func (c *Mysql57Controller) RestoreBackup(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"file": "required|min_len:1|max_len:255",
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql"
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
file := ctx.Request().Input("file")
|
||||
backupFile := backupPath + "/" + file
|
||||
if !tools.Exists(backupFile) {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "备份文件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Setenv("MYSQL_PWD", rootPassword)
|
||||
if err != nil {
|
||||
facades.Log().Error("[MYSQL57] 设置环境变量 MYSQL_PWD 失败:" + err.Error())
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "还原失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取文件拓展名
|
||||
ext := filepath.Ext(file)
|
||||
switch ext {
|
||||
case ".zip":
|
||||
tools.ExecShell("unzip -o " + backupFile + " -d " + backupPath)
|
||||
backupFile = strings.TrimSuffix(backupFile, ext)
|
||||
case ".gz":
|
||||
if strings.HasSuffix(file, ".tar.gz") {
|
||||
// 解压.tar.gz文件
|
||||
tools.ExecShell("tar -zxvf " + backupFile + " -C " + backupPath)
|
||||
backupFile = strings.TrimSuffix(backupFile, ".tar.gz")
|
||||
} else {
|
||||
// 解压.gz文件
|
||||
tools.ExecShell("gzip -d " + backupFile)
|
||||
backupFile = strings.TrimSuffix(backupFile, ext)
|
||||
}
|
||||
case ".bz2":
|
||||
tools.ExecShell("bzip2 -d " + backupFile)
|
||||
backupFile = strings.TrimSuffix(backupFile, ext)
|
||||
case ".tar":
|
||||
tools.ExecShell("tar -xvf " + backupFile + " -C " + backupPath)
|
||||
backupFile = strings.TrimSuffix(backupFile, ext)
|
||||
case ".rar":
|
||||
tools.ExecShell("unrar x " + backupFile + " " + backupPath)
|
||||
backupFile = strings.TrimSuffix(backupFile, ext)
|
||||
}
|
||||
|
||||
if !tools.Exists(backupFile) {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "自动解压备份文件失败,请手动解压")
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("mysql -uroot " + ctx.Request().Input("database") + " < " + backupFile)
|
||||
_ = os.Unsetenv("MYSQL_PWD")
|
||||
|
||||
controllers.Success(ctx, "还原成功")
|
||||
}
|
||||
|
||||
// UserList 用户列表
|
||||
func (c *Mysql57Controller) UserList(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
type user struct {
|
||||
User string `json:"user"`
|
||||
Host string `json:"host"`
|
||||
Grants []string `json:"grants"`
|
||||
}
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
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
|
||||
}
|
||||
|
||||
// 查询用户权限
|
||||
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
|
||||
}
|
||||
|
||||
u.Grants = append(u.Grants, grant)
|
||||
}
|
||||
|
||||
if err := grantsRows.Err(); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
userGrants = append(userGrants, u)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取用户列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, userGrants)
|
||||
}
|
||||
|
||||
// AddUser 添加用户
|
||||
func (c *Mysql57Controller) AddUser(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
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",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
database := ctx.Request().Input("database")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + ";'\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "添加成功")
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
func (c *Mysql57Controller) DeleteUser(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\"")
|
||||
|
||||
controllers.Success(ctx, "删除成功")
|
||||
}
|
||||
|
||||
// SetUserPassword 设置用户密码
|
||||
func (c *Mysql57Controller) SetUserPassword(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
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",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "修改成功")
|
||||
}
|
||||
|
||||
// SetUserPrivileges 设置用户权限
|
||||
func (c *Mysql57Controller) SetUserPrivileges(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql57") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"database": "required|min_len:1|max_len:255",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
database := ctx.Request().Input("database")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "修改成功")
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package mysql80
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -11,11 +12,8 @@ import (
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/spf13/cast"
|
||||
"golang.org/x/exp/slices"
|
||||
"panel/app/models"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/http/controllers/plugins"
|
||||
"panel/app/models"
|
||||
"panel/app/services"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
@@ -31,13 +29,12 @@ func NewMysql80Controller() *Mysql80Controller {
|
||||
}
|
||||
|
||||
// Status 获取运行状态
|
||||
func (r *Mysql80Controller) Status(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) Status(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
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
|
||||
@@ -51,14 +48,13 @@ func (r *Mysql80Controller) Status(ctx http.Context) {
|
||||
}
|
||||
|
||||
// Reload 重载配置
|
||||
func (r *Mysql80Controller) Reload(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) Reload(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl reload mysql")
|
||||
out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
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
|
||||
@@ -72,14 +68,13 @@ func (r *Mysql80Controller) Reload(ctx http.Context) {
|
||||
}
|
||||
|
||||
// Restart 重启服务
|
||||
func (r *Mysql80Controller) Restart(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) Restart(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl restart mysql")
|
||||
out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
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
|
||||
@@ -93,14 +88,13 @@ func (r *Mysql80Controller) Restart(ctx http.Context) {
|
||||
}
|
||||
|
||||
// Start 启动服务
|
||||
func (r *Mysql80Controller) Start(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) Start(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl start mysql")
|
||||
out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
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,14 +108,13 @@ func (r *Mysql80Controller) Start(ctx http.Context) {
|
||||
}
|
||||
|
||||
// Stop 停止服务
|
||||
func (r *Mysql80Controller) Stop(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) Stop(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl stop mysql")
|
||||
out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
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,13 +128,13 @@ func (r *Mysql80Controller) Stop(ctx http.Context) {
|
||||
}
|
||||
|
||||
// GetConfig 获取配置
|
||||
func (r *Mysql80Controller) GetConfig(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) GetConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
config := tools.ReadFile("mysql80")
|
||||
config := tools.ReadFile("/www/server/mysql/conf/my.cnf")
|
||||
if len(config) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
|
||||
return
|
||||
@@ -151,8 +144,8 @@ func (r *Mysql80Controller) GetConfig(ctx http.Context) {
|
||||
}
|
||||
|
||||
// SaveConfig 保存配置
|
||||
func (r *Mysql80Controller) SaveConfig(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) SaveConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -162,7 +155,7 @@ func (r *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
|
||||
}
|
||||
@@ -171,19 +164,19 @@ func (r *Mysql80Controller) SaveConfig(ctx http.Context) {
|
||||
}
|
||||
|
||||
// Load 获取负载
|
||||
func (r *Mysql80Controller) Load(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) Load(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if len(rootPassword) == 0 {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码为空")
|
||||
return
|
||||
}
|
||||
|
||||
status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if strings.TrimSpace(status) != "active" {
|
||||
if status != "active" {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "MySQL 已停止运行")
|
||||
return
|
||||
}
|
||||
@@ -203,24 +196,24 @@ func (r *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 {
|
||||
@@ -240,7 +233,7 @@ func (r *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)
|
||||
@@ -249,8 +242,8 @@ func (r *Mysql80Controller) Load(ctx http.Context) {
|
||||
}
|
||||
|
||||
// ErrorLog 获取错误日志
|
||||
func (r *Mysql80Controller) ErrorLog(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) ErrorLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -259,8 +252,8 @@ func (r *Mysql80Controller) ErrorLog(ctx http.Context) {
|
||||
}
|
||||
|
||||
// ClearErrorLog 清空错误日志
|
||||
func (r *Mysql80Controller) ClearErrorLog(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) ClearErrorLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -269,8 +262,8 @@ func (r *Mysql80Controller) ClearErrorLog(ctx http.Context) {
|
||||
}
|
||||
|
||||
// SlowLog 获取慢查询日志
|
||||
func (r *Mysql80Controller) SlowLog(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) SlowLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -279,8 +272,8 @@ func (r *Mysql80Controller) SlowLog(ctx http.Context) {
|
||||
}
|
||||
|
||||
// ClearSlowLog 清空慢查询日志
|
||||
func (r *Mysql80Controller) ClearSlowLog(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) ClearSlowLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -289,12 +282,12 @@ func (r *Mysql80Controller) ClearSlowLog(ctx http.Context) {
|
||||
}
|
||||
|
||||
// GetRootPassword 获取root密码
|
||||
func (r *Mysql80Controller) GetRootPassword(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) GetRootPassword(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if len(rootPassword) == 0 {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码为空")
|
||||
return
|
||||
@@ -304,13 +297,12 @@ func (r *Mysql80Controller) GetRootPassword(ctx http.Context) {
|
||||
}
|
||||
|
||||
// SetRootPassword 设置root密码
|
||||
func (r *Mysql80Controller) SetRootPassword(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) SetRootPassword(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
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
|
||||
@@ -326,11 +318,11 @@ func (r *Mysql80Controller) SetRootPassword(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
oldRootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if oldRootPassword != rootPassword {
|
||||
tools.ExecShell("mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + oldRootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
err := r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword)
|
||||
err := c.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword)
|
||||
if err != nil {
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
@@ -343,57 +335,73 @@ func (r *Mysql80Controller) SetRootPassword(ctx http.Context) {
|
||||
}
|
||||
|
||||
// DatabaseList 获取数据库列表
|
||||
func (r *Mysql80Controller) DatabaseList(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) DatabaseList(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
out := tools.ExecShell("mysql -uroot -p" + r.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 添加数据库
|
||||
func (r *Mysql80Controller) AddDatabase(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) AddDatabase(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
database := ctx.Request().Input("database")
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
@@ -407,8 +415,8 @@ func (r *Mysql80Controller) AddDatabase(ctx http.Context) {
|
||||
}
|
||||
|
||||
// DeleteDatabase 删除数据库
|
||||
func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) DeleteDatabase(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -420,11 +428,11 @@ func (r *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
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
database := ctx.Request().Input("database")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\"")
|
||||
|
||||
@@ -432,8 +440,8 @@ func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) {
|
||||
}
|
||||
|
||||
// BackupList 获取备份列表
|
||||
func (r *Mysql80Controller) BackupList(ctx http.Context) {
|
||||
backupPath := "/www/backup/mysql"
|
||||
func (c *Mysql80Controller) BackupList(ctx http.Context) {
|
||||
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql"
|
||||
|
||||
if !tools.Exists(backupPath) {
|
||||
tools.Mkdir(backupPath, 0644)
|
||||
@@ -465,9 +473,41 @@ func (r *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 (r *Mysql80Controller) CreateBackup(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) CreateBackup(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -479,14 +519,14 @@ func (r *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 := "/www/backup/mysql"
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
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)
|
||||
}
|
||||
@@ -497,17 +537,17 @@ func (r *Mysql80Controller) CreateBackup(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("mysqldump -uroot " + database + " > " + backupFile)
|
||||
tools.ExecShell("zip -r " + 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, "备份成功")
|
||||
}
|
||||
|
||||
// DeleteBackup 删除备份
|
||||
func (r *Mysql80Controller) DeleteBackup(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) DeleteBackup(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -519,11 +559,11 @@ func (r *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
|
||||
}
|
||||
|
||||
backupPath := "/www/backup/mysql"
|
||||
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql"
|
||||
file := ctx.Request().Input("file")
|
||||
tools.RemoveFile(backupPath + "/" + file)
|
||||
|
||||
@@ -531,8 +571,8 @@ func (r *Mysql80Controller) DeleteBackup(ctx http.Context) {
|
||||
}
|
||||
|
||||
// RestoreBackup 还原备份
|
||||
func (r *Mysql80Controller) RestoreBackup(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) RestoreBackup(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -545,12 +585,12 @@ func (r *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
|
||||
}
|
||||
|
||||
backupPath := "/www/backup/mysql"
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql"
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
file := ctx.Request().Input("file")
|
||||
backupFile := backupPath + "/" + file
|
||||
if !tools.Exists(backupFile) {
|
||||
@@ -560,7 +600,7 @@ func (r *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
|
||||
}
|
||||
@@ -604,64 +644,94 @@ func (r *Mysql80Controller) RestoreBackup(ctx http.Context) {
|
||||
}
|
||||
|
||||
// UserList 用户列表
|
||||
func (r *Mysql80Controller) UserList(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) UserList(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
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 := r.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" {
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
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 添加用户
|
||||
func (r *Mysql80Controller) AddUser(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) AddUser(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
database := ctx.Request().Input("database")
|
||||
@@ -673,8 +743,8 @@ func (r *Mysql80Controller) AddUser(ctx http.Context) {
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
func (r *Mysql80Controller) DeleteUser(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) DeleteUser(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -686,11 +756,11 @@ func (r *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
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\"")
|
||||
|
||||
@@ -698,25 +768,25 @@ func (r *Mysql80Controller) DeleteUser(ctx http.Context) {
|
||||
}
|
||||
|
||||
// SetUserPassword 设置用户密码
|
||||
func (r *Mysql80Controller) SetUserPassword(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) SetUserPassword(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
|
||||
@@ -726,8 +796,8 @@ func (r *Mysql80Controller) SetUserPassword(ctx http.Context) {
|
||||
}
|
||||
|
||||
// SetUserPrivileges 设置用户权限
|
||||
func (r *Mysql80Controller) SetUserPrivileges(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "mysql80") {
|
||||
func (c *Mysql80Controller) SetUserPrivileges(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "mysql80") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -740,11 +810,11 @@ func (r *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
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
database := ctx.Request().Input("database")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\"")
|
||||
|
||||
@@ -2,7 +2,6 @@ package openresty
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
@@ -11,7 +10,6 @@ import (
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/http/controllers/plugins"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
@@ -27,12 +25,11 @@ func NewOpenrestyController() *OpenRestyController {
|
||||
|
||||
// Status 获取运行状态
|
||||
func (r *OpenRestyController) Status(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "openresty") {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
|
||||
out := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
status := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败")
|
||||
return
|
||||
@@ -47,13 +44,12 @@ func (r *OpenRestyController) Status(ctx http.Context) {
|
||||
|
||||
// Reload 重载配置
|
||||
func (r *OpenRestyController) Reload(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "openresty") {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl reload openresty")
|
||||
out := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
status := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败")
|
||||
return
|
||||
@@ -62,19 +58,18 @@ func (r *OpenRestyController) Reload(ctx http.Context) {
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, "重载OpenResty成功")
|
||||
} else {
|
||||
controllers.Error(ctx, 1, "重载OpenResty失败: "+string(out))
|
||||
controllers.Error(ctx, 1, "重载OpenResty失败: "+status)
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动OpenResty
|
||||
func (r *OpenRestyController) Start(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "openresty") {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl start openresty")
|
||||
out := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
status := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败")
|
||||
return
|
||||
@@ -83,19 +78,18 @@ func (r *OpenRestyController) Start(ctx http.Context) {
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, "启动OpenResty成功")
|
||||
} else {
|
||||
controllers.Error(ctx, 1, "启动OpenResty失败: "+string(out))
|
||||
controllers.Error(ctx, 1, "启动OpenResty失败: "+status)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop 停止OpenResty
|
||||
func (r *OpenRestyController) Stop(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "openresty") {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl stop openresty")
|
||||
out := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
status := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败")
|
||||
return
|
||||
@@ -104,19 +98,18 @@ func (r *OpenRestyController) Stop(ctx http.Context) {
|
||||
if status != "active" {
|
||||
controllers.Success(ctx, "停止OpenResty成功")
|
||||
} else {
|
||||
controllers.Error(ctx, 1, "停止OpenResty失败: "+string(out))
|
||||
controllers.Error(ctx, 1, "停止OpenResty失败: "+status)
|
||||
}
|
||||
}
|
||||
|
||||
// Restart 重启OpenResty
|
||||
func (r *OpenRestyController) Restart(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "openresty") {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl restart openresty")
|
||||
out := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
status := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败")
|
||||
return
|
||||
@@ -125,13 +118,13 @@ func (r *OpenRestyController) Restart(ctx http.Context) {
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, "重启OpenResty成功")
|
||||
} else {
|
||||
controllers.Error(ctx, 1, "重启OpenResty失败: "+string(out))
|
||||
controllers.Error(ctx, 1, "重启OpenResty失败: "+status)
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfig 获取配置
|
||||
func (r *OpenRestyController) GetConfig(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "openresty") {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -146,7 +139,7 @@ func (r *OpenRestyController) GetConfig(ctx http.Context) {
|
||||
|
||||
// SaveConfig 保存配置
|
||||
func (r *OpenRestyController) SaveConfig(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "openresty") {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -166,7 +159,12 @@ func (r *OpenRestyController) SaveConfig(ctx http.Context) {
|
||||
|
||||
// ErrorLog 获取错误日志
|
||||
func (r *OpenRestyController) ErrorLog(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "openresty") {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
|
||||
if !tools.Exists("/www/wwwlogs/nginx_error.log") {
|
||||
controllers.Success(ctx, "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -176,17 +174,17 @@ func (r *OpenRestyController) ErrorLog(ctx http.Context) {
|
||||
|
||||
// ClearErrorLog 清空错误日志
|
||||
func (r *OpenRestyController) ClearErrorLog(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "openresty") {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
|
||||
_ = tools.ExecShell("echo '' > /www/wwwlogs/nginx_error.log")
|
||||
tools.ExecShell("echo '' > /www/wwwlogs/nginx_error.log")
|
||||
controllers.Success(ctx, "清空OpenResty错误日志成功")
|
||||
}
|
||||
|
||||
// Load 获取负载
|
||||
func (r *OpenRestyController) Load(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "openresty") {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -199,42 +197,63 @@ func (r *OpenRestyController) Load(ctx http.Context) {
|
||||
}
|
||||
|
||||
raw := resp.String()
|
||||
var data map[int]map[string]any
|
||||
type nginxStatus struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
var data []nginxStatus
|
||||
|
||||
out := tools.ExecShell("ps aux | grep nginx | grep 'worker process' | wc -l")
|
||||
workers := strings.TrimSpace(out)
|
||||
data[0]["name"] = "工作进程"
|
||||
data[0]["value"] = workers
|
||||
workers := tools.ExecShell("ps aux | grep nginx | grep 'worker process' | wc -l")
|
||||
data = append(data, nginxStatus{
|
||||
Name: "工作进程",
|
||||
Value: workers,
|
||||
})
|
||||
|
||||
out = tools.ExecShell("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
|
||||
mem := tools.FormatBytes(cast.ToFloat64(strings.TrimSpace(out)))
|
||||
data[1]["name"] = "内存占用"
|
||||
data[1]["value"] = mem
|
||||
out := tools.ExecShell("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
|
||||
mem := tools.FormatBytes(cast.ToFloat64(out))
|
||||
data = append(data, nginxStatus{
|
||||
Name: "内存占用",
|
||||
Value: mem,
|
||||
})
|
||||
|
||||
match := regexp.MustCompile(`Active connections:\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 2 {
|
||||
data[2]["name"] = "活跃连接数"
|
||||
data[2]["value"] = match[1]
|
||||
data = append(data, nginxStatus{
|
||||
Name: "活跃连接数",
|
||||
Value: match[1],
|
||||
})
|
||||
}
|
||||
|
||||
match = regexp.MustCompile(`server accepts handled requests\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 4 {
|
||||
data[3]["name"] = "总连接次数"
|
||||
data[3]["value"] = match[1]
|
||||
data[4]["name"] = "总握手次数"
|
||||
data[4]["value"] = match[2]
|
||||
data[5]["name"] = "总请求次数"
|
||||
data[5]["value"] = match[3]
|
||||
data = append(data, nginxStatus{
|
||||
Name: "总连接次数",
|
||||
Value: match[1],
|
||||
})
|
||||
data = append(data, nginxStatus{
|
||||
Name: "总握手次数",
|
||||
Value: match[2],
|
||||
})
|
||||
data = append(data, nginxStatus{
|
||||
Name: "总请求次数",
|
||||
Value: match[3],
|
||||
})
|
||||
}
|
||||
|
||||
match = regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 4 {
|
||||
data[6]["name"] = "请求数"
|
||||
data[6]["value"] = match[1]
|
||||
data[7]["name"] = "响应数"
|
||||
data[7]["value"] = match[2]
|
||||
data[8]["name"] = "驻留进程"
|
||||
data[8]["value"] = match[3]
|
||||
data = append(data, nginxStatus{
|
||||
Name: "请求数",
|
||||
Value: match[1],
|
||||
})
|
||||
data = append(data, nginxStatus{
|
||||
Name: "响应数",
|
||||
Value: match[2],
|
||||
})
|
||||
data = append(data, nginxStatus{
|
||||
Name: "驻留进程",
|
||||
Value: match[3],
|
||||
})
|
||||
}
|
||||
|
||||
controllers.Success(ctx, data)
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/imroc/req/v3"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/http/controllers/plugins"
|
||||
"panel/app/models"
|
||||
"panel/app/services"
|
||||
"panel/pkg/tools"
|
||||
@@ -32,7 +31,7 @@ func NewPhp74Controller() *Php74Controller {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) Status(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -51,7 +50,7 @@ func (c *Php74Controller) Status(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) Reload(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,7 +70,7 @@ func (c *Php74Controller) Reload(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) Start(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -91,7 +90,7 @@ func (c *Php74Controller) Start(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) Stop(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -111,7 +110,7 @@ func (c *Php74Controller) Stop(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) Restart(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -131,7 +130,7 @@ func (c *Php74Controller) Restart(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) GetConfig(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -140,7 +139,7 @@ func (c *Php74Controller) GetConfig(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) SaveConfig(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -150,12 +149,12 @@ func (c *Php74Controller) SaveConfig(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) Load(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
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+"] 获取运行状态失败")
|
||||
@@ -186,7 +185,7 @@ func (c *Php74Controller) Load(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) ErrorLog(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -195,7 +194,7 @@ func (c *Php74Controller) ErrorLog(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) SlowLog(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -204,7 +203,7 @@ func (c *Php74Controller) SlowLog(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) ClearErrorLog(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -213,7 +212,7 @@ func (c *Php74Controller) ClearErrorLog(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) ClearSlowLog(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -229,7 +228,7 @@ type Extension struct {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) GetExtensionList(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -238,7 +237,7 @@ func (c *Php74Controller) GetExtensionList(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) InstallExtension(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -259,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 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())
|
||||
@@ -278,7 +277,7 @@ func (c *Php74Controller) InstallExtension(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *Php74Controller) UninstallExtension(ctx http.Context) {
|
||||
if !plugins.Check(ctx, "php"+c.version) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -299,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 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())
|
||||
@@ -362,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
package php80
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/imroc/req/v3"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/models"
|
||||
"panel/app/services"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
type Php80Controller struct {
|
||||
setting services.Setting
|
||||
task services.Task
|
||||
version string
|
||||
}
|
||||
|
||||
func NewPhp80Controller() *Php80Controller {
|
||||
return &Php80Controller{
|
||||
setting: services.NewSettingImpl(),
|
||||
task: services.NewTaskImpl(),
|
||||
version: "80",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Php80Controller) Status(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Php80Controller) Reload(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl reload php-fpm-" + c.version)
|
||||
out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Php80Controller) Start(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl start php-fpm-" + c.version)
|
||||
out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Php80Controller) Stop(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl stop php-fpm-" + c.version)
|
||||
out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status != "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Php80Controller) Restart(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl restart php-fpm-" + c.version)
|
||||
out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Php80Controller) GetConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
config := tools.ReadFile("/www/server/php/" + c.version + "/etc/php.ini")
|
||||
controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
func (c *Php80Controller) SaveConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
config := ctx.Request().Input("config")
|
||||
tools.WriteFile("/www/server/php/"+c.version+"/etc/php.ini", config, 0644)
|
||||
c.Reload(ctx)
|
||||
}
|
||||
|
||||
func (c *Php80Controller) Load(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
client := req.C().SetTimeout(10 * time.Second)
|
||||
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+"] 获取运行状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
raw := resp.String()
|
||||
dataKeys := []string{"应用池", "工作模式", "启动时间", "接受连接", "监听队列", "最大监听队列", "监听队列长度", "空闲进程数量", "活动进程数量", "总进程数量", "最大活跃进程数量", "达到进程上限次数", "慢请求"}
|
||||
regexKeys := []string{"pool", "process manager", "start time", "accepted conn", "listen queue", "max listen queue", "listen queue len", "idle processes", "active processes", "total processes", "max active processes", "max children reached", "slow requests"}
|
||||
|
||||
type Data struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
data := make([]Data, len(dataKeys))
|
||||
for i := range dataKeys {
|
||||
data[i].Name = dataKeys[i]
|
||||
|
||||
r := regexp.MustCompile(fmt.Sprintf("%s:\\s+(.*)", regexKeys[i]))
|
||||
match := r.FindStringSubmatch(raw)
|
||||
|
||||
if len(match) > 1 {
|
||||
data[i].Value = strings.TrimSpace(match[1])
|
||||
}
|
||||
}
|
||||
|
||||
controllers.Success(ctx, data)
|
||||
}
|
||||
|
||||
func (c *Php80Controller) ErrorLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
log := tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/php-fpm.log")
|
||||
controllers.Success(ctx, log)
|
||||
}
|
||||
|
||||
func (c *Php80Controller) SlowLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
log := tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/slow.log")
|
||||
controllers.Success(ctx, log)
|
||||
}
|
||||
|
||||
func (c *Php80Controller) ClearErrorLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("echo '' > /www/server/php/" + c.version + "/var/log/php-fpm.log")
|
||||
controllers.Success(ctx, true)
|
||||
}
|
||||
|
||||
func (c *Php80Controller) ClearSlowLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("echo '' > /www/server/php/" + c.version + "/var/log/slow.log")
|
||||
controllers.Success(ctx, true)
|
||||
}
|
||||
|
||||
type Extension struct {
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Description string `json:"description"`
|
||||
Installed bool `json:"installed"`
|
||||
}
|
||||
|
||||
func (c *Php80Controller) GetExtensionList(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
extensions := c.GetExtensions()
|
||||
controllers.Success(ctx, extensions)
|
||||
}
|
||||
|
||||
func (c *Php80Controller) InstallExtension(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
slug := ctx.Request().Input("slug")
|
||||
if len(slug) == 0 {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
extensions := c.GetExtensions()
|
||||
for _, item := range extensions {
|
||||
if item.Slug == slug {
|
||||
if item.Installed {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "扩展已安装")
|
||||
return
|
||||
}
|
||||
|
||||
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.Log = "/tmp/" + item.Slug + ".log"
|
||||
if err := facades.Orm().Query().Create(&task); err != nil {
|
||||
facades.Log().Error("[PHP-" + c.version + "] 创建安装拓展任务失败:" + err.Error())
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return
|
||||
}
|
||||
|
||||
c.task.Process(task.ID)
|
||||
|
||||
controllers.Success(ctx, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
controllers.Error(ctx, http.StatusBadRequest, "扩展不存在")
|
||||
}
|
||||
|
||||
func (c *Php80Controller) UninstallExtension(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
slug := ctx.Request().Input("slug")
|
||||
if len(slug) == 0 {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
extensions := c.GetExtensions()
|
||||
for _, item := range extensions {
|
||||
if item.Slug == slug {
|
||||
if !item.Installed {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "扩展未安装")
|
||||
return
|
||||
}
|
||||
|
||||
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.Log = "/tmp/" + item.Slug + ".log"
|
||||
if err := facades.Orm().Query().Create(&task); err != nil {
|
||||
facades.Log().Error("[PHP-" + c.version + "] 创建卸载拓展任务失败:" + err.Error())
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return
|
||||
}
|
||||
|
||||
c.task.Process(task.ID)
|
||||
|
||||
controllers.Success(ctx, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
controllers.Error(ctx, http.StatusBadRequest, "扩展不存在")
|
||||
}
|
||||
|
||||
func (c *Php80Controller) GetExtensions() []Extension {
|
||||
var extensions []Extension
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "OPcache",
|
||||
Slug: "Zend OPcache",
|
||||
Description: "OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码可以省去每次加载和解析 PHP 脚本的开销。",
|
||||
Installed: false,
|
||||
})
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "PhpRedis",
|
||||
Slug: "redis",
|
||||
Description: "PhpRedis 是一个用C语言编写的PHP模块,用来连接并操作 Redis 数据库上的数据。",
|
||||
Installed: false,
|
||||
})
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "ImageMagick",
|
||||
Slug: "imagick",
|
||||
Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件。",
|
||||
Installed: false,
|
||||
})
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "Exif",
|
||||
Slug: "exif",
|
||||
Description: "通过 exif 扩展,你可以操作图像元数据。",
|
||||
Installed: false,
|
||||
})
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "pdo_pgsql",
|
||||
Slug: "pdo_pgsql",
|
||||
Description: "(需先安装PostgreSQL)pdo_pgsql 是一个驱动程序,它实现了 PHP 数据对象(PDO)接口以启用从 PHP 到 PostgreSQL 数据库的访问。",
|
||||
Installed: false,
|
||||
})
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "ionCube",
|
||||
Slug: "ionCube Loader",
|
||||
Description: "ionCube 是一个专业级的PHP加密解密工具。",
|
||||
Installed: false,
|
||||
})
|
||||
|
||||
raw := tools.ExecShell("/www/server/php/" + c.version + "/bin/php -m")
|
||||
rawExtensionList := strings.Split(raw, "\n")
|
||||
|
||||
for _, item := range rawExtensionList {
|
||||
if !strings.Contains(item, "[") && item != "" {
|
||||
for i := range extensions {
|
||||
if extensions[i].Slug == item {
|
||||
extensions[i].Installed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extensions
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/services"
|
||||
)
|
||||
|
||||
// Check 检查插件是否可用
|
||||
func Check(ctx http.Context, slug string) bool {
|
||||
plugin := services.NewPluginImpl().GetBySlug(slug)
|
||||
installedPlugin := services.NewPluginImpl().GetInstalledBySlug(slug)
|
||||
installedPlugins, err := services.NewPluginImpl().AllInstalled()
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][插件] 获取已安装插件失败")
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return false
|
||||
}
|
||||
|
||||
if installedPlugin.Version != plugin.Version || installedPlugin.Slug != plugin.Slug {
|
||||
controllers.Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要更新至 "+plugin.Version+" 版本")
|
||||
return false
|
||||
}
|
||||
|
||||
var lock sync.RWMutex
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
lock.Lock()
|
||||
pluginsMap[p.Slug] = true
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
lock.RLock()
|
||||
_, requireFound := pluginsMap[require]
|
||||
lock.RUnlock()
|
||||
if !requireFound {
|
||||
controllers.Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
lock.RLock()
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
lock.RUnlock()
|
||||
if excludeFound {
|
||||
controllers.Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func (r *SafeController) GetFirewallStatus(ctx http.Context) {
|
||||
|
||||
func (r *SafeController) SetFirewallStatus(ctx http.Context) {
|
||||
var out string
|
||||
if ctx.Request().QueryBool("status") {
|
||||
if ctx.Request().InputBool("status") {
|
||||
if tools.IsRHEL() {
|
||||
out = tools.ExecShell("systemctl start firewalld")
|
||||
} else {
|
||||
@@ -45,7 +45,7 @@ func (r *SafeController) SetFirewallStatus(ctx http.Context) {
|
||||
|
||||
func (r *SafeController) GetFirewallRules(ctx http.Context) {
|
||||
if !r.firewallStatus() {
|
||||
Error(ctx, http.StatusBadRequest, "防火墙未启动")
|
||||
Success(ctx, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -68,16 +68,13 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) {
|
||||
|
||||
Success(ctx, rules)
|
||||
} else {
|
||||
out := tools.ExecShell("ufw status numbered | grep ALLOW | awk '{print $2}'")
|
||||
out := tools.ExecShell("ufw status | grep -v '(v6)' | grep ALLOW | awk '{print $1}'")
|
||||
if len(out) == 0 {
|
||||
Success(ctx, nil)
|
||||
return
|
||||
}
|
||||
var rules []map[string]string
|
||||
for _, port := range strings.Split(out, "\n") {
|
||||
if strings.Contains(port, "]") {
|
||||
continue
|
||||
}
|
||||
rule := strings.Split(port, "/")
|
||||
rules = append(rules, map[string]string{
|
||||
"port": rule[0],
|
||||
@@ -162,7 +159,13 @@ func (r *SafeController) firewallStatus() bool {
|
||||
}
|
||||
|
||||
func (r *SafeController) GetSshStatus(ctx http.Context) {
|
||||
out := tools.ExecShell("systemctl status sshd | grep Active | awk '{print $3}'")
|
||||
var out string
|
||||
if tools.IsRHEL() {
|
||||
out = tools.ExecShell("systemctl status sshd | grep Active | awk '{print $3}'")
|
||||
} else {
|
||||
out = tools.ExecShell("systemctl status ssh | grep Active | awk '{print $3}'")
|
||||
}
|
||||
|
||||
running := false
|
||||
if out == "(running)" {
|
||||
running = true
|
||||
@@ -172,19 +175,29 @@ func (r *SafeController) GetSshStatus(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (r *SafeController) SetSshStatus(ctx http.Context) {
|
||||
if ctx.Request().QueryBool("status") {
|
||||
tools.ExecShell("systemctl enable sshd")
|
||||
tools.ExecShell("systemctl start sshd")
|
||||
if ctx.Request().InputBool("status") {
|
||||
if tools.IsRHEL() {
|
||||
tools.ExecShell("systemctl enable sshd")
|
||||
tools.ExecShell("systemctl start sshd")
|
||||
} else {
|
||||
tools.ExecShell("systemctl enable ssh")
|
||||
tools.ExecShell("systemctl start ssh")
|
||||
}
|
||||
} else {
|
||||
tools.ExecShell("systemctl stop sshd")
|
||||
tools.ExecShell("systemctl disable sshd")
|
||||
if tools.IsRHEL() {
|
||||
tools.ExecShell("systemctl stop sshd")
|
||||
tools.ExecShell("systemctl disable sshd")
|
||||
} else {
|
||||
tools.ExecShell("systemctl stop ssh")
|
||||
tools.ExecShell("systemctl disable ssh")
|
||||
}
|
||||
}
|
||||
|
||||
Success(ctx, nil)
|
||||
}
|
||||
|
||||
func (r *SafeController) GetSshPort(ctx http.Context) {
|
||||
out := tools.ExecShell("cat /etc/ssh/sshd_config | grep Port | awk '{print $2}'")
|
||||
out := tools.ExecShell("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
|
||||
Success(ctx, out)
|
||||
}
|
||||
|
||||
@@ -195,7 +208,7 @@ func (r *SafeController) SetSshPort(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
oldPort := tools.ExecShell("cat /etc/ssh/sshd_config | grep Port | awk '{print $2}'")
|
||||
oldPort := tools.ExecShell("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
|
||||
tools.ExecShell("sed -i 's/#Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
|
||||
tools.ExecShell("sed -i 's/Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
|
||||
|
||||
@@ -226,17 +239,17 @@ func (r *SafeController) GetPingStatus(ctx http.Context) {
|
||||
|
||||
func (r *SafeController) SetPingStatus(ctx http.Context) {
|
||||
if tools.IsRHEL() {
|
||||
if ctx.Request().QueryBool("status") {
|
||||
tools.ExecShell("firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'")
|
||||
} else {
|
||||
if ctx.Request().InputBool("status") {
|
||||
tools.ExecShell("firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'")
|
||||
} else {
|
||||
tools.ExecShell("firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'")
|
||||
}
|
||||
tools.ExecShell("firewall-cmd --reload")
|
||||
} else {
|
||||
if ctx.Request().QueryBool("status") {
|
||||
tools.ExecShell("sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules")
|
||||
} else {
|
||||
if ctx.Request().InputBool("status") {
|
||||
tools.ExecShell("sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/g' /etc/ufw/before.rules")
|
||||
} else {
|
||||
tools.ExecShell("sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules")
|
||||
}
|
||||
tools.ExecShell("ufw reload")
|
||||
}
|
||||
|
||||
@@ -46,10 +46,11 @@ func (r *SettingController) List(ctx http.Context) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
result["username"] = user.Username
|
||||
result["email"] = user.Email
|
||||
|
||||
result["port"] = tools.ExecShell(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
|
||||
Success(ctx, result)
|
||||
}
|
||||
|
||||
@@ -58,7 +59,7 @@ func (r *SettingController) Save(ctx http.Context) {
|
||||
port := ctx.Request().Input("port")
|
||||
backupPath := ctx.Request().Input("backup_path")
|
||||
websitePath := ctx.Request().Input("website_path")
|
||||
panelEntrance := ctx.Request().Input("panel_entrance")
|
||||
entrance := ctx.Request().Input("entrance")
|
||||
username := ctx.Request().Input("username")
|
||||
email := ctx.Request().Input("email")
|
||||
password := ctx.Request().Input("password")
|
||||
@@ -70,10 +71,13 @@ func (r *SettingController) Save(ctx http.Context) {
|
||||
|
||||
return
|
||||
}
|
||||
oldPort := tools.ExecShell("cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}'")
|
||||
oldPort := tools.ExecShell(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
if oldPort != port {
|
||||
tools.ExecShell("sed -i 's/APP_PORT=" + oldPort + "/APP_PORT=" + port + "/g' /www/panel/panel.conf")
|
||||
}
|
||||
if !tools.Exists(backupPath) {
|
||||
tools.Mkdir(backupPath, 0644)
|
||||
}
|
||||
err = r.setting.Set(models.SettingKeyBackupPath, backupPath)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][SettingController] 保存设置失败 ", err)
|
||||
@@ -81,6 +85,10 @@ func (r *SettingController) Save(ctx http.Context) {
|
||||
|
||||
return
|
||||
}
|
||||
if !tools.Exists(websitePath) {
|
||||
tools.Mkdir(websitePath, 0755)
|
||||
tools.Chown(websitePath, "www", "www")
|
||||
}
|
||||
err = r.setting.Set(models.SettingKeyWebsitePath, websitePath)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][SettingController] 保存设置失败 ", err)
|
||||
@@ -88,7 +96,7 @@ func (r *SettingController) Save(ctx http.Context) {
|
||||
|
||||
return
|
||||
}
|
||||
err = r.setting.Set(models.SettingKeyPanelEntrance, panelEntrance)
|
||||
err = r.setting.Set(models.SettingKeyEntrance, entrance)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][SettingController] 保存设置失败 ", err)
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
@@ -105,8 +113,12 @@ func (r *SettingController) Save(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
user.Username = username
|
||||
user.Email = email
|
||||
if len(username) > 0 {
|
||||
user.Username = username
|
||||
}
|
||||
if len(email) > 0 {
|
||||
user.Email = email
|
||||
}
|
||||
if len(password) > 0 {
|
||||
hash, err := facades.Hash().Make(password)
|
||||
if err != nil {
|
||||
|
||||
@@ -69,7 +69,7 @@ func (r *TaskController) Log(ctx http.Context) {
|
||||
|
||||
func (r *TaskController) Delete(ctx http.Context) {
|
||||
var task models.Task
|
||||
_, err := facades.Orm().Query().Where("id", ctx.Request().QueryInt("id")).Delete(&task)
|
||||
_, err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).Delete(&task)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][TaskController] 删除任务失败 ", err)
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"panel/app/models"
|
||||
"panel/pkg/tools"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/app/services"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
type WebsiteController struct {
|
||||
@@ -46,31 +46,37 @@ func (c *WebsiteController) List(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *WebsiteController) Add(ctx http.Context) {
|
||||
if !Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"name": "required|regex:^[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*$",
|
||||
"domain": "required",
|
||||
"php": "required",
|
||||
"db": "required",
|
||||
"db_type": "required_if:db,1",
|
||||
"db_name": "required_if:db,1",
|
||||
"db_user": "required_if:db,1",
|
||||
"db_password": "required_if:db,1",
|
||||
"db_type": "required_if:db,true",
|
||||
"db_name": "required_if:db,true",
|
||||
"db_user": "required_if:db,true",
|
||||
"db_password": "required_if:db,true",
|
||||
})
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
Error(ctx, http.StatusBadRequest, validator.Errors().All())
|
||||
Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
var website services.PanelWebsite
|
||||
err = ctx.Request().Bind(&website)
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
website.Name = ctx.Request().Input("name")
|
||||
website.Domain = ctx.Request().Input("domain")
|
||||
website.Php = ctx.Request().InputInt("php")
|
||||
website.Db = ctx.Request().InputBool("db")
|
||||
website.DbType = ctx.Request().Input("db_type")
|
||||
website.DbName = ctx.Request().Input("db_name")
|
||||
website.DbUser = ctx.Request().Input("db_user")
|
||||
website.DbPassword = ctx.Request().Input("db_password")
|
||||
|
||||
newSite, err := c.website.Add(website)
|
||||
if err != nil {
|
||||
@@ -83,23 +89,14 @@ func (c *WebsiteController) Add(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *WebsiteController) Delete(ctx http.Context) {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"id": "required|int",
|
||||
})
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusBadRequest, err.Error())
|
||||
if !Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
Error(ctx, http.StatusBadRequest, validator.Errors().All())
|
||||
return
|
||||
}
|
||||
|
||||
id := ctx.Request().InputInt("id")
|
||||
err = c.website.Delete(id)
|
||||
err := c.website.Delete(id)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][WebsiteController] 删除网站失败 ", err)
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
Error(ctx, http.StatusInternalServerError, "删除网站失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -107,6 +104,9 @@ func (c *WebsiteController) Delete(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *WebsiteController) GetDefaultConfig(ctx http.Context) {
|
||||
if !Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
index := tools.ReadFile("/www/server/openresty/html/index.html")
|
||||
stop := tools.ReadFile("/www/server/openresty/html/stop.html")
|
||||
|
||||
@@ -117,6 +117,9 @@ func (c *WebsiteController) GetDefaultConfig(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *WebsiteController) SaveDefaultConfig(ctx http.Context) {
|
||||
if !Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
index := ctx.Request().Input("index")
|
||||
stop := ctx.Request().Input("stop")
|
||||
|
||||
@@ -136,6 +139,9 @@ func (c *WebsiteController) SaveDefaultConfig(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *WebsiteController) GetConfig(ctx http.Context) {
|
||||
if !Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
id := ctx.Request().InputInt("id")
|
||||
if id == 0 {
|
||||
Error(ctx, http.StatusBadRequest, "参数错误")
|
||||
@@ -153,21 +159,40 @@ func (c *WebsiteController) GetConfig(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
if !Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"id": "required|int",
|
||||
"id": "required",
|
||||
"domains": "required",
|
||||
"ports": "required",
|
||||
"hsts": "bool",
|
||||
"ssl": "bool",
|
||||
"http_redirect": "bool",
|
||||
"open_basedir": "bool",
|
||||
"waf": "required",
|
||||
"waf_cache": "required",
|
||||
"waf_mode": "required",
|
||||
"waf_cc_deny": "required",
|
||||
"index": "required",
|
||||
"path": "required",
|
||||
"root": "required",
|
||||
"raw": "required",
|
||||
"php": "required",
|
||||
"ssl_certificate": "required_if:ssl,true",
|
||||
"ssl_certificate_key": "required_if:ssl,true",
|
||||
})
|
||||
if err != nil {
|
||||
Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
Error(ctx, http.StatusBadRequest, validator.Errors().All())
|
||||
Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
id := ctx.Request().InputInt("id")
|
||||
var website models.Website
|
||||
if facades.Orm().Query().Where("id", id).FirstOrFail(&website) != nil {
|
||||
if facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&website) != nil {
|
||||
Error(ctx, http.StatusBadRequest, "网站不存在")
|
||||
return
|
||||
}
|
||||
@@ -178,9 +203,9 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
}
|
||||
|
||||
// 原文
|
||||
raw := tools.ReadFile("/www/server/panel/vhost/openresty/" + website.Name + ".conf")
|
||||
raw := tools.ReadFile("/www/server/vhost/" + website.Name + ".conf")
|
||||
if strings.TrimSpace(raw) != strings.TrimSpace(ctx.Request().Input("raw")) {
|
||||
tools.WriteFile("/www/server/panel/vhost/openresty/"+website.Name+".conf", ctx.Request().Input("raw"), 0644)
|
||||
tools.WriteFile("/www/server/vhost/"+website.Name+".conf", ctx.Request().Input("raw"), 0644)
|
||||
Success(ctx, nil)
|
||||
return
|
||||
}
|
||||
@@ -195,7 +220,7 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
|
||||
// 域名
|
||||
domain := "server_name"
|
||||
domains := strings.Split(ctx.Request().Input("domain"), "\n")
|
||||
domains := strings.Split(ctx.Request().Input("domains"), "\n")
|
||||
if len(domains) == 0 {
|
||||
Error(ctx, http.StatusBadRequest, "域名不能为空")
|
||||
return
|
||||
@@ -216,7 +241,7 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
|
||||
// 端口
|
||||
var port strings.Builder
|
||||
ports := strings.Split(ctx.Request().Input("port"), "\n")
|
||||
ports := strings.Split(ctx.Request().Input("ports"), "\n")
|
||||
if len(ports) == 0 {
|
||||
Error(ctx, http.StatusBadRequest, "端口不能为空")
|
||||
return
|
||||
@@ -287,15 +312,13 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
wafMode := ctx.Request().Input("waf_mode", "DYNAMIC")
|
||||
wafCcDeny := ctx.Request().Input("waf_cc_deny", "rate=1000r/m duration=60m")
|
||||
wafCache := ctx.Request().Input("waf_cache", "capacity=50")
|
||||
wafConfig := `
|
||||
# waf标记位开始
|
||||
wafConfig := `# waf标记位开始
|
||||
waf ` + waf + `;
|
||||
waf_rule_path /www/server/openresty/ngx_waf/assets/rules/;
|
||||
waf_mode ` + wafMode + `;
|
||||
waf_cc_deny ` + wafCcDeny + `;
|
||||
waf_cache ` + wafCache + `;
|
||||
|
||||
`
|
||||
`
|
||||
wafConfigOld := tools.Cut(raw, "# waf标记位开始", "# waf标记位结束")
|
||||
if len(strings.TrimSpace(wafConfigOld)) != 0 {
|
||||
raw = strings.Replace(raw, wafConfigOld, "", -1)
|
||||
@@ -308,8 +331,7 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
if ssl {
|
||||
tools.WriteFile("/www/server/vhost/ssl/"+website.Name+".pem", ctx.Request().Input("ssl_certificate"), 0644)
|
||||
tools.WriteFile("/www/server/vhost/ssl/"+website.Name+".key", ctx.Request().Input("ssl_certificate_key"), 0644)
|
||||
sslConfig := `
|
||||
# ssl标记位开始
|
||||
sslConfig := `# ssl标记位开始
|
||||
ssl_certificate /www/server/vhost/ssl/` + website.Name + `.pem;
|
||||
ssl_certificate_key /www/server/vhost/ssl/` + website.Name + `.key;
|
||||
ssl_session_timeout 1d;
|
||||
@@ -318,28 +340,21 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
`
|
||||
`
|
||||
if ctx.Request().InputBool("http_redirect") {
|
||||
sslConfig += `
|
||||
|
||||
# http重定向标记位开始
|
||||
sslConfig += `# http重定向标记位开始
|
||||
if (\$server_port !~ 443){
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
error_page 497 https://\$host\$request_uri;
|
||||
# http重定向标记位结束
|
||||
|
||||
`
|
||||
`
|
||||
}
|
||||
if ctx.Request().InputBool("hsts") {
|
||||
sslConfig += `
|
||||
|
||||
# hsts标记位开始
|
||||
sslConfig += `# hsts标记位开始
|
||||
add_header Strict-Transport-Security "max-age=63072000" always;
|
||||
# hsts标记位结束
|
||||
|
||||
`
|
||||
`
|
||||
}
|
||||
sslConfigOld := tools.Cut(raw, "# ssl标记位开始", "# ssl标记位结束")
|
||||
if len(strings.TrimSpace(sslConfigOld)) != 0 {
|
||||
@@ -349,7 +364,7 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
} else {
|
||||
sslConfigOld := tools.Cut(raw, "# ssl标记位开始", "# ssl标记位结束")
|
||||
if len(strings.TrimSpace(sslConfigOld)) != 0 {
|
||||
raw = strings.Replace(raw, sslConfigOld, "", -1)
|
||||
raw = strings.Replace(raw, sslConfigOld, "\n ", -1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,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)
|
||||
}
|
||||
@@ -379,7 +393,10 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
Success(ctx, nil)
|
||||
}
|
||||
|
||||
func (c *WebsiteController) ClearSiteLpg(ctx http.Context) {
|
||||
func (c *WebsiteController) ClearLog(ctx http.Context) {
|
||||
if !Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
id := ctx.Request().InputInt("id")
|
||||
if id == 0 {
|
||||
Error(ctx, http.StatusBadRequest, "参数错误")
|
||||
@@ -426,7 +443,6 @@ func (c *WebsiteController) UpdateRemark(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *WebsiteController) BackupList(ctx http.Context) {
|
||||
|
||||
backupList, err := c.backup.WebsiteList()
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][WebsiteController] 获取网站备份列表失败 ", err)
|
||||
@@ -463,6 +479,9 @@ func (c *WebsiteController) CreateBackup(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (c *WebsiteController) ResetConfig(ctx http.Context) {
|
||||
if !Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
id := ctx.Request().InputInt("id")
|
||||
if id == 0 {
|
||||
Error(ctx, http.StatusBadRequest, "参数错误")
|
||||
@@ -511,7 +530,7 @@ server
|
||||
|
||||
# waf标记位开始
|
||||
waf on;
|
||||
waf_rule_path /www/server/nginx/ngx_waf/assets/rules/;
|
||||
waf_rule_path /www/server/openresty/ngx_waf/assets/rules/;
|
||||
waf_mode DYNAMIC;
|
||||
waf_cc_deny rate=1000r/m duration=60m;
|
||||
waf_cache capacity=50;
|
||||
@@ -550,7 +569,10 @@ server
|
||||
Success(ctx, nil)
|
||||
}
|
||||
|
||||
func (c *WebsiteController) SetStatus(ctx http.Context) {
|
||||
func (c *WebsiteController) Status(ctx http.Context) {
|
||||
if !Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
id := ctx.Request().InputInt("id")
|
||||
if id == 0 {
|
||||
Error(ctx, http.StatusBadRequest, "参数错误")
|
||||
@@ -574,26 +596,26 @@ func (c *WebsiteController) SetStatus(ctx http.Context) {
|
||||
raw := tools.ReadFile("/www/server/vhost/" + website.Name + ".conf")
|
||||
|
||||
// 运行目录
|
||||
rootConfig := tools.Cut(raw, "# root标记位开始", "# root标记位结束")
|
||||
rootConfig := tools.Cut(raw, "# root标记位开始\n", "# root标记位结束")
|
||||
match := regexp.MustCompile(`root\s+(.+);`).FindStringSubmatch(rootConfig)
|
||||
if len(match) == 2 {
|
||||
if website.Status {
|
||||
root := regexp.MustCompile(`# root\s+(.+);`).FindStringSubmatch(rootConfig)
|
||||
raw = strings.ReplaceAll(raw, rootConfig, "root "+root[1]+";")
|
||||
raw = strings.ReplaceAll(raw, rootConfig, " root "+root[1]+";\n ")
|
||||
} else {
|
||||
raw = strings.ReplaceAll(raw, rootConfig, "root /www/server/openresty/html;\n# root "+match[1]+";\n")
|
||||
raw = strings.ReplaceAll(raw, rootConfig, " root /www/server/openresty/html;\n # root "+match[1]+";\n ")
|
||||
}
|
||||
}
|
||||
|
||||
// 默认文件
|
||||
indexConfig := tools.Cut(raw, "# index标记位开始", "# index标记位结束")
|
||||
indexConfig := tools.Cut(raw, "# index标记位开始\n", "# index标记位结束")
|
||||
match = regexp.MustCompile(`index\s+(.+);`).FindStringSubmatch(indexConfig)
|
||||
if len(match) == 2 {
|
||||
if website.Status {
|
||||
index := regexp.MustCompile(`# index\s+(.+);`).FindStringSubmatch(indexConfig)
|
||||
raw = strings.ReplaceAll(raw, indexConfig, "index "+index[1]+";")
|
||||
raw = strings.ReplaceAll(raw, indexConfig, " index "+index[1]+";\n ")
|
||||
} else {
|
||||
raw = strings.ReplaceAll(raw, indexConfig, "index stop.html;\n# index "+match[1]+";\n")
|
||||
raw = strings.ReplaceAll(raw, indexConfig, " index stop.html;\n # index "+match[1]+";\n ")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func Static() contractshttp.Middleware {
|
||||
return func(ctx contractshttp.Context) {
|
||||
static.Serve(services.NewSettingImpl().Get("panel_entrance", "/"), static.LocalFile("./public", false))(ctx.(*frameworkhttp.GinContext).Instance())
|
||||
static.Serve(services.NewSettingImpl().Get("entrance", "/"), static.LocalFile("/www/panel/public", false))(ctx.(*frameworkhttp.GinContext).Instance())
|
||||
|
||||
ctx.Request().Next()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ const (
|
||||
SettingKeyMonitorDays = "monitor_days"
|
||||
SettingKeyBackupPath = "backup_path"
|
||||
SettingKeyWebsitePath = "website_path"
|
||||
SettingKeyPanelEntrance = "panel_entrance"
|
||||
SettingKeyEntrance = "entrance"
|
||||
SettingKeyMysqlRootPassword = "mysql_root_password"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,4 +8,7 @@ var (
|
||||
Version = "5.7.42"
|
||||
Requires = []string{}
|
||||
Excludes = []string{"mysql80"}
|
||||
Install = `bash /www/panel/scripts/mysql/install.sh 57`
|
||||
Uninstall = `bash /www/panel/scripts/mysql/uninstall.sh 57`
|
||||
Update = `echo "not support now"`
|
||||
)
|
||||
|
||||
@@ -3,9 +3,12 @@ package mysql80
|
||||
var (
|
||||
Name = "MySQL-8.0"
|
||||
Author = "耗子"
|
||||
Description = "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。"
|
||||
Description = "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。(内存 < 4G 无法安装)"
|
||||
Slug = "mysql80"
|
||||
Version = "8.0.33"
|
||||
Requires = []string{}
|
||||
Excludes = []string{"mysql57"}
|
||||
Install = `bash /www/panel/scripts/mysql/install.sh 80`
|
||||
Uninstall = `bash /www/panel/scripts/mysql/uninstall.sh 80`
|
||||
Update = `echo "not support now"`
|
||||
)
|
||||
|
||||
@@ -8,4 +8,7 @@ var (
|
||||
Version = "1.21.4.1"
|
||||
Requires = []string{}
|
||||
Excludes = []string{}
|
||||
Install = "bash /www/panel/scripts/openresty/install.sh"
|
||||
Uninstall = "bash /www/panel/scripts/openresty/uninstall.sh"
|
||||
Update = "bash /www/panel/scripts/openresty/install.sh"
|
||||
)
|
||||
|
||||
@@ -8,4 +8,7 @@ var (
|
||||
Version = "7.4.33"
|
||||
Requires = []string{}
|
||||
Excludes = []string{}
|
||||
Install = `bash /www/panel/scripts/php/install.sh 74`
|
||||
Uninstall = `bash /www/panel/scripts/php/uninstall.sh 74`
|
||||
Update = `bash /www/panel/scripts/php/install.sh 74`
|
||||
)
|
||||
|
||||
@@ -8,4 +8,7 @@ var (
|
||||
Version = "8.0.29"
|
||||
Requires = []string{}
|
||||
Excludes = []string{}
|
||||
Install = `bash /www/panel/scripts/php/install.sh 80`
|
||||
Uninstall = `bash /www/panel/scripts/php/uninstall.sh 80`
|
||||
Update = `bash /www/panel/scripts/php/install.sh 80`
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
@@ -24,17 +26,15 @@ func (r *CronImpl) AddToSystem(cron models.Cron) {
|
||||
} else {
|
||||
tools.ExecShell("echo \"" + cron.Time + " " + cron.Shell + " >> " + cron.Log + " 2>&1\" >> /var/spool/cron/crontabs/root")
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl restart crond")
|
||||
}
|
||||
|
||||
// DeleteFromSystem 从系统中删除
|
||||
func (r *CronImpl) DeleteFromSystem(cron models.Cron) {
|
||||
// 需要转义Shell路径的/为\/
|
||||
cron.Shell = strings.ReplaceAll(cron.Shell, "/", "\\/")
|
||||
if tools.IsRHEL() {
|
||||
tools.ExecShell("sed -i '/" + cron.Shell + "/d' /var/spool/cron/root")
|
||||
} else {
|
||||
tools.ExecShell("sed -i '/" + cron.Shell + "/d' /var/spool/cron/crontabs/root")
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl restart crond")
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/app/plugins/mysql57"
|
||||
"panel/app/plugins/mysql80"
|
||||
"panel/app/plugins/openresty"
|
||||
"panel/app/plugins/php74"
|
||||
"panel/app/plugins/php80"
|
||||
)
|
||||
|
||||
// PanelPlugin 插件元数据结构
|
||||
@@ -19,11 +21,16 @@ type PanelPlugin struct {
|
||||
Version string
|
||||
Requires []string
|
||||
Excludes []string
|
||||
Install string
|
||||
Uninstall string
|
||||
Update string
|
||||
}
|
||||
|
||||
type Plugin interface {
|
||||
AllInstalled() ([]models.Plugin, error)
|
||||
All() []PanelPlugin
|
||||
GetBySlug(slug string) PanelPlugin
|
||||
GetInstalledBySlug(slug string) models.Plugin
|
||||
}
|
||||
|
||||
type PluginImpl struct {
|
||||
@@ -55,6 +62,21 @@ func (r *PluginImpl) All() []PanelPlugin {
|
||||
Version: openresty.Version,
|
||||
Requires: openresty.Requires,
|
||||
Excludes: openresty.Excludes,
|
||||
Install: openresty.Install,
|
||||
Uninstall: openresty.Uninstall,
|
||||
Update: openresty.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: mysql57.Name,
|
||||
Author: mysql57.Author,
|
||||
Description: mysql57.Description,
|
||||
Slug: mysql57.Slug,
|
||||
Version: mysql57.Version,
|
||||
Requires: mysql57.Requires,
|
||||
Excludes: mysql57.Excludes,
|
||||
Install: mysql57.Install,
|
||||
Uninstall: mysql57.Uninstall,
|
||||
Update: mysql57.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: mysql80.Name,
|
||||
@@ -64,6 +86,9 @@ func (r *PluginImpl) All() []PanelPlugin {
|
||||
Version: mysql80.Version,
|
||||
Requires: mysql80.Requires,
|
||||
Excludes: mysql80.Excludes,
|
||||
Install: mysql80.Install,
|
||||
Uninstall: mysql80.Uninstall,
|
||||
Update: mysql80.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: php74.Name,
|
||||
@@ -73,6 +98,21 @@ func (r *PluginImpl) All() []PanelPlugin {
|
||||
Version: php74.Version,
|
||||
Requires: php74.Requires,
|
||||
Excludes: php74.Excludes,
|
||||
Install: php74.Install,
|
||||
Uninstall: php74.Uninstall,
|
||||
Update: php74.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: php80.Name,
|
||||
Author: php80.Author,
|
||||
Description: php80.Description,
|
||||
Slug: php80.Slug,
|
||||
Version: php80.Version,
|
||||
Requires: php80.Requires,
|
||||
Excludes: php80.Excludes,
|
||||
Install: php80.Install,
|
||||
Uninstall: php80.Uninstall,
|
||||
Update: php80.Update,
|
||||
})
|
||||
|
||||
return p
|
||||
|
||||
+21
-20
@@ -61,10 +61,13 @@ type WebsiteSetting struct {
|
||||
}
|
||||
|
||||
type WebsiteImpl struct {
|
||||
setting Setting
|
||||
}
|
||||
|
||||
func NewWebsiteImpl() *WebsiteImpl {
|
||||
return &WebsiteImpl{}
|
||||
return &WebsiteImpl{
|
||||
setting: NewSettingImpl(),
|
||||
}
|
||||
}
|
||||
|
||||
// List 列出网站
|
||||
@@ -88,7 +91,7 @@ func (r *WebsiteImpl) Add(website PanelWebsite) (models.Website, error) {
|
||||
|
||||
// path为空时,设置默认值
|
||||
if len(website.Path) == 0 {
|
||||
website.Path = "/www/wwwroot/" + website.Name
|
||||
website.Path = r.setting.Get(models.SettingKeyWebsitePath) + "/" + website.Name
|
||||
}
|
||||
// path不为/开头时,返回错误
|
||||
if website.Path[0] != '/' {
|
||||
@@ -113,8 +116,7 @@ func (r *WebsiteImpl) Add(website PanelWebsite) (models.Website, error) {
|
||||
|
||||
tools.Mkdir(website.Path, 0755)
|
||||
|
||||
index := `
|
||||
<!DOCTYPE html>
|
||||
index := `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
@@ -159,8 +161,7 @@ func (r *WebsiteImpl) Add(website PanelWebsite) (models.Website, error) {
|
||||
}
|
||||
}
|
||||
|
||||
nginxConf := fmt.Sprintf(`
|
||||
# 配置文件中的标记位请勿随意修改,改错将导致面板无法识别!
|
||||
nginxConf := fmt.Sprintf(`# 配置文件中的标记位请勿随意修改,改错将导致面板无法识别!
|
||||
# 有自定义配置需求的,请将自定义的配置写在各标记位下方。
|
||||
server
|
||||
{
|
||||
@@ -186,7 +187,7 @@ server
|
||||
|
||||
# waf标记位开始
|
||||
waf on;
|
||||
waf_rule_path /www/server/nginx/ngx_waf/assets/rules/;
|
||||
waf_rule_path /www/server/openresty/ngx_waf/assets/rules/;
|
||||
waf_mode DYNAMIC;
|
||||
waf_cc_deny rate=1000r/m duration=60m;
|
||||
waf_cache capacity=50;
|
||||
@@ -218,12 +219,12 @@ server
|
||||
|
||||
`, portList, domainList, website.Path, website.Php, website.Name, website.Name, website.Name)
|
||||
|
||||
tools.WriteFile("/www/server/panel/vhost/openresty/"+website.Name+".conf", nginxConf, 0644)
|
||||
tools.WriteFile("/www/server/panel/vhost/openresty/rewrite/"+website.Name+".conf", "", 0644)
|
||||
tools.WriteFile("/www/server/panel/vhost/openresty/ssl/"+website.Name+".pem", "", 0644)
|
||||
tools.WriteFile("/www/server/panel/vhost/openresty/ssl/"+website.Name+".key", "", 0644)
|
||||
tools.WriteFile("/www/server/vhost/"+website.Name+".conf", nginxConf, 0644)
|
||||
tools.WriteFile("/www/server/vhost/rewrite/"+website.Name+".conf", "", 0644)
|
||||
tools.WriteFile("/www/server/vhost/ssl/"+website.Name+".pem", "", 0644)
|
||||
tools.WriteFile("/www/server/vhost/ssl/"+website.Name+".key", "", 0644)
|
||||
|
||||
tools.ExecShellAsync("systemctl reload openresty")
|
||||
tools.ExecShell("systemctl reload openresty")
|
||||
|
||||
// TODO 创建数据库
|
||||
|
||||
@@ -233,7 +234,7 @@ server
|
||||
// Delete 删除网站
|
||||
func (r *WebsiteImpl) Delete(id int) error {
|
||||
var website models.Website
|
||||
if err := facades.Orm().Query().Where("id", id).First(&website); err != nil {
|
||||
if err := facades.Orm().Query().Where("id", id).FirstOrFail(&website); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -241,13 +242,13 @@ func (r *WebsiteImpl) Delete(id int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
tools.RemoveFile("/www/server/panel/vhost/openresty/" + website.Name + ".conf")
|
||||
tools.RemoveFile("/www/server/panel/vhost/openresty/rewrite/" + website.Name + ".conf")
|
||||
tools.RemoveFile("/www/server/panel/vhost/openresty/ssl/" + website.Name + ".pem")
|
||||
tools.RemoveFile("/www/server/panel/vhost/openresty/ssl/" + website.Name + ".key")
|
||||
tools.RemoveFile("/www/server/vhost/" + website.Name + ".conf")
|
||||
tools.RemoveFile("/www/server/vhost/rewrite/" + website.Name + ".conf")
|
||||
tools.RemoveFile("/www/server/vhost/ssl/" + website.Name + ".pem")
|
||||
tools.RemoveFile("/www/server/vhost/ssl/" + website.Name + ".key")
|
||||
tools.RemoveFile(website.Path)
|
||||
|
||||
tools.ExecShellAsync("systemctl reload openresty")
|
||||
tools.ExecShell("systemctl reload openresty")
|
||||
|
||||
// TODO 删除数据库
|
||||
|
||||
@@ -261,7 +262,7 @@ func (r *WebsiteImpl) GetConfig(id int) (WebsiteSetting, error) {
|
||||
return WebsiteSetting{}, err
|
||||
}
|
||||
|
||||
config := tools.ReadFile("/www/server/panel/vhost/openresty/" + website.Name + ".conf")
|
||||
config := tools.ReadFile("/www/server/vhost/" + website.Name + ".conf")
|
||||
|
||||
var setting WebsiteSetting
|
||||
setting.Name = website.Name
|
||||
@@ -339,7 +340,7 @@ func (r *WebsiteImpl) GetConfig(id int) (WebsiteSetting, error) {
|
||||
setting.WafCache = match[1]
|
||||
}
|
||||
|
||||
setting.Rewrite = tools.ReadFile("/www/server/panel/vhost/openresty/rewrite/" + website.Name + ".conf")
|
||||
setting.Rewrite = tools.ReadFile("/www/server/vhost/rewrite/" + website.Name + ".conf")
|
||||
setting.Log = tools.ExecShell("tail -n 100 /www/wwwlogs/" + website.Name + ".log")
|
||||
|
||||
return setting, nil
|
||||
|
||||
+6
-2
@@ -2,6 +2,7 @@ package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/foundation"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
|
||||
"panel/config"
|
||||
)
|
||||
@@ -9,9 +10,12 @@ import (
|
||||
func Boot() {
|
||||
app := foundation.NewApplication()
|
||||
|
||||
//Bootstrap the application
|
||||
// Bootstrap the application
|
||||
app.Boot()
|
||||
|
||||
//Bootstrap the config.
|
||||
// Bootstrap the config.
|
||||
config.Boot()
|
||||
|
||||
// 设置 Carbon 时区
|
||||
carbon.SetTimezone(carbon.PRC)
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ func init() {
|
||||
"connections": map[string]any{
|
||||
"panel": map[string]any{
|
||||
"driver": "sqlite",
|
||||
"database": "database/panel.db",
|
||||
"database": "/www/panel/database/panel.db",
|
||||
"prefix": "",
|
||||
"singular": false, // Table name is singular
|
||||
},
|
||||
|
||||
@@ -24,7 +24,7 @@ func init() {
|
||||
"disks": map[string]any{
|
||||
"local": map[string]any{
|
||||
"driver": "local",
|
||||
"root": "/",
|
||||
"root": "storage/app",
|
||||
"url": "http://localhost/",
|
||||
},
|
||||
},
|
||||
|
||||
+1
-1
@@ -8,6 +8,6 @@ func init() {
|
||||
config := facades.Config()
|
||||
config.Add("panel", map[string]any{
|
||||
"name": "耗子面板",
|
||||
"version": "2.0.0",
|
||||
"version": "v2.0.10",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -32,5 +32,8 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
// 启动计划任务
|
||||
go facades.Schedule().Run()
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_DEBUG=false
|
||||
APP_HOST=0.0.0.0
|
||||
APP_PORT=8888
|
||||
|
||||
|
||||
+5
-4
@@ -4,6 +4,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
@@ -51,11 +52,11 @@ func ExecShell(shell string) string {
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 执行命令 $s 失败: %s", shell, err.Error())
|
||||
facades.Log().Errorf("[面板][Helpers] 执行命令 %s 失败: %s", shell, err.Error())
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(output)
|
||||
return strings.TrimSpace(string(output))
|
||||
}
|
||||
|
||||
// ExecShellAsync 异步执行 shell 命令
|
||||
@@ -64,13 +65,13 @@ func ExecShellAsync(shell string) {
|
||||
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 执行命令 $s 失败: %s", shell, err.Error())
|
||||
facades.Log().Errorf("[面板][Helpers] 执行命令 %s 失败: %s", shell, err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 执行命令 $s 失败: %s", shell, err.Error())
|
||||
facades.Log().Errorf("[面板][Helpers] 执行命令 %s 失败: %s", shell, err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func (s *SystemHelperTestSuite) TestRemoveFile() {
|
||||
}
|
||||
|
||||
func (s *SystemHelperTestSuite) TestExecShell() {
|
||||
s.Equal("test\n", ExecShell("echo 'test'"))
|
||||
s.Equal("test", ExecShell("echo 'test'"))
|
||||
}
|
||||
|
||||
func (s *SystemHelperTestSuite) TestExecShellAsync() {
|
||||
|
||||
+42
-5
@@ -5,9 +5,11 @@ import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/host"
|
||||
@@ -114,17 +116,52 @@ func GetLatestPanelVersion() (PanelInfo, error) {
|
||||
}
|
||||
|
||||
// UpdatePanel 更新面板
|
||||
func UpdatePanel() error {
|
||||
func UpdatePanel(proxy bool) error {
|
||||
panelInfo, err := GetLatestPanelVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command("/bin/bash", "-c", "wget -O panel.tar.gz "+panelInfo.DownloadUrl+" && tar -zxvf panel.tar.gz && rm -rf panel.tar.gz && chmod +x panel && ./panel artisan migrate")
|
||||
_, err = cmd.Output()
|
||||
if err != nil {
|
||||
return errors.New("更新面板失败")
|
||||
color.Greenln("最新版本: " + panelInfo.Version)
|
||||
color.Greenln("下载链接: " + panelInfo.DownloadUrl)
|
||||
color.Greenln("使用代理: " + strconv.FormatBool(proxy))
|
||||
|
||||
color.Greenln("备份面板配置...")
|
||||
ExecShell("cp -f /www/panel/database/panel.db /tmp/panel.db.bak")
|
||||
ExecShell("cp -f /www/panel/panel.conf /tmp/panel.conf.bak")
|
||||
if !Exists("/tmp/panel.db.bak") || !Exists("/tmp/panel.conf.bak") {
|
||||
return errors.New("备份面板配置失败")
|
||||
}
|
||||
color.Greenln("备份完成")
|
||||
|
||||
color.Greenln("清理旧版本...")
|
||||
ExecShell("rm -rf /www/panel/*")
|
||||
color.Greenln("清理完成")
|
||||
|
||||
color.Greenln("正在下载...")
|
||||
if proxy {
|
||||
ExecShell("wget -O /www/panel/panel.zip https://ghproxy.com/" + panelInfo.DownloadUrl)
|
||||
} else {
|
||||
ExecShell("wget -O /www/panel/panel.zip " + panelInfo.DownloadUrl)
|
||||
}
|
||||
color.Greenln("下载完成")
|
||||
|
||||
color.Greenln("更新新版本...")
|
||||
ExecShell("cd /www/panel && unzip -o panel.zip && rm -rf panel.zip && chmod 700 panel")
|
||||
color.Greenln("更新完成")
|
||||
|
||||
color.Greenln("恢复面板配置...")
|
||||
ExecShell("cp -f /tmp/panel.db.bak /www/panel/database/panel.db")
|
||||
ExecShell("cp -f /tmp/panel.conf.bak /www/panel/panel.conf")
|
||||
if !Exists("/www/panel/database/panel.db") || !Exists("/www/panel/panel.conf") {
|
||||
return errors.New("恢复面板配置失败")
|
||||
}
|
||||
ExecShell("/www/panel/panel --env=panel.conf artisan migrate")
|
||||
color.Greenln("恢复完成")
|
||||
|
||||
color.Greenln("重启面板...")
|
||||
ExecShell("systemctl restart panel")
|
||||
color.Greenln("重启完成")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.env
|
||||
pnpm-lock.yaml
|
||||
@@ -0,0 +1,194 @@
|
||||
|
||||
/* 样式加载完毕的标识 */
|
||||
html #layuicss-cron {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 1989px;
|
||||
}
|
||||
|
||||
|
||||
/* 主体结构 */
|
||||
.layui-cron {
|
||||
width: 550px;
|
||||
position: absolute;
|
||||
z-index: 99999999;
|
||||
margin: 5px 0;
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
-webkit-animation-duration: 0.3s;
|
||||
animation-duration: 0.3s;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 2px 5px 0px;
|
||||
-webkit-animation-name: cron-upbit;
|
||||
animation-name: cron-upbit;
|
||||
border: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.layui-cron-main ul {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
@-webkit-keyframes cron-upbit {
|
||||
|
||||
/* 微微往上滑入 */
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 20px, 0);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cron-upbit {
|
||||
from {
|
||||
transform: translate3d(0, 20px, 0);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* tabs */
|
||||
.layui-cron>.layui-tab {
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 行 */
|
||||
.cron-row {
|
||||
padding-left: 13px;
|
||||
}
|
||||
/* 格 */
|
||||
.cron-grid {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
/* 表达式 */
|
||||
.cron-title {
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
margin: 10px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.cron-box {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.cron-box+.cron-box {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
.cron-footer-btns {
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.cron-footer-btns span {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
margin: 0 0 0 -1px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #C9C9C9;
|
||||
background-color: #fff;
|
||||
white-space: nowrap;
|
||||
vertical-align: top;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
box-sizing: border-box;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.cron-footer-btns span:hover {
|
||||
color: #5FB878;
|
||||
}
|
||||
|
||||
|
||||
/* 表单 */
|
||||
.layui-cron .layui-form-radio {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.cron-form {
|
||||
line-height: 28px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cron-input-mid {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-top: 6px;
|
||||
background-color: #e5e5e5;
|
||||
padding: 0 12px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
border: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cron-input {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-top: 6px;
|
||||
padding: 0 8px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
box-sizing: border-box;
|
||||
width: 80px;
|
||||
-webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
|
||||
-o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
|
||||
transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
|
||||
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
|
||||
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
|
||||
}
|
||||
|
||||
.cron-input:focus {
|
||||
outline: 0;
|
||||
border: 1px solid #01AAED;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 4px 0px #01AAED;
|
||||
translate: 1s;
|
||||
}
|
||||
|
||||
.layui-cron .layui-form-checkbox[lay-skin="primary"] span {
|
||||
padding-right: 10px;
|
||||
min-width: 16px;
|
||||
}
|
||||
|
||||
.layui-cron .layui-form-checkbox[lay-skin="primary"] {
|
||||
padding-left: 22px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.layui-cron input[type=number] {
|
||||
-moz-appearance:textfield;
|
||||
}
|
||||
.layui-cron input[type=number]::-webkit-inner-spin-button,
|
||||
.layui-cron input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
.cron-tips{
|
||||
color: grey;
|
||||
line-height: 28px;
|
||||
height: 28px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-top: 8px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
@@ -0,0 +1,970 @@
|
||||
/**
|
||||
@ Name:layui.cron Cron表达式解析器
|
||||
@ Author:贝哥哥
|
||||
@ License:MIT
|
||||
*/
|
||||
|
||||
layui.define(['lay', 'element', 'form'], function (exports) { //假如该组件依赖 layui.form
|
||||
var $ = layui.$, layer = layui.layer, lay = layui.lay, element = layui.element, form = layui.form
|
||||
|
||||
|
||||
//字符常量
|
||||
, MOD_NAME = 'cron', ELEM = '.layui-cron', THIS = 'layui-this', SHOW = 'layui-show', HIDE = 'layui-hide'
|
||||
|
||||
, ELEM_STATIC = 'layui-cron-static', ELEM_FOOTER = 'layui-cron-footer', ELEM_CONFIRM = '.cron-btns-confirm',
|
||||
ELEM_HINT = 'layui-cron-hint'
|
||||
|
||||
, ELEM_RUN_HINT = 'layui-cron-run-hint'
|
||||
|
||||
//外部接口
|
||||
, cron = {
|
||||
v: '2.0.0' // cron 组件当前版本
|
||||
, index: layui.cron ? (layui.cron.index + 10000) : 0 // corn 实例标识
|
||||
|
||||
//设置全局项
|
||||
, set: function (options) {
|
||||
var that = this;
|
||||
that.config = $.extend({}, that.config, options);
|
||||
return that;
|
||||
}
|
||||
|
||||
//事件监听
|
||||
, on: function (events, callback) {
|
||||
return layui.onevent.call(this, MOD_NAME, events, callback);
|
||||
}
|
||||
|
||||
//主体CSS等待事件
|
||||
, ready: function (fn) {
|
||||
var cssPath = layui.cache.base + "cron.css?v=" + cron.v;
|
||||
layui.link(cssPath, fn, "cron"); //此处的“cron”要对应 cron.css 中的样式: html #layuicss-cron{}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
//操作当前实例
|
||||
, thisIns = function () {
|
||||
var that = this, options = that.config, id = options.id || options.index;
|
||||
|
||||
return {
|
||||
//提示框
|
||||
hint: function (content) {
|
||||
that.hint.call(that, content);
|
||||
}, config: options
|
||||
}
|
||||
}
|
||||
|
||||
//构造器,创建实例
|
||||
, Class = function (options) {
|
||||
var that = this;
|
||||
that.index = ++cron.index;
|
||||
that.config = $.extend({}, that.config, cron.config, options);
|
||||
cron.ready(function () {
|
||||
that.init();
|
||||
});
|
||||
};
|
||||
|
||||
//默认配置
|
||||
Class.prototype.config = {
|
||||
value: null // 当前表达式值,每秒执行一次
|
||||
,
|
||||
isInitValue: true //用于控制是否自动向元素填充初始值(需配合 value 参数使用)
|
||||
,
|
||||
lang: "cn" //语言,只支持cn/en,即中文和英文
|
||||
,
|
||||
tabs: [{key: 'minutes', range: '0-59'}, {
|
||||
key: 'hours', range: '0-23'
|
||||
}, {key: 'days', range: '1-31'}, {key: 'months', range: '1-12'}, {key: 'weeks', range: '1-7'}],
|
||||
defaultCron: {minutes: "*", hours: "*", days: "*", months: "*", weeks: "*"},
|
||||
trigger: "click" //呼出控件的事件
|
||||
,
|
||||
btns: ['run', 'confirm'] //右下角显示的按钮,会按照数组顺序排列
|
||||
,
|
||||
position: null //控件定位方式定位, 默认absolute,支持:fixed/absolute/static
|
||||
,
|
||||
zIndex: null //控件层叠顺序
|
||||
,
|
||||
show: false //是否直接显示,如果设置 true,则默认直接显示控件
|
||||
,
|
||||
showBottom: true //是否显示底部栏
|
||||
,
|
||||
done: null //控件选择完毕后的回调,点击运行/确定也均会触发
|
||||
,
|
||||
run: null // 最近运行时间接口
|
||||
};
|
||||
|
||||
//多语言
|
||||
Class.prototype.lang = function () {
|
||||
var that = this, options = that.config, text = {
|
||||
cn: {
|
||||
tabs: [{title: "分"}, {title: "时"}, {title: "日"}, {title: "月"}, {
|
||||
title: "周",
|
||||
rateBegin: "第",
|
||||
rateMid: "周的星期",
|
||||
rateEnd: ""
|
||||
}],
|
||||
every: "每",
|
||||
unspecified: "不指定",
|
||||
period: "周期",
|
||||
periodFrom: "从",
|
||||
rate: "按照",
|
||||
rateBegin: "从",
|
||||
rateMid: "开始,每",
|
||||
rateEnd: "执行一次",
|
||||
weekday: "工作日",
|
||||
weekdayPrefix: "每月",
|
||||
weekdaySuffix: "号最近的那个工作日",
|
||||
lastday: "本月最后一日",
|
||||
lastweek: "本月最后一个星期",
|
||||
custom: "指定",
|
||||
tools: {
|
||||
confirm: '确定', run: '运行'
|
||||
},
|
||||
formatError: ['Cron格式不合法', '<br>已为你重置']
|
||||
}, en: {
|
||||
tabs: [{title: "Minutes"}, {title: "Hours"}, {title: "Days"}, {title: "Months"}, {title: "Weeks"}],
|
||||
every: "Every ",
|
||||
unspecified: "Unspecified",
|
||||
period: "Period",
|
||||
periodFrom: "From",
|
||||
rate: "According to",
|
||||
rateBegin: "begin at",
|
||||
rateMid: ", every",
|
||||
rateEnd: " execute once",
|
||||
weekday: "Weekday",
|
||||
weekdayPrefix: "Every month at ",
|
||||
weekdaySuffix: "号最近的那个工作日",
|
||||
lastday: "Last day of the month",
|
||||
lastweek: "本月最后一个星期",
|
||||
custom: "Custom",
|
||||
tools: {
|
||||
confirm: 'Confirm', run: 'Run'
|
||||
},
|
||||
formatError: ['The cron format error', '<br>It has been reset']
|
||||
}
|
||||
};
|
||||
return text[options.lang] || text['cn'];
|
||||
};
|
||||
|
||||
//初始准备
|
||||
Class.prototype.init = function () {
|
||||
var that = this, options = that.config, isStatic = options.position === 'static';
|
||||
|
||||
options.elem = lay(options.elem);
|
||||
|
||||
options.eventElem = lay(options.eventElem);
|
||||
|
||||
if (!options.elem[0]) return;
|
||||
|
||||
//如果不是input|textarea元素,则默认采用click事件
|
||||
if (!that.isInput(options.elem[0])) {
|
||||
if (options.trigger === 'focus') {
|
||||
options.trigger = 'click';
|
||||
}
|
||||
}
|
||||
|
||||
// 设置渲染所绑定元素的唯一KEY
|
||||
if (!options.elem.attr('lay-key')) {
|
||||
options.elem.attr('lay-key', that.index);
|
||||
options.eventElem.attr('lay-key', that.index);
|
||||
}
|
||||
|
||||
// 当前实例主面板ID
|
||||
that.elemID = 'layui-icon' + options.elem.attr('lay-key');
|
||||
|
||||
//默认赋值
|
||||
if (options.value && options.isInitValue) {
|
||||
that.setValue(options.value);
|
||||
}
|
||||
if (!options.value) {
|
||||
options.value = options.elem[0].value || '';
|
||||
}
|
||||
var cronArr = options.value.split(' ');
|
||||
if (cronArr.length >= 6) {
|
||||
options.cron = {
|
||||
minutes: cronArr[0],
|
||||
hours: cronArr[1],
|
||||
days: cronArr[2],
|
||||
months: cronArr[3],
|
||||
weeks: cronArr[4],
|
||||
};
|
||||
} else {
|
||||
options.cron = lay.extend({}, options.defaultCron);
|
||||
}
|
||||
|
||||
|
||||
if (options.show || isStatic) that.render();
|
||||
isStatic || that.events();
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
// 控件主体渲染
|
||||
Class.prototype.render = function () {
|
||||
var that = this, options = that.config, lang = that.lang(), isStatic = options.position === 'static',
|
||||
tabFilter = 'cron-tab' + options.elem.attr('lay-key')
|
||||
//主面板
|
||||
, elem = that.elem = lay.elem('div', {
|
||||
id: that.elemID, 'class': ['layui-cron', isStatic ? (' ' + ELEM_STATIC) : ''].join('')
|
||||
})
|
||||
|
||||
// tab 内容区域
|
||||
, elemTab = that.elemTab = lay.elem('div', {
|
||||
'class': 'layui-tab layui-tab-card', 'lay-filter': tabFilter
|
||||
}), tabHead = lay.elem('ul', {
|
||||
'class': 'layui-tab-title'
|
||||
}), tabContent = lay.elem('div', {
|
||||
'class': 'layui-tab-content'
|
||||
})
|
||||
|
||||
//底部区域
|
||||
, divFooter = that.footer = lay.elem('div', {
|
||||
'class': ELEM_FOOTER
|
||||
});
|
||||
|
||||
if (options.zIndex) elem.style.zIndex = options.zIndex;
|
||||
|
||||
// 生成tab 内容区域
|
||||
elemTab.appendChild(tabHead);
|
||||
elemTab.appendChild(tabContent);
|
||||
lay.each(lang.tabs, function (i, item) {
|
||||
// 表头
|
||||
var li = lay.elem('li', {
|
||||
'class': i === 0 ? THIS : "", 'lay-id': i
|
||||
});
|
||||
li.innerHTML = item.title;
|
||||
tabHead.appendChild(li);
|
||||
|
||||
// 表体
|
||||
tabContent.appendChild(that.getTabContentChildElem(i));
|
||||
});
|
||||
|
||||
// 主区域
|
||||
elemMain = that.elemMain = lay.elem('div', {
|
||||
'class': 'layui-cron-main'
|
||||
});
|
||||
elemMain.appendChild(elemTab);
|
||||
|
||||
//生成底部栏
|
||||
lay(divFooter).html(function () {
|
||||
var html = [], btns = [];
|
||||
lay.each(options.btns, function (i, item) {
|
||||
var title = lang.tools[item] || 'btn';
|
||||
btns.push('<span lay-type="' + item + '" class="cron-btns-' + item + '">' + title + '</span>');
|
||||
});
|
||||
html.push('<div class="cron-footer-btns">' + btns.join('') + '</div>');
|
||||
return html.join('');
|
||||
}());
|
||||
|
||||
//插入到主区域
|
||||
elem.appendChild(elemMain);
|
||||
|
||||
options.showBottom && elem.appendChild(divFooter);
|
||||
|
||||
|
||||
//移除上一个控件
|
||||
that.remove(Class.thisElemCron);
|
||||
|
||||
//如果是静态定位,则插入到指定的容器中,否则,插入到body
|
||||
isStatic ? options.elem.append(elem) : (document.body.appendChild(elem)
|
||||
, that.position());
|
||||
|
||||
|
||||
that.checkCron();
|
||||
|
||||
that.elemEvent(); // 主面板事件
|
||||
|
||||
Class.thisElemCron = that.elemID;
|
||||
|
||||
form.render();
|
||||
|
||||
}
|
||||
|
||||
// 渲染 tab 子控件
|
||||
Class.prototype.getTabContentChildElem = function (index) {
|
||||
var that = this, options = that.config, tabItem = options.tabs[index], tabItemKey = tabItem.key,
|
||||
lang = that.lang(), tabItemLang = lang.tabs[index], cron = options.cron,
|
||||
formFilter = 'cronForm' + tabItemKey + options.elem.attr('lay-key'), data = function () {
|
||||
if (cron[tabItemKey].indexOf('-') != -1) {
|
||||
// 周期数据
|
||||
var arr = cron[tabItemKey].split('-');
|
||||
return {
|
||||
type: 'range', start: arr[0], end: arr[1]
|
||||
};
|
||||
}
|
||||
if (cron[tabItemKey].indexOf('/') != -1) {
|
||||
// 频率数据
|
||||
var arr = cron[tabItemKey].split('/');
|
||||
return {
|
||||
type: 'rate', begin: arr[0], rate: arr[1]
|
||||
};
|
||||
}
|
||||
if (cron[tabItemKey].indexOf(',') != -1 || /^\+?[0-9][0-9]*$/.test(cron[tabItemKey])) {
|
||||
// 按照指定执行
|
||||
var arr = cron[tabItemKey].split(',').map(Number);
|
||||
return {
|
||||
type: 'custom', values: arr
|
||||
};
|
||||
}
|
||||
if (cron[tabItemKey].indexOf('W') != -1) {
|
||||
// 最近的工作日
|
||||
var value = cron[tabItemKey].replace('W', '');
|
||||
return {
|
||||
type: 'weekday', value: value
|
||||
};
|
||||
}
|
||||
if (index === 2 && cron[tabItemKey] === 'L') {
|
||||
// 本月最后一日
|
||||
return {
|
||||
type: 'lastday', value: 'L'
|
||||
};
|
||||
}
|
||||
if (index === 4 && cron[tabItemKey].indexOf('L') != -1) {
|
||||
// 本月最后一个周 value
|
||||
var value = cron[tabItemKey].replace('L', '');
|
||||
return {
|
||||
type: 'lastweek', value: value
|
||||
};
|
||||
}
|
||||
if (cron[tabItemKey] === '*') {
|
||||
// 每次
|
||||
return {
|
||||
type: 'every', value: '*'
|
||||
};
|
||||
}
|
||||
if (cron[tabItemKey] === '?' || cron[tabItemKey] === undefined || cron[tabItemKey] === '') {
|
||||
// 不指定
|
||||
return {
|
||||
//type: 'unspecified', value: cron[tabItemKey]
|
||||
type: 'every', value: '*'
|
||||
};
|
||||
}
|
||||
}(), rangeData = function () {
|
||||
if (tabItem.range) {
|
||||
var arr = tabItem.range.split('-');
|
||||
return {
|
||||
min: parseInt(arr[0]), max: parseInt(arr[1])
|
||||
};
|
||||
}
|
||||
}();
|
||||
var elem = lay.elem('div', {
|
||||
'class': 'layui-tab-item layui-form ' + (index === 0 ? SHOW : ""), 'lay-filter': formFilter
|
||||
});
|
||||
|
||||
// 每次
|
||||
elem.appendChild(function () {
|
||||
var everyRadio = lay.elem('input', {
|
||||
'name': tabItemKey + '[type]',
|
||||
'type': 'radio',
|
||||
'value': 'every',
|
||||
'title': lang.every + tabItemLang.title
|
||||
});
|
||||
if (data.type === 'every') {
|
||||
lay(everyRadio).attr('checked', true);
|
||||
}
|
||||
var everyDiv = lay.elem('div', {
|
||||
'class': 'cron-row'
|
||||
});
|
||||
everyDiv.appendChild(everyRadio);
|
||||
return everyDiv;
|
||||
}());
|
||||
|
||||
// 不指定,从日开始
|
||||
/*if (index >= 2) {
|
||||
elem.appendChild(function () {
|
||||
var unspecifiedRadio = lay.elem('input', {
|
||||
'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'unspecified', 'title': lang.unspecified
|
||||
});
|
||||
if (data.type === 'unspecified') {
|
||||
lay(unspecifiedRadio).attr('checked', true);
|
||||
}
|
||||
var unspecifiedDiv = lay.elem('div', {
|
||||
'class': 'cron-row'
|
||||
});
|
||||
unspecifiedDiv.appendChild(unspecifiedRadio);
|
||||
return unspecifiedDiv;
|
||||
}());
|
||||
}*/
|
||||
|
||||
// 周期
|
||||
var rangeChild = [function () {
|
||||
var rangeRadio = lay.elem('input', {
|
||||
'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'range', 'title': lang.period
|
||||
});
|
||||
if (data.type === 'range') {
|
||||
lay(rangeRadio).attr('checked', true);
|
||||
}
|
||||
return rangeRadio;
|
||||
}(), function () {
|
||||
var elem = lay.elem('div', {
|
||||
'class': 'cron-input-mid'
|
||||
});
|
||||
elem.innerHTML = lang.periodFrom;
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('input', {
|
||||
'class': 'cron-input', 'type': 'number', 'name': 'rangeStart', 'value': data.start || ''
|
||||
});
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('div', {
|
||||
'class': 'cron-input-mid'
|
||||
});
|
||||
elem.innerHTML = '-';
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('input', {
|
||||
'class': 'cron-input', 'type': 'number', 'name': 'rangeEnd', 'value': data.end || ''
|
||||
});
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('div', {
|
||||
'class': 'cron-input-mid'
|
||||
});
|
||||
elem.innerHTML = tabItemLang.title;
|
||||
return elem;
|
||||
}()]
|
||||
|
||||
, rangeDiv = lay.elem('div', {
|
||||
'class': 'cron-row'
|
||||
});
|
||||
lay.each(rangeChild, function (i, item) {
|
||||
rangeDiv.appendChild(item);
|
||||
});
|
||||
if (tabItem.range) {
|
||||
var rangeTip = lay.elem('div', {
|
||||
'class': 'cron-tips'
|
||||
});
|
||||
rangeTip.innerHTML = ['(', tabItem.range, ')'].join('');
|
||||
rangeDiv.appendChild(rangeTip);
|
||||
}
|
||||
elem.appendChild(rangeDiv);
|
||||
|
||||
// 频率,年没有
|
||||
if (index < 6) {
|
||||
var rateChild = [function () {
|
||||
var rateRadio = lay.elem('input', {
|
||||
'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'rate', 'title': lang.rate
|
||||
});
|
||||
if (data.type === 'rate') {
|
||||
lay(rateRadio).attr('checked', true);
|
||||
}
|
||||
return rateRadio;
|
||||
}(), function () {
|
||||
var elem = lay.elem('div', {
|
||||
'class': 'cron-input-mid'
|
||||
});
|
||||
elem.innerHTML = tabItemLang.rateBegin || lang.rateBegin;
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('input', {
|
||||
'class': 'cron-input', 'type': 'number', 'name': 'begin', 'value': data.begin || ''
|
||||
});
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('div', {
|
||||
'class': 'cron-input-mid'
|
||||
});
|
||||
elem.innerHTML = tabItemLang.rateMid || (tabItemLang.title + lang.rateMid);
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('input', {
|
||||
'class': 'cron-input', 'type': 'number', 'name': 'rate', 'value': data.rate || ''
|
||||
});
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('div', {
|
||||
'class': 'cron-input-mid'
|
||||
});
|
||||
elem.innerHTML = undefined != tabItemLang.rateEnd ? tabItemLang.rateEnd : (tabItemLang.title + lang.rateEnd);
|
||||
if (undefined != tabItemLang.rateEnd && tabItemLang.rateEnd === '') {
|
||||
lay(elem).addClass(HIDE);
|
||||
}
|
||||
return elem;
|
||||
}()]
|
||||
|
||||
, rateDiv = lay.elem('div', {
|
||||
'class': 'cron-row'
|
||||
});
|
||||
lay.each(rateChild, function (i, item) {
|
||||
rateDiv.appendChild(item);
|
||||
});
|
||||
if (tabItem.range) {
|
||||
var rateTip = lay.elem('div', {
|
||||
'class': 'cron-tips'
|
||||
});
|
||||
if (index === 4) {
|
||||
// 周
|
||||
rateTip.innerHTML = '(1-4/1-7)';
|
||||
} else {
|
||||
rateTip.innerHTML = ['(', rangeData.min, '/', (rangeData.max + (index <= 1 ? 1 : 0)), ')'].join('');
|
||||
}
|
||||
rateDiv.appendChild(rateTip);
|
||||
}
|
||||
elem.appendChild(rateDiv);
|
||||
}
|
||||
|
||||
// 特殊:日(最近的工作日、最后一日),周(最后一周)
|
||||
/*if (index === 2) {
|
||||
// 日
|
||||
// 最近的工作日
|
||||
var weekChild = [function () {
|
||||
var weekRadio = lay.elem('input', {
|
||||
'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'weekday', 'title': lang.weekday
|
||||
});
|
||||
if (data.type === 'weekday') {
|
||||
lay(weekRadio).attr('checked', true);
|
||||
}
|
||||
return weekRadio;
|
||||
}(), function () {
|
||||
var elem = lay.elem('div', {
|
||||
'class': 'cron-input-mid'
|
||||
});
|
||||
elem.innerHTML = lang.weekdayPrefix;
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('input', {
|
||||
'class': 'cron-input', 'type': 'number', 'name': 'weekday', 'value': data.value || ''
|
||||
});
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('div', {
|
||||
'class': 'cron-input-mid'
|
||||
});
|
||||
elem.innerHTML = lang.weekdaySuffix;
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('div', {
|
||||
'class': 'cron-tips'
|
||||
});
|
||||
elem.innerHTML = ['(', tabItem.range, ')'].join('');
|
||||
return elem;
|
||||
}()]
|
||||
|
||||
, weekDiv = lay.elem('div', {
|
||||
'class': 'cron-row'
|
||||
});
|
||||
lay.each(weekChild, function (i, item) {
|
||||
weekDiv.appendChild(item);
|
||||
});
|
||||
elem.appendChild(weekDiv);
|
||||
|
||||
// 本月最后一日
|
||||
elem.appendChild(function () {
|
||||
var lastRadio = lay.elem('input', {
|
||||
'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'lastday', 'title': lang.lastday
|
||||
});
|
||||
if (data.type === 'lastday') {
|
||||
lay(lastRadio).attr('checked', true);
|
||||
}
|
||||
var lastDiv = lay.elem('div', {
|
||||
'class': 'cron-row'
|
||||
});
|
||||
lastDiv.appendChild(lastRadio);
|
||||
return lastDiv;
|
||||
}());
|
||||
|
||||
}
|
||||
|
||||
if (index === 4) {
|
||||
// 本月最后一个周几
|
||||
var lastWeekChild = [function () {
|
||||
var lastWeekRadio = lay.elem('input', {
|
||||
'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'lastweek', 'title': lang.lastweek
|
||||
});
|
||||
if (data.type === 'lastweek') {
|
||||
lay(lastWeekRadio).attr('checked', true);
|
||||
}
|
||||
return lastWeekRadio;
|
||||
}(), function () {
|
||||
var elem = lay.elem('input', {
|
||||
'class': 'cron-input', 'type': 'number', 'name': 'lastweek', 'value': data.value || ''
|
||||
});
|
||||
return elem;
|
||||
}(), function () {
|
||||
var elem = lay.elem('div', {
|
||||
'class': 'cron-tips'
|
||||
});
|
||||
elem.innerHTML = ['(', tabItem.range, ')'].join('');
|
||||
return elem;
|
||||
}()]
|
||||
|
||||
, lastWeekDiv = lay.elem('div', {
|
||||
'class': 'cron-row'
|
||||
});
|
||||
lay.each(lastWeekChild, function (i, item) {
|
||||
lastWeekDiv.appendChild(item);
|
||||
});
|
||||
elem.appendChild(lastWeekDiv);
|
||||
|
||||
}*/
|
||||
|
||||
// 指定
|
||||
if (index <= 4) {
|
||||
elem.appendChild(function () {
|
||||
var customRadio = lay.elem('input', {
|
||||
'name': tabItemKey + '[type]', 'type': 'radio', 'value': 'custom', 'title': lang.custom
|
||||
});
|
||||
if (data.type === 'custom') {
|
||||
lay(customRadio).attr('checked', true);
|
||||
}
|
||||
var customDiv = lay.elem('div', {
|
||||
'class': 'cron-row'
|
||||
});
|
||||
customDiv.appendChild(customRadio);
|
||||
return customDiv;
|
||||
}());
|
||||
|
||||
// 指定数值,时分秒显示两位数,自动补零
|
||||
elem.appendChild(function () {
|
||||
var customGrid = lay.elem('div', {
|
||||
'class': 'cron-grid'
|
||||
});
|
||||
var i = rangeData.min;
|
||||
while (i <= rangeData.max) {
|
||||
// 时分秒显示两位数,自动补零
|
||||
var gridItemValue = index <= 1 ? lay.digit(i, 2) : i;
|
||||
var gridItem = lay.elem('input', {
|
||||
'type': 'checkbox',
|
||||
'title': gridItemValue,
|
||||
'lay-skin': 'primary',
|
||||
'name': tabItemKey + '[custom]',
|
||||
'value': i
|
||||
});
|
||||
if (data.values && data.values.includes(i)) {
|
||||
lay(gridItem).attr('checked', true);
|
||||
}
|
||||
customGrid.appendChild(gridItem);
|
||||
i++;
|
||||
}
|
||||
return customGrid;
|
||||
}());
|
||||
}
|
||||
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
//是否输入框
|
||||
Class.prototype.isInput = function (elem) {
|
||||
return /input|textarea/.test(elem.tagName.toLocaleLowerCase());
|
||||
};
|
||||
|
||||
// 绑定的元素事件处理
|
||||
Class.prototype.events = function () {
|
||||
var that = this, options = that.config
|
||||
|
||||
//绑定呼出控件事件
|
||||
, showEvent = function (elem, bind) {
|
||||
elem.on(options.trigger, function () {
|
||||
bind && (that.bindElem = this);
|
||||
that.render();
|
||||
});
|
||||
};
|
||||
|
||||
if (!options.elem[0] || options.elem[0].eventHandler) return;
|
||||
|
||||
showEvent(options.elem, 'bind');
|
||||
showEvent(options.eventElem);
|
||||
|
||||
//绑定关闭控件事件
|
||||
lay(document).on('click', function (e) {
|
||||
if (e.target === options.elem[0] || e.target === options.eventElem[0] || e.target === lay(options.closeStop)[0]) {
|
||||
return;
|
||||
}
|
||||
that.remove();
|
||||
}).on('keydown', function (e) {
|
||||
if (e.keyCode === 13) {
|
||||
if (lay('#' + that.elemID)[0] && that.elemID === Class.thisElemDate) {
|
||||
e.preventDefault();
|
||||
lay(that.footer).find(ELEM_CONFIRM)[0].click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//自适应定位
|
||||
lay(window).on('resize', function () {
|
||||
if (!that.elem || !lay(ELEM)[0]) {
|
||||
return false;
|
||||
}
|
||||
that.position();
|
||||
});
|
||||
|
||||
options.elem[0].eventHandler = true;
|
||||
};
|
||||
|
||||
// 主面板事件
|
||||
Class.prototype.elemEvent = function () {
|
||||
var that = this, options = that.config, tabFilter = 'cron-tab' + options.elem.attr('lay-key');
|
||||
|
||||
// 阻止主面板点击冒泡,避免因触发文档事件而关闭主面
|
||||
lay(that.elem).on('click', function (e) {
|
||||
lay.stope(e);
|
||||
});
|
||||
|
||||
// tab选项卡切换
|
||||
var lis = lay(that.elemTab).find('li');
|
||||
lis.on('click', function () {
|
||||
var layid = lay(this).attr('lay-id');
|
||||
if (undefined === layid) {
|
||||
return;
|
||||
}
|
||||
element.tabChange(tabFilter, layid);
|
||||
});
|
||||
|
||||
// cron选项点击
|
||||
form.on('radio', function (data) {
|
||||
var $parent = data.othis.parent();
|
||||
var formFilter = $parent.parent().attr('lay-filter');
|
||||
var formData = form.val(formFilter);
|
||||
var radioType = data.value;
|
||||
if ('range' === radioType) {
|
||||
// 范围
|
||||
form.val(formFilter, {
|
||||
rangeStart: formData.rangeStart || 0, rangeEnd: formData.rangeEnd || 2
|
||||
});
|
||||
}
|
||||
if ('rate' === radioType) {
|
||||
// 频率
|
||||
form.val(formFilter, {
|
||||
begin: formData.begin || 0, rate: formData.rate || 2
|
||||
});
|
||||
}
|
||||
if ('custom' === radioType) {
|
||||
// custom
|
||||
var $grid = $parent.next();
|
||||
if ($grid.find(':checkbox:checked').length <= 0) {
|
||||
$grid.children(':checkbox:first').next().click()
|
||||
}
|
||||
}
|
||||
if ('weekday' === radioType) {
|
||||
// weekday
|
||||
form.val(formFilter, {
|
||||
weekday: formData.weekday || 1
|
||||
});
|
||||
}
|
||||
if ('lastweek' === radioType) {
|
||||
// lastweek
|
||||
form.val(formFilter, {
|
||||
lastweek: formData.lastweek || 1
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//gird checkbox点击时,自动选中radio
|
||||
form.on('checkbox', function (data) {
|
||||
//触发选项的点击事件
|
||||
var $parent = data.othis.parent().parent();
|
||||
//循环父级,找到子级的子级找到value="custom"的radio
|
||||
var $radio = $parent.children().find('input[value="custom"]');
|
||||
//触发radio的点击事件
|
||||
$radio.next().click();
|
||||
});
|
||||
|
||||
//点击底部按钮
|
||||
lay(that.footer).find('span').on('click', function () {
|
||||
var type = lay(this).attr('lay-type');
|
||||
that.tool(this, type);
|
||||
});
|
||||
};
|
||||
|
||||
//底部按钮点击事件
|
||||
Class.prototype.tool = function (btn, type) {
|
||||
var that = this, options = that.config, lang = that.lang(), isStatic = options.position === 'static', active = {
|
||||
//运行
|
||||
run: function () {
|
||||
var value = that.parse();
|
||||
var loading = layer.load();
|
||||
$.get(options.run, {cron: value}, function (res) {
|
||||
layer.close(loading);
|
||||
if (res.code !== 0) {
|
||||
return that.hint(res.msg);
|
||||
}
|
||||
that.runHint(res.data);
|
||||
}, 'json').fail(function () {
|
||||
layer.close(loading);
|
||||
that.hint('服务器异常!');
|
||||
});
|
||||
}
|
||||
|
||||
//确定
|
||||
, confirm: function () {
|
||||
var value = that.parse();
|
||||
that.done([value]);
|
||||
that.setValue(value).remove()
|
||||
}
|
||||
};
|
||||
active[type] && active[type]();
|
||||
};
|
||||
|
||||
//执行 done/change 回调
|
||||
Class.prototype.done = function (param, type) {
|
||||
var that = this, options = that.config;
|
||||
|
||||
param = param || [that.parse()];
|
||||
typeof options[type || 'done'] === 'function' && options[type || 'done'].apply(options, param);
|
||||
|
||||
return that;
|
||||
};
|
||||
|
||||
// 解析cron表达式
|
||||
Class.prototype.parse = function () {
|
||||
var that = this, options = that.config, valueArr = [];
|
||||
|
||||
lay.each(options.tabs, function (index, item) {
|
||||
var key = item.key;
|
||||
var formFilter = 'cronForm' + key + options.elem.attr('lay-key');
|
||||
var formData = form.val(formFilter);
|
||||
var radioType = (key + '[type]');
|
||||
var current = "";
|
||||
if (formData[radioType] === 'every') {
|
||||
// 每次
|
||||
current = "*";
|
||||
}
|
||||
if (formData[radioType] === 'range') {
|
||||
// 范围
|
||||
current = formData.rangeStart + "-" + formData.rangeEnd;
|
||||
}
|
||||
if (formData[radioType] === 'rate') {
|
||||
// 频率
|
||||
current = formData.begin + "/" + formData.rate;
|
||||
}
|
||||
if (formData[radioType] === 'custom') {
|
||||
// 指定
|
||||
var checkboxName = (item.key + '[custom]');
|
||||
var customArr = [];
|
||||
$('input[name="' + checkboxName + '"]:checked').each(function () {
|
||||
customArr.push($(this).val());
|
||||
});
|
||||
current = customArr.join(',');
|
||||
}
|
||||
if (formData[radioType] === 'weekday') {
|
||||
// 每月 formData.weekday 号最近的那个工作日
|
||||
current = formData.weekday + "W";
|
||||
}
|
||||
if (formData[radioType] === 'lastday') {
|
||||
// 本月最后一日
|
||||
current = "L";
|
||||
}
|
||||
if (formData[radioType] === 'lastweek') {
|
||||
// 本月最后星期
|
||||
current = formData.lastweek + "L";
|
||||
}
|
||||
|
||||
if (formData[radioType] === 'unspecified' && index != 6) {
|
||||
// 不指定
|
||||
current = "?";
|
||||
}
|
||||
if (current !== "") {
|
||||
valueArr.push(current);
|
||||
options.cron[key] = current;
|
||||
}
|
||||
});
|
||||
return valueArr.join(' ');
|
||||
};
|
||||
|
||||
//控件移除
|
||||
Class.prototype.remove = function (prev) {
|
||||
var that = this, options = that.config, elem = lay('#' + (prev || that.elemID));
|
||||
if (!elem[0]) return that;
|
||||
|
||||
if (!elem.hasClass(ELEM_STATIC)) {
|
||||
that.checkCron(function () {
|
||||
elem.remove();
|
||||
});
|
||||
}
|
||||
return that;
|
||||
};
|
||||
|
||||
//定位算法
|
||||
Class.prototype.position = function () {
|
||||
var that = this, options = that.config;
|
||||
lay.position(that.bindElem || options.elem[0], that.elem, {
|
||||
position: options.position
|
||||
});
|
||||
return that;
|
||||
};
|
||||
|
||||
//提示
|
||||
Class.prototype.hint = function (content) {
|
||||
var that = this, options = that.config, div = lay.elem('div', {
|
||||
'class': ELEM_HINT
|
||||
});
|
||||
|
||||
if (!that.elem) return;
|
||||
|
||||
div.innerHTML = content || '';
|
||||
lay(that.elem).find('.' + ELEM_HINT).remove();
|
||||
that.elem.appendChild(div);
|
||||
|
||||
clearTimeout(that.hinTimer);
|
||||
that.hinTimer = setTimeout(function () {
|
||||
lay(that.elem).find('.' + ELEM_HINT).remove();
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
//运行提示
|
||||
Class.prototype.runHint = function (runList) {
|
||||
var that = this, options = that.config, div = lay.elem('div', {
|
||||
'class': ELEM_RUN_HINT
|
||||
});
|
||||
// debugger;
|
||||
if (!that.elem || !runList || !runList.length) return;
|
||||
|
||||
|
||||
lay(div).html(function () {
|
||||
var html = [];
|
||||
lay.each(runList, function (i, item) {
|
||||
html.push('<div class="cron-run-list">' + item + '</div>');
|
||||
});
|
||||
return html.join('');
|
||||
}());
|
||||
|
||||
lay(that.elem).find('.' + ELEM_RUN_HINT).remove();
|
||||
that.elem.appendChild(div);
|
||||
};
|
||||
|
||||
//赋值
|
||||
Class.prototype.setValue = function (value = '') {
|
||||
var that = this, options = that.config, elem = that.bindElem || options.elem[0],
|
||||
valType = that.isInput(elem) ? 'val' : 'html'
|
||||
|
||||
options.position === 'static' || lay(elem)[valType](value || '');
|
||||
elem.textContent = '生成';
|
||||
return this;
|
||||
};
|
||||
|
||||
//cron校验
|
||||
Class.prototype.checkCron = function (fn) {
|
||||
var that = this, options = that.config, lang = that.lang(), elem = that.bindElem || options.elem[0],
|
||||
value = that.isInput(elem) ? elem.value : (options.position === 'static' ? '' : elem.innerHTML)
|
||||
|
||||
, checkValid = function (value = "") {
|
||||
|
||||
};
|
||||
|
||||
// cron 值,多个空格替换为一个空格,去掉首尾空格
|
||||
value = value || options.value;
|
||||
if (typeof value === 'string') {
|
||||
value = value.replace(/\s+/g, ' ').replace(/^\s|\s$/g, '');
|
||||
}
|
||||
|
||||
if (fn === 'init') return checkValid(value), that;
|
||||
|
||||
value = that.parse();
|
||||
if (value) {
|
||||
that.setValue(value);
|
||||
}
|
||||
fn && fn();
|
||||
return that;
|
||||
};
|
||||
|
||||
//核心入口
|
||||
cron.render = function (options) {
|
||||
var ins = new Class(options);
|
||||
return thisIns.call(ins);
|
||||
};
|
||||
|
||||
exports('cron', cron);
|
||||
});
|
||||
@@ -0,0 +1,285 @@
|
||||
<!--
|
||||
Name: 计划任务
|
||||
Author: 耗子
|
||||
Date: 2023-07-21
|
||||
-->
|
||||
<title>计划任务</title>
|
||||
|
||||
<div class="layui-fluid">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">添加计划任务</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" action="" lay-filter="cron-add-form">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">任务名</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="name" lay-verify="required" placeholder="请输入任务名称"
|
||||
autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">请填写任务名称</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">执行周期</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="time" id="cron-add-time"
|
||||
lay-verify="required" placeholder="请选择或输入cron表达式" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">请务必正确填写执行周期</div>
|
||||
</div>
|
||||
<div class="layui-form-item layui-form-text">
|
||||
<label class="layui-form-label">脚本内容</label>
|
||||
<div class="layui-input-block">
|
||||
<div id="cron-add-script-editor"
|
||||
style="height: 250px;"># 在此输入你要执行的脚本内容</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit="" lay-filter="cron-add-submit">立即提交</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">计划任务列表</div>
|
||||
<div class="layui-card-body">
|
||||
<table id="panel-cron" lay-filter="panel-cron"></table>
|
||||
<!-- 操作按钮模板 -->
|
||||
<script type="text/html" id="cron-table-edit">
|
||||
<a class="layui-btn layui-btn-xs" lay-event="log">日志</a>
|
||||
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
|
||||
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="del">删除</a>
|
||||
</script>
|
||||
<!-- 运行开关 -->
|
||||
<script type="text/html" id="cron-table-status">
|
||||
<input type="checkbox" name="cron-status" lay-skin="switch" lay-text="ON|OFF"
|
||||
lay-filter="cron-status"
|
||||
value="{{ d.status }}" data-id="{{ d.id }}"
|
||||
{{ d.status==
|
||||
1 ? 'checked' : '' }}>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var cronAddScriptEditor = ace.edit("cron-add-script-editor", {
|
||||
mode: "ace/mode/sh",
|
||||
selectionStyle: "text"
|
||||
});
|
||||
var cronEditScriptEditor;
|
||||
layui.use(['admin', 'table', 'jquery', 'cron'], function () {
|
||||
var $ = layui.$
|
||||
, form = layui.form
|
||||
, table = layui.table
|
||||
, admin = layui.admin
|
||||
, cron = layui.cron;
|
||||
|
||||
cron.render({
|
||||
elem: "#cron-add-time",
|
||||
btns: ['confirm'],
|
||||
show: false,
|
||||
done: function (value) {
|
||||
$('#cron-add-time').val(value);
|
||||
}
|
||||
});
|
||||
|
||||
form.render();
|
||||
|
||||
table.render({
|
||||
elem: '#panel-cron'
|
||||
, url: '/api/panel/cron/list'
|
||||
, cols: [[
|
||||
{field: 'id', hide: true, title: 'ID'}
|
||||
, {field: 'name', width: 150, title: '任务名', sort: true}
|
||||
, {field: 'type', width: 150, title: '任务类型', sort: true}
|
||||
, {field: 'status', title: '启用', width: 100, templet: '#cron-table-status', unresize: true}
|
||||
, {field: 'time', width: 200, title: '任务周期(cron表达式)'}
|
||||
, {field: 'updated_at', title: '上次运行时间'}
|
||||
, {
|
||||
field: 'edit',
|
||||
width: 180,
|
||||
title: '操作',
|
||||
templet: '#cron-table-edit',
|
||||
fixed: 'right',
|
||||
align: 'left'
|
||||
}
|
||||
]]
|
||||
, page: true
|
||||
, text: {
|
||||
none: '暂无数据'
|
||||
}
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.message,
|
||||
"count": res.data.total,
|
||||
"data": res.data.items
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 工具条
|
||||
table.on('tool(panel-cron)', function (obj) {
|
||||
let data = obj.data;
|
||||
if (obj.event === 'log') {
|
||||
// 打开日志弹窗
|
||||
admin.popup({
|
||||
title: '日志'
|
||||
,
|
||||
area: ['80%', '80%']
|
||||
,
|
||||
id: 'cron-log'
|
||||
,
|
||||
content: '<pre id="cron-log-view" style="overflow: auto; height: 95%;border: 0 none; line-height:23px; padding: 15px; margin: 0; white-space: pre-wrap; background-color: rgb(51,51,51); color:#f1f1f1; border-radius:0;"></pre>'
|
||||
,
|
||||
success: function (layero, index) {
|
||||
admin.req({
|
||||
url: '/api/panel/cron/log?id=' + data.id
|
||||
, type: 'GET'
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
$('#cron-log-view').html(res.data);
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} else if (obj.event === 'edit') {
|
||||
// 打开编辑弹窗
|
||||
admin.req({
|
||||
url: '/api/panel/cron/script?id=' + data.id
|
||||
, type: 'GET'
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
admin.popup({
|
||||
title: '编辑'
|
||||
,
|
||||
area: ['80%', '80%']
|
||||
,
|
||||
id: 'cron-log'
|
||||
,
|
||||
content: '任务名 <div class="layui-input-inline" style="width: 190px;"><input type="text" name="cron-edit-name" placeholder="请输入任务名称" autocomplete="off" class="layui-input" value="' + data.name + '"></div> 执行周期 <div class="layui-input-inline" style="width: 190px;"><input id="cron-edit-time-' + data.id + '" type="text" name="cron-edit-time" placeholder="请输入执行周期" autocomplete="off" class="layui-input" value="' + data.time + '"/></div><hr><div id="cron-edit-script-editor" style="height: 80%;">' + res.data + '</div><br><button id="cron-edit-' + data.id + '" class="layui-btn">保存</button>'
|
||||
,
|
||||
success: function (layero, index) {
|
||||
cronEditScriptEditor = ace.edit("cron-edit-script-editor", {
|
||||
mode: "ace/mode/sh",
|
||||
selectionStyle: "text"
|
||||
});
|
||||
cron.render({
|
||||
elem: "#cron-edit-time-" + data.id,
|
||||
btns: ['confirm'],
|
||||
show: false,
|
||||
done: function (value) {
|
||||
$('#cron-add-time').val(value);
|
||||
}
|
||||
});
|
||||
$('#cron-edit-' + data.id).click(function () {
|
||||
admin.req({
|
||||
url: '/api/panel/cron/update'
|
||||
, type: 'POST'
|
||||
, data: {
|
||||
id: data.id,
|
||||
name: $('input[name="cron-edit-name"]').val(),
|
||||
time: $('input[name="cron-edit-time"]').val(),
|
||||
script: cronEditScriptEditor.getValue()
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('保存成功', {icon: 1, time: 1000});
|
||||
table.reload('panel-cron');
|
||||
layer.close(index);
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else if (obj.event === 'del') {
|
||||
layer.confirm('确定删除计划任务' + data.name + '吗?', function (index) {
|
||||
layer.close(index);
|
||||
admin.req({
|
||||
url: '/api/panel/cron/delete',
|
||||
type: 'POST',
|
||||
data: {
|
||||
id: data.id
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
table.reload('panel-cron');
|
||||
layer.msg('计划任务:' + data.name + ' 已删除', {
|
||||
icon: 1,
|
||||
time: 1000
|
||||
});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
form.on('switch(cron-status)', function (obj) {
|
||||
let $ = layui.$;
|
||||
let id = $(this).data('id');
|
||||
let status = obj.elem.checked;
|
||||
|
||||
admin.req({
|
||||
url: '/api/panel/cron/status',
|
||||
type: 'POST',
|
||||
data: {
|
||||
id: id,
|
||||
status: status
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('设置成功', {icon: 1, time: 1000});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
form.on('submit(cron-add-submit)', function (data) {
|
||||
data.field.script = cronAddScriptEditor.getValue();
|
||||
admin.req({
|
||||
url: "/api/panel/cron/add"
|
||||
, type: 'post'
|
||||
, data: data.field
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:计划任务添加失败,接口返回' + result);
|
||||
layer.msg('计划任务添加失败!')
|
||||
return false;
|
||||
}
|
||||
table.reload('panel-cron');
|
||||
layer.alert('计划任务添加成功!', {
|
||||
icon: 1
|
||||
, title: '提示'
|
||||
, btn: ['确定']
|
||||
, yes: function (index) {
|
||||
layer.closeAll();
|
||||
//location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -209,7 +209,7 @@ Date: 2023-06-22
|
||||
let cpu_info
|
||||
admin.req({
|
||||
url: '/api/panel/info/nowMonitor'
|
||||
, method: 'get'
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
layer.msg('系统资源获取失败,请刷新重试!')
|
||||
@@ -276,7 +276,7 @@ Date: 2023-06-22
|
||||
})
|
||||
admin.req({
|
||||
url: '/api/panel/info/systemInfo'
|
||||
, method: 'get'
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:系统信息获取失败,接口返回' + result)
|
||||
@@ -313,29 +313,41 @@ Date: 2023-06-22
|
||||
admin.req(
|
||||
{
|
||||
url: '/api/panel/info/checkUpdate'
|
||||
, method: 'get'
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
layer.close(index)
|
||||
if (result.code !== 0) {
|
||||
layer.msg('获取版本信息失败,请刷新重试!')
|
||||
return false
|
||||
}
|
||||
if (result.data.version) {
|
||||
admin.popup({
|
||||
title: '提示'
|
||||
,
|
||||
shade: 0
|
||||
,
|
||||
anim: -1
|
||||
,
|
||||
area: ['400px', '200px']
|
||||
,
|
||||
id: 'layadmin-layer-skin-update-panel'
|
||||
,
|
||||
skin: 'layui-anim layui-anim-upbit'
|
||||
,
|
||||
content: '最新版本:' + result.data.version + '<br><br>更新日志:' + result.data.describe + '<br><br>请在SSH执行<span class="layui-badge-rim">panel update</span>以更新面板!'
|
||||
})
|
||||
if (result.data.update) {
|
||||
layer.confirm('更新日期: <br>'+new Date(result.data.date).toLocaleString()+'<br>更新日志: <pre>'+result.data.body+'</pre>', {
|
||||
title: '最新版本: '+result.data.version+' ,是否更新?',
|
||||
btn: ['更新', '取消']
|
||||
}, function () {
|
||||
let proxy = false
|
||||
layer.confirm('对于大陆服务器,建议使用代理进行更新', {
|
||||
title: '是否使用代理更新?',
|
||||
btn: ['是', '否']
|
||||
}, function () {
|
||||
proxy = true
|
||||
index = layer.msg('正在更新,稍后请手动刷新...', { icon: 16, time: 0, shade: 0.3 })
|
||||
admin.req({
|
||||
url: '/api/panel/info/update'
|
||||
, type: 'post'
|
||||
, data: { proxy: proxy }
|
||||
})
|
||||
}, function(){
|
||||
proxy = false
|
||||
index = layer.msg('正在更新,稍后请手动刷新...', { icon: 16, time: 0, shade: 0.3 })
|
||||
admin.req({
|
||||
url: '/api/panel/info/update'
|
||||
, type: 'post'
|
||||
, data: { proxy: proxy }
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
} else {
|
||||
layer.msg('当前已是最新版本!')
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script type="text/html" template>
|
||||
<link rel="stylesheet" href="/panel/adminui/src/css/login.css?v={{ layui.admin.v }}" media="all">
|
||||
<link rel="stylesheet" href="panel/adminui/src/css/login.css?v={{ layui.admin.v }}" media="all">
|
||||
</script>
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
placeholder="密码" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-item" style="margin-bottom: 20px;">
|
||||
<a target="_blank" href="https://jq.qq.com/?_wv=1027&k=I1oJKSTH" class="layadmin-user-jump-change layadmin-link"
|
||||
<a target="_blank" href="https://jq.qq.com/?_wv=1027&k=I1oJKSTH"
|
||||
class="layadmin-user-jump-change layadmin-link"
|
||||
style="margin-top: 7px;">忘记密码?</a>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
@@ -41,55 +42,59 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
layui.use(['admin', 'form'], function () {
|
||||
let $ = layui.$
|
||||
, setter = layui.setter
|
||||
, admin = layui.admin
|
||||
, form = layui.form
|
||||
, router = layui.router()
|
||||
, search = router.search;
|
||||
layui.use(['admin', 'form'], function () {
|
||||
let $ = layui.$
|
||||
, setter = layui.setter
|
||||
, admin = layui.admin
|
||||
, form = layui.form
|
||||
, router = layui.router()
|
||||
, search = router.search;
|
||||
|
||||
// 设置面板名称
|
||||
$('#panel-login-name').text(setter.name);
|
||||
|
||||
form.render();
|
||||
|
||||
//提交
|
||||
form.on('submit(panel-login-submit)', function (obj) {
|
||||
|
||||
// 判断obj.field.remember是否存在
|
||||
if (obj.field.remember) {
|
||||
obj.field.remember = 1;
|
||||
} else {
|
||||
obj.field.remember = 0;
|
||||
}
|
||||
admin.req({
|
||||
url: '/api/panel/user/login'
|
||||
, data: obj.field
|
||||
, method: 'post'
|
||||
, done: function (res) {
|
||||
// 请求成功后,写入 access_token
|
||||
layui.data(setter.tableName, {
|
||||
key: setter.request.tokenName
|
||||
, value: res.data.access_token
|
||||
});
|
||||
|
||||
// 登入成功的提示与跳转
|
||||
layer.msg('登录成功', {
|
||||
offset: '15px'
|
||||
, icon: 1
|
||||
, time: 1000
|
||||
}, function () {
|
||||
/**
|
||||
* 可能是LayuiAdmin的bug,如果直接跳转到主页,会出现表格无法获取access_token的问题
|
||||
* 所以这里强制刷新到主页
|
||||
* location.hash = search.redirect ? decodeURIComponent(search.redirect) : '/';
|
||||
*/
|
||||
location.href = '/';
|
||||
});
|
||||
// 判断是否已登录
|
||||
if (layui.data(setter.tableName)[setter.request.tokenName]) {
|
||||
location.hash = search.redirect ? decodeURIComponent(search.redirect) : '/';
|
||||
}
|
||||
});
|
||||
|
||||
// 设置面板名称
|
||||
$('#panel-login-name').text(setter.name);
|
||||
|
||||
form.render();
|
||||
|
||||
//提交
|
||||
form.on('submit(panel-login-submit)', function (obj) {
|
||||
|
||||
// 判断obj.field.remember是否存在
|
||||
if (obj.field.remember) {
|
||||
obj.field.remember = 1;
|
||||
} else {
|
||||
obj.field.remember = 0;
|
||||
}
|
||||
admin.req({
|
||||
url: '/api/panel/user/login'
|
||||
, data: obj.field
|
||||
, type: 'post'
|
||||
, done: function (res) {
|
||||
// 请求成功后,写入 access_token
|
||||
layui.data(setter.tableName, {
|
||||
key: setter.request.tokenName
|
||||
, value: res.data.access_token
|
||||
});
|
||||
|
||||
// 登入成功的提示与跳转
|
||||
layer.msg('登录成功', {
|
||||
offset: '15px'
|
||||
, icon: 1
|
||||
, time: 1000
|
||||
}, function () {
|
||||
/**
|
||||
* 可能是LayuiAdmin的bug,如果直接跳转到主页,会出现表格无法获取access_token的问题
|
||||
* 所以这里强制刷新一次页面
|
||||
*/
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script type="text/html" template>
|
||||
<link rel="stylesheet" href="/panel/adminui/src/css/login.css?v={{ layui.admin.v }}" media="all">
|
||||
<link rel="stylesheet" href="panel/adminui/src/css/login.css?v={{ layui.admin.v }}" media="all">
|
||||
</script>
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
layui.use(['admin'], function () {
|
||||
let admin = layui.admin
|
||||
setTimeout(function () {
|
||||
admin.exit()
|
||||
window.location = '/'
|
||||
}, 3000)
|
||||
})
|
||||
layui.use(['admin'], function () {
|
||||
let admin = layui.admin
|
||||
setTimeout(function () {
|
||||
admin.exit()
|
||||
location.hash = '/login'
|
||||
}, 3000)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
<!--
|
||||
Name: 资源监控
|
||||
Author: 耗子
|
||||
Date: 2023-07-21
|
||||
-->
|
||||
<title>资源监控</title>
|
||||
|
||||
<div class="layui-fluid">
|
||||
<div class="layui-card">
|
||||
<div class="layui-form layui-card-body" style="overflow: hidden;">
|
||||
<div class="layui-inline">
|
||||
<span style="margin-right: 10px;">开启监控</span><input type="checkbox" id="monitor-switch"
|
||||
lay-filter="monitor" lay-skin="switch"
|
||||
lay-text="ON|OFF">
|
||||
<span style="margin-left: 40px; margin-right: 10px;">保存天数</span>
|
||||
<div class="layui-input-inline"><input type="number" name="monitor-save-days" class="layui-input"
|
||||
style="height: 30px; margin-top: 5px;" min=0 max=30 disabled>
|
||||
</div>
|
||||
<div class="layui-input-inline">
|
||||
<button id="save_monitor_date" class="layui-btn layui-btn-sm" style="margin-left: 10px;">确定
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline" style="float: right;">
|
||||
<span style="margin-right: 10px;">时间选择</span>
|
||||
<div id="monitor-date" class="layui-input-inline">
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" autocomplete="off" id="monitor-startDate"
|
||||
class="layui-input"
|
||||
placeholder="开始时间">
|
||||
</div>
|
||||
-
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" autocomplete="off" id="monitor-endDate"
|
||||
class="layui-input"
|
||||
placeholder="结束时间">
|
||||
</div>
|
||||
</div>
|
||||
<span style="margin-left: 40px;"></span>
|
||||
<button id="clear_monitor_record" class="layui-btn layui-btn-sm layui-btn-danger">清空监控记录
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-row layui-col-space10">
|
||||
<div class="layui-col-xs12 layui-col-md6">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">
|
||||
<span>负载</span>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<div id="load_monitor" style="width: 100%;height: 400px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-col-xs12 layui-col-md6">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">
|
||||
<span>CPU</span>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<div id="cpu_monitor" style="width: 100%;height: 400px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-row layui-col-space10">
|
||||
<div class="layui-col-xs12 layui-col-md6">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">
|
||||
<span>内存</span>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<div id="memory_monitor" style="width: 100%;height: 400px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs12 layui-col-md6">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">
|
||||
<span>网络</span>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<div id="network_monitor" style="width: 100%;height: 400px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
layui.use(['admin', 'view', 'form', 'element', 'carousel'], function () {
|
||||
var admin = layui.admin;
|
||||
var view = layui.view;
|
||||
var $ = layui.jquery;
|
||||
var form = layui.form;
|
||||
var laydate = layui.laydate;
|
||||
|
||||
// 渲染时间选择器
|
||||
// 默认开始时间为今天0点,结束时间为当前时间
|
||||
var startDate = new Date(new Date().setHours(0, 0, 0, 0));
|
||||
var endDate = new Date();
|
||||
// 拼接时间字符串
|
||||
var startStr = startDate.getFullYear() + '-' + (startDate.getMonth() + 1) + '-' + startDate.getDate() + ' ' + startDate.getHours() + ':' + startDate.getMinutes() + ':' + startDate.getSeconds();
|
||||
var endStr = endDate.getFullYear() + '-' + (endDate.getMonth() + 1) + '-' + endDate.getDate() + ' ' + endDate.getHours() + ':' + endDate.getMinutes() + ':' + endDate.getSeconds();
|
||||
laydate.render({
|
||||
elem: '#monitor-date'
|
||||
, type: 'datetime'
|
||||
, range: ['#monitor-startDate', '#monitor-endDate']
|
||||
, value: startStr + ' - ' + endStr
|
||||
, calendar: true
|
||||
, done: function (value, date, endDate) {
|
||||
let start_date,
|
||||
end_date;
|
||||
// 初始化时间日期对象
|
||||
start_date = new Date(date.year, date.month, date.date, date.hours, date.minutes, date.seconds);
|
||||
end_date = new Date(endDate.year, endDate.month, endDate.date, endDate.hours, endDate.minutes, endDate.seconds);
|
||||
getMonitorData(start_date.getTime(), end_date.getTime());
|
||||
}
|
||||
});
|
||||
|
||||
// 获取监控开关和保存天数
|
||||
admin.req({
|
||||
url: '/api/panel/monitor/switchAndDays',
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
if (res.code === 0) {
|
||||
if (res.data.switch) {
|
||||
$('#monitor-switch').attr('checked', true);
|
||||
} else {
|
||||
$('#monitor-switch').attr('checked', false);
|
||||
}
|
||||
$('input[name="monitor-save-days"]').val(res.data.days);
|
||||
$('input[name="monitor-save-days"]').removeAttr('disabled');
|
||||
form.render();
|
||||
}
|
||||
}
|
||||
});
|
||||
// 监听switch开关:是否开启监控
|
||||
form.on('switch(monitor)', function (data) {
|
||||
admin.req({
|
||||
url: '/api/panel/monitor/switch',
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
data: {switch: data.elem.checked},
|
||||
success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('修改成功', {icon: 1});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// 监听保存天数按钮
|
||||
$('#save_monitor_date').click(function () {
|
||||
var days = $('input[name="monitor-save-days"]').val();
|
||||
if (days == '') {
|
||||
layer.msg('请输入保存天数', {icon: 2});
|
||||
return false;
|
||||
}
|
||||
admin.req({
|
||||
url: '/api/panel/monitor/saveDays',
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
data: {days: days},
|
||||
success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('修改成功', {icon: 1});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 监听清除监控数据按钮
|
||||
$('#clear_monitor_record').click(function () {
|
||||
layer.confirm('确定要清除监控数据吗?', function (index) {
|
||||
admin.req({
|
||||
url: '/api/panel/monitor/clear',
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('清除成功', {icon: 1});
|
||||
setTimeout(function () {
|
||||
admin.render();
|
||||
}, 1000);
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2});
|
||||
}
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
});
|
||||
|
||||
getMonitorData(startDate.getTime(), endDate.getTime());
|
||||
});
|
||||
|
||||
|
||||
// 获取监控数据
|
||||
function getMonitorData(startDate, endDate) {
|
||||
layui.use(['admin'], function () {
|
||||
let admin = layui.admin;
|
||||
// 获取监控数据
|
||||
admin.req({
|
||||
url: '/api/panel/monitor/list?start=' + startDate + '&end=' + endDate,
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
if (res.code !== 0) {
|
||||
layer.msg(res.message, {icon: 2, shade: 0.3});
|
||||
return false;
|
||||
}
|
||||
let loadChart = renderEcharts('load_monitor', '负载', {
|
||||
x: 'left',
|
||||
data: ["1分钟", "5分钟", "15分钟"]
|
||||
}, res.data.times, [{
|
||||
name: '1分钟',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: res.data.load.load1,
|
||||
markPoint: {
|
||||
data: [{type: 'max', name: '最大值'}, {type: 'min', name: '最小值'}]
|
||||
},
|
||||
markLine: {
|
||||
data: [{type: 'average', name: '平均值'}]
|
||||
}
|
||||
}, {
|
||||
name: '5分钟',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: res.data.load.load5,
|
||||
markPoint: {
|
||||
data: [{type: 'max', name: '最大值'}, {type: 'min', name: '最小值'}]
|
||||
},
|
||||
markLine: {
|
||||
data: [{type: 'average', name: '平均值'}]
|
||||
}
|
||||
}, {
|
||||
name: '15分钟',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: res.data.load.load15,
|
||||
markPoint: {
|
||||
data: [{type: 'max', name: '最大值'}, {type: 'min', name: '最小值'}]
|
||||
},
|
||||
markLine: {
|
||||
data: [{type: 'average', name: '平均值'}]
|
||||
}
|
||||
}], [{
|
||||
type: 'value',
|
||||
}]);
|
||||
let cpuChart = renderEcharts('cpu_monitor', 'CPU', undefined, res.data.times, [{
|
||||
name: '使用率',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: res.data.cpu.percent,
|
||||
markPoint: {
|
||||
data: [{type: 'max', name: '最大值'}, {type: 'min', name: '最小值'}]
|
||||
},
|
||||
markLine: {
|
||||
data: [{type: 'average', name: '平均值'}]
|
||||
}
|
||||
}], [{
|
||||
name: '单位 %',
|
||||
min: 0,
|
||||
max: 100,
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value} %'
|
||||
}
|
||||
}]);
|
||||
let memoryChart = renderEcharts('memory_monitor', '内存', {
|
||||
x: 'left',
|
||||
data: ["内存", "Swap"]
|
||||
}, res.data.times, [{
|
||||
name: '内存',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: res.data.mem.used,
|
||||
markPoint: {
|
||||
data: [{type: 'max', name: '最大值'}, {type: 'min', name: '最小值'}]
|
||||
},
|
||||
markLine: {
|
||||
data: [{type: 'average', name: '平均值'}]
|
||||
}
|
||||
}, {
|
||||
name: 'Swap',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: res.data.swap.used,
|
||||
markPoint: {
|
||||
data: [{type: 'max', name: '最大值'}, {type: 'min', name: '最小值'}]
|
||||
},
|
||||
markLine: {
|
||||
data: [{type: 'average', name: '平均值'}]
|
||||
}
|
||||
}], [{
|
||||
name: '单位 MB',
|
||||
min: 0,
|
||||
max: res.data.mem.total,
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value} M'
|
||||
}
|
||||
}]);
|
||||
let networkChart = renderEcharts('network_monitor', '网络', {
|
||||
x: 'left',
|
||||
data: ["总计出", "总计入", "每秒出", "每秒入"]
|
||||
}, res.data.times, [{
|
||||
name: '总计出',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: res.data.net.sent,
|
||||
markPoint: {
|
||||
data: [{type: 'max', name: '最大值'}, {type: 'min', name: '最小值'}]
|
||||
},
|
||||
markLine: {
|
||||
data: [{type: 'average', name: '平均值'}]
|
||||
}
|
||||
}, {
|
||||
name: '总计入',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: res.data.net.recv,
|
||||
markPoint: {
|
||||
data: [{type: 'max', name: '最大值'}, {type: 'min', name: '最小值'}]
|
||||
},
|
||||
markLine: {
|
||||
data: [{type: 'average', name: '平均值'}]
|
||||
}
|
||||
}, {
|
||||
name: '每秒出',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: res.data.net.tx,
|
||||
markPoint: {
|
||||
data: [{type: 'max', name: '最大值'}, {type: 'min', name: '最小值'}]
|
||||
},
|
||||
markLine: {
|
||||
data: [{type: 'average', name: '平均值'}]
|
||||
}
|
||||
}, {
|
||||
name: '每秒入',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {normal: {areaStyle: {type: 'default'}}},
|
||||
data: res.data.net.rx,
|
||||
markPoint: {
|
||||
data: [{type: 'max', name: '最大值'}, {type: 'min', name: '最小值'}]
|
||||
},
|
||||
markLine: {
|
||||
data: [{type: 'average', name: '平均值'}]
|
||||
}
|
||||
}], [{
|
||||
name: '单位 Kb/s',
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value} Kb'
|
||||
}
|
||||
}]);
|
||||
|
||||
// 在窗口大小改变时,重置图表大小
|
||||
window.addEventListener("resize", function () {
|
||||
loadChart.resize();
|
||||
cpuChart.resize();
|
||||
memoryChart.resize();
|
||||
networkChart.resize();
|
||||
});
|
||||
}, error: function (xhr, status, error) {
|
||||
console.log('耗子Linux面板:ajax请求出错,错误' + error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染图表
|
||||
function renderEcharts(element_id, title, legend = undefined, data_xAxis, series, yAxis = undefined) {
|
||||
var Chart = echarts.init(document.getElementById(element_id), layui.echartsTheme);
|
||||
var option = {
|
||||
title: {text: title, x: 'center', textStyle: {fontSize: 20}},
|
||||
tooltip: {trigger: 'axis'},
|
||||
legend: legend,
|
||||
xAxis: [{type: 'category', boundaryGap: false, data: data_xAxis}],
|
||||
yAxis: yAxis,
|
||||
dataZoom: {
|
||||
show: true,
|
||||
realtime: true,
|
||||
start: 0,
|
||||
end: 100
|
||||
},
|
||||
series: series
|
||||
};
|
||||
|
||||
Chart.setOption(option);
|
||||
return Chart;
|
||||
}
|
||||
</script>
|
||||
+139
-137
@@ -35,150 +35,152 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
layui.use(['admin', 'table', 'jquery'], function () {
|
||||
var $ = layui.$
|
||||
, form = layui.form
|
||||
, table = layui.table
|
||||
, admin = layui.admin
|
||||
layui.use(['admin', 'table', 'jquery'], function () {
|
||||
var $ = layui.$
|
||||
, form = layui.form
|
||||
, table = layui.table
|
||||
, admin = layui.admin
|
||||
|
||||
table.render({
|
||||
elem: '#panel-plugin'
|
||||
, url: '/api/panel/plugin/list'
|
||||
, cols: [[
|
||||
{ field: 'slug', hide: true, title: 'Slug' }
|
||||
, { field: 'name', width: 150, title: '插件名', sort: true }
|
||||
, { field: 'description', title: '描述' }
|
||||
, { field: 'author', width: 100, title: '作者' }
|
||||
, { field: 'installed_version', width: 140, title: '已装版本' }
|
||||
, { field: 'version', width: 140, title: '最新版本' }
|
||||
, { field: 'show', title: '首页显示', width: 90, templet: '#plugin-show', unresize: true }
|
||||
, {
|
||||
field: 'control',
|
||||
width: 180,
|
||||
title: '操作',
|
||||
templet: '#panel-plugin-control',
|
||||
fixed: 'right',
|
||||
align: 'left'
|
||||
}
|
||||
]]
|
||||
, page: false
|
||||
, text: '耗子Linux面板:数据加载出现异常!'
|
||||
, done: function () {
|
||||
//element.render('progress');
|
||||
}
|
||||
})
|
||||
|
||||
// 工具条
|
||||
table.on('tool(panel-plugin)', function (obj) {
|
||||
let data = obj.data
|
||||
if (obj.event === 'open') {
|
||||
location.hash = '/plugins/' + data.slug
|
||||
} else if (obj.event === 'install') {
|
||||
layer.confirm('确定安装该插件吗?', function (index) {
|
||||
layer.close(index)
|
||||
admin.req({
|
||||
url: '/api/panel/plugin/install',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: data.slug
|
||||
table.render({
|
||||
elem: '#panel-plugin'
|
||||
, url: '/api/panel/plugin/list'
|
||||
, cols: [[
|
||||
{field: 'slug', hide: true, title: 'Slug'}
|
||||
, {field: 'name', width: 150, title: '插件名', sort: true}
|
||||
, {field: 'description', title: '描述'}
|
||||
, {field: 'author', width: 100, title: '作者'}
|
||||
, {field: 'installed_version', width: 140, title: '已装版本'}
|
||||
, {field: 'version', width: 140, title: '最新版本'}
|
||||
, {field: 'show', title: '首页显示', width: 90, templet: '#plugin-show', unresize: true}
|
||||
, {
|
||||
field: 'control',
|
||||
width: 180,
|
||||
title: '操作',
|
||||
templet: '#panel-plugin-control',
|
||||
fixed: 'right',
|
||||
align: 'left'
|
||||
}
|
||||
]]
|
||||
, page: false
|
||||
, text: {
|
||||
none: '无数据'
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
table.reload('panel-plugin')
|
||||
layer.msg('安装:' + data.name + ' 成功加入任务队列', {
|
||||
icon: 1,
|
||||
time: 1000
|
||||
})
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2, time: 1000 })
|
||||
}
|
||||
, done: function () {
|
||||
//element.render('progress');
|
||||
}
|
||||
})
|
||||
})
|
||||
} else if (obj.event === 'uninstall') {
|
||||
layer.confirm('确定卸载该插件吗?', function (index) {
|
||||
layer.close(index)
|
||||
admin.req({
|
||||
url: '/api/panel/plugin/uninstall',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: data.slug
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
table.reload('panel-plugin')
|
||||
layer.msg('卸载:' + data.name + ' 成功加入任务队列', {
|
||||
icon: 1,
|
||||
time: 1000
|
||||
|
||||
// 工具条
|
||||
table.on('tool(panel-plugin)', function (obj) {
|
||||
let data = obj.data
|
||||
if (obj.event === 'open') {
|
||||
location.hash = '/plugins/' + data.slug
|
||||
} else if (obj.event === 'install') {
|
||||
layer.confirm('确定安装该插件吗?', function (index) {
|
||||
layer.close(index)
|
||||
admin.req({
|
||||
url: '/api/panel/plugin/install',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: data.slug
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
table.reload('panel-plugin')
|
||||
layer.msg('安装:' + data.name + ' 成功加入任务队列', {
|
||||
icon: 1,
|
||||
time: 1000
|
||||
})
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2, time: 1000 })
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
} else if (obj.event === 'update') {
|
||||
layer.confirm('确定升级该插件吗?', function (index) {
|
||||
layer.close(index)
|
||||
admin.req({
|
||||
url: '/api/panel/plugin/update',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: data.slug
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
table.reload('panel-plugin')
|
||||
layer.msg('升级:' + data.name + ' 成功加入任务队列', {
|
||||
icon: 1,
|
||||
time: 1000
|
||||
} else if (obj.event === 'uninstall') {
|
||||
layer.confirm('确定卸载该插件吗?', function (index) {
|
||||
layer.close(index)
|
||||
admin.req({
|
||||
url: '/api/panel/plugin/uninstall',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: data.slug
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
table.reload('panel-plugin')
|
||||
layer.msg('卸载:' + data.name + ' 成功加入任务队列', {
|
||||
icon: 1,
|
||||
time: 1000
|
||||
})
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
} else if (obj.event === 'update') {
|
||||
layer.confirm('确定升级该插件吗?', function (index) {
|
||||
layer.close(index)
|
||||
admin.req({
|
||||
url: '/api/panel/plugin/update',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: data.slug
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
table.reload('panel-plugin')
|
||||
layer.msg('升级:' + data.name + ' 成功加入任务队列', {
|
||||
icon: 1,
|
||||
time: 1000
|
||||
})
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2, time: 1000 })
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
form.on('switch(plugin-show-home)', function (obj) {
|
||||
let $ = layui.$
|
||||
let plugin_slug = $(this).data('plugin-slug')
|
||||
let show = obj.elem.checked ? 1 : 0
|
||||
|
||||
admin.req({
|
||||
url: '/api/panel/plugin/updateShow',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: plugin_slug,
|
||||
show: show
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('设置成功', {icon: 1, time: 1000})
|
||||
} else {
|
||||
// 还原开关状态
|
||||
obj.elem.checked = !obj.elem.checked
|
||||
form.render('checkbox')
|
||||
layer.msg(res.message, {icon: 2, time: 1000})
|
||||
}
|
||||
}
|
||||
, error: function (xhr, status, error) {
|
||||
console.log('耗子Linux面板:ajax请求出错,错误' + error)
|
||||
}
|
||||
})
|
||||
})
|
||||
/*form.render(null, 'plugin-form');
|
||||
|
||||
//搜索
|
||||
form.on('submit(plugin-search-submit)', function (data) {
|
||||
var field = data.field;
|
||||
|
||||
//执行重载
|
||||
table.reload('plugin-search-submit', {
|
||||
where: field
|
||||
});
|
||||
});*/
|
||||
})
|
||||
|
||||
form.on('switch(plugin-show-home)', function (obj) {
|
||||
let $ = layui.$
|
||||
let plugin_slug = $(this).data('plugin-slug')
|
||||
let show = obj.elem.checked ? 1 : 0
|
||||
|
||||
admin.req({
|
||||
url: '/api/panel/plugin/updateShow',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: plugin_slug,
|
||||
show: show
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('设置成功', { icon: 1, time: 1000 })
|
||||
} else {
|
||||
// 还原开关状态
|
||||
obj.elem.checked = !obj.elem.checked
|
||||
form.render('checkbox')
|
||||
layer.msg(res.msg, { icon: 2, time: 1000 })
|
||||
}
|
||||
}
|
||||
, error: function (xhr, status, error) {
|
||||
console.log('耗子Linux面板:ajax请求出错,错误' + error)
|
||||
}
|
||||
})
|
||||
})
|
||||
/*form.render(null, 'plugin-form');
|
||||
|
||||
//搜索
|
||||
form.on('submit(plugin-search-submit)', function (data) {
|
||||
var field = data.field;
|
||||
|
||||
//执行重载
|
||||
table.reload('plugin-search-submit', {
|
||||
where: field
|
||||
});
|
||||
});*/
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -43,7 +43,7 @@ Date: 2023-06-24
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<div class="layui-btn-container">
|
||||
<button id="openresty-clean-error-log" class="layui-btn">清空日志</button>
|
||||
<button id="openresty-clear-error-log" class="layui-btn">清空日志</button>
|
||||
</div>
|
||||
<pre id="openresty-error-log" class="layui-code">
|
||||
获取中...
|
||||
@@ -68,7 +68,7 @@ Date: 2023-06-24
|
||||
|
||||
admin.req({
|
||||
url: '/api/plugins/openresty/status'
|
||||
, method: 'get'
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:OpenResty运行状态获取失败,接口返回' + result)
|
||||
@@ -86,7 +86,7 @@ Date: 2023-06-24
|
||||
// 获取openresty错误日志并渲染
|
||||
admin.req({
|
||||
url: '/api/plugins/openresty/errorLog'
|
||||
, method: 'get'
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:OpenResty错误日志获取失败,接口返回' + result)
|
||||
@@ -114,7 +114,7 @@ Date: 2023-06-24
|
||||
// 获取openresty配置并渲染
|
||||
admin.req({
|
||||
url: '/api/plugins/openresty/config'
|
||||
, method: 'get'
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:OpenResty主配置获取失败,接口返回' + result)
|
||||
@@ -141,54 +141,50 @@ Date: 2023-06-24
|
||||
|
||||
// 事件监听
|
||||
$('#openresty-start').click(function () {
|
||||
admin.popup({
|
||||
title: '<span style="color: red;">警告</span>'
|
||||
,
|
||||
shade: 0
|
||||
,
|
||||
anim: -1
|
||||
,
|
||||
area: ['300px', '200px']
|
||||
,
|
||||
id: 'layadmin-layer-skin-openresty-start'
|
||||
,
|
||||
skin: 'layui-anim layui-anim-upbit'
|
||||
,
|
||||
content: '面板的正常访问依赖OpenResty,因此不支持在面板启动OpenResty,如您确需操作,请在SSH执行<span class="layui-badge-rim">systemctl start nginx</span>以启动OpenResty!'
|
||||
})
|
||||
index = layer.msg('正在启动OpenResty...', { icon: 16, time: 0, shade: 0.3 })
|
||||
admin.req({
|
||||
url: '/api/plugins/openresty/start'
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index)
|
||||
if (result.code !== 0) {
|
||||
return false
|
||||
}
|
||||
admin.events.refresh()
|
||||
layer.alert('OpenResty启动成功!')
|
||||
}
|
||||
})
|
||||
})
|
||||
$('#openresty-stop').click(function () {
|
||||
admin.popup({
|
||||
title: '<span style="color: red;">警告</span>'
|
||||
,
|
||||
shade: 0
|
||||
,
|
||||
anim: -1
|
||||
,
|
||||
area: ['300px', '200px']
|
||||
,
|
||||
id: 'layadmin-layer-skin-openresty-stop'
|
||||
,
|
||||
skin: 'layui-anim layui-anim-upbit'
|
||||
,
|
||||
content: '面板的正常访问依赖OpenResty,因此不支持在面板停止OpenResty,如您确需操作,请在SSH执行<span class="layui-badge-rim">systemctl stop nginx</span>以停止OpenResty!'
|
||||
})
|
||||
layer.confirm('停止OpenResty将导致网站无法访问,是否继续停止?', {
|
||||
btn: ['停止', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在停止OpenResty...', { icon: 16, time: 0, shade: 0.3 })
|
||||
admin.req({
|
||||
url: '/api/plugins/openresty/stop'
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index)
|
||||
if (result.code !== 0) {
|
||||
return false
|
||||
}
|
||||
admin.events.refresh()
|
||||
layer.alert('OpenResty停止成功!')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
$('#openresty-restart').click(function () {
|
||||
layer.confirm('重启OpenResty有可能导致面板短时间无法访问,是否继续重启?', {
|
||||
layer.confirm('重启OpenResty将导致网站短时间无法访问,是否继续重启?', {
|
||||
btn: ['重启', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在重启OpenResty...', { icon: 16, time: 0 })
|
||||
index = layer.msg('正在重启OpenResty...', { icon: 16, time: 0, shade: 0.3 })
|
||||
admin.req({
|
||||
url: '/api/plugins/openresty/restart'
|
||||
, method: 'post'
|
||||
, beforeSend: function () {
|
||||
layer.msg('已发送重启请求,请稍后刷新确认重启状态。')
|
||||
}
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index)
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:OpenResty重启失败,接口返回' + result)
|
||||
return false
|
||||
}
|
||||
admin.events.refresh()
|
||||
@@ -198,43 +194,42 @@ Date: 2023-06-24
|
||||
})
|
||||
})
|
||||
$('#openresty-reload').click(function () {
|
||||
index = layer.msg('正在重载OpenResty...', { icon: 16, time: 0 })
|
||||
index = layer.msg('正在重载OpenResty...', { icon: 16, time: 0, shade: 0.3 })
|
||||
admin.req({
|
||||
url: '/api/plugins/openresty/reload'
|
||||
, method: 'post'
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index)
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:OpenResty重载失败,接口返回' + result)
|
||||
return false
|
||||
}
|
||||
admin.events.refresh()
|
||||
layer.alert('OpenResty重载成功!')
|
||||
}
|
||||
})
|
||||
})
|
||||
$('#openresty-config-save').click(function () {
|
||||
index = layer.msg('正在保存OpenResty主配置...', { icon: 16, time: 0 })
|
||||
index = layer.msg('正在保存OpenResty主配置...', { icon: 16, time: 0, shade: 0.3 })
|
||||
admin.req({
|
||||
url: '/api/plugins/openresty/config'
|
||||
, method: 'post'
|
||||
, type: 'post'
|
||||
, data: {
|
||||
config: openresty_config_editor.getValue()
|
||||
}
|
||||
, success: function (result) {
|
||||
layer.close(index)
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:OpenResty配置保存失败,接口返回' + result)
|
||||
return false
|
||||
}
|
||||
layer.alert('OpenResty配置保存成功!')
|
||||
}
|
||||
})
|
||||
})
|
||||
$('#openresty-clean-error-log').click(function () {
|
||||
index = layer.msg('正在清空OpenResty错误日志...', { icon: 16, time: 0 })
|
||||
$('#openresty-clear-error-log').click(function () {
|
||||
index = layer.msg('正在清空OpenResty错误日志...', { icon: 16, time: 0, shade: 0.3 })
|
||||
admin.req({
|
||||
url: '/api/plugins/openresty/cleanErrorLog'
|
||||
, method: 'post'
|
||||
url: '/api/plugins/openresty/clearErrorLog'
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index)
|
||||
if (result.code !== 0) {
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,300 @@
|
||||
<!--
|
||||
Name: 系统安全
|
||||
Author: 耗子
|
||||
Date: 2023-07-21
|
||||
-->
|
||||
<title>系统安全</title>
|
||||
|
||||
<div class="layui-fluid">
|
||||
<div class="layui-card">
|
||||
<div class="layui-form layui-card-header layuiadmin-card-header-auto">
|
||||
<div class="layui-inline">
|
||||
<span style="margin-right: 10px;">防火墙</span>
|
||||
<input type="checkbox" id="safe_firewall" lay-filter="safe_firewall" lay-skin="switch"
|
||||
lay-text="ON|OFF">
|
||||
<span style="margin: 0px 10px;">启用SSH</span>
|
||||
<input type="checkbox" id="safe_ssh" lay-filter="safe_ssh" lay-skin="switch" lay-text="ON|OFF">
|
||||
<span style="margin: 0px 10px 0px 20px;">SSH端口</span>
|
||||
<div class="layui-input-inline" style="width: 80px;">
|
||||
<input type="number" id="safe_ssh_port" class="layui-input" style="height: 30px; margin-top: 5px;"
|
||||
min=1
|
||||
max=65535 disabled>
|
||||
</div>
|
||||
<div class="layui-input-inline">
|
||||
<button id="safe_ssh_port_save" class="layui-btn layui-btn-sm layui-btn-primary">确定
|
||||
</button>
|
||||
</div>
|
||||
<span style="margin: 0px 10px 0px 20px;">允许Ping</span>
|
||||
<input type="checkbox" id="switch_ping" lay-filter="safe_ping" lay-skin="switch" lay-text="ON|OFF">
|
||||
</div>
|
||||
<div class="layui-inline" style="float: right;">
|
||||
<button class="layui-btn layui-btn-sm layui-btn-danger">清空 OpenResty 日志
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="vm_security">
|
||||
<div class="layui-card">
|
||||
<div class="layui-form layui-card-header layuiadmin-card-header-auto">
|
||||
<div class="layui-inline">
|
||||
<span style="margin-right: 10px;">端口控制</span>
|
||||
<div class="layui-input-inline">
|
||||
<input id="safe_add_firewall_rule_port" type="text" name="safe_add_firewall_rule_port"
|
||||
class="layui-input"
|
||||
placeholder="例如:3306、1000-2000">
|
||||
</div>
|
||||
<div class="layui-input-inline">
|
||||
<select id="safe_add_firewall_rule_protocol" lay-filter="safe_add_firewall_rule_protocol"
|
||||
style="height: 30px; margin-top: 5px;">
|
||||
<option value="tcp">TCP</option>
|
||||
<option value="udp">UDP</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="layui-input-inline">
|
||||
<button id="safe_add_firewall_rule" class="layui-btn layui-btn-sm" style="margin-top: -4px;">放行
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<table class="layui-hide" id="safe-port" lay-filter="safe-port"></table>
|
||||
<!-- 右侧删除端口 -->
|
||||
<script type="text/html" id="safe-port-setting">
|
||||
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
layui.use(['layer', 'admin', 'form', 'laypage', 'table'], function () {
|
||||
var $ = layui.$;
|
||||
var admin = layui.admin;
|
||||
var table = layui.table;
|
||||
var form = layui.form;
|
||||
var layer = layui.layer;
|
||||
|
||||
// 获取防火墙状态
|
||||
admin.req({
|
||||
url: '/api/panel/safe/firewallStatus'
|
||||
, type: 'get'
|
||||
, dataType: 'json'
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
// 防火墙
|
||||
if (res.data) {
|
||||
$('#safe_firewall').attr('checked', true);
|
||||
} else {
|
||||
$('#safe_firewall').attr('checked', false);
|
||||
}
|
||||
form.render();
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, shade: 0.1});
|
||||
}
|
||||
}
|
||||
});
|
||||
// 获取SSH状态
|
||||
admin.req({
|
||||
url: '/api/panel/safe/sshStatus'
|
||||
, type: 'get'
|
||||
, dataType: 'json'
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
// SSH
|
||||
if (res.data) {
|
||||
$('#safe_ssh').attr('checked', true);
|
||||
} else {
|
||||
$('#safe_ssh').attr('checked', false);
|
||||
}
|
||||
form.render();
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, shade: 0.1});
|
||||
}
|
||||
}
|
||||
});
|
||||
// 获取SSH端口
|
||||
admin.req({
|
||||
url: '/api/panel/safe/sshPort'
|
||||
, type: 'get'
|
||||
, dataType: 'json'
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
// SSH端口
|
||||
$('#safe_ssh_port').val(res.data);
|
||||
$('#safe_ssh_port').attr('disabled', false);
|
||||
form.render();
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, shade: 0.1});
|
||||
}
|
||||
}
|
||||
});
|
||||
// 获取ping状态
|
||||
admin.req({
|
||||
url: '/api/panel/safe/pingStatus'
|
||||
, type: 'get'
|
||||
, dataType: 'json'
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
// ping
|
||||
if (res.data) {
|
||||
$('#switch_ping').attr('checked', true);
|
||||
} else {
|
||||
$('#switch_ping').attr('checked', false);
|
||||
}
|
||||
form.render();
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, shade: 0.1});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 设置防火墙开关
|
||||
form.on('switch(safe_firewall)', function (data) {
|
||||
admin.req({
|
||||
url: '/api/panel/safe/firewallStatus'
|
||||
, type: 'post'
|
||||
, dataType: 'json'
|
||||
, data: {
|
||||
status: data.elem.checked
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('设置成功', {icon: 1, shade: 0.1});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, shade: 0.1});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 设置SSH开关
|
||||
form.on('switch(safe_ssh)', function (data) {
|
||||
admin.req({
|
||||
url: '/api/panel/safe/sshStatus'
|
||||
, type: 'post'
|
||||
, dataType: 'json'
|
||||
, data: {
|
||||
status: data.elem.checked
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('设置成功', {icon: 1, shade: 0.1});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, shade: 0.1});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 设置ping开关
|
||||
form.on('switch(safe_ping)', function (data) {
|
||||
admin.req({
|
||||
url: '/api/panel/safe/pingStatus'
|
||||
, type: 'post'
|
||||
, dataType: 'json'
|
||||
, data: {
|
||||
status: data.elem.checked
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('设置成功', {icon: 1, shade: 0.1});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, shade: 0.1});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
table.render({
|
||||
elem: '#safe-port'
|
||||
, url: '/api/panel/safe/firewallRules'
|
||||
, title: '防火墙'
|
||||
, cols: [[
|
||||
{field: 'port', title: '端口', width: 100, sort: true}
|
||||
, {field: 'protocol', title: '协议', sort: true}
|
||||
, {fixed: 'right', title: '操作', toolbar: '#safe-port-setting', width: 150}
|
||||
]]
|
||||
});
|
||||
table.on('tool(safe-port)', function (obj) {
|
||||
let data = obj.data;
|
||||
if (obj.event === 'del') {
|
||||
layer.confirm('确定要删除 <b style="color: red;">' + data.protocol + '</b> 端口 <b style="color: red;">' + data.port + '</b> 吗?', function (index) {
|
||||
admin.req({
|
||||
url: "/api/panel/safe/deleteFirewallRule"
|
||||
, type: 'post'
|
||||
, data: data
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:端口删除失败,接口返回' + result);
|
||||
layer.msg('网站删除失败,请刷新重试!')
|
||||
return false;
|
||||
}
|
||||
obj.del();
|
||||
layer.alert('<b style="color: red;">' + data.protocol + '</b> 端口 <b style="color: red;">' + data.port + '</b> 删除成功!');
|
||||
}
|
||||
, error: function (xhr, status, error) {
|
||||
console.log('耗子Linux面板:ajax请求出错,错误' + error);
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 监听ssh端口保存
|
||||
$('#safe_ssh_port_save').click(function () {
|
||||
var port = Number($('#safe_ssh_port').val());
|
||||
// 判断端口是否合法
|
||||
if (isNaN(port) || port < 1 || port > 65535) {
|
||||
layer.msg('端口号不合法', {icon: 2, shade: 0.1});
|
||||
return false;
|
||||
}
|
||||
var index = layer.load();
|
||||
admin.req({
|
||||
url: '/api/panel/safe/sshPort'
|
||||
, type: 'post'
|
||||
, dataType: 'json'
|
||||
, data: {
|
||||
port: port
|
||||
}
|
||||
, success: function (res) {
|
||||
layer.close(index);
|
||||
if (res.code === 0) {
|
||||
layer.msg('设置成功', {icon: 1, shade: 0.1});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, shade: 0.1});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 监听添加端口保存
|
||||
$('#safe_add_firewall_rule').click(function () {
|
||||
var port = $('#safe_add_firewall_rule_port').val();
|
||||
var protocol = $('#safe_add_firewall_rule_protocol').val();
|
||||
var index = layer.load();
|
||||
admin.req({
|
||||
url: '/api/panel/safe/addFirewallRule'
|
||||
, type: 'post'
|
||||
, dataType: 'json'
|
||||
, data: {
|
||||
port: port,
|
||||
protocol: protocol
|
||||
}
|
||||
, success: function (res) {
|
||||
layer.close(index);
|
||||
if (res.code === 0) {
|
||||
layer.msg('设置成功', {icon: 1, shade: 0.1});
|
||||
table.reload('safe-port');
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, shade: 0.1});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,178 @@
|
||||
<!--
|
||||
Name: 面板设置
|
||||
Author: 耗子
|
||||
Date: 2023-07-21
|
||||
-->
|
||||
<title>面板设置</title>
|
||||
<div class="layui-fluid">
|
||||
<div class="layui-row layui-col-space15">
|
||||
<div class="layui-col-md12">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">设置</div>
|
||||
<div class="layui-card-body">
|
||||
|
||||
<div class="layui-form" lay-filter="panel_setting">
|
||||
<!--<div class="layui-form-item">
|
||||
<label class="layui-form-label">API 开关</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="checkbox" name="api" lay-skin="switch" lay-text="ON|OFF">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">开启后将提供面板API接口的访问支持</div>
|
||||
</div>
|
||||
<div id="setting-api-token" class="layui-form-item">
|
||||
<label class="layui-form-label">API Token</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="api_token" value="获取中ing..." class="layui-input" disabled>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">API Token,用于携带访问面板接口</div>
|
||||
</div>-->
|
||||
<!--<div class="layui-form-item">
|
||||
<label class="layui-form-label">多设备登录</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="checkbox" name="multi_login" lay-skin="switch" lay-text="ON|OFF">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">
|
||||
开启后将允许多设备同时登录面板,可能具有一定安全隐患
|
||||
</div>
|
||||
</div>-->
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">面板名称</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="name" value="获取中ing..." class="layui-input" disabled>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">修改面板的显示名称</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">面板用户名</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="username" value="获取中ing..." class="layui-input" disabled>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">修改面板的登录用户名</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">面板密码</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="password" name="password" value="" class="layui-input" disabled>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">修改面板的登录密码(留空不修改)</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">面板邮箱</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="email" value="获取中ing..." class="layui-input" disabled>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">修改面板账号的邮箱,目前用于签发免费SSL证书</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">面板端口</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="port" value="" class="layui-input" disabled>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">修改面板的访问端口(<b style="color: red;">保存后需要重启面板并修改浏览器地址栏的端口为新端口以访问面板</b>)
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">面板入口</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="entrance" value="" class="layui-input" disabled>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">修改面板的访问入口(<b style="color: red;">保存后需要重启面板并修改浏览器地址栏的入口为新入口以访问面板</b>)
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">建站目录</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="website_path" value="获取中ing..." class="layui-input" disabled>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">修改面板的默认建站目录</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">备份目录</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="backup_path" value="获取中ing..." class="layui-input" disabled>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">修改面板的默认备份目录</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="panel_setting_submit">确认修改</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
layui.define(['form', 'upload'], function () {
|
||||
var $ = layui.$
|
||||
, layer = layui.layer
|
||||
, admin = layui.admin
|
||||
, form = layui.form;
|
||||
|
||||
// 渲染表单
|
||||
form.render();
|
||||
$('#setting-api-token').hide();
|
||||
|
||||
admin.req({
|
||||
url: "/api/panel/setting/list"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:系统信息获取失败,接口返回' + result);
|
||||
layer.msg('系统信息获取失败,请刷新重试!')
|
||||
return false;
|
||||
}
|
||||
form.val("panel_setting",
|
||||
result.data
|
||||
);
|
||||
$('input').attr('disabled', false);
|
||||
if (result.data.api === 1) {
|
||||
$('#setting-api-token').show();
|
||||
$('#setting-api-token input').attr('readonly', true);
|
||||
}
|
||||
}
|
||||
, error: function (xhr, status, error) {
|
||||
console.log('耗子Linux面板:ajax请求出错,错误' + error);
|
||||
}
|
||||
});
|
||||
|
||||
// 面板设置
|
||||
form.on('submit(panel_setting_submit)', function (obj) {
|
||||
// 面板API
|
||||
if (obj.field.api === "on") {
|
||||
obj.field.api = 1;
|
||||
} else {
|
||||
obj.field.api = 0;
|
||||
}
|
||||
// 多设备登录
|
||||
if (obj.field.multi_login === "on") {
|
||||
obj.field.multi_login = 1;
|
||||
} else {
|
||||
obj.field.multi_login = 0;
|
||||
}
|
||||
// 提交修改
|
||||
admin.req({
|
||||
url: "/api/panel/setting/save"
|
||||
, type: 'post'
|
||||
, data: obj.field
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:设置保存失败,接口返回' + result);
|
||||
layer.msg('面板设置保存失败,请刷新重试!')
|
||||
return false;
|
||||
}
|
||||
admin.render();
|
||||
layer.msg('面板设置保存成功!');
|
||||
}
|
||||
, error: function (xhr, status, error) {
|
||||
console.log('耗子Linux面板:ajax请求出错,错误' + error);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,162 @@
|
||||
<!--
|
||||
Name: 任务中心
|
||||
Author: 耗子
|
||||
Date: 2023-07-22
|
||||
-->
|
||||
|
||||
<title>任务中心</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">任务列表</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="layui-tab">
|
||||
<ul class="layui-tab-title">
|
||||
<li class="layui-this">进行中</li>
|
||||
<li>等待中</li>
|
||||
<li>已完成</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
<script type="text/html" template
|
||||
lay-url="/api/panel/task/list?status=running&page=1&limit=10"
|
||||
lay-done="layui.data.done(d);">
|
||||
{{# if(d.data.total != 0){ }}
|
||||
<blockquote class="layui-elem-quote">{{ d.data.items[0].name }}</blockquote>
|
||||
<pre id="plugin-install-log" class="layui-code">
|
||||
日志获取中...
|
||||
</pre>
|
||||
{{# } else { }}
|
||||
<blockquote class="layui-elem-quote">暂无任务</blockquote>
|
||||
{{# } }}
|
||||
</script>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<table id="panel-task-waiting" lay-filter="panel-task-waiting"></table>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<table id="panel-task-finished" lay-filter="panel-task-finished"></table>
|
||||
<script type="text/html" id="panel-task-finished-control-tpl">
|
||||
<a class="layui-btn layui-btn-xs" lay-event="remove">移除</a>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function render_plugin_install_log(d) {
|
||||
layui.use(['admin', 'jquery'], function () {
|
||||
let admin = layui.admin
|
||||
, $ = layui.jquery;
|
||||
admin.req({
|
||||
url: "/api/panel/task/log?id=" + d.data.items[0].id
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
$('#plugin-install-log').html('实时安装日志获取失败,请刷新重试!');
|
||||
return false;
|
||||
}
|
||||
$('#plugin-install-log').html(result.data);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
layui.data.done = function (d) {
|
||||
if (d.data.items[0] !== undefined) {
|
||||
render_plugin_install_log(d);
|
||||
setInterval(function () {
|
||||
render_plugin_install_log(d);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
layui.use(['admin', 'table', 'jquery'], function () {
|
||||
var table = layui.table
|
||||
, admin = layui.admin;
|
||||
|
||||
table.render({
|
||||
elem: '#panel-task-waiting'
|
||||
, url: '/api/panel/task/list?status=waiting'
|
||||
, cols: [[
|
||||
{field: 'id', hide: true, title: 'ID', sort: true}
|
||||
, {field: 'name', width: '100%', title: '任务名'}
|
||||
]]
|
||||
, page: true
|
||||
, text: {
|
||||
none: '无数据'
|
||||
}
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.message,
|
||||
"count": res.data.total,
|
||||
"data": res.data.items
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
table.render({
|
||||
elem: '#panel-task-finished'
|
||||
, id: 'panel-task-finished-table'
|
||||
, url: '/api/panel/task/list?status=finished'
|
||||
, cols: [[
|
||||
{field: 'id', hide: true, title: 'ID', sort: true}
|
||||
, {field: 'name', width: '80%', title: '任务名'}
|
||||
, {
|
||||
field: 'control',
|
||||
title: '操作',
|
||||
templet: '#panel-task-finished-control-tpl',
|
||||
fixed: 'right',
|
||||
align: 'center'
|
||||
}
|
||||
]]
|
||||
, page: true
|
||||
, text: {
|
||||
none: '无数据'
|
||||
}
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.message,
|
||||
"count": res.data.total,
|
||||
"data": res.data.items
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 工具条
|
||||
table.on('tool(panel-task-finished)', function (obj) {
|
||||
let data = obj.data;
|
||||
if (obj.event === 'remove') {
|
||||
layer.confirm('确定移除该记录吗?', function (index) {
|
||||
layer.close(index);
|
||||
admin.req({
|
||||
url: '/api/panel/task/delete',
|
||||
type: 'post',
|
||||
data: {
|
||||
id: data.id
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code == 0) {
|
||||
layer.msg('移除任务:' + data.name + ' 成功!', {icon: 1, time: 1000}, function () {
|
||||
// 重载表格
|
||||
table.reload('panel-task-finished-table');
|
||||
});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -25,9 +25,8 @@ Date: 2023-06-24
|
||||
<label class="layui-form-label">PHP版本</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="php" lay-filter="add-website-php">
|
||||
<option value="00" selected="">不使用</option>
|
||||
{{# layui.each(d.params.php, function(index, item){ }}
|
||||
<option value="{{ item }}">{{ item }}</option>
|
||||
<option value="{{ item.slug }}">{{ item.name }}</option>
|
||||
{{# }); }}
|
||||
</select>
|
||||
</div>
|
||||
@@ -57,7 +56,7 @@ Date: 2023-06-24
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">数据库用户</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="db_username" autocomplete="off" class="layui-input">
|
||||
<input type="text" name="db_user" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
@@ -73,7 +72,7 @@ Date: 2023-06-24
|
||||
<label class="layui-form-label">目录</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="path"
|
||||
placeholder="请输入网站根目录(不填默认为/www/wwwroot/网站名)"
|
||||
placeholder="请输入网站根目录(不填默认为建站目录/网站名)"
|
||||
autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,11 +121,11 @@ Date: 2023-06-24
|
||||
if (data.value === 'mysql') {
|
||||
$('#add-website-db-info').show()
|
||||
$('input[name="db_name"]').val($('input[name="name"]').val() + '_mysql')
|
||||
$('input[name="db_username"]').val($('input[name="name"]').val() + '_mysql')
|
||||
$('input[name="db_user"]').val($('input[name="name"]').val() + '_mysql')
|
||||
} else if (data.value === 'postgresql') {
|
||||
$('#add-website-db-info').show()
|
||||
$('input[name="db_name"]').val($('input[name="name"]').val() + '_postgresql')
|
||||
$('input[name="db_username"]').val($('input[name="name"]').val() + '_postgresql')
|
||||
$('input[name="db_user"]').val($('input[name="name"]').val() + '_postgresql')
|
||||
}
|
||||
})
|
||||
// 提交
|
||||
@@ -139,7 +138,7 @@ Date: 2023-06-24
|
||||
}
|
||||
admin.req({
|
||||
url: '/api/panel/website/add'
|
||||
, method: 'post'
|
||||
, type: 'post'
|
||||
, data: data.field
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
|
||||
@@ -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>
|
||||
+6
-12
@@ -1,9 +1,9 @@
|
||||
<!--
|
||||
Name: 网站 - 全局设置
|
||||
Author: 耗子
|
||||
Date: 2023-06-24
|
||||
Date: 2023-07-21
|
||||
-->
|
||||
<script type="text/html" template lay-url="/api/panel/website/getDefaultSettings"
|
||||
<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">
|
||||
@@ -38,10 +38,7 @@ Date: 2023-06-24
|
||||
layui.use(['admin', 'form', 'laydate', 'code'], function () {
|
||||
var $ = layui.$
|
||||
, admin = layui.admin
|
||||
, element = layui.element
|
||||
, layer = layui.layer
|
||||
, laydate = layui.laydate
|
||||
, code = layui.code;
|
||||
, layer = layui.layer;
|
||||
indexEditor = ace.edit("index-editor", {
|
||||
mode: "ace/mode/html",
|
||||
selectionStyle: "text"
|
||||
@@ -54,7 +51,7 @@ Date: 2023-06-24
|
||||
$('#save-website-default-settings').click(function () {
|
||||
layer.load();
|
||||
admin.req({
|
||||
url: '/api/panel/website/saveDefaultSettings'
|
||||
url: '/api/panel/website/defaultConfig'
|
||||
, type: 'post'
|
||||
, data: {
|
||||
index: indexEditor.getValue(),
|
||||
@@ -63,17 +60,14 @@ Date: 2023-06-24
|
||||
, success: function (res) {
|
||||
layer.closeAll('loading');
|
||||
if (res.code === 0) {
|
||||
layer.msg('保存成功', {icon: 1});
|
||||
layer.msg('保存成功', {icon: 1, shade: 0.3});
|
||||
setTimeout(function () {
|
||||
admin.render();
|
||||
}, 1000);
|
||||
} else {
|
||||
layer.msg(res.msg, {icon: 2});
|
||||
layer.msg(res.message, {icon: 2, shade: 0.3});
|
||||
}
|
||||
}
|
||||
, error: function (xhr, status, error) {
|
||||
console.log('耗子Linux面板:ajax请求出错,错误' + error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--
|
||||
Name: 网站 - 编辑
|
||||
Author: 耗子
|
||||
Date: 2023-06-24
|
||||
Date: 2023-07-21
|
||||
-->
|
||||
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
|
||||
<div class="layui-tab" lay-filter="website-edit-tab">
|
||||
@@ -17,22 +17,62 @@ Date: 2023-06-24
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
<!-- 域名绑定 -->
|
||||
<div class="layui-form layui-form-pane">
|
||||
<div class="layui-form">
|
||||
<div class="layui-form-item layui-form-text">
|
||||
<label class="layui-form-label">域名</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="domain" lay-verify="required"
|
||||
placeholder="请输入域名,一行一个支持泛域名"
|
||||
class="layui-textarea">{{ d.params.config.domain }}</textarea>
|
||||
<div class="layui-input-inline" id="domains">
|
||||
{{# layui.each(d.params.config.domains, function(index, item){ }}
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="domains[]" lay-verify="required" value="{{ item }}"
|
||||
class="layui-input" style="width: 200%;">
|
||||
</div>
|
||||
<div class="layui-input-block" style="margin-left: 480px">
|
||||
<button type="button"
|
||||
class="layui-btn layui-btn-danger layui-btn-sm removeclass"><i
|
||||
class="layui-icon"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{{# }); }}
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block" style="margin-left: 480px">
|
||||
<button id="add-domain" type="button"
|
||||
class="layui-btn layui-btn-warm layui-btn-sm">
|
||||
<i class="layui-icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="layui-form-item layui-form-text">
|
||||
<label class="layui-form-label">端口</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="port" lay-verify="required"
|
||||
placeholder="请输入访问端口,一行一个"
|
||||
class="layui-textarea">{{ d.params.config.port }}</textarea>
|
||||
<div class="layui-input-inline" id="ports">
|
||||
{{# layui.each(d.params.config.ports, function(index, item){ }}
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="ports[]" lay-verify="required" value="{{ item }}"
|
||||
class="layui-input" style="width: 200%;">
|
||||
</div>
|
||||
<div class="layui-input-block" style="margin-left: 480px">
|
||||
<button type="button"
|
||||
class="layui-btn layui-btn-danger layui-btn-sm removeclass"><i
|
||||
class="layui-icon"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{{# }); }}
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block" style="margin-left: 480px">
|
||||
<button id="add-port" type="button"
|
||||
class="layui-btn layui-btn-warm layui-btn-sm">
|
||||
<i class="layui-icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,11 +106,11 @@ Date: 2023-06-24
|
||||
<label class="layui-form-label">PHP版本</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="php" lay-filter="website-php">
|
||||
{{# layui.each(d.params.php_version, function(index, item){ }}
|
||||
{{# if(item == d.params.config.php){ }}
|
||||
<option value="{{ item }}" selected="">{{ item }}</option>
|
||||
{{# layui.each(d.params.php, function(index, item){ }}
|
||||
{{# if(item.slug == d.params.config.php){ }}
|
||||
<option value="{{ item.slug }}" selected="">{{ item.name }}</option>
|
||||
{{# }else{ }}
|
||||
<option value="{{ item }}">{{ item }}</option>
|
||||
<option value="{{ item.slug }}">{{ item.name }}</option>
|
||||
{{# } }}
|
||||
{{# }); }}
|
||||
</select>
|
||||
@@ -80,7 +120,7 @@ Date: 2023-06-24
|
||||
<label class="layui-form-label">防跨站攻击</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="checkbox" name="open_basedir" lay-skin="switch" lay-text="ON|OFF"
|
||||
{{ d.params.config.open_basedir== 1 ? 'checked' : '' }}>
|
||||
{{ d.params.config.open_basedir ? 'checked' : '' }}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,7 +137,7 @@ Date: 2023-06-24
|
||||
<label class="layui-form-label">总开关</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="checkbox" name="waf" lay-skin="switch" lay-text="ON|OFF"
|
||||
{{ d.params.config.waf== 1 ? 'checked' : '' }}>
|
||||
{{ d.params.config.waf ? 'checked' : '' }}>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">只有打开了总开关,下面的设置才会生效!</div>
|
||||
</div>
|
||||
@@ -132,7 +172,7 @@ Date: 2023-06-24
|
||||
<label class="layui-form-label">总开关</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="checkbox" name="ssl" lay-skin="switch" lay-text="ON|OFF"
|
||||
{{ d.params.config.ssl== 1 ? 'checked' : '' }}>
|
||||
{{ d.params.config.ssl ? 'checked' : '' }}>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">只有打开了总开关,下面的设置才会生效!</div>
|
||||
</div>
|
||||
@@ -142,25 +182,25 @@ Date: 2023-06-24
|
||||
<label class="layui-form-label">HTTP跳转</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="checkbox" name="http_redirect" lay-skin="switch" lay-text="ON|OFF"
|
||||
{{ d.params.config.http_redirect== 1 ? 'checked' : '' }}>
|
||||
{{ d.params.config.http_redirect ? 'checked' : '' }}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">HSTS</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="checkbox" name="hsts" lay-skin="switch" lay-text="ON|OFF"
|
||||
{{ d.params.config.hsts== 1 ? 'checked' : '' }}>
|
||||
{{ d.params.config.hsts ? 'checked' : '' }}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<!--<div class="layui-inline">
|
||||
<div class="layui-input-inline">
|
||||
<button id="issue-ssl" class="layui-btn layui-btn-sm">签发免费SSL证书</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-form-text">
|
||||
{{# if(d.params.config.ssl == 1){ }}
|
||||
{{# if(d.params.config.ssl){ }}
|
||||
<label class="layui-form-label">证书 <span style="color: red; float: right;">剩余有效期:{{ d.params.config.ssl_date }}天</span></label>
|
||||
{{# }else{ }}
|
||||
<label class="layui-form-label">证书</label>
|
||||
@@ -195,7 +235,7 @@ Date: 2023-06-24
|
||||
<br>
|
||||
如果你修改了原文,那么点击保存后,其余的修改将不会生效!
|
||||
</blockquote>
|
||||
<div id="config-editor" style="height: 400px;">{{ d.params.config.config_raw }}</div>
|
||||
<div id="config-editor" style="height: 400px;">{{ d.params.config.raw }}</div>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<!-- 访问日志 -->
|
||||
@@ -209,171 +249,205 @@ Date: 2023-06-24
|
||||
</div>
|
||||
</script>
|
||||
<script>
|
||||
let rewriteEditor = ''
|
||||
let configEditor = ''
|
||||
layui.data.sendParams = function (params) {
|
||||
layui.use(['admin', 'form', 'laydate', 'code'], function () {
|
||||
var $ = layui.$
|
||||
, admin = layui.admin
|
||||
, layer = layui.layer
|
||||
, code = layui.code
|
||||
, form = layui.form
|
||||
, element = layui.element
|
||||
form.render()
|
||||
element.render()
|
||||
element.on('tab(website-edit-tab)', function (data) {
|
||||
if (data.index === 6) {
|
||||
// 隐藏保存按钮
|
||||
$('.layui-footer').hide()
|
||||
} else {
|
||||
$('.layui-footer').show()
|
||||
}
|
||||
})
|
||||
rewriteEditor = ace.edit('rewrite-editor', {
|
||||
mode: 'ace/mode/nginx',
|
||||
selectionStyle: 'text'
|
||||
})
|
||||
configEditor = ace.edit('config-editor', {
|
||||
mode: 'ace/mode/nginx',
|
||||
selectionStyle: 'text'
|
||||
})
|
||||
code({
|
||||
elem: '#website-log'
|
||||
, encode: true
|
||||
, about: false
|
||||
let rewriteEditor = ''
|
||||
let configEditor = ''
|
||||
layui.data.sendParams = function (params) {
|
||||
layui.use(['admin', 'form', 'laydate', 'code'], function () {
|
||||
var $ = layui.$
|
||||
, admin = layui.admin
|
||||
, layer = layui.layer
|
||||
, code = layui.code
|
||||
, form = layui.form
|
||||
, element = layui.element
|
||||
form.render()
|
||||
element.render()
|
||||
element.on('tab(website-edit-tab)', function (data) {
|
||||
if (data.index === 6) {
|
||||
// 隐藏保存按钮
|
||||
$('.layui-footer').hide()
|
||||
} else {
|
||||
$('.layui-footer').show()
|
||||
}
|
||||
})
|
||||
rewriteEditor = ace.edit('rewrite-editor', {
|
||||
mode: 'ace/mode/nginx',
|
||||
selectionStyle: 'text'
|
||||
})
|
||||
configEditor = ace.edit('config-editor', {
|
||||
mode: 'ace/mode/nginx',
|
||||
selectionStyle: 'text'
|
||||
})
|
||||
code({
|
||||
elem: '#website-log'
|
||||
, encode: true
|
||||
, about: false
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
$('#clean-site-log').click(function () {
|
||||
layer.confirm('确定要清空日志吗?', function (index) {
|
||||
layer.close(index)
|
||||
layer.load()
|
||||
admin.req({
|
||||
url: '/api/panel/website/clearSiteLog'
|
||||
, type: 'post'
|
||||
, data: { name: params.config.name }
|
||||
, success: function (res) {
|
||||
layer.closeAll('loading')
|
||||
if (res.code === 0) {
|
||||
layer.msg('已清空', { icon: 1 })
|
||||
setTimeout(function () {
|
||||
admin.render()
|
||||
}, 1000)
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 })
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
$('#save-site-config').click(function () {
|
||||
layer.load()
|
||||
var port = $('textarea[name="port"]').val()
|
||||
var reg = new RegExp(/\n443.*\n?/)
|
||||
// 如果开启了https,就自动添加443端口
|
||||
if ($('input[name="ssl"]').prop('checked') && !reg.test(port)) {
|
||||
port = port + '\n443'
|
||||
}
|
||||
// 如果关闭了https,就自动删除443端口
|
||||
if (!$('input[name="ssl"]').prop('checked') && reg.test(port)) {
|
||||
// 正则替换
|
||||
port = port.replace(/443.*\n?/, '')
|
||||
}
|
||||
admin.req({
|
||||
url: '/api/panel/website/saveSiteSettings'
|
||||
, type: 'post'
|
||||
, data: {
|
||||
name: params.config.name,
|
||||
config: {
|
||||
domain: $('textarea[name="domain"]').val(),
|
||||
port: port,
|
||||
ssl: $('input[name="ssl"]').prop('checked') ? 1 : 0,
|
||||
http_redirect: $('input[name="http_redirect"]').prop('checked') ? 1 : 0,
|
||||
hsts: $('input[name="hsts"]').prop('checked') ? 1 : 0,
|
||||
ssl_certificate: $('textarea[name="ssl_certificate"]').val(),
|
||||
ssl_certificate_key: $('textarea[name="ssl_certificate_key"]').val(),
|
||||
path: $('input[name="path"]').val(),
|
||||
root: $('input[name="root"]').val(),
|
||||
index: $('input[name="index"]').val(),
|
||||
php: $('select[name="php"]').val(),
|
||||
open_basedir: $('input[name="open_basedir"]').prop('checked') ? 1 : 0,
|
||||
waf: $('input[name="waf"]').prop('checked') ? 1 : 0,
|
||||
waf_mode: $('input[name="waf_mode"]').val(),
|
||||
waf_cc_deny: $('input[name="waf_cc_deny"]').val(),
|
||||
waf_cache: $('input[name="waf_cache"]').val(),
|
||||
rewrite: rewriteEditor.getValue(),
|
||||
config_raw: configEditor.getValue()
|
||||
}
|
||||
}
|
||||
, success: function (res) {
|
||||
layer.closeAll('loading')
|
||||
if (res.code === 0) {
|
||||
layer.msg('保存成功', { icon: 1 })
|
||||
setTimeout(function () {
|
||||
admin.render()
|
||||
}, 1000)
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 })
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 重置配置
|
||||
$('#site-config-restore').click(function () {
|
||||
layer.confirm('高风险操作,网站配置重置后所有配置均需重新设置,确定要重置配置吗?', function (index) {
|
||||
index = layer.msg('重置网站配置', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
})
|
||||
admin.req({
|
||||
url: '/api/panel/website/resetSiteConfig'
|
||||
, type: 'post'
|
||||
, data: { name: params.config.name }
|
||||
, success: function (res) {
|
||||
layer.close(index)
|
||||
if (res.code === 0) {
|
||||
layer.alert('重置成功,你需要重新添加域名/端口绑定,设置各配置参数!', function (index) {
|
||||
admin.render()
|
||||
layer.close(index)
|
||||
$('#clean-site-log').click(function () {
|
||||
layer.confirm('确定要清空日志吗?', function (index) {
|
||||
layer.close(index)
|
||||
layer.load()
|
||||
admin.req({
|
||||
url: '/api/panel/website/clearSiteLog'
|
||||
, type: 'post'
|
||||
, data: {name: params.config.name}
|
||||
, success: function (res) {
|
||||
layer.closeAll('loading')
|
||||
if (res.code === 0) {
|
||||
layer.msg('已清空', {icon: 1})
|
||||
setTimeout(function () {
|
||||
admin.render()
|
||||
}, 1000)
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
layer.msg(res.msg, { icon: 2 })
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 监听签发证书按钮
|
||||
$('#issue-ssl').click(function () {
|
||||
layer.confirm('确定要申请签发免费SSL证书吗?', function (index) {
|
||||
index = layer.msg('正在签发证书,可能需要较长时间,请勿操作...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
})
|
||||
admin.req({
|
||||
url: '/api/panel/website/issueSsl'
|
||||
, type: 'post'
|
||||
, data: {
|
||||
name: params.config.name
|
||||
, type: 'lets'
|
||||
}
|
||||
, success: function (res) {
|
||||
layer.close(index)
|
||||
if (res.code === 0) {
|
||||
layer.msg('签发成功', { icon: 1 })
|
||||
setTimeout(function () {
|
||||
admin.render()
|
||||
}, 1000)
|
||||
} else {
|
||||
layer.alert(res.msg, { icon: 2 })
|
||||
}
|
||||
}
|
||||
})
|
||||
$('#save-site-config').click(function () {
|
||||
layer.load()
|
||||
let ports = $("input[name='ports[]']").map(function () {
|
||||
return $(this).val();
|
||||
}).get()
|
||||
|
||||
let domains = $("input[name='domains[]']").map(function () {
|
||||
return $(this).val();
|
||||
}).get()
|
||||
|
||||
admin.req({
|
||||
url: '/api/panel/website/config'
|
||||
, type: 'post'
|
||||
, data: {
|
||||
id: params.id,
|
||||
domains: domains.join('\n'),
|
||||
ports: ports.join('\n'),
|
||||
ssl: $('input[name="ssl"]').prop('checked'),
|
||||
http_redirect: $('input[name="http_redirect"]').prop('checked'),
|
||||
hsts: $('input[name="hsts"]').prop('checked'),
|
||||
ssl_certificate: $('textarea[name="ssl_certificate"]').val(),
|
||||
ssl_certificate_key: $('textarea[name="ssl_certificate_key"]').val(),
|
||||
path: $('input[name="path"]').val(),
|
||||
root: $('input[name="root"]').val(),
|
||||
index: $('input[name="index"]').val(),
|
||||
php: $('select[name="php"]').val(),
|
||||
open_basedir: $('input[name="open_basedir"]').prop('checked'),
|
||||
waf: $('input[name="waf"]').prop('checked') ? 'on' : 'off',
|
||||
waf_mode: $('input[name="waf_mode"]').val(),
|
||||
waf_cc_deny: $('input[name="waf_cc_deny"]').val(),
|
||||
waf_cache: $('input[name="waf_cache"]').val(),
|
||||
rewrite: rewriteEditor.getValue(),
|
||||
raw: configEditor.getValue()
|
||||
}
|
||||
, success: function (res) {
|
||||
layer.closeAll('loading')
|
||||
if (res.code === 0) {
|
||||
layer.msg('保存成功', {icon: 1})
|
||||
setTimeout(function () {
|
||||
admin.render()
|
||||
}, 1000)
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 重置配置
|
||||
$('#site-config-restore').click(function () {
|
||||
layer.confirm('高风险操作,网站配置重置后所有配置均需重新设置,确定要重置配置吗?', function (index) {
|
||||
index = layer.msg('重置网站配置', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
})
|
||||
admin.req({
|
||||
url: '/api/panel/website/resetConfig'
|
||||
, type: 'post'
|
||||
, data: {id: params.id}
|
||||
, success: function (res) {
|
||||
layer.close(index)
|
||||
if (res.code === 0) {
|
||||
layer.alert('重置成功,你需要重新添加域名/端口绑定,设置各配置参数!', function (index) {
|
||||
admin.render()
|
||||
layer.close(index)
|
||||
})
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 监听签发证书按钮
|
||||
$('#issue-ssl').click(function () {
|
||||
layer.confirm('确定要申请签发免费SSL证书吗?', function (index) {
|
||||
index = layer.msg('正在签发证书,可能需要较长时间,请勿操作...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
})
|
||||
admin.req({
|
||||
url: '/api/panel/website/issueSsl'
|
||||
, type: 'post'
|
||||
, data: {
|
||||
name: params.config.name
|
||||
, type: 'lets'
|
||||
}
|
||||
, success: function (res) {
|
||||
layer.close(index)
|
||||
if (res.code === 0) {
|
||||
layer.msg('签发成功', {icon: 1})
|
||||
setTimeout(function () {
|
||||
admin.render()
|
||||
}, 1000)
|
||||
} else {
|
||||
layer.alert(res.message, {icon: 2})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 动态添加input输入框
|
||||
$("#add-domain").click(function () {
|
||||
var str = '<div class="layui-form-item">' +
|
||||
'<div class="layui-input-inline">' +
|
||||
'<input type="text" name="domains[]" placeholder="example.com" lay-verify="required"' +
|
||||
'class="layui-input" style="width: 200%;">' +
|
||||
'</div>' +
|
||||
'<div class="layui-input-block" style="margin-left: 480px">' +
|
||||
'<button type="button" class="layui-btn layui-btn-danger layui-btn-sm removeclass"><i class="layui-icon"></i></button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
$("#domains").append(str);
|
||||
|
||||
form.render();
|
||||
});
|
||||
$("#add-port").click(function () {
|
||||
var str = '<div class="layui-form-item">' +
|
||||
'<div class="layui-input-inline">' +
|
||||
'<input type="text" name="ports[]" placeholder="443 ssl" lay-verify="required"' +
|
||||
'class="layui-input" style="width: 200%;">' +
|
||||
'</div>' +
|
||||
'<div class="layui-input-block" style="margin-left: 480px">' +
|
||||
'<button type="button" class="layui-btn layui-btn-danger layui-btn-sm removeclass"><i class="layui-icon"></i></button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
$("#ports").append(str);
|
||||
|
||||
form.render();
|
||||
});
|
||||
|
||||
//删除动态添加的input输入框
|
||||
$("body").on('click', ".removeclass", function () {
|
||||
let parentEle = $(this).parent().parent();
|
||||
parentEle.remove();
|
||||
form.render();
|
||||
});
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--
|
||||
Name: 网站 - 列表
|
||||
Author: 耗子
|
||||
Date: 2023-06-24
|
||||
Date: 2023-07-71
|
||||
-->
|
||||
<title>网站</title>
|
||||
<div class="layui-fluid">
|
||||
@@ -16,7 +16,7 @@ Date: 2023-06-24
|
||||
<script type="text/html" id="website-list-bar">
|
||||
<div class="layui-btn-container">
|
||||
<button class="layui-btn layui-btn-sm" lay-event="website_add">添加网站</button>
|
||||
<button class="layui-btn layui-btn-sm" lay-event="website_default_settings">全局设置
|
||||
<button class="layui-btn layui-btn-sm" lay-event="website_default_config">全局设置
|
||||
</button>
|
||||
</div>
|
||||
</script>
|
||||
@@ -30,12 +30,16 @@ Date: 2023-06-24
|
||||
<script type="text/html" id="website-run">
|
||||
<input type="checkbox" name="run" lay-skin="switch" lay-text="ON|OFF"
|
||||
lay-filter="website-run-checkbox"
|
||||
value="{{ d.status }}" data-website-name="{{ d.name }}"
|
||||
{{ d.status== 1 ? 'checked' : '' }}>
|
||||
value="{{ d.status }}" data-website-id="{{ d.id }}"
|
||||
{{ d.status ? 'checked' : '' }}>
|
||||
</script>
|
||||
<!-- 网站SSL状态 -->
|
||||
<script type="text/html" id="website-ssl">
|
||||
{{ d.ssl == 1 ? '已开启' : '未开启' }}
|
||||
{{ d.ssl ? '已开启' : '未开启' }}
|
||||
</script>
|
||||
<!-- 网站PHP版本 -->
|
||||
<script type="text/html" id="website-php">
|
||||
{{ d.php == '0' ? '不使用' : d.php }}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,198 +48,200 @@ Date: 2023-06-24
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let php, mysql, postgresql
|
||||
layui.use(['admin', 'table', 'form', 'view'], function () {
|
||||
var admin = layui.admin
|
||||
, table = layui.table
|
||||
, form = layui.form
|
||||
, view = layui.view
|
||||
|
||||
// 获取已安装的PHP和DB版本
|
||||
admin.req({
|
||||
url: '/api/panel/info/installedDbAndPhp'
|
||||
, method: 'get'
|
||||
, success: function (result) {
|
||||
php = result.data.php
|
||||
mysql = result.data.mysql
|
||||
postgresql = result.data.postgresql
|
||||
}
|
||||
})
|
||||
table.render({
|
||||
elem: '#website-list'
|
||||
, url: '/api/panel/website/list'
|
||||
, toolbar: '#website-list-bar'
|
||||
, title: '网站列表'
|
||||
, cols: [[
|
||||
{ field: 'name', title: '网站名', width: 150, unresize: true, sort: true }
|
||||
, { field: 'run', title: '运行', width: 100, templet: '#website-run', unresize: true }
|
||||
, { field: 'path', title: '目录', width: 250 }
|
||||
, { field: 'php', title: 'PHP', width: 60 }
|
||||
, { field: 'ssl', title: 'SSL', width: 110, templet: '#website-ssl' }
|
||||
, { field: 'note', title: '备注', edit: 'textarea' }
|
||||
, { fixed: 'right', title: '操作', unresize: true, toolbar: '#website-control', width: 180 }
|
||||
]]
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
'code': res.code,
|
||||
'msg': res.message,
|
||||
'count': res.data.total,
|
||||
'data': res.data.items
|
||||
}
|
||||
}
|
||||
, page: true
|
||||
})
|
||||
|
||||
// 头工具栏事件
|
||||
table.on('toolbar(website-list)', function (obj) {
|
||||
if (obj.event === 'website_add') {
|
||||
admin.popup({
|
||||
title: '添加网站'
|
||||
, area: ['80%', '60%']
|
||||
, id: 'LAY-popup-website-add'
|
||||
, success: function (layer, index) {
|
||||
view(this.id).render('website/add', {
|
||||
php: php,
|
||||
mysql: mysql,
|
||||
postgresql: postgresql
|
||||
}).done(function () {
|
||||
form.render(null, 'LAY-popup-website-add')
|
||||
})
|
||||
}
|
||||
})
|
||||
} else if (obj.event === 'website_default_settings') {
|
||||
admin.popup({
|
||||
title: '全局设置'
|
||||
, area: ['80%', '80%']
|
||||
, id: 'LAY-popup-website-add'
|
||||
, success: function (layer, index) {
|
||||
view(this.id).render('website/default_settings', {}).done(function () {
|
||||
form.render(null, 'LAY-popup-website-default-settings')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 行工具事件
|
||||
table.on('tool(website-list)', function (obj) {
|
||||
let data = obj.data
|
||||
if (obj.event === 'del') {
|
||||
layer.confirm('删除网站将一并删除站点目录(不包括数据库),是否继续?', function (index) {
|
||||
admin.req({
|
||||
url: '/api/panel/website/delete'
|
||||
, method: 'post'
|
||||
, data: data
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:网站删除失败,接口返回' + result)
|
||||
layer.msg('网站删除失败,请刷新重试!')
|
||||
return false
|
||||
}
|
||||
obj.del()
|
||||
layer.alert('网站' + data.name + '删除成功!')
|
||||
}
|
||||
})
|
||||
layer.close(index)
|
||||
})
|
||||
} else if (obj.event === 'edit') {
|
||||
let config
|
||||
let php, mysql, postgresql
|
||||
layui.use(['admin', 'table', 'form', 'view'], function () {
|
||||
var admin = layui.admin
|
||||
, table = layui.table
|
||||
, form = layui.form
|
||||
, view = layui.view
|
||||
|
||||
// 获取已安装的PHP和DB版本
|
||||
admin.req({
|
||||
url: '/api/panel/website/getSiteSettings?name=' + data.name
|
||||
, method: 'get'
|
||||
, beforeSend: function (request) {
|
||||
layer.load()
|
||||
}
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:网站设置获取失败,接口返回' + result)
|
||||
layer.alert('网站设置获取失败!')
|
||||
return false
|
||||
url: '/api/panel/info/installedDbAndPhp'
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
php = result.data.php
|
||||
mysql = result.data.mysql
|
||||
postgresql = result.data.postgresql
|
||||
}
|
||||
config = result.data
|
||||
layer.closeAll('loading')
|
||||
// 打开编辑网站页面
|
||||
admin.popup({
|
||||
title: '编辑网站 - ' + data.name
|
||||
, area: ['80%', '80%']
|
||||
, id: 'LAY-popup-website-edit'
|
||||
, success: function (layero, index) {
|
||||
view(this.id).render('website/edit', {
|
||||
php: php,
|
||||
mysql: mysql,
|
||||
postgresql: postgresql,
|
||||
data: data,
|
||||
config: config
|
||||
}).done(function () {
|
||||
form.render(null, 'LAY-popup-website-edit')
|
||||
})
|
||||
table.render({
|
||||
elem: '#website-list'
|
||||
, url: '/api/panel/website/list'
|
||||
, toolbar: '#website-list-bar'
|
||||
, title: '网站列表'
|
||||
, cols: [[
|
||||
{field: 'id', hide: true, title: 'ID', sort: true}
|
||||
, {field: 'name', title: '网站名', width: 150, unresize: true, sort: true}
|
||||
, {field: 'run', title: '运行', width: 100, templet: '#website-run', unresize: true}
|
||||
, {field: 'path', title: '目录', width: 250}
|
||||
, {field: 'php', title: 'PHP', width: 100, templet: '#website-php'}
|
||||
, {field: 'ssl', title: 'SSL', width: 100, templet: '#website-ssl'}
|
||||
, {field: 'remark', title: '备注', edit: 'textarea'}
|
||||
, {fixed: 'right', title: '操作', unresize: true, toolbar: '#website-control', width: 180}
|
||||
]]
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
'code': res.code,
|
||||
'msg': res.message,
|
||||
'count': res.data.total,
|
||||
'data': res.data.items
|
||||
}
|
||||
}
|
||||
, page: true
|
||||
})
|
||||
|
||||
// 头工具栏事件
|
||||
table.on('toolbar(website-list)', function (obj) {
|
||||
if (obj.event === 'website_add') {
|
||||
admin.popup({
|
||||
title: '添加网站'
|
||||
, area: ['80%', '60%']
|
||||
, id: 'LAY-popup-website-add'
|
||||
, success: function (layer, index) {
|
||||
view(this.id).render('website/add', {
|
||||
php: php,
|
||||
mysql: mysql,
|
||||
postgresql: postgresql
|
||||
}).done(function () {
|
||||
form.render(null, 'LAY-popup-website-add')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (obj.event === 'website_default_config') {
|
||||
admin.popup({
|
||||
title: '全局设置'
|
||||
, area: ['80%', '80%']
|
||||
, id: 'LAY-popup-website-add'
|
||||
, success: function (layer, index) {
|
||||
view(this.id).render('website/default_config', {}).done(function () {
|
||||
form.render(null, 'LAY-popup-website-default-config')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
} else if (obj.event === 'backup') {
|
||||
// 打开备份页面
|
||||
admin.popup({
|
||||
title: '备份管理 - ' + data.name
|
||||
, area: ['70%', '80%']
|
||||
, id: 'LAY-popup-website-backup'
|
||||
, success: function (layero, index) {
|
||||
view(this.id).render('website/backup', {
|
||||
data: data
|
||||
}).done(function () {
|
||||
form.render(null, 'LAY-popup-website-backup')
|
||||
})
|
||||
}
|
||||
|
||||
// 行工具事件
|
||||
table.on('tool(website-list)', function (obj) {
|
||||
let data = obj.data
|
||||
if (obj.event === 'del') {
|
||||
layer.confirm('删除网站将一并删除站点目录(不包括数据库),是否继续?', function (index) {
|
||||
admin.req({
|
||||
url: '/api/panel/website/delete'
|
||||
, type: 'post'
|
||||
, data: data
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:网站删除失败,接口返回' + result)
|
||||
layer.msg('网站删除失败,请刷新重试!')
|
||||
return false
|
||||
}
|
||||
obj.del()
|
||||
layer.alert('网站' + data.name + '删除成功!')
|
||||
}
|
||||
})
|
||||
layer.close(index)
|
||||
})
|
||||
} else if (obj.event === 'edit') {
|
||||
let config
|
||||
|
||||
admin.req({
|
||||
url: '/api/panel/website/config?id=' + data.id
|
||||
, type: 'get'
|
||||
, beforeSend: function (request) {
|
||||
layer.load()
|
||||
}
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:网站设置获取失败,接口返回' + result)
|
||||
layer.alert('网站设置获取失败!')
|
||||
return false
|
||||
}
|
||||
config = result.data
|
||||
layer.closeAll('loading')
|
||||
// 打开编辑网站页面
|
||||
admin.popup({
|
||||
title: '编辑网站 - ' + data.name
|
||||
, area: ['80%', '80%']
|
||||
, id: 'LAY-popup-website-edit'
|
||||
, success: function (layero, index) {
|
||||
view(this.id).render('website/edit', {
|
||||
id : data.id,
|
||||
php: php,
|
||||
mysql: mysql,
|
||||
postgresql: postgresql,
|
||||
data: data,
|
||||
config: config
|
||||
}).done(function () {
|
||||
form.render(null, 'LAY-popup-website-edit')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
} else if (obj.event === 'backup') {
|
||||
// 打开备份页面
|
||||
admin.popup({
|
||||
title: '备份管理 - ' + data.name
|
||||
, area: ['70%', '80%']
|
||||
, id: 'LAY-popup-website-backup'
|
||||
, success: function (layero, index) {
|
||||
view(this.id).render('website/backup', {
|
||||
data: data
|
||||
}).done(function () {
|
||||
form.render(null, 'LAY-popup-website-backup')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 网站备注编辑
|
||||
table.on('edit(website-list)', function (obj) {
|
||||
let value = obj.value // 得到修改后的值
|
||||
, data = obj.data // 得到行数据
|
||||
admin.req({
|
||||
url: '/api/panel/website/updateRemark'
|
||||
, type: 'post'
|
||||
, data: {
|
||||
id: data.id,
|
||||
remark: value
|
||||
}
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:网站备注更新失败,接口返回' + result)
|
||||
layer.msg('网站备注更新失败,请刷新重试!')
|
||||
return false
|
||||
}
|
||||
layer.alert('网站 ' + data.name + ' 备注更新成功!')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 网站运行状态操作
|
||||
form.on('switch(website-run-checkbox)', function (obj) {
|
||||
let $ = layui.$
|
||||
let website_id = $(this).data('website-id')
|
||||
let status = obj.elem.checked
|
||||
|
||||
admin.req({
|
||||
url: '/api/panel/website/status'
|
||||
, type: 'post'
|
||||
, data: {
|
||||
id: website_id,
|
||||
status: status
|
||||
}
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:网站运行状态设置失败,接口返回' + result)
|
||||
layer.msg('网站运行状态设置失败,请刷新重试!')
|
||||
return false
|
||||
}
|
||||
layer.alert('运行状态修改成功')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// 网站备注编辑
|
||||
table.on('edit(website-list)', function (obj) {
|
||||
let value = obj.value // 得到修改后的值
|
||||
, data = obj.data // 得到行数据
|
||||
admin.req({
|
||||
url: '/api/panel/website/updateSiteNote'
|
||||
, method: 'post'
|
||||
, data: {
|
||||
name: data.name,
|
||||
note: value
|
||||
}
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:网站备注更新失败,接口返回' + result)
|
||||
layer.msg('网站备注更新失败,请刷新重试!')
|
||||
return false
|
||||
}
|
||||
layer.alert('网站 ' + data.name + ' 备注更新成功!')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 网站运行状态操作
|
||||
form.on('switch(website-run-checkbox)', function (obj) {
|
||||
let $ = layui.$
|
||||
let website_name = $(this).data('website-name')
|
||||
let status = obj.elem.checked ? 1 : 0
|
||||
|
||||
admin.req({
|
||||
url: '/api/panel/website/setSiteStatus'
|
||||
, method: 'post'
|
||||
, data: {
|
||||
name: website_name,
|
||||
status: status
|
||||
}
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:网站运行状态设置失败,接口返回' + result)
|
||||
layer.msg('网站运行状态设置失败,请刷新重试!')
|
||||
return false
|
||||
}
|
||||
layer.alert('网站 ' + website_name + ' 运行状态设置成功!')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
+58
-7
@@ -3,6 +3,8 @@ package routes
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/route"
|
||||
"github.com/goravel/framework/facades"
|
||||
"panel/app/http/controllers/plugins/mysql57"
|
||||
"panel/app/http/controllers/plugins/php80"
|
||||
|
||||
"panel/app/http/controllers/plugins/mysql80"
|
||||
"panel/app/http/controllers/plugins/openresty"
|
||||
@@ -23,7 +25,37 @@ func Plugin() {
|
||||
route.Get("config", openRestyController.GetConfig)
|
||||
route.Post("config", openRestyController.SaveConfig)
|
||||
route.Get("errorLog", openRestyController.ErrorLog)
|
||||
route.Get("clearErrorLog", openRestyController.ClearErrorLog)
|
||||
route.Post("clearErrorLog", openRestyController.ClearErrorLog)
|
||||
})
|
||||
facades.Route().Prefix("api/plugins/mysql57").Middleware(middleware.Jwt()).Group(func(route route.Route) {
|
||||
mysql57Controller := mysql57.NewMysql57Controller()
|
||||
route.Get("status", mysql57Controller.Status)
|
||||
route.Post("reload", mysql57Controller.Reload)
|
||||
route.Post("start", mysql57Controller.Start)
|
||||
route.Post("stop", mysql57Controller.Stop)
|
||||
route.Post("restart", mysql57Controller.Restart)
|
||||
route.Get("load", mysql57Controller.Load)
|
||||
route.Get("config", mysql57Controller.GetConfig)
|
||||
route.Post("config", mysql57Controller.SaveConfig)
|
||||
route.Get("errorLog", mysql57Controller.ErrorLog)
|
||||
route.Post("clearErrorLog", mysql57Controller.ClearErrorLog)
|
||||
route.Get("slowLog", mysql57Controller.SlowLog)
|
||||
route.Post("clearSlowLog", mysql57Controller.ClearSlowLog)
|
||||
route.Get("rootPassword", mysql57Controller.GetRootPassword)
|
||||
route.Post("rootPassword", mysql57Controller.SetRootPassword)
|
||||
route.Get("database", mysql57Controller.DatabaseList)
|
||||
route.Post("addDatabase", mysql57Controller.AddDatabase)
|
||||
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("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()
|
||||
@@ -36,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)
|
||||
@@ -46,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()
|
||||
@@ -66,10 +99,28 @@ 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)
|
||||
})
|
||||
facades.Route().Prefix("api/plugins/php80").Middleware(middleware.Jwt()).Group(func(route route.Route) {
|
||||
php80Controller := php80.NewPhp80Controller()
|
||||
route.Get("status", php80Controller.Status)
|
||||
route.Post("reload", php80Controller.Reload)
|
||||
route.Post("start", php80Controller.Start)
|
||||
route.Post("stop", php80Controller.Stop)
|
||||
route.Post("restart", php80Controller.Restart)
|
||||
route.Get("load", php80Controller.Load)
|
||||
route.Get("config", php80Controller.GetConfig)
|
||||
route.Post("config", php80Controller.SaveConfig)
|
||||
route.Get("errorLog", php80Controller.ErrorLog)
|
||||
route.Get("slowLog", php80Controller.SlowLog)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
+19
-2
@@ -10,6 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func Web() {
|
||||
facades.Route().StaticFile("favicon.ico", "/www/panel/public/favicon.ico")
|
||||
facades.Route().Prefix("api/panel").Group(func(r route.Route) {
|
||||
r.Prefix("info").Group(func(r route.Route) {
|
||||
infoController := controllers.NewInfoController()
|
||||
@@ -19,6 +20,8 @@ func Web() {
|
||||
r.Middleware(middleware.Jwt()).Get("nowMonitor", infoController.NowMonitor)
|
||||
r.Middleware(middleware.Jwt()).Get("systemInfo", infoController.SystemInfo)
|
||||
r.Middleware(middleware.Jwt()).Get("installedDbAndPhp", infoController.InstalledDbAndPhp)
|
||||
r.Middleware(middleware.Jwt()).Get("checkUpdate", infoController.CheckUpdate)
|
||||
r.Middleware(middleware.Jwt()).Post("update", infoController.Update)
|
||||
})
|
||||
r.Prefix("user").Group(func(r route.Route) {
|
||||
userController := controllers.NewUserController()
|
||||
@@ -35,6 +38,18 @@ func Web() {
|
||||
r.Prefix("website").Middleware(middleware.Jwt()).Group(func(r route.Route) {
|
||||
websiteController := controllers.NewWebsiteController()
|
||||
r.Get("list", websiteController.List)
|
||||
r.Post("add", websiteController.Add)
|
||||
r.Post("delete", websiteController.Delete)
|
||||
r.Get("defaultConfig", websiteController.GetDefaultConfig)
|
||||
r.Post("defaultConfig", websiteController.SaveDefaultConfig)
|
||||
r.Get("config", websiteController.GetConfig)
|
||||
r.Post("config", websiteController.SaveConfig)
|
||||
r.Get("clearLog", websiteController.ClearLog)
|
||||
r.Post("updateRemark", websiteController.UpdateRemark)
|
||||
r.Get("backupList", websiteController.BackupList)
|
||||
r.Post("createBackup", websiteController.CreateBackup)
|
||||
r.Post("resetConfig", websiteController.ResetConfig)
|
||||
r.Post("status", websiteController.Status)
|
||||
})
|
||||
r.Prefix("plugin").Middleware(middleware.Jwt()).Group(func(r route.Route) {
|
||||
pluginController := controllers.NewPluginController()
|
||||
@@ -47,6 +62,7 @@ func Web() {
|
||||
r.Prefix("cron").Middleware(middleware.Jwt()).Group(func(r route.Route) {
|
||||
cronController := controllers.NewCronController()
|
||||
r.Get("list", cronController.List)
|
||||
r.Get("script", cronController.Script)
|
||||
r.Post("add", cronController.Add)
|
||||
r.Post("update", cronController.Update)
|
||||
r.Post("delete", cronController.Delete)
|
||||
@@ -58,8 +74,8 @@ func Web() {
|
||||
r.Get("firewallStatus", safeController.GetFirewallStatus)
|
||||
r.Post("firewallStatus", safeController.SetFirewallStatus)
|
||||
r.Get("firewallRules", safeController.GetFirewallRules)
|
||||
r.Post("addFirewallRules", safeController.AddFirewallRule)
|
||||
r.Post("deleteFirewallRules", safeController.DeleteFirewallRule)
|
||||
r.Post("addFirewallRule", safeController.AddFirewallRule)
|
||||
r.Post("deleteFirewallRule", safeController.DeleteFirewallRule)
|
||||
r.Get("sshStatus", safeController.GetSshStatus)
|
||||
r.Post("sshStatus", safeController.SetSshStatus)
|
||||
r.Get("sshPort", safeController.GetSshPort)
|
||||
@@ -73,6 +89,7 @@ func Web() {
|
||||
r.Post("saveDays", monitorController.SaveDays)
|
||||
r.Post("clear", monitorController.Clear)
|
||||
r.Get("list", monitorController.List)
|
||||
r.Get("switchAndDays", monitorController.SwitchAndDays)
|
||||
})
|
||||
r.Prefix("setting").Middleware(middleware.Jwt()).Group(func(r route.Route) {
|
||||
settingController := controllers.NewSettingController()
|
||||
|
||||
+48
-31
@@ -58,8 +58,7 @@ Prepare_system() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
wwwUserCheck=$(cat /etc/passwd | grep www)
|
||||
if [ "${wwwUserCheck}" == "" ]; then
|
||||
if ! id -u "www" > /dev/null 2>&1; then
|
||||
groupadd www
|
||||
useradd -s /sbin/nologin -g www www
|
||||
fi
|
||||
@@ -68,29 +67,29 @@ Prepare_system() {
|
||||
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
|
||||
[ -s /etc/selinux/config ] && sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
|
||||
setenforce 0 >/dev/null 2>&1
|
||||
setenforce 0 > /dev/null 2>&1
|
||||
|
||||
ulimit -n 204800
|
||||
echo 6553560 >/proc/sys/fs/file-max
|
||||
echo 6553560 > /proc/sys/fs/file-max
|
||||
checkSoftNofile=$(cat /etc/security/limits.conf | grep '^* soft nofile .*$')
|
||||
checkHardNofile=$(cat /etc/security/limits.conf | grep '^* hard nofile .*$')
|
||||
checkSoftNproc=$(cat /etc/security/limits.conf | grep '^* soft nproc .*$')
|
||||
checkHardNproc=$(cat /etc/security/limits.conf | grep '^* hard nproc .*$')
|
||||
checkFsFileMax=$(cat /etc/sysctl.conf | grep '^fs.file-max.*$')
|
||||
if [ "${checkSoftNofile}" == "" ]; then
|
||||
echo "* soft nofile 204800" >>/etc/security/limits.conf
|
||||
echo "* soft nofile 204800" >> /etc/security/limits.conf
|
||||
fi
|
||||
if [ "${checkHardNofile}" == "" ]; then
|
||||
echo "* hard nofile 204800" >>/etc/security/limits.conf
|
||||
echo "* hard nofile 204800" >> /etc/security/limits.conf
|
||||
fi
|
||||
if [ "${checkSoftNproc}" == "" ]; then
|
||||
echo "* soft nproc 204800" >>/etc/security/limits.conf
|
||||
echo "* soft nproc 204800" >> /etc/security/limits.conf
|
||||
fi
|
||||
if [ "${checkHardNproc}" == "" ]; then
|
||||
echo "* hard nproc 204800 " >>/etc/security/limits.conf
|
||||
echo "* hard nproc 204800 " >> /etc/security/limits.conf
|
||||
fi
|
||||
if [ "${checkFsFileMax}" == "" ]; then
|
||||
echo fs.file-max = 6553560 >>/etc/sysctl.conf
|
||||
echo fs.file-max = 6553560 >> /etc/sysctl.conf
|
||||
fi
|
||||
|
||||
if [ "${OS}" == "centos" ]; then
|
||||
@@ -121,19 +120,25 @@ Prepare_system() {
|
||||
dnf config-manager --set-enabled crb
|
||||
/usr/bin/crb enable
|
||||
dnf makecache
|
||||
dnf install -y curl wget zip unzip tar git jq git-core
|
||||
dnf install -y curl wget zip unzip tar git jq git-core dos2unix
|
||||
elif [ "${OS}" == "debian" ]; then
|
||||
if [[ ${ipLocation} =~ "中国" ]]; then
|
||||
sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list
|
||||
sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list
|
||||
fi
|
||||
apt update
|
||||
apt install -y curl wget zip unzip tar git jq git
|
||||
apt install -y curl wget zip unzip tar git jq git dos2unix
|
||||
else
|
||||
echo -e $HR
|
||||
echo "错误:该系统不支持安装耗子面板,请更换Debian12/RHEL9安装。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:安装面板依赖软件失败,请截图错误信息寻求帮助。"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
Auto_Swap() {
|
||||
@@ -153,14 +158,14 @@ Auto_Swap() {
|
||||
chmod 600 $swapFile
|
||||
mkswap -f $swapFile
|
||||
swapon $swapFile
|
||||
echo "$swapFile swap swap defaults 0 0" >>/etc/fstab
|
||||
echo "$swapFile swap swap defaults 0 0" >> /etc/fstab
|
||||
}
|
||||
|
||||
Init_Panel() {
|
||||
mkdir ${setup_Path}/server
|
||||
mkdir ${setup_Path}/server/cron
|
||||
mkdir ${setup_Path}/server/cron/logs
|
||||
chmod -R 644 ${setup_Path}/server/cron
|
||||
chmod -R 644 ${setup_Path}/server/cron/logs
|
||||
chmod -R 755 ${setup_Path}/server
|
||||
mkdir ${setup_Path}/panel
|
||||
rm -rf ${setup_Path}/panel/*
|
||||
# 下载面板zip包并解压
|
||||
@@ -173,6 +178,11 @@ Init_Panel() {
|
||||
echo "错误:该系统架构不支持安装耗子面板,请更换x86_64/aarch64架构安装。"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$?" != "0" ] || [ "${panelZip}" == "" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:获取面板下载链接失败,请截图错误信息寻求帮助。"
|
||||
exit 1
|
||||
fi
|
||||
wget -O ${setup_Path}/panel/panel.zip "${download_Url}${panelZip}"
|
||||
cd ${setup_Path}/panel
|
||||
unzip -o panel.zip
|
||||
@@ -180,6 +190,7 @@ Init_Panel() {
|
||||
cp panel-example.conf panel.conf
|
||||
${setup_Path}/panel/panel --env="panel.conf" artisan key:generate
|
||||
${setup_Path}/panel/panel --env="panel.conf" artisan jwt:secret
|
||||
${setup_Path}/panel/panel --env="panel.conf" artisan migrate
|
||||
chmod -R 700 ${setup_Path}/panel
|
||||
cp scripts/panel.sh /usr/bin/panel
|
||||
chmod -R 700 /usr/bin/panel
|
||||
@@ -188,24 +199,30 @@ Init_Panel() {
|
||||
yum install firewalld -y
|
||||
systemctl enable firewalld
|
||||
systemctl start firewalld
|
||||
firewall-cmd --set-default-zone=public >/dev/null 2>&1
|
||||
firewall-cmd --permanent --zone=public --add-port=22/tcp >/dev/null 2>&1
|
||||
firewall-cmd --permanent --zone=public --add-port=80/tcp >/dev/null 2>&1
|
||||
firewall-cmd --permanent --zone=public --add-port=443/tcp >/dev/null 2>&1
|
||||
firewall-cmd --permanent --zone=public --add-port=8888/tcp >/dev/null 2>&1
|
||||
firewall-cmd --permanent --zone=public --add-port=${sshPort}/tcp >/dev/null 2>&1
|
||||
firewall-cmd --set-default-zone=public > /dev/null 2>&1
|
||||
firewall-cmd --permanent --zone=public --add-port=22/tcp > /dev/null 2>&1
|
||||
firewall-cmd --permanent --zone=public --add-port=80/tcp > /dev/null 2>&1
|
||||
firewall-cmd --permanent --zone=public --add-port=443/tcp > /dev/null 2>&1
|
||||
firewall-cmd --permanent --zone=public --add-port=8888/tcp > /dev/null 2>&1
|
||||
firewall-cmd --permanent --zone=public --add-port=${sshPort}/tcp > /dev/null 2>&1
|
||||
firewall-cmd --reload
|
||||
elif [ "${OS}" == "debian" ]; then
|
||||
apt install ufw -y
|
||||
sudo ufw enable
|
||||
sudo ufw allow 22/tcp
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 443/tcp
|
||||
sudo ufw allow 8888/tcp
|
||||
sudo ufw allow ${sshPort}/tcp
|
||||
echo y | ufw enable
|
||||
ufw allow 22/tcp
|
||||
ufw allow 80/tcp
|
||||
ufw allow 443/tcp
|
||||
ufw allow 8888/tcp
|
||||
ufw allow ${sshPort}/tcp
|
||||
ufw reload
|
||||
fi
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:防火墙放行失败,请截图错误信息寻求帮助。"
|
||||
exit 1
|
||||
fi
|
||||
# 写入服务文件
|
||||
cat >/etc/systemd/system/panel.service <<EOF
|
||||
cat > /etc/systemd/system/panel.service << EOF
|
||||
[Unit]
|
||||
Description=HaoZi Panel
|
||||
After=syslog.target network.target
|
||||
@@ -214,7 +231,7 @@ Wants=network.target
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=${setup_Path}/panel/
|
||||
ExecStart=./panel --env="panel.conf"
|
||||
ExecStart=/www/panel/panel --env="/www/panel/panel.conf"
|
||||
ExecReload=kill -s HUP \$MAINPID
|
||||
ExecStop=kill -s QUIT \$MAINPID
|
||||
User=root
|
||||
@@ -239,21 +256,21 @@ clear
|
||||
echo -e $LOGO
|
||||
|
||||
# 安装确认
|
||||
read -p "面板将安装至${setup_Path}目录,请输入 y 并回车以开始安装:" install
|
||||
read -p "面板将安装至 ${setup_Path} 目录,请输入 y 并回车以开始安装:" install
|
||||
if [ "$install" != 'y' ]; then
|
||||
echo "输入不正确,已退出安装。"
|
||||
exit
|
||||
fi
|
||||
|
||||
#代理设置
|
||||
read -p "是否使用GitHub代理安装?(y/n)" proxy
|
||||
read -p "是否使用GitHub代理安装(建议大陆机器使用)?(y/n)" proxy
|
||||
if [ "$proxy" == 'y' ]; then
|
||||
download_Url="https://ghproxy.com/"
|
||||
fi
|
||||
|
||||
clear
|
||||
echo -e $LOGO
|
||||
echo '安装面板依赖软件(如报错请检查 Dnf/Yum 源是否正常)'
|
||||
echo '安装面板依赖软件(如报错请检查 APT/Yum 源是否正常)'
|
||||
echo -e $HR
|
||||
sleep 3s
|
||||
Prepare_system
|
||||
|
||||
+20
-15
@@ -26,10 +26,11 @@ setupPath="/www"
|
||||
mysqlPath="${setupPath}/server/mysql"
|
||||
mysqlVersion=""
|
||||
mysqlPassword=$(cat /dev/urandom | head -n 16 | md5sum | head -c 16)
|
||||
cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l)
|
||||
|
||||
if [[ "${1}" == "8.0" ]]; then
|
||||
if [[ "${1}" == "80" ]]; then
|
||||
mysqlVersion="8.0.33"
|
||||
elif [[ "${1}" == "5.7" ]]; then
|
||||
elif [[ "${1}" == "57" ]]; then
|
||||
mysqlVersion="5.7.42"
|
||||
else
|
||||
echo -e $HR
|
||||
@@ -37,7 +38,7 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${memTotal}" -lt "4096" ]] && [[ "${1}" == "8.0" ]]; then
|
||||
if [[ "${memTotal}" -lt "4096" ]] && [[ "${1}" == "80" ]]; then
|
||||
echo -e $HR
|
||||
echo "错误:这点内存(${memTotal}M)还想装 MySQL 8.0?洗洗睡吧!"
|
||||
exit 1
|
||||
@@ -102,7 +103,12 @@ cd src
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_INSTALL_PREFIX=${mysqlPath} -DMYSQL_DATADIR=${mysqlPath}/data -DSYSCONFDIR=${mysqlPath}/conf -DWITH_MYISAM_STORAGE_ENGINE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_PARTITION_STORAGE_ENGINE=1 -DWITH_ARCHIVE_STORAGE_ENGINE=1 -DWITH_FEDERATED_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DWITH_EXTRA_CHARSETS=all -DEXTRA_CHARSETS=all -DDEFAULT_CHARSET=utf8mb4 -DDEFAULT_COLLATION=utf8mb4_general_ci -DENABLED_LOCAL_INFILE=1 -DWITH_SYSTEMD=1 -DSYSTEMD_PID_DIR=${mysqlPath} -DWITH_SSL=/usr/local/openssl-1.1 -DWITH_BOOST=../boost
|
||||
make -j$(nproc)
|
||||
|
||||
if [[ "${cpuCore}" -gt "1" ]]; then
|
||||
make -j2
|
||||
else
|
||||
make
|
||||
fi
|
||||
|
||||
# 安装
|
||||
make install
|
||||
@@ -175,14 +181,13 @@ interactive-timeout
|
||||
EOF
|
||||
|
||||
# 根据CPU核心数确定写入线程数
|
||||
cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l)
|
||||
sed -i 's/innodb_write_io_threads = 4/innodb_write_io_threads = '${cpuCore}'/g' ${mysqlPath}/conf/my.cnf
|
||||
sed -i 's/innodb_read_io_threads = 4/innodb_read_io_threads = '${cpuCore}'/g' ${mysqlPath}/conf/my.cnf
|
||||
|
||||
if [[ "${1}" == "8.0" ]]; then
|
||||
if [[ "${1}" == "80" ]]; then
|
||||
sed -i '/query_cache_size/d' ${mysqlPath}/conf/my.cnf
|
||||
fi
|
||||
if [[ "${1}" == "5.7" ]]; then
|
||||
if [[ "${1}" == "57" ]]; then
|
||||
sed -i '/innodb_redo_log_capacity/d' ${mysqlPath}/conf/my.cnf
|
||||
fi
|
||||
|
||||
@@ -261,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}/data
|
||||
chmod -R 755 ${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
|
||||
|
||||
@@ -279,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
|
||||
@@ -287,4 +289,7 @@ systemctl start mysqld
|
||||
|
||||
${mysqlPath}/bin/mysqladmin -u root password ${mysqlPassword}
|
||||
|
||||
echo "mysql root password: ${mysqlPassword}"
|
||||
panel writePlugin mysql${1} ${mysqlVersion}
|
||||
panel writeMysqlPassword ${mysqlPassword}
|
||||
|
||||
echo -e "${HR}\nMySQL-${1} 安装完成\n${HR}"
|
||||
|
||||
@@ -36,6 +36,6 @@ groupdel mysql
|
||||
sed -i '#export PATH=/www/server/mysql#d' /etc/profile
|
||||
source /etc/profile
|
||||
|
||||
panel deletePlugin mysql
|
||||
panel deletePlugin mysql${1}
|
||||
|
||||
echo -e "${HR}\nMySQL uninstall completed.\n${HR}"
|
||||
echo -e "${HR}\nMySQL-${1} 卸载完成\n${HR}"
|
||||
|
||||
@@ -24,6 +24,7 @@ downloadUrl="https://dl.cdn.haozi.net/panel/openresty"
|
||||
setupPath="/www"
|
||||
openrestyPath="${setupPath}/server/openresty"
|
||||
openrestyVersion="1.21.4.1"
|
||||
cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l)
|
||||
|
||||
# 安装依赖
|
||||
if [ "${OS}" == "centos" ]; then
|
||||
@@ -38,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面板不支持该系统"
|
||||
@@ -93,14 +94,15 @@ rm -f nginx-dav-ext-module-3.0.0.tar.gz
|
||||
mv nginx-dav-ext-module-3.0.0 nginx-dav-ext-module
|
||||
|
||||
# waf
|
||||
wget -T 20 -O ngx_waf.zip ${downloadUrl}/modules/ngx_waf-6.1.9.zip
|
||||
unzip -o ngx_waf.zip
|
||||
mv ngx_waf-6.1.9 ngx_waf
|
||||
rm -f ngx_waf.zip
|
||||
wget -T 60 -O uthash.zip ${downloadUrl}/modules/uthash-2.3.0.zip
|
||||
unzip -o uthash.zip
|
||||
mv uthash-2.3.0 uthash
|
||||
rm -f uthash.zip
|
||||
cd ../
|
||||
wget -T 20 -O ngx_waf.zip ${downloadUrl}/modules/ngx_waf-6.1.9.zip
|
||||
unzip -o ngx_waf.zip
|
||||
mv ngx_waf-6.1.9 ngx_waf
|
||||
rm -f ngx_waf.zip
|
||||
cd ngx_waf/inc
|
||||
wget -T 60 -O libinjection.zip ${downloadUrl}/modules/libinjection-3.10.0.zip
|
||||
unzip -o libinjection.zip
|
||||
@@ -133,18 +135,17 @@ cd ${openrestyPath}/src
|
||||
export LD_LIBRARY_PATH=/usr/local/lib/:$LD_LIBRARY_PATH
|
||||
export LIB_UTHASH=${openrestyPath}/src/uthash
|
||||
|
||||
./configure --user=www --group=www --prefix=${openrestyPath} --with-luajit --add-module=${openrestyPath}/src/ngx_cache_purge --add-module=${openrestyPath}/src/nginx-sticky-module --with-openssl=${openrestyPath}/src/openssl --with-pcre=${openrestyPath}/src/pcre --with-http_v2_module --with-http_slice_module --with-threads --with-stream --with-stream_ssl_module --with-stream_realip_module --with-stream_geoip_module --with-stream_ssl_preread_module --with-http_stub_status_module --with-http_ssl_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_geoip_module --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt="-Wl,-E" --with-cc-opt="-O2 -std=gnu99" --with-cpu-opt="amd64" --with-http_dav_module --add-module=${openrestyPath}/src/nginx-dav-ext-module --add-module=${openrestyPath}/src/ngx_brotli --add-module=${openrestyPath}/src/ngx_waf
|
||||
make -j$(nproc)
|
||||
./configure --user=www --group=www --prefix=${openrestyPath} --with-luajit --add-module=${openrestyPath}/src/ngx_cache_purge --add-module=${openrestyPath}/src/nginx-sticky-module --with-openssl=${openrestyPath}/src/openssl --with-pcre=${openrestyPath}/src/pcre --with-http_v2_module --with-http_slice_module --with-threads --with-stream --with-stream_ssl_module --with-stream_realip_module --with-stream_geoip_module --with-stream_ssl_preread_module --with-http_stub_status_module --with-http_ssl_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_geoip_module --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt="-Wl,-E" --with-cc-opt="-O2 -std=gnu99" --with-cpu-opt="amd64" --with-http_dav_module --add-module=${openrestyPath}/src/nginx-dav-ext-module --add-module=${openrestyPath}/src/ngx_brotli --add-module=${openrestyPath}/ngx_waf
|
||||
if [[ "${cpuCore}" -gt "1" ]]; then
|
||||
make -j2
|
||||
else
|
||||
make
|
||||
fi
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "提示:OpenResty多线程编译失败,尝试单线程编译..."
|
||||
make
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:OpenResty编译失败,请截图错误信息寻求帮助。"
|
||||
rm -rf ${openrestyPath}
|
||||
exit 1
|
||||
fi
|
||||
echo "错误:OpenResty编译失败,请截图错误信息寻求帮助。"
|
||||
rm -rf ${openrestyPath}
|
||||
exit 1
|
||||
fi
|
||||
make install
|
||||
if [ ! -f "${openrestyPath}/nginx/sbin/nginx" ]; then
|
||||
@@ -362,6 +363,6 @@ systemctl daemon-reload
|
||||
systemctl enable openresty.service
|
||||
systemctl start openresty.service
|
||||
|
||||
panel writePlugin openresty
|
||||
panel writePlugin openresty ${openrestyVersion}
|
||||
|
||||
echo -e "${HR}\nOpenResty install completed.\n${HR}"
|
||||
echo -e "${HR}\nOpenResty 安装完成\n${HR}"
|
||||
|
||||
@@ -28,4 +28,4 @@ rm -rf /www/server/openresty
|
||||
|
||||
panel deletePlugin openresty
|
||||
|
||||
echo -e "${HR}\nOpenResty uninstall completed.\n${HR}"
|
||||
echo -e "${HR}\nOpenResty 卸载完成\n${HR}"
|
||||
|
||||
+1
-1
@@ -16,4 +16,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
'
|
||||
/www/panel/panel --env="panel.conf" artisan panel "$@"
|
||||
/www/panel/panel --env="/www/panel/panel.conf" artisan panel "$@"
|
||||
|
||||
+15
-8
@@ -23,7 +23,9 @@ OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } ||
|
||||
downloadUrl="https://dl.cdn.haozi.net/panel/php"
|
||||
setupPath="/www"
|
||||
phpVersion="${1}"
|
||||
phpVersionCode=""
|
||||
phpPath="${setupPath}/server/php/${phpVersion}"
|
||||
cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l)
|
||||
|
||||
# 安装依赖
|
||||
if [ "${OS}" == "centos" ]; then
|
||||
@@ -55,19 +57,20 @@ cd ${phpPath}
|
||||
|
||||
# 下载源码
|
||||
if [ "${phpVersion}" == "74" ]; then
|
||||
wget -O ${phpPath}/php-${phpVersion}.tar.gz ${downloadUrl}/php-7.4.33.tar.gz
|
||||
phpVersionCode="7.4.33"
|
||||
elif [ "${phpVersion}" == "80" ]; then
|
||||
wget -O ${phpPath}/php-${phpVersion}.tar.gz ${downloadUrl}/php-8.0.29.tar.gz
|
||||
phpVersionCode="8.0.29"
|
||||
elif [ "${phpVersion}" == "81" ]; then
|
||||
wget -O ${phpPath}/php-${phpVersion}.tar.gz ${downloadUrl}/php-8.1.21.tar.gz
|
||||
phpVersionCode="8.1.21"
|
||||
elif [ "${phpVersion}" == "82" ]; then
|
||||
wget -O ${phpPath}/php-${phpVersion}.tar.gz ${downloadUrl}/php-8.2.8.tar.gz
|
||||
phpVersionCode="8.2.8"
|
||||
else
|
||||
echo -e $HR
|
||||
echo "错误:PHP-${phpVersion}不支持,请检查版本号是否正确。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
wget -O ${phpPath}/php-${phpVersion}.tar.gz ${downloadUrl}/php-${phpVersionCode}.tar.gz
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:PHP-${phpVersion}下载失败,请检查网络是否正常。"
|
||||
@@ -101,7 +104,11 @@ cd src
|
||||
./configure --prefix=${phpPath} --with-config-file-path=${phpPath}/etc --enable-fpm --with-fpm-user=www --with-fpm-group=www --enable-mysqlnd --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd --with-freetype --with-jpeg --with-zlib --with-libxml-dir=/usr --enable-xml --disable-rpath --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curl --enable-mbregex --enable-mbstring --enable-intl --enable-pcntl --enable-ftp --enable-gd --with-openssl --with-mhash --enable-pcntl --enable-sockets --with-xmlrpc --enable-soap --with-gettext --enable-fileinfo --enable-opcache --with-sodium --with-webp
|
||||
|
||||
# 编译安装
|
||||
make -j${nproc}
|
||||
if [[ "${cpuCore}" -gt "1" ]]; then
|
||||
make -j2
|
||||
else
|
||||
make
|
||||
fi
|
||||
make install
|
||||
if [ ! -f "${phpPath}/bin/php" ]; then
|
||||
echo -e $HR
|
||||
@@ -167,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
|
||||
|
||||
@@ -242,6 +249,6 @@ systemctl daemon-reload
|
||||
systemctl enable php-fpm-${phpVersion}.service
|
||||
systemctl start php-fpm-${phpVersion}.service
|
||||
|
||||
panel writePlugin php${phpVersion}
|
||||
panel writePlugin php${phpVersion} ${phpVersionCode}
|
||||
|
||||
echo -e "${HR}\nPHP-${phpVersion} 安装成功!\n${HR}"
|
||||
echo -e "${HR}\nPHP-${phpVersion} 安装完成\n${HR}"
|
||||
|
||||
@@ -30,6 +30,6 @@ systemctl daemon-reload
|
||||
rm -rf ${phpPath}
|
||||
rm -f /usr/bin/php-${phpVersion}
|
||||
|
||||
panel deletePlugin openresty
|
||||
panel deletePlugin php${phpVersion}
|
||||
|
||||
echo -e "${HR}\nPHP uninstall completed.\n${HR}"
|
||||
echo -e "${HR}\nPHP-${phpVersion} 卸载完成\n${HR}"
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user