mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 01:12:17 +08:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6c3bd27fd | |||
| 137c5ceba4 | |||
| c6f5b99373 | |||
| d7f647a8db | |||
| 77277a6af6 | |||
| d9acaec3c5 | |||
| c8692844b6 | |||
| 0b49301e9e | |||
| 30fded4552 | |||
| 9b3b76d1d0 | |||
| f37f22b86b | |||
| 52eedad3da | |||
| 0e2dda597f | |||
| 313511b474 | |||
| d2ed2b5082 | |||
| cce19e1a64 | |||
| 62b84a8909 | |||
| ff7b4f5f3b | |||
| 43a8d8cc2a | |||
| bf09ffd316 | |||
| b0d08e2901 | |||
| 62684ccfda | |||
| 6353231f4a | |||
| 6abd6b139f | |||
| 40358b2551 | |||
| 6e6342f0a6 | |||
| cea485133f | |||
| 08e14a84a5 | |||
| bc179923a0 | |||
| 67ef7ccd7a | |||
| 1abb9691f4 | |||
| 4dc2895781 | |||
| e7635d4b36 | |||
| e731c025b5 | |||
| 8234a16f9e | |||
| 0e3dba7dc8 | |||
| 7e8ef74bee | |||
| 26ef34ef9b | |||
| 4aa6dfa1ee | |||
| 51989254d6 | |||
| 5cfeb49468 | |||
| 614a5f15b5 | |||
| f5a6f17108 | |||
| 6356b82a21 | |||
| b3b1afe77e | |||
| f9628643be | |||
| c8352d567d | |||
| 2b92a0c693 | |||
| 8fec8af0d7 | |||
| 5b83e042c2 | |||
| 525cb154ca | |||
| 1cbd282e40 | |||
| 9ffa32160b | |||
| 872da5f8e1 | |||
| eed3651cb1 | |||
| 653900fd73 | |||
| ff1f2569dd | |||
| 0c6354fc6e | |||
| 2ed5c36890 | |||
| ecaf8edf14 | |||
| 2429cf023a | |||
| c7adc39887 | |||
| f4e267ef1b | |||
| 98bb24d97a | |||
| 9e603a3003 | |||
| 7164541415 | |||
| a51b391634 | |||
| edd3b2a622 | |||
| c55ef5c1f2 | |||
| 65a46796ea | |||
| b4720a1c84 |
@@ -58,7 +58,7 @@ body:
|
||||
attributes:
|
||||
label: 耗子面板版本 (HaoZi Panel Version)
|
||||
description: |
|
||||
请提供耗子面板的版本号。
|
||||
请提供面板的版本号。
|
||||
Please provide the version number of the panel.
|
||||
placeholder: "2.0.0"
|
||||
validations:
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
labels:
|
||||
- "🤖 Dependencies"
|
||||
schedule:
|
||||
interval: daily
|
||||
groups:
|
||||
go-modules:
|
||||
patterns:
|
||||
- "*"
|
||||
open-pull-requests-limit: 100
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
labels:
|
||||
- "🤖 Dependencies"
|
||||
schedule:
|
||||
interval: daily
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
open-pull-requests-limit: 100
|
||||
@@ -1,22 +0,0 @@
|
||||
name: Code Review
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, reopened, synchronize, labeled ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, '🚀 Review Ready') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: anc95/ChatGPT-CodeReview@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_API_ENDPOINT: https://api.openai-sb.com/v1
|
||||
MODEL: gpt-3.5-turbo
|
||||
PROMPT: "Below is a code patch, please help me do a brief code review on it. Only answer important bug risks and/or important improvement suggestions, Answer in Chinese and answer must be concisely and short: "
|
||||
@@ -1,11 +0,0 @@
|
||||
name: PR Check Title
|
||||
on:
|
||||
pull_request:
|
||||
jobs:
|
||||
pr-check-title:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: Slashgear/action-check-pr-title@v4.3.0
|
||||
with:
|
||||
regexp: "^(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release|revert)(\\(.+\\))?!?: .+"
|
||||
helpMessage: "Your PR title is invalid. Please follow the Conventional Commits specification: https://www.conventionalcommits.org/zh-hans/v1.0.0/"
|
||||
+3
-5
@@ -1,8 +1,6 @@
|
||||
tmp
|
||||
.env
|
||||
.history
|
||||
.air.toml
|
||||
panel.conf
|
||||
/.air.toml
|
||||
/panel.conf
|
||||
|
||||
# Golang #
|
||||
# `go test -c` 生成的二进制文件
|
||||
@@ -23,7 +21,7 @@ _cgo_export.*
|
||||
*.exe
|
||||
*.o
|
||||
*.so
|
||||
panel
|
||||
/panel
|
||||
|
||||
# 压缩包 #
|
||||
# Git 自带压缩,如果这些压缩包里有代码,建议解压后 commit
|
||||
|
||||
@@ -1,35 +1,41 @@
|
||||
# 耗子Linux面板
|
||||
|
||||
[](https://github.com/HaoZi-Team/Panel/releases)
|
||||
[](https://github.com/HaoZi-Team/Panel/actions)
|
||||
[](https://goreportcard.com/report/github.com/HaoZi-Team/Panel)
|
||||
[](https://codecov.io/gh/HaoZi-Team/Panel)
|
||||

|
||||
[](https://github.com/haozi-team/panel/releases)
|
||||
[](https://github.com/haozi-team/panel/actions)
|
||||
[](https://goreportcard.com/report/github.com/haozi-team/panel)
|
||||
[](https://codecov.io/gh/haozi-team/panel)
|
||||

|
||||
|
||||
这是耗子Linux面板的开源仓库,基于Apache License 2.0协议进行开源,目前处于积极开发状态。
|
||||
这是耗子Linux面板,一种使用GO开发的轻量Linux服务器运维管理面板。
|
||||
|
||||
#### 交流QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | QQ频道:[耗子](https://pd.qq.com/s/fyol46wfy)
|
||||
通知:严禁使用耗子Linux面板从事任何非法活动,非法站点请勿向我们请求任何形式的技术支持,如果在技术支持过程中发现非法内容,我们将立即停止技术支持并留存相关证据。
|
||||
|
||||
#### 交流QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | QQ频道:[pd.qq.com/s/fyol46wfy](https://pd.qq.com/s/fyol46wfy)
|
||||
|
||||
广告: [`WeAvatar` 多端多元化的统一头像服务](https://weavatar.com)
|
||||
|
||||
## 运行环境
|
||||
|
||||
面板仅支持主流 `amd64` | `arm64` 系统的最新版本,且随着系统版本的更新,面板也会逐步停止对旧版本的支持。
|
||||
面板仅支持主流 `amd64` | `arm64` 系统的最新版本,且随着系统版本的更新,面板也会逐步停止对旧版本系统的支持。
|
||||
|
||||
不支持 `Ubuntu`,因为其发版太频繁,难以维护。
|
||||
不支持 `Ubuntu`,因为其发版太过频繁,难以维护。
|
||||
|
||||
低配机器建议使用 `Debian`,资源占用较 `RHEL` 系更低。其他机器建议使用 `RockyLinux` | `AlmaLinux`,维护周期更长。
|
||||
|
||||
不在下表中的其他同版本的 `RHEL` 系统(OpenCloudOS、Anolis、CentOS Stream等),可自行尝试安装,但不保证能够正常运行,且不提供技术支持。
|
||||
不在下表中的其他系统(OpenCloudOS 8、Anolis 8、CentOS Stream 8/9、Debian 11等),可自行尝试安装,但不保证能够正常运行,且不提供技术支持(理论上不会有大问题)。
|
||||
|
||||
| 系统 | 版本 |
|
||||
|------------|----|
|
||||
| RHEL | 9 |
|
||||
| RockyLinux | 9 |
|
||||
| AlmaLinux | 9 |
|
||||
| Debian | 12 |
|
||||
|
||||
## 安装面板
|
||||
|
||||
安装面板前,你需要了解LNMP环境的基本知识,以及如何处理常见的LNMP环境问题,我们不建议0基础的用户安装和使用耗子Linux面板。
|
||||
|
||||
如果你决定继续,请以`root`用户登录服务器,执行以下命令安装面板:
|
||||
|
||||
```shell
|
||||
bash <(curl -sSL https://dl.cdn.haozi.net/panel/install_panel.sh)
|
||||
@@ -37,18 +43,33 @@ bash <(curl -sSL https://dl.cdn.haozi.net/panel/install_panel.sh)
|
||||
|
||||
## 日常维护
|
||||
|
||||
使用`panel`命令进行日常维护:
|
||||
|
||||
```shell
|
||||
panel
|
||||
```
|
||||
|
||||
## 问题反馈
|
||||
|
||||
使用类问题,可在 [WePublish社区论坛](https://wepublish.cn/forums) 提问寻求帮助。
|
||||
使用类问题,可在 [WePublish社区论坛](https://wepublish.cn/forums) 提问或QQ群`@汤姆猫`寻求帮助。
|
||||
|
||||
对于面板自身问题,可在 GitHub 的`Issues`
|
||||
面板自身问题,可在 GitHub 的`Issues`
|
||||
页面提交问题反馈,注意[提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)。
|
||||
|
||||
交流QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | QQ频道:[耗子](https://pd.qq.com/s/fyol46wfy)
|
||||
## 贡献代码
|
||||
|
||||
### 寻找/创建 Issue
|
||||
|
||||
您可以在 [Issue 列表](https://github.com/haozi-team/panel/issues) 中寻找或创建一个 Issue,留言表达想要处理该 Issue
|
||||
的意愿,得到维护者的确认后,即可开始处理。
|
||||
|
||||
### 创建 PR
|
||||
|
||||
- 在开发过程中,如果遇到问题可以随时在 Issue 中详尽描述该问题,以进一步沟通,但在此之前请确保自己已通过 Google
|
||||
等方式尽可能的尝试解决问题;
|
||||
- PR 须提交至我们的极狐GitLab仓库[https://jihulab.com/haozi-team/panel](https://jihulab.com/haozi-team/panel)
|
||||
- 当 PR 开发完毕后,请为其添加 `🚀 Review Ready` 标签,维护者将及时进行评审;
|
||||
- 我们非常欢迎您的贡献,将在下次发版时将您添加到首页贡献者中;❤️
|
||||
|
||||
## 赞助商
|
||||
|
||||
@@ -62,12 +83,48 @@ panel
|
||||
|
||||
- [无畏云加速](https://su.sctes.com/register?code=8st689ujpmm2p)
|
||||
|
||||
- [AnyCast.Ai](https://www.anycast.ai/)
|
||||
|
||||
- [迅捷盾](https://console.fastdun.com/)
|
||||
|
||||
### DevOps
|
||||
|
||||
- [极狐GitLab](https://www.jihulab.com/)
|
||||
|
||||
#### 接受云资源和资金赞助,可通过QQ群咨询联系
|
||||
|
||||
## Star 趋势
|
||||
## 行为准则
|
||||
|
||||
[](https://starchart.cc/HaoZi-Team/Panel)
|
||||
耗子Linux面板遵守业界通用的行为准则。任何违反行为准则的行为都可以报告给我们:
|
||||
|
||||
- 参与者将容忍反对意见。
|
||||
- 参与者必须确保他们的语言和行为没有人身攻击和贬低个人言论。
|
||||
- 在解释他人的言行时,参与者应始终保持良好的意图。
|
||||
- 不能容忍可合理视为骚扰的行为。
|
||||
|
||||
## 安全说明
|
||||
|
||||
安全性是我们最关心的问题之一,我们已在生产环境广泛应用耗子Linux面板。
|
||||
|
||||
耗子Linux面板采用业界多种方案尽可能保证面板的安全性,但是我们不能保证面板的绝对安全性,**因此我们不对面板的安全性做任何保证**。
|
||||
|
||||
如果您在使用面板的过程中发现任何安全问题,请勿提交 Issue,可通过以下方式直接联系我们:
|
||||
|
||||
- 邮箱:i@haozi.net
|
||||
- QQ:826896000
|
||||
|
||||
## 贡献者
|
||||
|
||||
这个项目的存在要归功于所有做出贡献的人,参与贡献请先查看贡献代码部分。
|
||||
|
||||
<a href="https://github.com/DevHaoZi" target="_blank"><img src="https://avatars.githubusercontent.com/u/115467771?v=4" width="48" height="48"></a>
|
||||
|
||||
## Star 历史
|
||||
|
||||
<a href="https://star-history.com/#haozi-team/panel&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=haozi-team/panel&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=haozi-team/panel&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=haozi-team/panel&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -55,7 +56,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.SettingKeyEntrance, 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: "/"}, {Key: models.SettingKeyVersion, Value: facades.Config().GetString("panel.version")}}
|
||||
err = facades.Orm().Query().Create(&settings)
|
||||
if err != nil {
|
||||
color.Redln("初始化失败")
|
||||
@@ -457,6 +458,20 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
|
||||
color.Greenln("写入设置成功")
|
||||
|
||||
case "getSetting":
|
||||
key := arg1
|
||||
if len(key) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
var setting models.Setting
|
||||
if err := facades.Orm().Query().Where("key", key).FirstOrFail(&setting); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("%s", setting.Value)
|
||||
|
||||
case "deleteSetting":
|
||||
key := arg1
|
||||
if len(key) == 0 {
|
||||
@@ -490,6 +505,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
color.Yellowln("panel writeMysqlPassword {password} 写入MySQL root密码")
|
||||
color.Yellowln("panel writeSite {name} {status} {path} {php} {ssl} 写入网站数据到面板")
|
||||
color.Yellowln("panel deleteSite {name} 删除面板网站数据")
|
||||
color.Yellowln("panel getSetting {name} 获取面板设置数据")
|
||||
color.Yellowln("panel writeSetting {name} {value} 写入 / 更新面板设置数据")
|
||||
color.Yellowln("panel deleteSetting {name} 删除面板设置数据")
|
||||
}
|
||||
|
||||
@@ -75,12 +75,15 @@ func (c *CronController) Add(ctx http.Context) {
|
||||
if backupType == "website" {
|
||||
backupName = ctx.Request().Input("website")
|
||||
}
|
||||
backupPath := ctx.Request().Input("backup_path", c.setting.Get(models.SettingKeyBackupPath)+"/"+backupType)
|
||||
backupPath := ctx.Request().Input("backup_path")
|
||||
if len(backupName) == 0 {
|
||||
backupPath = c.setting.Get(models.SettingKeyBackupPath) + "/" + backupType
|
||||
}
|
||||
backupSave := ctx.Request().InputInt("save", 10)
|
||||
shell = `#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
# 耗子面板 - 数据备份脚本
|
||||
# 耗子Linux面板 - 数据备份脚本
|
||||
|
||||
type=` + backupType + `
|
||||
path=` + backupPath + `
|
||||
@@ -97,7 +100,7 @@ panel backup ${type} ${name} ${path} ${save} 2>&1
|
||||
shell = `#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
# 耗子面板 - 日志切割脚本
|
||||
# 耗子Linux面板 - 日志切割脚本
|
||||
|
||||
name=` + website + `
|
||||
save=` + cast.ToString(save) + `
|
||||
|
||||
@@ -2,6 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
@@ -51,6 +52,7 @@ func (c *InfoController) Menu(ctx http.Context) {
|
||||
{Name: "safe", Title: "系统安全", Icon: "layui-icon-auz", Jump: "safe"},
|
||||
/*{Name: "file", Title: "文件管理", Icon: "layui-icon-file", Jump: "file"},*/
|
||||
{Name: "cron", Title: "计划任务", Icon: "layui-icon-date", Jump: "cron"},
|
||||
{Name: "ssh", Title: "SSH", Icon: "layui-icon-layer", Jump: "ssh"},
|
||||
{Name: "plugin", Title: "插件中心", Icon: "layui-icon-app", Jump: "plugin"},
|
||||
{Name: "setting", Title: "面板设置", Icon: "layui-icon-set", Jump: "setting"},
|
||||
})
|
||||
@@ -124,6 +126,11 @@ func (c *InfoController) InstalledDbAndPhp(ctx http.Context) {
|
||||
var phpData []data
|
||||
phpData = append(phpData, data{Slug: "0", Name: "不使用"})
|
||||
for _, p := range php {
|
||||
match := regexp.MustCompile(`php(\d+)`).FindStringSubmatch(p.Slug)
|
||||
if len(match) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
phpData = append(phpData, data{Slug: strings.ReplaceAll(p.Slug, "php", ""), Name: c.plugin.GetBySlug(p.Slug).Name})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,459 @@
|
||||
package fail2ban
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/models"
|
||||
"panel/app/services"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
type Fail2banController struct {
|
||||
website services.Website
|
||||
}
|
||||
|
||||
func NewFail2banController() *Fail2banController {
|
||||
return &Fail2banController{
|
||||
website: services.NewWebsiteImpl(),
|
||||
}
|
||||
}
|
||||
|
||||
type Jail struct {
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
LogPath string `json:"log_path"`
|
||||
MaxRetry int `json:"max_retry"`
|
||||
FindTime int `json:"find_time"`
|
||||
BanTime int `json:"ban_time"`
|
||||
}
|
||||
|
||||
// Status 获取运行状态
|
||||
func (c *Fail2banController) Status(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
status := tools.ExecShell("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Reload 重载配置
|
||||
func (c *Fail2banController) Reload(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl reload fail2ban")
|
||||
status := tools.ExecShell("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Restart 重启服务
|
||||
func (c *Fail2banController) Restart(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl restart fail2ban")
|
||||
status := tools.ExecShell("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动服务
|
||||
func (c *Fail2banController) Start(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl start fail2ban")
|
||||
status := tools.ExecShell("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop 停止服务
|
||||
func (c *Fail2banController) Stop(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl stop fail2ban")
|
||||
status := tools.ExecShell("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status != "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// List 所有 Fail2ban 规则
|
||||
func (c *Fail2banController) List(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
page := ctx.Request().QueryInt("page", 1)
|
||||
limit := ctx.Request().QueryInt("limit", 10)
|
||||
raw := tools.ReadFile("/etc/fail2ban/jail.local")
|
||||
if len(raw) == 0 {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "Fail2ban 规则为空")
|
||||
return
|
||||
}
|
||||
|
||||
jailList := regexp.MustCompile(`\[(.*?)]`).FindAllStringSubmatch(raw, -1)
|
||||
if len(jailList) == 0 {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "Fail2ban 规则为空")
|
||||
return
|
||||
}
|
||||
|
||||
var jails []Jail
|
||||
for i, jail := range jailList {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
jailName := jail[1]
|
||||
jailRaw := tools.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
|
||||
if len(jailRaw) == 0 {
|
||||
continue
|
||||
}
|
||||
jailEnabled := strings.Contains(jailRaw, "enabled = true")
|
||||
jailLogPath := regexp.MustCompile(`logpath = (.*)`).FindStringSubmatch(jailRaw)
|
||||
jailMaxRetry := regexp.MustCompile(`maxretry = (.*)`).FindStringSubmatch(jailRaw)
|
||||
jailFindTime := regexp.MustCompile(`findtime = (.*)`).FindStringSubmatch(jailRaw)
|
||||
jailBanTime := regexp.MustCompile(`bantime = (.*)`).FindStringSubmatch(jailRaw)
|
||||
|
||||
jails = append(jails, Jail{
|
||||
Name: jailName,
|
||||
Enabled: jailEnabled,
|
||||
LogPath: jailLogPath[1],
|
||||
MaxRetry: cast.ToInt(jailMaxRetry[1]),
|
||||
FindTime: cast.ToInt(jailFindTime[1]),
|
||||
BanTime: cast.ToInt(jailBanTime[1]),
|
||||
})
|
||||
}
|
||||
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(jails) {
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []Jail{},
|
||||
})
|
||||
return
|
||||
}
|
||||
if endIndex > len(jails) {
|
||||
endIndex = len(jails)
|
||||
}
|
||||
pagedJails := jails[startIndex:endIndex]
|
||||
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": len(jails),
|
||||
"items": pagedJails,
|
||||
})
|
||||
}
|
||||
|
||||
// Add 添加 Fail2ban 规则
|
||||
func (c *Fail2banController) Add(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"name": "required",
|
||||
"type": "required|in:website,service",
|
||||
"maxretry": "required",
|
||||
"findtime": "required",
|
||||
"bantime": "required",
|
||||
"website_mode": "required_if:type,website",
|
||||
"website_path": "required_if:type,website",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
jailName := ctx.Request().Input("name")
|
||||
jailType := ctx.Request().Input("type")
|
||||
jailMaxRetry := ctx.Request().Input("maxretry")
|
||||
jailFindTime := ctx.Request().Input("findtime")
|
||||
jailBanTime := ctx.Request().Input("bantime")
|
||||
jailWebsiteMode := ctx.Request().Input("website_mode")
|
||||
jailWebsitePath := ctx.Request().Input("website_path")
|
||||
|
||||
raw := tools.ReadFile("/etc/fail2ban/jail.local")
|
||||
if strings.Contains(raw, "["+jailName+"]") || (strings.Contains(raw, "["+jailName+"]"+"-cc") && jailWebsiteMode == "cc") || (strings.Contains(raw, "["+jailName+"]"+"-path") && jailWebsiteMode == "path") {
|
||||
controllers.Error(ctx, http.StatusUnprocessableEntity, "规则已存在")
|
||||
return
|
||||
}
|
||||
|
||||
switch jailType {
|
||||
case "website":
|
||||
var website models.Website
|
||||
err := facades.Orm().Query().Where("name", jailName).FirstOrFail(&website)
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusUnprocessableEntity, "网站不存在")
|
||||
return
|
||||
}
|
||||
config, err := c.website.GetConfig(int(website.ID))
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusUnprocessableEntity, "获取网站配置失败")
|
||||
return
|
||||
}
|
||||
var ports string
|
||||
for _, port := range config.Ports {
|
||||
if len(strings.Split(port, " ")) > 1 {
|
||||
ports += strings.Split(port, " ")[0] + ","
|
||||
} else {
|
||||
ports += port + ","
|
||||
}
|
||||
}
|
||||
|
||||
rule := `
|
||||
# ` + jailName + `-` + jailWebsiteMode + `-START
|
||||
[` + jailName + `-` + jailWebsiteMode + `]
|
||||
enabled = true
|
||||
filter = haozi-` + jailName + `-` + jailWebsiteMode + `
|
||||
port = ` + ports + `
|
||||
maxretry = ` + jailMaxRetry + `
|
||||
findtime = ` + jailFindTime + `
|
||||
bantime = ` + jailBanTime + `
|
||||
action = %(action_mwl)s
|
||||
logpath = /www/wwwlogs/` + website.Name + `.log
|
||||
# ` + jailName + `-` + jailWebsiteMode + `-END
|
||||
`
|
||||
raw += rule
|
||||
tools.WriteFile("/etc/fail2ban/jail.local", raw, 0644)
|
||||
|
||||
var filter string
|
||||
if jailWebsiteMode == "cc" {
|
||||
filter = `
|
||||
[Definition]
|
||||
failregex = ^<HOST>\s-.*HTTP/.*$
|
||||
ignoreregex =
|
||||
`
|
||||
} else {
|
||||
filter = `
|
||||
[Definition]
|
||||
failregex = ^<HOST>\s-.*\s` + jailWebsitePath + `.*HTTP/.*$
|
||||
ignoreregex =
|
||||
`
|
||||
}
|
||||
tools.WriteFile("/etc/fail2ban/filter.d/haozi-"+jailName+"-"+jailWebsiteMode+".conf", filter, 0644)
|
||||
|
||||
case "service":
|
||||
var logPath string
|
||||
var filter string
|
||||
var port string
|
||||
switch jailName {
|
||||
case "ssh":
|
||||
if tools.IsDebian() {
|
||||
logPath = "/var/log/auth.log"
|
||||
} else {
|
||||
logPath = "/var/log/secure"
|
||||
}
|
||||
filter = "sshd"
|
||||
port = tools.ExecShell("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
|
||||
case "mysql":
|
||||
logPath = "/www/server/mysql/mysql-error.log"
|
||||
filter = "mysqld-auth"
|
||||
port = tools.ExecShell("cat /www/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'")
|
||||
case "pure-ftpd":
|
||||
logPath = "/var/log/messages"
|
||||
filter = "pure-ftpd"
|
||||
port = tools.ExecShell(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
|
||||
default:
|
||||
controllers.Error(ctx, http.StatusUnprocessableEntity, "未知服务")
|
||||
return
|
||||
}
|
||||
if len(port) == 0 {
|
||||
controllers.Error(ctx, http.StatusUnprocessableEntity, "获取服务端口失败,请检查是否安装")
|
||||
return
|
||||
}
|
||||
|
||||
rule := `
|
||||
# ` + jailName + `-START
|
||||
[` + jailName + `]
|
||||
enabled = true
|
||||
filter = ` + filter + `
|
||||
port = ` + port + `
|
||||
maxretry = ` + jailMaxRetry + `
|
||||
findtime = ` + jailFindTime + `
|
||||
bantime = ` + jailBanTime + `
|
||||
action = %(action_mwl)s
|
||||
logpath = ` + logPath + `
|
||||
# ` + jailName + `-END
|
||||
`
|
||||
raw += rule
|
||||
tools.WriteFile("/etc/fail2ban/jail.local", raw, 0644)
|
||||
}
|
||||
|
||||
tools.ExecShell("fail2ban-client reload")
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Delete 删除规则
|
||||
func (c *Fail2banController) Delete(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
jailName := ctx.Request().Input("name")
|
||||
raw := tools.ReadFile("/etc/fail2ban/jail.local")
|
||||
if !strings.Contains(raw, "["+jailName+"]") {
|
||||
controllers.Error(ctx, http.StatusUnprocessableEntity, "规则不存在")
|
||||
return
|
||||
}
|
||||
|
||||
rule := tools.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
|
||||
raw = strings.Replace(raw, "\n# "+jailName+"-START"+rule+"# "+jailName+"-END", "", -1)
|
||||
raw = strings.TrimSpace(raw)
|
||||
tools.WriteFile("/etc/fail2ban/jail.local", raw, 0644)
|
||||
|
||||
tools.ExecShell("fail2ban-client reload")
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// BanList 获取封禁列表
|
||||
func (c *Fail2banController) BanList(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
name := ctx.Request().Query("name")
|
||||
if len(name) == 0 {
|
||||
controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
|
||||
return
|
||||
}
|
||||
|
||||
currentlyBan := tools.ExecShell(`fail2ban-client status ` + name + ` | grep "Currently banned" | awk '{print $4}'`)
|
||||
totalBan := tools.ExecShell(`fail2ban-client status ` + name + ` | grep "Total banned" | awk '{print $4}'`)
|
||||
bannedIp := tools.ExecShell(`fail2ban-client status ` + name + ` | grep "Banned IP list" | awk -F ":" '{print $2}'`)
|
||||
bannedIpList := strings.Split(bannedIp, " ")
|
||||
|
||||
var list []map[string]string
|
||||
for _, ip := range bannedIpList {
|
||||
if len(ip) > 0 {
|
||||
list = append(list, map[string]string{
|
||||
"name": name,
|
||||
"ip": ip,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
controllers.Success(ctx, http.Json{
|
||||
"currentlyBan": currentlyBan,
|
||||
"totalBan": totalBan,
|
||||
"bannedIpList": list,
|
||||
})
|
||||
}
|
||||
|
||||
// Unban 解封
|
||||
func (c *Fail2banController) Unban(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
name := ctx.Request().Input("name")
|
||||
ip := ctx.Request().Input("ip")
|
||||
if len(name) == 0 || len(ip) == 0 {
|
||||
controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("fail2ban-client set " + name + " unbanip " + ip)
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// SetWhiteList 设置白名单
|
||||
func (c *Fail2banController) SetWhiteList(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
ip := ctx.Request().Input("ip")
|
||||
if len(ip) == 0 {
|
||||
controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
|
||||
return
|
||||
}
|
||||
|
||||
raw := tools.ReadFile("/etc/fail2ban/jail.local")
|
||||
// 正则替换
|
||||
reg := regexp.MustCompile(`ignoreip\s*=\s*.*\n`)
|
||||
if reg.MatchString(raw) {
|
||||
raw = reg.ReplaceAllString(raw, "ignoreip = "+ip+"\n")
|
||||
} else {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏")
|
||||
return
|
||||
}
|
||||
|
||||
tools.WriteFile("/etc/fail2ban/jail.local", raw, 0644)
|
||||
tools.ExecShell("fail2ban-client reload")
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// GetWhiteList 获取白名单
|
||||
func (c *Fail2banController) GetWhiteList(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "fail2ban") {
|
||||
return
|
||||
}
|
||||
|
||||
raw := tools.ReadFile("/etc/fail2ban/jail.local")
|
||||
reg := regexp.MustCompile(`ignoreip\s*=\s*(.*)\n`)
|
||||
if reg.MatchString(raw) {
|
||||
ignoreIp := reg.FindStringSubmatch(raw)[1]
|
||||
controllers.Success(ctx, ignoreIp)
|
||||
} else {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏")
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func (c *Mysql57Controller) SaveConfig(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, "保存MySQL配置成功")
|
||||
c.Restart(ctx)
|
||||
}
|
||||
|
||||
// Load 获取负载
|
||||
@@ -181,7 +181,7 @@ func (c *Mysql57Controller) Load(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
raw := tools.ExecShell("mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
|
||||
raw := tools.ExecShell("/www/server/mysql/bin/mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
|
||||
if strings.Contains(raw, "Access denied for user") {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码错误")
|
||||
return
|
||||
@@ -320,12 +320,12 @@ func (c *Mysql57Controller) SetRootPassword(ctx http.Context) {
|
||||
|
||||
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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
|
||||
return
|
||||
}
|
||||
@@ -378,7 +378,26 @@ func (c *Mysql57Controller) DatabaseList(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, databases)
|
||||
page := ctx.Request().QueryInt("page", 1)
|
||||
limit := ctx.Request().QueryInt("limit", 10)
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(databases) {
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []database{},
|
||||
})
|
||||
return
|
||||
}
|
||||
if endIndex > len(databases) {
|
||||
endIndex = len(databases)
|
||||
}
|
||||
pagedDatabases := databases[startIndex:endIndex]
|
||||
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": len(databases),
|
||||
"items": pagedDatabases,
|
||||
})
|
||||
}
|
||||
|
||||
// AddDatabase 添加数据库
|
||||
@@ -406,10 +425,10 @@ func (c *Mysql57Controller) AddDatabase(ctx http.Context) {
|
||||
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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + database + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "添加数据库成功")
|
||||
}
|
||||
@@ -434,7 +453,7 @@ func (c *Mysql57Controller) DeleteDatabase(ctx http.Context) {
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
database := ctx.Request().Input("database")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\"")
|
||||
|
||||
controllers.Success(ctx, "删除数据库成功")
|
||||
}
|
||||
@@ -626,7 +645,26 @@ func (c *Mysql57Controller) UserList(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, userGrants)
|
||||
page := ctx.Request().QueryInt("page", 1)
|
||||
limit := ctx.Request().QueryInt("limit", 10)
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(userGrants) {
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []user{},
|
||||
})
|
||||
return
|
||||
}
|
||||
if endIndex > len(userGrants) {
|
||||
endIndex = len(userGrants)
|
||||
}
|
||||
pagedUserGrants := userGrants[startIndex:endIndex]
|
||||
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": len(userGrants),
|
||||
"items": pagedUserGrants,
|
||||
})
|
||||
}
|
||||
|
||||
// AddUser 添加用户
|
||||
@@ -653,9 +691,9 @@ func (c *Mysql57Controller) AddUser(ctx http.Context) {
|
||||
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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + ";'\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "添加成功")
|
||||
}
|
||||
@@ -680,7 +718,7 @@ func (c *Mysql57Controller) DeleteUser(ctx http.Context) {
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\"")
|
||||
|
||||
controllers.Success(ctx, "删除成功")
|
||||
}
|
||||
@@ -707,8 +745,8 @@ func (c *Mysql57Controller) SetUserPassword(ctx http.Context) {
|
||||
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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "修改成功")
|
||||
}
|
||||
@@ -735,9 +773,9 @@ func (c *Mysql57Controller) SetUserPrivileges(ctx http.Context) {
|
||||
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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "修改成功")
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ func (c *Mysql80Controller) SaveConfig(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, "保存MySQL配置成功")
|
||||
c.Restart(ctx)
|
||||
}
|
||||
|
||||
// Load 获取负载
|
||||
@@ -181,7 +181,7 @@ func (c *Mysql80Controller) Load(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
raw := tools.ExecShell("mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
|
||||
raw := tools.ExecShell("/www/server/mysql/bin/mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
|
||||
if strings.Contains(raw, "Access denied for user") {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码错误")
|
||||
return
|
||||
@@ -320,12 +320,12 @@ func (c *Mysql80Controller) SetRootPassword(ctx http.Context) {
|
||||
|
||||
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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
|
||||
return
|
||||
}
|
||||
@@ -378,7 +378,26 @@ func (c *Mysql80Controller) DatabaseList(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, databases)
|
||||
page := ctx.Request().QueryInt("page", 1)
|
||||
limit := ctx.Request().QueryInt("limit", 10)
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(databases) {
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []database{},
|
||||
})
|
||||
return
|
||||
}
|
||||
if endIndex > len(databases) {
|
||||
endIndex = len(databases)
|
||||
}
|
||||
pagedDatabases := databases[startIndex:endIndex]
|
||||
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": len(databases),
|
||||
"items": pagedDatabases,
|
||||
})
|
||||
}
|
||||
|
||||
// AddDatabase 添加数据库
|
||||
@@ -406,10 +425,10 @@ func (c *Mysql80Controller) AddDatabase(ctx http.Context) {
|
||||
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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + database + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "添加数据库成功")
|
||||
}
|
||||
@@ -434,7 +453,7 @@ func (c *Mysql80Controller) DeleteDatabase(ctx http.Context) {
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
database := ctx.Request().Input("database")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\"")
|
||||
|
||||
controllers.Success(ctx, "删除数据库成功")
|
||||
}
|
||||
@@ -626,7 +645,26 @@ func (c *Mysql80Controller) UserList(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, userGrants)
|
||||
page := ctx.Request().QueryInt("page", 1)
|
||||
limit := ctx.Request().QueryInt("limit", 10)
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(userGrants) {
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []user{},
|
||||
})
|
||||
return
|
||||
}
|
||||
if endIndex > len(userGrants) {
|
||||
endIndex = len(userGrants)
|
||||
}
|
||||
pagedUserGrants := userGrants[startIndex:endIndex]
|
||||
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": len(userGrants),
|
||||
"items": pagedUserGrants,
|
||||
})
|
||||
}
|
||||
|
||||
// AddUser 添加用户
|
||||
@@ -653,9 +691,9 @@ func (c *Mysql80Controller) AddUser(ctx http.Context) {
|
||||
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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + ";'\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "添加成功")
|
||||
}
|
||||
@@ -680,7 +718,7 @@ func (c *Mysql80Controller) DeleteUser(ctx http.Context) {
|
||||
|
||||
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\"")
|
||||
|
||||
controllers.Success(ctx, "删除成功")
|
||||
}
|
||||
@@ -707,8 +745,8 @@ func (c *Mysql80Controller) SetUserPassword(ctx http.Context) {
|
||||
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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "修改成功")
|
||||
}
|
||||
@@ -735,9 +773,9 @@ func (c *Mysql80Controller) SetUserPrivileges(ctx http.Context) {
|
||||
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;\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
|
||||
controllers.Success(ctx, "修改成功")
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ func NewOpenrestyController() *OpenRestyController {
|
||||
}
|
||||
|
||||
// Status 获取运行状态
|
||||
func (r *OpenRestyController) Status(ctx http.Context) {
|
||||
func (c *OpenRestyController) Status(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func (r *OpenRestyController) Status(ctx http.Context) {
|
||||
}
|
||||
|
||||
// Reload 重载配置
|
||||
func (r *OpenRestyController) Reload(ctx http.Context) {
|
||||
func (c *OpenRestyController) Reload(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func (r *OpenRestyController) Reload(ctx http.Context) {
|
||||
}
|
||||
|
||||
// Start 启动OpenResty
|
||||
func (r *OpenRestyController) Start(ctx http.Context) {
|
||||
func (c *OpenRestyController) Start(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
@@ -83,7 +83,7 @@ func (r *OpenRestyController) Start(ctx http.Context) {
|
||||
}
|
||||
|
||||
// Stop 停止OpenResty
|
||||
func (r *OpenRestyController) Stop(ctx http.Context) {
|
||||
func (c *OpenRestyController) Stop(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func (r *OpenRestyController) Stop(ctx http.Context) {
|
||||
}
|
||||
|
||||
// Restart 重启OpenResty
|
||||
func (r *OpenRestyController) Restart(ctx http.Context) {
|
||||
func (c *OpenRestyController) Restart(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (r *OpenRestyController) Restart(ctx http.Context) {
|
||||
}
|
||||
|
||||
// GetConfig 获取配置
|
||||
func (r *OpenRestyController) GetConfig(ctx http.Context) {
|
||||
func (c *OpenRestyController) GetConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
@@ -138,7 +138,7 @@ func (r *OpenRestyController) GetConfig(ctx http.Context) {
|
||||
}
|
||||
|
||||
// SaveConfig 保存配置
|
||||
func (r *OpenRestyController) SaveConfig(ctx http.Context) {
|
||||
func (c *OpenRestyController) SaveConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
@@ -154,11 +154,11 @@ func (r *OpenRestyController) SaveConfig(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, "保存OpenResty配置成功")
|
||||
c.Reload(ctx)
|
||||
}
|
||||
|
||||
// ErrorLog 获取错误日志
|
||||
func (r *OpenRestyController) ErrorLog(ctx http.Context) {
|
||||
func (c *OpenRestyController) ErrorLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
@@ -173,7 +173,7 @@ func (r *OpenRestyController) ErrorLog(ctx http.Context) {
|
||||
}
|
||||
|
||||
// ClearErrorLog 清空错误日志
|
||||
func (r *OpenRestyController) ClearErrorLog(ctx http.Context) {
|
||||
func (c *OpenRestyController) ClearErrorLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
@@ -183,7 +183,7 @@ func (r *OpenRestyController) ClearErrorLog(ctx http.Context) {
|
||||
}
|
||||
|
||||
// Load 获取负载
|
||||
func (r *OpenRestyController) Load(ctx http.Context) {
|
||||
func (c *OpenRestyController) Load(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "openresty") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ func (c *Php74Controller) Status(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
status := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败")
|
||||
return
|
||||
|
||||
@@ -35,8 +35,7 @@ func (c *Php80Controller) Status(ctx http.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'")
|
||||
status := strings.TrimSpace(out)
|
||||
status := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败")
|
||||
return
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
package php81
|
||||
|
||||
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 Php81Controller struct {
|
||||
setting services.Setting
|
||||
task services.Task
|
||||
version string
|
||||
}
|
||||
|
||||
func NewPhp81Controller() *Php81Controller {
|
||||
return &Php81Controller{
|
||||
setting: services.NewSettingImpl(),
|
||||
task: services.NewTaskImpl(),
|
||||
version: "81",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Php81Controller) Status(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
status := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'")
|
||||
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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) GetExtensionList(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
extensions := c.GetExtensions()
|
||||
controllers.Success(ctx, extensions)
|
||||
}
|
||||
|
||||
func (c *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
package php82
|
||||
|
||||
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 Php82Controller struct {
|
||||
setting services.Setting
|
||||
task services.Task
|
||||
version string
|
||||
}
|
||||
|
||||
func NewPhp82Controller() *Php82Controller {
|
||||
return &Php82Controller{
|
||||
setting: services.NewSettingImpl(),
|
||||
task: services.NewTaskImpl(),
|
||||
version: "82",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Php82Controller) Status(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
status := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'")
|
||||
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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) GetExtensionList(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "php"+c.version) {
|
||||
return
|
||||
}
|
||||
|
||||
extensions := c.GetExtensions()
|
||||
controllers.Success(ctx, extensions)
|
||||
}
|
||||
|
||||
func (c *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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,
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
package pureftpd
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
type PureFtpdController struct {
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Username string `json:"username"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func NewPureFtpdController() *PureFtpdController {
|
||||
return &PureFtpdController{}
|
||||
}
|
||||
|
||||
// Status 获取运行状态
|
||||
func (c *PureFtpdController) Status(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "pureftpd") {
|
||||
return
|
||||
}
|
||||
|
||||
status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Reload 重载配置
|
||||
func (c *PureFtpdController) Reload(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "pureftpd") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl reload pure-ftpd")
|
||||
status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Restart 重启服务
|
||||
func (c *PureFtpdController) Restart(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "pureftpd") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl restart pure-ftpd")
|
||||
status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动服务
|
||||
func (c *PureFtpdController) Start(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "pureftpd") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl start pure-ftpd")
|
||||
status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop 停止服务
|
||||
func (c *PureFtpdController) Stop(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "pureftpd") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl stop pure-ftpd")
|
||||
status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status != "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// List 获取用户列表
|
||||
func (c *PureFtpdController) List(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "pureftpd") {
|
||||
return
|
||||
}
|
||||
|
||||
listRaw := tools.ExecShell("pure-pw list")
|
||||
if len(listRaw) == 0 {
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []User{},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
listArr := strings.Split(listRaw, "\n")
|
||||
var users []User
|
||||
for _, v := range listArr {
|
||||
if len(v) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
match := regexp.MustCompile(`(\S+)\s+(\S+)`).FindStringSubmatch(v)
|
||||
users = append(users, User{
|
||||
Username: match[1],
|
||||
Path: strings.Replace(match[2], "/./", "/", 1),
|
||||
})
|
||||
}
|
||||
|
||||
page := ctx.Request().QueryInt("page", 1)
|
||||
limit := ctx.Request().QueryInt("limit", 10)
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(users) {
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []User{},
|
||||
})
|
||||
return
|
||||
}
|
||||
if endIndex > len(users) {
|
||||
endIndex = len(users)
|
||||
}
|
||||
pagedUsers := users[startIndex:endIndex]
|
||||
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": len(users),
|
||||
"items": pagedUsers,
|
||||
})
|
||||
}
|
||||
|
||||
// Add 添加用户
|
||||
func (c *PureFtpdController) Add(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "pureftpd") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"username": "required",
|
||||
"password": "required|min_len:6",
|
||||
"path": "required",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
username := ctx.Request().Input("username")
|
||||
password := ctx.Request().Input("password")
|
||||
path := ctx.Request().Input("path")
|
||||
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
if !tools.Exists(path) {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "目录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
tools.Chmod(path, 0755)
|
||||
tools.Chown(path, "www", "www")
|
||||
tools.ExecShell(`yes '` + password + `' | pure-pw useradd ` + username + ` -u www -g www -d ` + path)
|
||||
tools.ExecShell("pure-pw mkdb")
|
||||
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Delete 删除用户
|
||||
func (c *PureFtpdController) Delete(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "pureftpd") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"username": "required",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
username := ctx.Request().Input("username")
|
||||
|
||||
tools.ExecShell("pure-pw userdel " + username + " -m")
|
||||
tools.ExecShell("pure-pw mkdb")
|
||||
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
func (c *PureFtpdController) ChangePassword(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "pureftpd") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"username": "required",
|
||||
"password": "required|min_len:6",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
username := ctx.Request().Input("username")
|
||||
password := ctx.Request().Input("password")
|
||||
|
||||
tools.ExecShell(`yes '` + password + `' | pure-pw passwd ` + username + ` -m`)
|
||||
tools.ExecShell("pure-pw mkdb")
|
||||
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// GetPort 获取端口
|
||||
func (c *PureFtpdController) GetPort(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "pureftpd") {
|
||||
return
|
||||
}
|
||||
|
||||
port := tools.ExecShell(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
|
||||
if len(port) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd端口失败")
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, port)
|
||||
}
|
||||
|
||||
// SetPort 设置端口
|
||||
func (c *PureFtpdController) SetPort(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "pureftpd") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"port": "required",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
port := ctx.Request().Input("port")
|
||||
tools.ExecShell(`sed -i "s/Bind.*/Bind 0.0.0.0,` + port + `/g" /www/server/pure-ftpd/etc/pure-ftpd.conf`)
|
||||
if tools.IsRHEL() {
|
||||
tools.ExecShell("firewall-cmd --zone=public --add-port=" + port + "/tcp --permanent")
|
||||
tools.ExecShell("firewall-cmd --reload")
|
||||
} else {
|
||||
tools.ExecShell("ufw allow " + port + "/tcp")
|
||||
tools.ExecShell("ufw reload")
|
||||
}
|
||||
tools.ExecShell("systemctl restart pure-ftpd")
|
||||
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
type RedisController struct {
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func NewRedisController() *RedisController {
|
||||
return &RedisController{}
|
||||
}
|
||||
|
||||
// Status 获取运行状态
|
||||
func (c *RedisController) Status(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "redis") {
|
||||
return
|
||||
}
|
||||
|
||||
status := tools.ExecShell("systemctl status redis | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Reload 重载配置
|
||||
func (c *RedisController) Reload(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "redis") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl reload redis")
|
||||
status := tools.ExecShell("systemctl status redis | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Restart 重启服务
|
||||
func (c *RedisController) Restart(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "redis") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl restart redis")
|
||||
status := tools.ExecShell("systemctl status redis | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动服务
|
||||
func (c *RedisController) Start(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "redis") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl start redis")
|
||||
status := tools.ExecShell("systemctl status redis | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop 停止服务
|
||||
func (c *RedisController) Stop(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "redis") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell("systemctl stop redis")
|
||||
status := tools.ExecShell("systemctl status redis | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if len(status) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
if status != "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfig 获取配置
|
||||
func (c *RedisController) GetConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "redis") {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
config := tools.ReadFile("/www/server/redis/redis.conf")
|
||||
if len(config) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取Redis配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// SaveConfig 保存配置
|
||||
func (c *RedisController) SaveConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "redis") {
|
||||
return
|
||||
}
|
||||
|
||||
config := ctx.Request().Input("config")
|
||||
if len(config) == 0 {
|
||||
controllers.Error(ctx, http.StatusBadRequest, "配置不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if !tools.WriteFile("/www/server/redis/redis.conf", config, 0644) {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "写入Redis配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
c.Restart(ctx)
|
||||
}
|
||||
|
||||
// Load 获取负载
|
||||
func (c *RedisController) Load(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "redis") {
|
||||
return
|
||||
}
|
||||
|
||||
status := tools.ExecShell("systemctl status redis | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if status != "active" {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "Redis 已停止运行")
|
||||
return
|
||||
}
|
||||
|
||||
raw := tools.ExecShell("redis-cli info")
|
||||
if len(raw) == 0 {
|
||||
controllers.Error(ctx, http.StatusInternalServerError, "获取Redis负载失败")
|
||||
return
|
||||
}
|
||||
|
||||
infoLines := strings.Split(raw, "\n")
|
||||
dataRaw := make(map[string]string)
|
||||
|
||||
for _, item := range infoLines {
|
||||
parts := strings.Split(item, ":")
|
||||
if len(parts) == 2 {
|
||||
dataRaw[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
|
||||
data := []Info{
|
||||
{"TCP 端口", dataRaw["tcp_port"]},
|
||||
{"已运行天数", dataRaw["uptime_in_days"]},
|
||||
{"连接的客户端数", dataRaw["connected_clients"]},
|
||||
{"已分配的内存总量", dataRaw["used_memory_human"]},
|
||||
{"占用内存总量", dataRaw["used_memory_rss_human"]},
|
||||
{"占用内存峰值", dataRaw["used_memory_peak_human"]},
|
||||
{"内存碎片比率", dataRaw["mem_fragmentation_ratio"]},
|
||||
{"运行以来连接过的客户端的总数", dataRaw["total_connections_received"]},
|
||||
{"运行以来执行过的命令的总数", dataRaw["total_commands_processed"]},
|
||||
{"每秒执行的命令数", dataRaw["instantaneous_ops_per_sec"]},
|
||||
{"查找数据库键成功次数", dataRaw["keyspace_hits"]},
|
||||
{"查找数据库键失败次数", dataRaw["keyspace_misses"]},
|
||||
{"最近一次 fork() 操作耗费的毫秒数", dataRaw["latest_fork_usec"]},
|
||||
}
|
||||
|
||||
controllers.Success(ctx, data)
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
package supervisor
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"panel/pkg/tools"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
)
|
||||
|
||||
type SupervisorController struct {
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
func NewSupervisorController() *SupervisorController {
|
||||
var serviceName string
|
||||
if tools.IsRHEL() {
|
||||
serviceName = "supervisord"
|
||||
} else {
|
||||
serviceName = "supervisor"
|
||||
}
|
||||
|
||||
return &SupervisorController{
|
||||
ServiceName: serviceName,
|
||||
}
|
||||
}
|
||||
|
||||
// Status 状态
|
||||
func (c *SupervisorController) Status(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
status := tools.ExecShell(`systemctl status ` + c.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`)
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动
|
||||
func (c *SupervisorController) Start(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell(`systemctl start ` + c.ServiceName)
|
||||
status := tools.ExecShell(`systemctl status ` + c.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`)
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop 停止
|
||||
func (c *SupervisorController) Stop(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell(`systemctl stop ` + c.ServiceName)
|
||||
status := tools.ExecShell(`systemctl status ` + c.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`)
|
||||
if status != "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Restart 重启
|
||||
func (c *SupervisorController) Restart(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell(`systemctl restart ` + c.ServiceName)
|
||||
status := tools.ExecShell(`systemctl status ` + c.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`)
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Reload 重载
|
||||
func (c *SupervisorController) Reload(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell(`systemctl reload ` + c.ServiceName)
|
||||
status := tools.ExecShell(`systemctl status ` + c.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`)
|
||||
if status == "active" {
|
||||
controllers.Success(ctx, true)
|
||||
} else {
|
||||
controllers.Success(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Log 日志
|
||||
func (c *SupervisorController) Log(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
log := tools.ExecShell(`tail -n 200 /var/log/supervisor/supervisord.log`)
|
||||
controllers.Success(ctx, log)
|
||||
}
|
||||
|
||||
// ClearLog 清空日志
|
||||
func (c *SupervisorController) ClearLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
tools.ExecShell(`echo "" > /var/log/supervisor/supervisord.log`)
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Config 获取配置
|
||||
func (c *SupervisorController) Config(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
var config string
|
||||
if tools.IsRHEL() {
|
||||
config = tools.ReadFile(`/etc/supervisord.conf`)
|
||||
} else {
|
||||
config = tools.ReadFile(`/etc/supervisor/supervisord.conf`)
|
||||
}
|
||||
controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// SaveConfig 保存配置
|
||||
func (c *SupervisorController) SaveConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
config := ctx.Request().Input("config")
|
||||
if tools.IsRHEL() {
|
||||
tools.WriteFile(`/etc/supervisord.conf`, config, 0644)
|
||||
} else {
|
||||
tools.WriteFile(`/etc/supervisor/supervisord.conf`, config, 0644)
|
||||
}
|
||||
|
||||
c.Restart(ctx)
|
||||
}
|
||||
|
||||
// Processes 进程列表
|
||||
func (c *SupervisorController) Processes(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
page := ctx.Request().QueryInt("page", 1)
|
||||
limit := ctx.Request().QueryInt("limit", 10)
|
||||
|
||||
type process struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Pid string `json:"pid"`
|
||||
Uptime string `json:"uptime"`
|
||||
}
|
||||
|
||||
out := tools.ExecShell(`supervisorctl status | awk '{print $1}'`)
|
||||
var processList []process
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var p process
|
||||
p.Name = line
|
||||
p.Status = tools.ExecShell(`supervisorctl status ` + line + ` | awk '{print $2}'`)
|
||||
if p.Status == "RUNNING" {
|
||||
p.Pid = strings.ReplaceAll(tools.ExecShell(`supervisorctl status `+line+` | awk '{print $4}'`), ",", "")
|
||||
p.Uptime = tools.ExecShell(`supervisorctl status ` + line + ` | awk '{print $6}'`)
|
||||
} else {
|
||||
p.Pid = "-"
|
||||
p.Uptime = "-"
|
||||
}
|
||||
processList = append(processList, p)
|
||||
}
|
||||
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(processList) {
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []process{},
|
||||
})
|
||||
return
|
||||
}
|
||||
if endIndex > len(processList) {
|
||||
endIndex = len(processList)
|
||||
}
|
||||
pagedProcessList := processList[startIndex:endIndex]
|
||||
|
||||
controllers.Success(ctx, http.Json{
|
||||
"total": len(processList),
|
||||
"items": pagedProcessList,
|
||||
})
|
||||
}
|
||||
|
||||
// StartProcess 启动进程
|
||||
func (c *SupervisorController) StartProcess(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
process := ctx.Request().Input("process")
|
||||
tools.ExecShell(`supervisorctl start ` + process)
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// StopProcess 停止进程
|
||||
func (c *SupervisorController) StopProcess(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
process := ctx.Request().Input("process")
|
||||
tools.ExecShell(`supervisorctl stop ` + process)
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// RestartProcess 重启进程
|
||||
func (c *SupervisorController) RestartProcess(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
process := ctx.Request().Input("process")
|
||||
tools.ExecShell(`supervisorctl restart ` + process)
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// ProcessLog 进程日志
|
||||
func (c *SupervisorController) ProcessLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
process := ctx.Request().Input("process")
|
||||
var logPath string
|
||||
if tools.IsRHEL() {
|
||||
logPath = tools.ExecShell(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
|
||||
} else {
|
||||
logPath = tools.ExecShell(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
|
||||
}
|
||||
|
||||
log := tools.ExecShell(`tail -n 200 ` + logPath)
|
||||
controllers.Success(ctx, log)
|
||||
}
|
||||
|
||||
// ClearProcessLog 清空进程日志
|
||||
func (c *SupervisorController) ClearProcessLog(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
process := ctx.Request().Input("process")
|
||||
var logPath string
|
||||
if tools.IsRHEL() {
|
||||
logPath = tools.ExecShell(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
|
||||
} else {
|
||||
logPath = tools.ExecShell(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
|
||||
}
|
||||
|
||||
tools.ExecShell(`echo "" > ` + logPath)
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// ProcessConfig 获取进程配置
|
||||
func (c *SupervisorController) ProcessConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
process := ctx.Request().Query("process")
|
||||
var config string
|
||||
if tools.IsRHEL() {
|
||||
config = tools.ReadFile(`/etc/supervisord.d/` + process + `.conf`)
|
||||
} else {
|
||||
config = tools.ReadFile(`/etc/supervisor/conf.d/` + process + `.conf`)
|
||||
}
|
||||
|
||||
controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// SaveProcessConfig 保存进程配置
|
||||
func (c *SupervisorController) SaveProcessConfig(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
process := ctx.Request().Input("process")
|
||||
config := ctx.Request().Input("config")
|
||||
if tools.IsRHEL() {
|
||||
tools.WriteFile(`/etc/supervisord.d/`+process+`.conf`, config, 0644)
|
||||
} else {
|
||||
tools.WriteFile(`/etc/supervisor/conf.d/`+process+`.conf`, config, 0644)
|
||||
}
|
||||
tools.ExecShell(`supervisorctl reread`)
|
||||
tools.ExecShell(`supervisorctl update`)
|
||||
tools.ExecShell(`supervisorctl start ` + process)
|
||||
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// AddProcess 添加进程
|
||||
func (c *SupervisorController) AddProcess(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"name": "required|alpha_dash",
|
||||
"user": "required|alpha_dash",
|
||||
"path": "required",
|
||||
"command": "required",
|
||||
"num": "required",
|
||||
})
|
||||
if err != nil {
|
||||
controllers.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if validator.Fails() {
|
||||
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
|
||||
return
|
||||
}
|
||||
|
||||
name := ctx.Request().Input("name")
|
||||
user := ctx.Request().Input("user")
|
||||
path := ctx.Request().Input("path")
|
||||
command := ctx.Request().Input("command")
|
||||
num := ctx.Request().InputInt("num", 1)
|
||||
config := `[program:` + name + `]
|
||||
command=` + command + `
|
||||
process_name=%(program_name)s
|
||||
directory=` + path + `
|
||||
autostart=true
|
||||
autorestart=true
|
||||
user=` + user + `
|
||||
numprocs=` + strconv.Itoa(num) + `
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/supervisor/` + name + `.log
|
||||
stdout_logfile_maxbytes=2MB
|
||||
`
|
||||
if tools.IsRHEL() {
|
||||
tools.WriteFile(`/etc/supervisord.d/`+name+`.conf`, config, 0644)
|
||||
} else {
|
||||
tools.WriteFile(`/etc/supervisor/conf.d/`+name+`.conf`, config, 0644)
|
||||
}
|
||||
tools.ExecShell(`supervisorctl reread`)
|
||||
tools.ExecShell(`supervisorctl update`)
|
||||
tools.ExecShell(`supervisorctl start ` + name)
|
||||
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// DeleteProcess 删除进程
|
||||
func (c *SupervisorController) DeleteProcess(ctx http.Context) {
|
||||
if !controllers.Check(ctx, "supervisor") {
|
||||
return
|
||||
}
|
||||
|
||||
process := ctx.Request().Input("process")
|
||||
tools.ExecShell(`supervisorctl stop ` + process)
|
||||
var logPath string
|
||||
if tools.IsRHEL() {
|
||||
logPath = tools.ExecShell(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
|
||||
tools.RemoveFile(`/etc/supervisord.d/` + process + `.conf`)
|
||||
} else {
|
||||
logPath = tools.ExecShell(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
|
||||
tools.RemoveFile(`/etc/supervisor/conf.d/` + process + `.conf`)
|
||||
}
|
||||
tools.RemoveFile(logPath)
|
||||
tools.ExecShell(`supervisorctl reread`)
|
||||
tools.ExecShell(`supervisorctl update`)
|
||||
|
||||
controllers.Success(ctx, nil)
|
||||
}
|
||||
@@ -48,12 +48,17 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) {
|
||||
Success(ctx, nil)
|
||||
return
|
||||
}
|
||||
page := ctx.Request().QueryInt("page", 1)
|
||||
limit := ctx.Request().QueryInt("limit", 10)
|
||||
|
||||
if tools.IsRHEL() {
|
||||
out := tools.ExecShell("firewall-cmd --list-all 2>&1")
|
||||
match := regexp.MustCompile(`ports: (.*)`).FindStringSubmatch(out)
|
||||
if len(match) == 0 {
|
||||
Success(ctx, nil)
|
||||
Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []map[string]string{},
|
||||
})
|
||||
return
|
||||
}
|
||||
ports := strings.Split(match[1], " ")
|
||||
@@ -66,11 +71,31 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
Success(ctx, rules)
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(rules) {
|
||||
Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []map[string]string{},
|
||||
})
|
||||
return
|
||||
}
|
||||
if endIndex > len(rules) {
|
||||
endIndex = len(rules)
|
||||
}
|
||||
pagedRules := rules[startIndex:endIndex]
|
||||
|
||||
Success(ctx, http.Json{
|
||||
"total": len(rules),
|
||||
"items": pagedRules,
|
||||
})
|
||||
} else {
|
||||
out := tools.ExecShell("ufw status | grep -v '(v6)' | grep ALLOW | awk '{print $1}'")
|
||||
if len(out) == 0 {
|
||||
Success(ctx, nil)
|
||||
Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []map[string]string{},
|
||||
})
|
||||
return
|
||||
}
|
||||
var rules []map[string]string
|
||||
@@ -82,7 +107,24 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
Success(ctx, rules)
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(rules) {
|
||||
Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []map[string]string{},
|
||||
})
|
||||
return
|
||||
}
|
||||
if endIndex > len(rules) {
|
||||
endIndex = len(rules)
|
||||
}
|
||||
pagedRules := rules[startIndex:endIndex]
|
||||
|
||||
Success(ctx, http.Json{
|
||||
"total": len(rules),
|
||||
"items": pagedRules,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,8 +263,8 @@ func (r *SafeController) SetSshPort(ctx http.Context) {
|
||||
|
||||
func (r *SafeController) GetPingStatus(ctx http.Context) {
|
||||
if tools.IsRHEL() {
|
||||
out := tools.ExecShell("firewall-cmd --query-rich-rule='rule protocol value=icmp drop' 2>&1")
|
||||
if out == "no" {
|
||||
out := tools.ExecShell(`firewall-cmd --list-all 2>&1`)
|
||||
if !strings.Contains(out, `rule protocol value="icmp" drop`) {
|
||||
Success(ctx, true)
|
||||
} else {
|
||||
Success(ctx, false)
|
||||
@@ -240,18 +282,18 @@ func (r *SafeController) GetPingStatus(ctx http.Context) {
|
||||
func (r *SafeController) SetPingStatus(ctx http.Context) {
|
||||
if tools.IsRHEL() {
|
||||
if ctx.Request().InputBool("status") {
|
||||
tools.ExecShell("firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'")
|
||||
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 --permanent --add-rich-rule='rule protocol value=icmp drop'`)
|
||||
}
|
||||
tools.ExecShell("firewall-cmd --reload")
|
||||
tools.ExecShell(`firewall-cmd --reload`)
|
||||
} 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")
|
||||
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(`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")
|
||||
tools.ExecShell(`ufw reload`)
|
||||
}
|
||||
|
||||
Success(ctx, nil)
|
||||
|
||||
@@ -39,7 +39,7 @@ func (r *UserController) Login(ctx http.Context) {
|
||||
}
|
||||
|
||||
if user.ID == 0 || !facades.Hash().Check(loginRequest.Password, user.Password) {
|
||||
Error(ctx, http.StatusUnauthorized, "用户名或密码错误")
|
||||
Error(ctx, http.StatusForbidden, "用户名或密码错误")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
raw := tools.ReadFile("/www/server/vhost/" + website.Name + ".conf")
|
||||
if strings.TrimSpace(raw) != strings.TrimSpace(ctx.Request().Input("raw")) {
|
||||
tools.WriteFile("/www/server/vhost/"+website.Name+".conf", ctx.Request().Input("raw"), 0644)
|
||||
tools.ExecShell("systemctl reload openresty")
|
||||
Success(ctx, nil)
|
||||
return
|
||||
}
|
||||
@@ -285,7 +286,7 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
Error(ctx, http.StatusBadRequest, "配置文件中root标记位格式错误")
|
||||
return
|
||||
}
|
||||
rootNew := strings.Replace(root, match[1], path, -1)
|
||||
rootNew := strings.Replace(root, match[1], ctx.Request().Input("root"), -1)
|
||||
raw = strings.Replace(raw, root, rootNew, -1)
|
||||
|
||||
// 默认文件
|
||||
@@ -303,14 +304,15 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
raw = strings.Replace(raw, index, indexNew, -1)
|
||||
|
||||
// 防跨站
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
root = ctx.Request().Input("root")
|
||||
if !strings.HasSuffix(root, "/") {
|
||||
root += "/"
|
||||
}
|
||||
if ctx.Request().InputBool("open_basedir") {
|
||||
tools.WriteFile(path+".user.ini", "open_basedir="+path+":/tmp/", 0644)
|
||||
tools.WriteFile(root+".user.ini", "open_basedir="+path+":/tmp/", 0644)
|
||||
} else {
|
||||
if tools.Exists(path + ".user.ini") {
|
||||
tools.RemoveFile(path + ".user.ini")
|
||||
if tools.Exists(root + ".user.ini") {
|
||||
tools.RemoveFile(root + ".user.ini")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,9 +337,9 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
// SSL
|
||||
ssl := ctx.Request().InputBool("ssl")
|
||||
website.Ssl = 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)
|
||||
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标记位开始
|
||||
ssl_certificate /www/server/vhost/ssl/` + website.Name + `.pem;
|
||||
ssl_certificate_key /www/server/vhost/ssl/` + website.Name + `.key;
|
||||
@@ -350,10 +352,10 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
|
||||
`
|
||||
if ctx.Request().InputBool("http_redirect") {
|
||||
sslConfig += `# http重定向标记位开始
|
||||
if (\$server_port !~ 443){
|
||||
return 301 https://\$host\$request_uri;
|
||||
if ($server_port !~ 443){
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
error_page 497 https://\$host\$request_uri;
|
||||
error_page 497 https://$host$request_uri;
|
||||
# http重定向标记位结束
|
||||
`
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func Jwt() http.Middleware {
|
||||
token, err = facades.Auth().Refresh(ctx)
|
||||
if err != nil {
|
||||
// Refresh time exceeded
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
|
||||
"code": 401,
|
||||
"message": "登录已过期",
|
||||
})
|
||||
@@ -37,7 +37,7 @@ func Jwt() http.Middleware {
|
||||
|
||||
token = "Bearer " + token
|
||||
} else {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
|
||||
"code": 401,
|
||||
"message": "登录已过期",
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import "github.com/goravel/framework/support/carbon"
|
||||
|
||||
const (
|
||||
SettingKeyName = "name"
|
||||
SettingKeyVersion = "version"
|
||||
SettingKeyMonitor = "monitor"
|
||||
SettingKeyMonitorDays = "monitor_days"
|
||||
SettingKeyBackupPath = "backup_path"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package fail2ban
|
||||
|
||||
var (
|
||||
Name = "Fail2ban"
|
||||
Description = "Fail2ban 扫描系统日志文件并从中找出多次尝试失败的IP地址,将该IP地址加入防火墙的拒绝访问列表中。"
|
||||
Slug = "fail2ban"
|
||||
Version = "1.0.0"
|
||||
Requires = []string{}
|
||||
Excludes = []string{}
|
||||
Install = `bash /www/panel/scripts/fail2ban/install.sh`
|
||||
Uninstall = `bash /www/panel/scripts/fail2ban/uninstall.sh`
|
||||
Update = `bash /www/panel/scripts/fail2ban/update.sh`
|
||||
)
|
||||
@@ -4,7 +4,7 @@ var (
|
||||
Name = "OpenResty"
|
||||
Description = "OpenResty® 是一款基于 NGINX 和 LuaJIT 的 Web 平台。"
|
||||
Slug = "openresty"
|
||||
Version = "1.21.4.1"
|
||||
Version = "1.21.4.2"
|
||||
Requires = []string{}
|
||||
Excludes = []string{}
|
||||
Install = "bash /www/panel/scripts/openresty/install.sh"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package php81
|
||||
|
||||
var (
|
||||
Name = "PHP-8.1"
|
||||
Description = "PHP 是世界上最好的语言!"
|
||||
Slug = "php81"
|
||||
Version = "8.1.21"
|
||||
Requires = []string{}
|
||||
Excludes = []string{}
|
||||
Install = `bash /www/panel/scripts/php/install.sh 81`
|
||||
Uninstall = `bash /www/panel/scripts/php/uninstall.sh 81`
|
||||
Update = `bash /www/panel/scripts/php/install.sh 81`
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package php82
|
||||
|
||||
var (
|
||||
Name = "PHP-8.2"
|
||||
Description = "PHP 是世界上最好的语言!"
|
||||
Slug = "php82"
|
||||
Version = "8.2.8"
|
||||
Requires = []string{}
|
||||
Excludes = []string{}
|
||||
Install = `bash /www/panel/scripts/php/install.sh 82`
|
||||
Uninstall = `bash /www/panel/scripts/php/uninstall.sh 82`
|
||||
Update = `bash /www/panel/scripts/php/install.sh 82`
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package pureftpd
|
||||
|
||||
var (
|
||||
Name = "Pure-FTPd"
|
||||
Description = "Pure-Ftpd 是一个快速、高效、轻便、安全的 FTP 服务器,它以安全和配置简单为设计目标,支持虚拟主机,IPV6,PAM 等功能。"
|
||||
Slug = "pureftpd"
|
||||
Version = "1.0.50"
|
||||
Requires = []string{}
|
||||
Excludes = []string{}
|
||||
Install = `bash /www/panel/scripts/pureftpd/install.sh`
|
||||
Uninstall = `bash /www/panel/scripts/pureftpd/uninstall.sh`
|
||||
Update = `bash /www/panel/scripts/pureftpd/update.sh`
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package redis
|
||||
|
||||
var (
|
||||
Name = "Redis"
|
||||
Description = "Redis 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。"
|
||||
Slug = "redis"
|
||||
Version = "7.0.12"
|
||||
Requires = []string{}
|
||||
Excludes = []string{}
|
||||
Install = `bash /www/panel/scripts/redis/install.sh`
|
||||
Uninstall = `bash /www/panel/scripts/redis/uninstall.sh`
|
||||
Update = `bash /www/panel/scripts/redis/update.sh`
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package supervisor
|
||||
|
||||
var (
|
||||
Name = "Supervisor"
|
||||
Description = "Supervisor 是一个客户端/服务器系统,允许用户监视和控制类 UNIX 操作系统上的多个进程。"
|
||||
Slug = "supervisor"
|
||||
Version = "4.2.5"
|
||||
Requires = []string{}
|
||||
Excludes = []string{}
|
||||
Install = `bash /www/panel/scripts/supervisor/install.sh`
|
||||
Uninstall = `bash /www/panel/scripts/supervisor/uninstall.sh`
|
||||
Update = `bash /www/panel/scripts/supervisor/install.sh`
|
||||
)
|
||||
@@ -155,7 +155,7 @@ func (s *BackupImpl) MysqlBackup(database string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
tools.ExecShell("mysqldump -uroot " + database + " > " + backupPath + "/" + backupFile)
|
||||
tools.ExecShell("/www/server/mysql/bin/mysqldump -uroot " + database + " > " + backupPath + "/" + backupFile)
|
||||
tools.ExecShell("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile)
|
||||
tools.RemoveFile(backupPath + "/" + backupFile)
|
||||
_ = os.Unsetenv("MYSQL_PWD")
|
||||
@@ -207,7 +207,7 @@ func (s *BackupImpl) MysqlRestore(database string, backupFile string) error {
|
||||
return errors.New("自动解压失败,请手动解压")
|
||||
}
|
||||
|
||||
tools.ExecShell("mysql -uroot " + database + " < " + backupFile)
|
||||
tools.ExecShell("/www/server/mysql/bin/mysql -uroot " + database + " < " + backupFile)
|
||||
_ = os.Unsetenv("MYSQL_PWD")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -5,13 +5,19 @@ import (
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/app/plugins/fail2ban"
|
||||
"panel/app/plugins/mysql57"
|
||||
"panel/app/plugins/mysql80"
|
||||
"panel/app/plugins/openresty"
|
||||
"panel/app/plugins/php74"
|
||||
"panel/app/plugins/php80"
|
||||
"panel/app/plugins/php81"
|
||||
"panel/app/plugins/php82"
|
||||
"panel/app/plugins/phpmyadmin"
|
||||
"panel/app/plugins/pureftpd"
|
||||
"panel/app/plugins/redis"
|
||||
"panel/app/plugins/s3fs"
|
||||
"panel/app/plugins/supervisor"
|
||||
)
|
||||
|
||||
// PanelPlugin 插件元数据结构
|
||||
@@ -110,6 +116,28 @@ func (r *PluginImpl) All() []PanelPlugin {
|
||||
Uninstall: php80.Uninstall,
|
||||
Update: php80.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: php81.Name,
|
||||
Description: php81.Description,
|
||||
Slug: php81.Slug,
|
||||
Version: php81.Version,
|
||||
Requires: php81.Requires,
|
||||
Excludes: php81.Excludes,
|
||||
Install: php81.Install,
|
||||
Uninstall: php81.Uninstall,
|
||||
Update: php81.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: php82.Name,
|
||||
Description: php82.Description,
|
||||
Slug: php82.Slug,
|
||||
Version: php82.Version,
|
||||
Requires: php82.Requires,
|
||||
Excludes: php82.Excludes,
|
||||
Install: php82.Install,
|
||||
Uninstall: php82.Uninstall,
|
||||
Update: php82.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: phpmyadmin.Name,
|
||||
Description: phpmyadmin.Description,
|
||||
@@ -121,6 +149,28 @@ func (r *PluginImpl) All() []PanelPlugin {
|
||||
Uninstall: phpmyadmin.Uninstall,
|
||||
Update: phpmyadmin.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: pureftpd.Name,
|
||||
Description: pureftpd.Description,
|
||||
Slug: pureftpd.Slug,
|
||||
Version: pureftpd.Version,
|
||||
Requires: pureftpd.Requires,
|
||||
Excludes: pureftpd.Excludes,
|
||||
Install: pureftpd.Install,
|
||||
Uninstall: pureftpd.Uninstall,
|
||||
Update: pureftpd.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: redis.Name,
|
||||
Description: redis.Description,
|
||||
Slug: redis.Slug,
|
||||
Version: redis.Version,
|
||||
Requires: redis.Requires,
|
||||
Excludes: redis.Excludes,
|
||||
Install: redis.Install,
|
||||
Uninstall: redis.Uninstall,
|
||||
Update: redis.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: s3fs.Name,
|
||||
Description: s3fs.Description,
|
||||
@@ -132,6 +182,28 @@ func (r *PluginImpl) All() []PanelPlugin {
|
||||
Uninstall: s3fs.Uninstall,
|
||||
Update: s3fs.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: supervisor.Name,
|
||||
Description: supervisor.Description,
|
||||
Slug: supervisor.Slug,
|
||||
Version: supervisor.Version,
|
||||
Requires: supervisor.Requires,
|
||||
Excludes: supervisor.Excludes,
|
||||
Install: supervisor.Install,
|
||||
Uninstall: supervisor.Uninstall,
|
||||
Update: supervisor.Update,
|
||||
})
|
||||
p = append(p, PanelPlugin{
|
||||
Name: fail2ban.Name,
|
||||
Description: fail2ban.Description,
|
||||
Slug: fail2ban.Slug,
|
||||
Version: fail2ban.Version,
|
||||
Requires: fail2ban.Requires,
|
||||
Excludes: fail2ban.Excludes,
|
||||
Install: fail2ban.Install,
|
||||
Uninstall: fail2ban.Uninstall,
|
||||
Update: fail2ban.Update,
|
||||
})
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
+6
-14
@@ -231,10 +231,10 @@ server
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if website.Db && website.DbType == "mysql" {
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + website.DbName + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + website.DbUser + "'@'localhost' IDENTIFIED BY '" + website.DbPassword + "';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + website.DbName + ".* TO '" + website.DbUser + "'@'localhost';\"")
|
||||
tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
|
||||
tools.ExecShell(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "CREATE DATABASE IF NOT EXISTS ` + website.DbName + ` DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;"`)
|
||||
tools.ExecShell(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "CREATE USER '` + website.DbUser + `'@'localhost' IDENTIFIED BY '` + website.DbPassword + `';"`)
|
||||
tools.ExecShell(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "GRANT ALL PRIVILEGES ON ` + website.DbName + `.* TO '` + website.DbUser + `'@'localhost';"`)
|
||||
tools.ExecShell(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "FLUSH PRIVILEGES;"`)
|
||||
}
|
||||
|
||||
return w, nil
|
||||
@@ -313,21 +313,13 @@ func (r *WebsiteImpl) GetConfig(id int) (WebsiteSetting, error) {
|
||||
setting.OpenBasedir = false
|
||||
}
|
||||
|
||||
setting.SslCertificate = tools.ReadFile("/www/server/vhost/ssl/" + website.Name + ".pem")
|
||||
setting.SslCertificateKey = tools.ReadFile("/www/server/vhost/ssl/" + website.Name + ".key")
|
||||
if setting.Ssl {
|
||||
ssl := tools.Cut(config, "# ssl标记位开始", "# ssl标记位结束")
|
||||
match = regexp.MustCompile(`ssl_certificate\s+(.*);`).FindStringSubmatch(ssl)
|
||||
if len(match) > 1 {
|
||||
setting.SslCertificate = tools.ReadFile(match[1])
|
||||
}
|
||||
match = regexp.MustCompile(`ssl_certificate_key\s+(.*);`).FindStringSubmatch(ssl)
|
||||
if len(match) > 1 {
|
||||
setting.SslCertificateKey = tools.ReadFile(match[1])
|
||||
}
|
||||
setting.HttpRedirect = strings.Contains(ssl, "# http重定向标记位")
|
||||
setting.Hsts = strings.Contains(ssl, "# hsts标记位")
|
||||
} else {
|
||||
setting.SslCertificate = ""
|
||||
setting.SslCertificateKey = ""
|
||||
setting.HttpRedirect = false
|
||||
setting.Hsts = false
|
||||
}
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ func init() {
|
||||
// Here you may specify the default timezone for your application.
|
||||
// Example: UTC, Asia/Shanghai
|
||||
// More: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
"timezone": carbon.UTC,
|
||||
"timezone": carbon.PRC,
|
||||
|
||||
// Encryption Key
|
||||
//
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ import (
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("panel", map[string]any{
|
||||
"name": "耗子面板",
|
||||
"version": "v2.0.15",
|
||||
"name": "耗子Linux面板",
|
||||
"version": "v2.0.30",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,16 +7,16 @@ require (
|
||||
github.com/gertd/go-pluralize v0.2.1
|
||||
github.com/gin-contrib/static v0.0.1
|
||||
github.com/gookit/color v1.5.4
|
||||
github.com/goravel/framework v1.12.1-0.20230721095426-6f24ecdaf6a7
|
||||
github.com/goravel/framework v1.12.1-0.20230726100652-342097efc4ce
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/imroc/req/v3 v3.37.2
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/imroc/req/v3 v3.40.1
|
||||
github.com/mojocn/base64Captcha v1.3.5
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/cast v1.5.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -51,7 +51,7 @@ require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gaukas/godicttls v0.0.3 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
@@ -68,6 +68,7 @@ require (
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.16.2 // indirect
|
||||
github.com/golang-module/carbon/v2 v2.2.3 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
@@ -79,16 +80,16 @@ require (
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gomodule/redigo v2.0.0+incompatible // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
|
||||
github.com/google/s2a-go v0.1.3 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/wire v0.5.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
|
||||
github.com/gookit/filter v1.1.4 // indirect
|
||||
github.com/gookit/goutil v0.5.15 // indirect
|
||||
github.com/gookit/validate v1.4.6 // indirect
|
||||
github.com/gookit/filter v1.2.0 // indirect
|
||||
github.com/gookit/goutil v0.6.12 // indirect
|
||||
github.com/gookit/validate v1.5.0 // indirect
|
||||
github.com/goravel/file-rotatelogs v0.0.0-20211215053220-2ab31dd9575c // indirect
|
||||
github.com/goravel/file-rotatelogs/v2 v2.4.1 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
@@ -105,7 +106,7 @@ require (
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kelseyhightower/envconfig v1.4.0 // indirect
|
||||
github.com/klauspost/compress v1.16.6 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.5 // indirect
|
||||
@@ -128,10 +129,9 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/quic-go/quic-go v0.36.0 // indirect
|
||||
github.com/refraction-networking/utls v1.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.0 // indirect
|
||||
github.com/quic-go/quic-go v0.37.0 // indirect
|
||||
github.com/refraction-networking/utls v1.3.3 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
@@ -163,14 +163,14 @@ require (
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.11.0 // indirect
|
||||
golang.org/x/image v0.11.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/oauth2 v0.7.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/tools v0.11.0 // indirect
|
||||
google.golang.org/api v0.122.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230525234025-438c736192d0 // indirect
|
||||
|
||||
@@ -156,8 +156,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
|
||||
github.com/gaukas/godicttls v0.0.3/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA=
|
||||
github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@@ -244,6 +244,8 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA=
|
||||
github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o=
|
||||
github.com/golang-module/carbon/v2 v2.2.3 h1:WvGIc5+qzq9drNzH+Gnjh1TZ0JgDY/IA+m2Dvk7Qm4Q=
|
||||
@@ -322,8 +324,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA=
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.3 h1:FAgZmpLl/SXurPEZyCMPBIiiYeTbqfjlbdnCNTAkbGE=
|
||||
github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
@@ -343,22 +345,20 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
|
||||
github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc=
|
||||
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.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=
|
||||
github.com/gookit/goutil v0.5.15 h1:FaRyj0uVqi7j92QHsG+2Sc1VZ7/7ma77UD3/wBpwyTc=
|
||||
github.com/gookit/goutil v0.5.15/go.mod h1:ozPE16eJS9f89aVbVk05ocEJsia3KPrYUqPTs8GvUTw=
|
||||
github.com/gookit/validate v1.4.6 h1:Ix8NRy2+6z4YGHWXgZL9+emy9wRI2GWyhW2smPcIlSU=
|
||||
github.com/gookit/validate v1.4.6/go.mod h1:1rjeYaYlMK/8od4oge5C+Gt/3DnHkXymLPda7+3urC8=
|
||||
github.com/gookit/filter v1.2.0 h1:r7E01dHVkysb5WgzooiGsfblHGShEZCeGcyYM+5IpYU=
|
||||
github.com/gookit/filter v1.2.0/go.mod h1:bXs9RcB4Blxwny970opiwABeIEqQ/gzOMmHBhKwBdms=
|
||||
github.com/gookit/goutil v0.6.12 h1:73vPUcTtVGXbhSzBOFcnSB1aJl7Jq9np3RAE50yIDZc=
|
||||
github.com/gookit/goutil v0.6.12/go.mod h1:g6krlFib8xSe3G1h02IETowOtrUGpAmetT8IevDpvpM=
|
||||
github.com/gookit/validate v1.5.0 h1:aTy974I1vPDYPFzoFH4vtJONrK4oRDvjUxteUan7beE=
|
||||
github.com/gookit/validate v1.5.0/go.mod h1:SskOHUQokzMNt6T3r7N+N/4me/6fxDx+tmoXf/3ZQog=
|
||||
github.com/goravel/file-rotatelogs v0.0.0-20211215053220-2ab31dd9575c h1:obhFK91JAhcf7s6h5sggZishm1VyGW/gBCreo+7/SwQ=
|
||||
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.20230721095426-6f24ecdaf6a7 h1:2rkC/6M7tLx/Ya1TulO2GjtVRIGXTmpp0XppzZRloYA=
|
||||
github.com/goravel/framework v1.12.1-0.20230721095426-6f24ecdaf6a7/go.mod h1:1lxwtCXMkotSmt0YWRg/s7EnMUFfmne9L1a50X9J0fA=
|
||||
github.com/goravel/framework v1.12.1-0.20230726100652-342097efc4ce h1:1qgkBySstIMAkzRjbrjPjF6J0UIKBfXY1osSfL7azNY=
|
||||
github.com/goravel/framework v1.12.1-0.20230726100652-342097efc4ce/go.mod h1:qLv6cmf4RqXkTZ782H38cY/7vhkFLamPV9pAU0s7nKQ=
|
||||
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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
@@ -379,14 +379,14 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/imroc/req/v3 v3.37.2 h1:vEemuA0cq9zJ6lhe+mSRhsZm951bT0CdiSH47+KTn6I=
|
||||
github.com/imroc/req/v3 v3.37.2/go.mod h1:DECzjVIrj6jcUr5n6e+z0ygmCO93rx4Jy0RjOEe1YCI=
|
||||
github.com/imroc/req/v3 v3.40.1 h1:/7YAmkrI4HsOTO6Md5FUUWPId90bTD074BV8sA0bt18=
|
||||
github.com/imroc/req/v3 v3.40.1/go.mod h1:4wMbz0QYY5jmXNWk0BsWrRTR9ItZqOxzSJdGL0M9kzY=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
@@ -428,8 +428,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
|
||||
github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
@@ -458,7 +458,6 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v
|
||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/microsoft/go-mssqldb v1.1.0 h1:jsV+tpvcPTbNNKW0o3kiCD69kOHICsfjZ2VcVu2lKYc=
|
||||
@@ -526,14 +525,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/quic-go/quic-go v0.36.0 h1:JIrO7p7Ug6hssFcARjWDiqS2RAKJHCiwPxBAA989rbI=
|
||||
github.com/quic-go/quic-go v0.36.0/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
|
||||
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
|
||||
github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.0 h1:NrCXmDl8BddZwO67vlvEpBTwT89bJfKYygxv4HQvuDk=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.0/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.37.0 h1:wf/Ym2yeWi98oQn4ahiBSqdnaXVxNQGj2oBQFgiVChc=
|
||||
github.com/quic-go/quic-go v0.37.0/go.mod h1:XtCUOCALTTWbPyd0IxFfHf6h0sEMubRFvEYHl3QxKw8=
|
||||
github.com/refraction-networking/utls v1.3.3 h1:f/TBLX7KBciRyFH3bwupp+CE4fzoYKCirhdRcC490sw=
|
||||
github.com/refraction-networking/utls v1.3.3/go.mod h1:DlecWW1LMlMJu+9qpzzQqdHDT/C2LAe03EdpLUz/RL8=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
@@ -634,7 +631,6 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
@@ -689,12 +685,11 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -705,12 +700,13 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -735,8 +731,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -780,8 +776,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -874,22 +870,19 @@ golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -903,8 +896,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -968,8 +961,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
|
||||
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
+5
-3
@@ -69,7 +69,7 @@ type PanelInfo struct {
|
||||
func GetLatestPanelVersion() (PanelInfo, error) {
|
||||
var info PanelInfo
|
||||
|
||||
cmd := exec.Command("/bin/bash", "-c", "curl \"https://api.github.com/repos/HaoZi-Team/Panel/releases/latest\"")
|
||||
cmd := exec.Command("/bin/bash", "-c", "curl \"https://api.github.com/repos/haozi-team/panel/releases/latest\"")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return info, errors.New("获取最新版本失败")
|
||||
@@ -107,7 +107,7 @@ func GetLatestPanelVersion() (PanelInfo, error) {
|
||||
downloadUrlOutput, _ := downloadUrl.Output()
|
||||
info.DownloadUrl = strings.TrimSpace(string(downloadUrlOutput))
|
||||
} else {
|
||||
downloadUrl := exec.Command("/bin/bash", "-c", "jq -r '.assets[] | select(.name | contains(\"amd64v3\")) | .browser_download_url' "+fileName)
|
||||
downloadUrl := exec.Command("/bin/bash", "-c", "jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .browser_download_url' "+fileName)
|
||||
downloadUrlOutput, _ := downloadUrl.Output()
|
||||
info.DownloadUrl = strings.TrimSpace(string(downloadUrlOutput))
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func UpdatePanel(proxy bool) error {
|
||||
color.Greenln("下载完成")
|
||||
|
||||
color.Greenln("更新新版本...")
|
||||
ExecShell("cd /www/panel && unzip -o panel.zip && rm -rf panel.zip && chmod 700 panel")
|
||||
ExecShell("cd /www/panel && unzip -o panel.zip && rm -rf panel.zip && chmod 700 panel && bash scripts/update_panel.sh")
|
||||
color.Greenln("更新完成")
|
||||
|
||||
color.Greenln("恢复面板配置...")
|
||||
@@ -159,6 +159,8 @@ func UpdatePanel(proxy bool) error {
|
||||
ExecShell("/www/panel/panel --env=panel.conf artisan migrate")
|
||||
color.Greenln("恢复完成")
|
||||
|
||||
ExecShell("panel writeSetting version " + panelInfo.Version)
|
||||
|
||||
color.Greenln("重启面板...")
|
||||
ExecShell("systemctl restart panel")
|
||||
color.Greenln("重启完成")
|
||||
|
||||
@@ -99,8 +99,7 @@ Date: 2023-07-21
|
||||
<label class="layui-form-label">脚本内容</label>
|
||||
<div class="layui-input-block">
|
||||
<div id="cron-add-script-editor"
|
||||
style="height: 250px;"># 在此输入你要执行的脚本内容
|
||||
</div>
|
||||
style="height: 250px;"># 在此输入你要执行的脚本内容</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
@@ -172,7 +171,7 @@ Date: 2023-07-21
|
||||
, url: '/api/panel/cron/list'
|
||||
, cols: [[
|
||||
{field: 'id', hide: true, title: 'ID'}
|
||||
, {field: 'name', width: 150, title: '任务名', sort: true}
|
||||
, {field: 'name', width: 250, title: '任务名', sort: true}
|
||||
, {field: 'type', width: 150, title: '任务类型', templet: '#cron-table-type', sort: true}
|
||||
, {field: 'status', title: '启用', width: 100, templet: '#cron-table-status', unresize: true}
|
||||
, {field: 'time', width: 200, title: '任务周期(cron表达式)'}
|
||||
@@ -205,7 +204,7 @@ Date: 2023-07-21
|
||||
if (obj.event === 'log') {
|
||||
// 打开日志弹窗
|
||||
admin.popup({
|
||||
title: '日志'
|
||||
title: '日志 - ' + data.name
|
||||
,
|
||||
area: ['80%', '80%']
|
||||
,
|
||||
@@ -214,10 +213,12 @@ Date: 2023-07-21
|
||||
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) {
|
||||
index2 = layer.msg('正在获取日志...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: '/api/panel/cron/log?id=' + data.id
|
||||
, type: 'GET'
|
||||
, success: function (res) {
|
||||
layer.close(index2);
|
||||
if (res.code === 0) {
|
||||
$('#cron-log-view').html(res.data);
|
||||
} else {
|
||||
@@ -236,7 +237,7 @@ Date: 2023-07-21
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
admin.popup({
|
||||
title: '编辑'
|
||||
title: '编辑 - ' + data.name
|
||||
,
|
||||
area: ['80%', '80%']
|
||||
,
|
||||
@@ -258,6 +259,7 @@ Date: 2023-07-21
|
||||
}
|
||||
});
|
||||
$('#cron-edit-' + data.id).click(function () {
|
||||
index2 = layer.msg('正在保存...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: '/api/panel/cron/update'
|
||||
, type: 'POST'
|
||||
@@ -271,6 +273,7 @@ Date: 2023-07-21
|
||||
if (res.code === 0) {
|
||||
layer.msg('保存成功', {icon: 1, time: 1000});
|
||||
table.reload('panel-cron');
|
||||
layer.close(index2);
|
||||
layer.close(index);
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000});
|
||||
@@ -340,6 +343,7 @@ Date: 2023-07-21
|
||||
$("#cron-add-backup-type-input").hide();
|
||||
$('#cron-add-backup-path-input').hide();
|
||||
}
|
||||
form.render();
|
||||
});
|
||||
form.on('radio(cron-add-backup-type-radio)', function (data) {
|
||||
if (data.value == 'website') {
|
||||
@@ -349,6 +353,7 @@ Date: 2023-07-21
|
||||
$('#cron-add-website-input').hide();
|
||||
$('#cron-add-backup-database-input').show();
|
||||
}
|
||||
form.render();
|
||||
});
|
||||
|
||||
form.on('switch(cron-status)', function (obj) {
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
<!--
|
||||
Name: Fail2ban管理器
|
||||
Author: 耗子
|
||||
Date: 2023-07-30
|
||||
-->
|
||||
<title>Fail2ban</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">Fail2ban 运行状态</div>
|
||||
<div class="layui-card-body">
|
||||
<blockquote id="fail2ban-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="fail2ban-start" class="layui-btn">启动</button>
|
||||
<button id="fail2ban-stop" class="layui-btn layui-btn-danger">停止</button>
|
||||
<button id="fail2ban-restart" class="layui-btn layui-btn-warm">重启</button>
|
||||
<button id="fail2ban-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="fail2ban_setting">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" style="font-size: 13px;">IP白名单</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="fail2ban_white_list" value="获取中ing..." class="layui-input"
|
||||
disabled>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">IP白名单,以英文逗号,分隔</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn layui-btn-sm" lay-submit lay-filter="fail2ban_setting_submit">
|
||||
确认修改
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">Fail2ban 规则列表</div>
|
||||
<div class="layui-card-body">
|
||||
<table class="layui-hide" id="fail2ban-rule-list" lay-filter="fail2ban-rule-list"></table>
|
||||
<!-- 顶部工具栏 -->
|
||||
<script type="text/html" id="fail2ban-rule-list-bar">
|
||||
<div class="layui-btn-container">
|
||||
<button class="layui-btn layui-btn-sm" lay-event="add_rule">新建规则</button>
|
||||
</div>
|
||||
</script>
|
||||
<!-- 右侧管理 -->
|
||||
<script type="text/html" id="fail2ban-rule-list-control">
|
||||
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="view">查看</a>
|
||||
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
layui.use(['index', 'form', 'table', 'view'], function () {
|
||||
let $ = layui.$
|
||||
, admin = layui.admin
|
||||
, table = layui.table
|
||||
, form = layui.form
|
||||
, view = layui.view;
|
||||
|
||||
// 获取运行状态并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/fail2ban/status"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
if (result.data) {
|
||||
$('#fail2ban-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
|
||||
} else {
|
||||
$('#fail2ban-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 获取白名单并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/fail2ban/whiteList"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
$('input[name=fail2ban_white_list]').val(result.data);
|
||||
$('input').attr('disabled', false);
|
||||
form.render();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听提交
|
||||
form.on('submit(fail2ban_setting_submit)', function (data) {
|
||||
data.field.ip = $('input[name=fail2ban_white_list]').val();
|
||||
index = layer.msg('请稍候...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/fail2ban/whiteList"
|
||||
, type: 'post'
|
||||
, data: data.field
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.msg('设置成功', {icon: 1});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
let websiteList = [];
|
||||
admin.req({
|
||||
url: '/api/panel/website/list?page=1&limit=10000'
|
||||
, type: 'get'
|
||||
, async: false
|
||||
, success: function (res) {
|
||||
websiteList = res.data.items;
|
||||
}
|
||||
});
|
||||
|
||||
// 获取规则列表
|
||||
table.render({
|
||||
elem: '#fail2ban-rule-list'
|
||||
, url: '/api/plugins/fail2ban/list'
|
||||
, toolbar: '#fail2ban-rule-list-bar'
|
||||
, title: 'Fail2ban 规则列表'
|
||||
, cols: [[
|
||||
{field: 'name', title: '规则名', fixed: 'left', unresize: true, sort: true}
|
||||
, {
|
||||
field: 'enabled', title: '状态', templet: function (d) {
|
||||
if (d.enabled) {
|
||||
return '<span class="layui-badge layui-bg-green">启用</span>';
|
||||
} else {
|
||||
return '<span class="layui-badge layui-bg-gray">禁用</span>';
|
||||
}
|
||||
}, sort: true
|
||||
}
|
||||
, {field: 'max_retry', title: '最大尝试次数', sort: true}
|
||||
, {field: 'ban_time', title: '封禁时间', sort: true}
|
||||
, {field: 'find_time', title: '查找时间', sort: true}
|
||||
, {field: 'log_path', title: '日志路径'}
|
||||
, {fixed: 'right', title: '操作', toolbar: '#fail2ban-rule-list-control', width: 150}
|
||||
]]
|
||||
, page: true
|
||||
, text: {
|
||||
none: '无数据'
|
||||
}
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.message,
|
||||
"count": res.data.total,
|
||||
"data": res.data.items
|
||||
};
|
||||
}
|
||||
});
|
||||
// 头工具栏事件
|
||||
table.on('toolbar(fail2ban-rule-list)', function (obj) {
|
||||
if (obj.event === 'add_rule') {
|
||||
index = layer.msg('加载中...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
admin.popup({
|
||||
title: '新建Fail2ban规则'
|
||||
, area: ['600px', '600px']
|
||||
, id: 'LAY-popup-fail2ban-rule-add'
|
||||
, success: function () {
|
||||
layer.close(index);
|
||||
view(this.id).render('plugins/fail2ban/add_rule', {
|
||||
websiteList: websiteList
|
||||
}).done(function () {
|
||||
form.render(null, 'LAY-popup-fail2ban-rule-add');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// 行工具事件
|
||||
table.on('tool(fail2ban-rule-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/fail2ban/delete"
|
||||
, type: 'post'
|
||||
, data: data
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
obj.del();
|
||||
layer.alert(data.name + '删除成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (obj.event === 'view') {
|
||||
index = layer.msg('加载中...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
admin.req({
|
||||
url: "/api/plugins/fail2ban/ban?name=" + data.name
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.popup({
|
||||
title: '查看Fail2ban规则'
|
||||
, area: ['600px', '600px']
|
||||
, id: 'LAY-popup-fail2ban-rule-view'
|
||||
, success: function () {
|
||||
layer.close(index);
|
||||
view(this.id).render('plugins/fail2ban/view_rule', {
|
||||
data: data
|
||||
, banList: result.data
|
||||
}).done(function () {
|
||||
form.render(null, 'LAY-popup-fail2ban-rule-view');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#fail2ban-start').click(function () {
|
||||
layer.confirm('确定要启动Fail2ban吗?', {
|
||||
btn: ['启动', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('请稍等...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
admin.req({
|
||||
url: "/api/plugins/fail2ban/start"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Fail2ban启动成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#fail2ban-stop').click(function () {
|
||||
layer.confirm('停止Fail2ban将导致Fail2ban防护失效,是否继续停止?', {
|
||||
btn: ['停止', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('请稍等...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
admin.req({
|
||||
url: "/api/plugins/fail2ban/stop"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Fail2ban停止成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#fail2ban-restart').click(function () {
|
||||
layer.confirm('确定要重启Fail2ban吗?', {
|
||||
btn: ['重启', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('请稍等...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
admin.req({
|
||||
url: "/api/plugins/fail2ban/restart"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Fail2ban重启成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#fail2ban-reload').click(function () {
|
||||
index = layer.msg('请稍等...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
admin.req({
|
||||
url: "/api/plugins/fail2ban/reload"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.alert('Fail2ban重载成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,170 @@
|
||||
<!--
|
||||
Name: Fail2ban管理器 - 新建规则
|
||||
Author: 耗子
|
||||
Date: 2023-07-30
|
||||
-->
|
||||
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
|
||||
<form class="layui-form" action="" lay-filter="add-pure-ftpd-user-form">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">类型</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" lay-filter="add-fail2ban-rule-type-radio" name="type" value="website"
|
||||
title="网站" checked="">
|
||||
<input type="radio" lay-filter="add-fail2ban-rule-type-radio" name="type" value="service"
|
||||
title="服务">
|
||||
</div>
|
||||
</div>
|
||||
<div id="add-fail2ban-rule-website-input" class="layui-form-item">
|
||||
<label class="layui-form-label">网站</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="website" lay-filter="add-fail2ban-rule-website">
|
||||
{{# layui.each(d.params.websiteList, function(index, item){ }}
|
||||
{{# if(index == 0){ }}
|
||||
<option value="{{ item.name }}" selected="">{{ item.name }}</option>
|
||||
{{# }else{ }}
|
||||
<option value="{{ item.name }}">{{ item.name }}</option>
|
||||
{{# } }}
|
||||
{{# }); }}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="add-fail2ban-rule-service-input" class="layui-form-item">
|
||||
<label class="layui-form-label">服务</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="service" lay-filter="add-fail2ban-rule-service">
|
||||
<option value="ssh" selected="">ssh</option>
|
||||
<option value="mysql">mysql</option>
|
||||
<option value="pure-ftpd">pure-ftpd</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="add-fail2ban-rule-website-mode-input" class="layui-form-item">
|
||||
<label class="layui-form-label">网站模式</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" lay-filter="add-fail2ban-rule-website-mode-radio" name="website_mode" value="cc"
|
||||
title="CC" checked="">
|
||||
<input type="radio" lay-filter="add-fail2ban-rule-website-mode-radio" name="website_mode" value="path"
|
||||
title="目录">
|
||||
</div>
|
||||
</div>
|
||||
<div id="add-fail2ban-rule-website-path-input" class="layui-form-item">
|
||||
<label class="layui-form-label">网站目录</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="website_path"
|
||||
lay-verify="required" placeholder="输入一个禁止访问的目录" class="layui-input"
|
||||
value="/admin">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">最大重试</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="maxretry"
|
||||
lay-verify="required" placeholder="单位:次" class="layui-input"
|
||||
value="30">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">周期</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="findtime"
|
||||
lay-verify="required" placeholder="单位:秒" class="layui-input"
|
||||
value="300">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">禁止时间</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="bantime"
|
||||
lay-verify="required" placeholder="单位:秒" class="layui-input"
|
||||
value="600">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">
|
||||
在设置周期内(秒)有超过最大重试(次)的IP访问,将禁止该IP禁止时间(秒)
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">
|
||||
<span style="color: red;">防护端口自动获取,如果修改了规则项对应的端口,请删除重新添加,否则防护可能不会生效</span>
|
||||
</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-fail2ban-rule-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', 'jquery', 'table'], function () {
|
||||
var $ = layui.jquery
|
||||
, admin = layui.admin
|
||||
, layer = layui.layer
|
||||
, form = layui.form
|
||||
, table = layui.table;
|
||||
|
||||
form.render();
|
||||
|
||||
// 监听类型选择
|
||||
$('#add-fail2ban-rule-service-input').hide();
|
||||
form.on('radio(add-fail2ban-rule-type-radio)', function (data) {
|
||||
if (data.value == 'website') {
|
||||
$('#add-fail2ban-rule-website-input').show();
|
||||
$('#add-fail2ban-rule-website-mode-input').show();
|
||||
$('#add-fail2ban-rule-service-input').hide();
|
||||
if ($('input[name="website_mode"]:checked').val() == 'cc') {
|
||||
$('#add-fail2ban-rule-website-path-input').hide();
|
||||
} else {
|
||||
$('#add-fail2ban-rule-website-path-input').show();
|
||||
}
|
||||
} else if (data.value == 'service') {
|
||||
$('#add-fail2ban-rule-website-input').hide();
|
||||
$('#add-fail2ban-rule-website-mode-input').hide();
|
||||
$('#add-fail2ban-rule-service-input').show();
|
||||
$('#add-fail2ban-rule-website-path-input').hide();
|
||||
}
|
||||
});
|
||||
// 监听网站模式选择
|
||||
$('#add-fail2ban-rule-website-path-input').hide();
|
||||
form.on('radio(add-fail2ban-rule-website-mode-radio)', function (data) {
|
||||
if (data.value == 'cc') {
|
||||
$('#add-fail2ban-rule-website-path-input').hide();
|
||||
} else if (data.value == 'path') {
|
||||
$('#add-fail2ban-rule-website-path-input').show();
|
||||
}
|
||||
});
|
||||
|
||||
// 提交
|
||||
form.on('submit(add-fail2ban-rule-submit)', function (data) {
|
||||
if (data.field.type === 'website') {
|
||||
data.field.name = data.field.website;
|
||||
} else if (data.field.type === 'service') {
|
||||
data.field.name = data.field.service;
|
||||
}
|
||||
index = layer.msg('提交中...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/fail2ban/add"
|
||||
, type: 'post'
|
||||
, data: data.field
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
table.reload('fail2ban-rule-list');
|
||||
layer.alert('Fail2ban规则添加成功!', {
|
||||
icon: 1
|
||||
, title: '提示'
|
||||
, btn: ['确定']
|
||||
, yes: function (index) {
|
||||
layer.closeAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,101 @@
|
||||
<!--
|
||||
Name: Fail2ban管理器 - 查看规则
|
||||
Author: 耗子
|
||||
Date: 2023-07-30
|
||||
-->
|
||||
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
|
||||
{{# console.log(d.params) }}
|
||||
<div class="layui-fluid">
|
||||
<div class="layui-row layui-col-space15">
|
||||
<div class="layui-col-sm6 layui-col-md6">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-body layuiadmin-card-list">
|
||||
<p class="layuiadmin-big-font">{{ d.params.banList.currentlyBan }}</p>
|
||||
<p>当前封禁IP数</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-sm6 layui-col-md6">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-body layuiadmin-card-list">
|
||||
<p class="layuiadmin-big-font">{{ d.params.banList.totalBan }}</p>
|
||||
<p>累计封禁IP数</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-row layui-col-space15">
|
||||
<div class="layui-col-sm12 layui-col-md12">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-body layuiadmin-card-list">
|
||||
<table class="layui-table" id="fail2ban-view-rule-table"
|
||||
lay-filter="fail2ban-view-rule-table"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/html" id="fail2ban-view-rule-ip-control">
|
||||
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="unBan">解封</a>
|
||||
</script>
|
||||
<script>
|
||||
layui.data.sendParams = function (params) {
|
||||
if (params.banList.bannedIpList === null) {
|
||||
params.banList.bannedIpList = [];
|
||||
}
|
||||
|
||||
layui.use(['admin', 'jquery'], function () {
|
||||
var admin = layui.admin
|
||||
, layer = layui.layer
|
||||
, table = layui.table;
|
||||
|
||||
table.render({
|
||||
elem: '#fail2ban-view-rule-table'
|
||||
, cols: [[
|
||||
{field: 'name', title: '规则名', unresize: true, hide: true}
|
||||
, {field: 'ip', title: 'IP', unresize: true, sort: true}
|
||||
, {
|
||||
fixed: 'right',
|
||||
title: '操作',
|
||||
width: 150,
|
||||
unresize: true,
|
||||
toolbar: '#fail2ban-view-rule-ip-control'
|
||||
}
|
||||
]]
|
||||
, data: params.banList.bannedIpList
|
||||
, page: true
|
||||
, text: {
|
||||
none: '无数据'
|
||||
}
|
||||
});
|
||||
|
||||
// 监听工具条
|
||||
table.on('tool(fail2ban-view-rule-table)', function (obj) {
|
||||
var data = obj.data;
|
||||
if (obj.event === 'unBan') {
|
||||
layer.confirm('确定要解封 <b style="color: red;">' + data.ip + '</b> 吗?', function (index) {
|
||||
index = layer.msg('请稍等...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
});
|
||||
admin.req({
|
||||
url: "/api/plugins/fail2ban/unban"
|
||||
, type: 'post'
|
||||
, data: data
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
obj.del();
|
||||
layer.alert(data.ip + '解封成功!');
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -203,6 +203,15 @@ Date: 2022-07-22
|
||||
{field: 'name', title: '库名', fixed: 'left', unresize: true, sort: true}
|
||||
, {fixed: 'right', title: '操作', toolbar: '#mysql-database-list-control', width: 150}
|
||||
]]
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.message,
|
||||
"count": res.data.total,
|
||||
"data": res.data.items
|
||||
};
|
||||
}
|
||||
, page: true
|
||||
});
|
||||
// 头工具栏事件
|
||||
table.on('toolbar(mysql-database-list)', function (obj) {
|
||||
@@ -272,6 +281,15 @@ Date: 2022-07-22
|
||||
, {field: 'grants', title: '权限'}
|
||||
, {fixed: 'right', title: '操作', toolbar: '#mysql-user-list-control', width: 150}
|
||||
]]
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.message,
|
||||
"count": res.data.total,
|
||||
"data": res.data.items
|
||||
};
|
||||
}
|
||||
, page: true
|
||||
});
|
||||
// 头工具栏事件
|
||||
table.on('toolbar(mysql-user-list)', function (obj) {
|
||||
|
||||
@@ -203,6 +203,15 @@ Date: 2022-07-22
|
||||
{field: 'name', title: '库名', fixed: 'left', unresize: true, sort: true}
|
||||
, {fixed: 'right', title: '操作', toolbar: '#mysql-database-list-control', width: 150}
|
||||
]]
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.message,
|
||||
"count": res.data.total,
|
||||
"data": res.data.items
|
||||
};
|
||||
}
|
||||
, page: true
|
||||
});
|
||||
// 头工具栏事件
|
||||
table.on('toolbar(mysql-database-list)', function (obj) {
|
||||
@@ -272,6 +281,15 @@ Date: 2022-07-22
|
||||
, {field: 'grants', title: '权限'}
|
||||
, {fixed: 'right', title: '操作', toolbar: '#mysql-user-list-control', width: 150}
|
||||
]]
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.message,
|
||||
"count": res.data.total,
|
||||
"data": res.data.items
|
||||
};
|
||||
}
|
||||
, page: true
|
||||
});
|
||||
// 头工具栏事件
|
||||
table.on('toolbar(mysql-user-list)', function (obj) {
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
<!--
|
||||
Name: PHP管理器
|
||||
Author: 耗子
|
||||
Date: 2023-07-22
|
||||
-->
|
||||
<title>PHP-8.1</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.1管理</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="php81-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="php81-start" class="layui-btn">启动</button>
|
||||
<button id="php81-stop" class="layui-btn layui-btn-danger">停止</button>
|
||||
<button id="php81-restart" class="layui-btn layui-btn-warm">重启</button>
|
||||
<button id="php81-reload" class="layui-btn layui-btn-normal">重载</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<table id="php81-extension" lay-filter="php81-extension"></table>
|
||||
<!-- 操作按钮模板 -->
|
||||
<script type="text/html" id="php81-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="php81-config-editor"
|
||||
style="height: 600px;"></div>
|
||||
<div class="layui-btn-container" style="padding-top: 30px;">
|
||||
<button id="php81-config-save" class="layui-btn">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<table class="layui-hide" id="php81-load-status"></table>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<div class="layui-btn-container">
|
||||
<button id="php81-clean-error-log" class="layui-btn">清空日志</button>
|
||||
</div>
|
||||
<pre id="php81-error-log" class="layui-code">
|
||||
获取中...
|
||||
</pre>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<div class="layui-btn-container">
|
||||
<button id="php81-clean-slow-log" class="layui-btn">清空日志</button>
|
||||
</div>
|
||||
<pre id="php81-slow-log" class="layui-code">
|
||||
获取中...
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let php81_config_editor;
|
||||
layui.use(['index', 'code', 'table'], function () {
|
||||
let $ = layui.$
|
||||
, admin = layui.admin
|
||||
, element = layui.element
|
||||
, code = layui.code
|
||||
, table = layui.table;
|
||||
|
||||
// 获取php81运行状态并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/php81/status"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:PHP运行状态获取失败,接口返回' + result);
|
||||
return false;
|
||||
}
|
||||
if (result.data) {
|
||||
$('#php81-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
|
||||
} else {
|
||||
$('#php81-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// 获取php81错误日志并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/php81/errorLog"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
$('#php81-error-log').text('PHP日志获取失败,请刷新重试!');
|
||||
code({
|
||||
elem: '#php81-error-log'
|
||||
, title: 'php-fpm.log'
|
||||
, encode: true
|
||||
, about: false
|
||||
|
||||
});
|
||||
return false;
|
||||
}
|
||||
$('#php81-error-log').text(result.data);
|
||||
code({
|
||||
elem: '#php81-error-log'
|
||||
, title: 'php-fpm.log'
|
||||
, encode: true
|
||||
, about: false
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取php81慢日志并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/php81/slowLog"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
$('#php81-slow-log').text('PHP慢日志获取失败,请刷新重试!');
|
||||
code({
|
||||
elem: '#php81-slow-log'
|
||||
, title: 'slow.log'
|
||||
, encode: true
|
||||
, about: false
|
||||
|
||||
});
|
||||
return false;
|
||||
}
|
||||
$('#php81-slow-log').text(result.data);
|
||||
code({
|
||||
elem: '#php81-slow-log'
|
||||
, title: 'slow.log'
|
||||
, encode: true
|
||||
, about: false
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取php81配置并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/php81/config"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:PHP主配置获取失败,接口返回' + result);
|
||||
return false;
|
||||
}
|
||||
$('#php81-config-editor').text(result.data);
|
||||
php81_config_editor = ace.edit("php81-config-editor", {
|
||||
mode: "ace/mode/ini",
|
||||
selectionStyle: "text"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取php81负载状态并渲染
|
||||
table.render({
|
||||
elem: '#php81-load-status'
|
||||
, url: '/api/plugins/php81/load'
|
||||
, cols: [[
|
||||
{field: 'name', width: '81%', title: '属性',}
|
||||
, {field: 'value', width: '20%', title: '当前值'}
|
||||
]]
|
||||
});
|
||||
element.render();
|
||||
|
||||
// 获取php81扩展并渲染
|
||||
table.render({
|
||||
elem: '#php81-extension'
|
||||
, url: '/api/plugins/php81/extensions'
|
||||
, cols: [[
|
||||
{field: 'slug', hide: true, title: 'Slug', sort: true}
|
||||
, {field: 'name', width: '20%', title: '拓展名'}
|
||||
, {field: 'description', width: '70%', title: '描述'}
|
||||
, {
|
||||
field: 'control',
|
||||
title: '操作',
|
||||
templet: '#php81-extension-control',
|
||||
fixed: 'right',
|
||||
align: 'left'
|
||||
}
|
||||
]]
|
||||
, page: false
|
||||
, text: {
|
||||
none: '暂无拓展'
|
||||
}
|
||||
});
|
||||
// 工具条
|
||||
table.on('tool(php81-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/php81/installExtension',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: data.slug
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.close(index);
|
||||
table.reload('php81-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/php81/uninstallExtension',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: data.slug
|
||||
}
|
||||
, success: function (res) {
|
||||
layer.close(index);
|
||||
if (res.code === 0) {
|
||||
table.reload('php81-extension');
|
||||
layer.msg('卸载:' + data.name + ' 成功加入任务队列', {icon: 1, time: 1000});
|
||||
} else {
|
||||
layer.msg(res.msg, {icon: 2, time: 1000});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 事件监听
|
||||
$('#php81-start').click(function () {
|
||||
index = layer.msg('正在启动PHP,请稍后...', {icon: 16, time: 0});
|
||||
layer.confirm('确定要启动PHP吗?', {
|
||||
btn: ['启动', '取消']
|
||||
}, function () {
|
||||
admin.req({
|
||||
url: "/api/plugins/php81/start"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('PHP启动成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#php81-stop').click(function () {
|
||||
layer.confirm('停止PHP将导致使用PHP的网站无法访问,是否继续停止?', {
|
||||
btn: ['停止', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在停止PHP,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php81/stop"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('PHP停止成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#php81-restart').click(function () {
|
||||
layer.confirm('重启PHP将导致使用PHP的网站短时间无法访问,是否继续重启?', {
|
||||
btn: ['重启', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在重启PHP,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php81/restart"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('PHP重启成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#php81-reload').click(function () {
|
||||
index = layer.msg('正在重载PHP,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php81/reload"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.alert('PHP重载成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#php81-config-save').click(function () {
|
||||
index = layer.msg('正在保存配置,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php81/config"
|
||||
, type: 'post'
|
||||
, data: {
|
||||
config: php81_config_editor.getValue()
|
||||
}
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.alert('PHP配置保存成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#php81-clean-error-log').click(function () {
|
||||
index = layer.msg('正在清空错误日志,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php81/clearErrorLog"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.msg('PHP日志已清空!');
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#php81-clean-slow-log').click(function () {
|
||||
index = layer.msg('正在清空慢日志,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php81/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.2</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.2管理</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="php82-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="php82-start" class="layui-btn">启动</button>
|
||||
<button id="php82-stop" class="layui-btn layui-btn-danger">停止</button>
|
||||
<button id="php82-restart" class="layui-btn layui-btn-warm">重启</button>
|
||||
<button id="php82-reload" class="layui-btn layui-btn-normal">重载</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<table id="php82-extension" lay-filter="php82-extension"></table>
|
||||
<!-- 操作按钮模板 -->
|
||||
<script type="text/html" id="php82-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="php82-config-editor"
|
||||
style="height: 600px;"></div>
|
||||
<div class="layui-btn-container" style="padding-top: 30px;">
|
||||
<button id="php82-config-save" class="layui-btn">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<table class="layui-hide" id="php82-load-status"></table>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<div class="layui-btn-container">
|
||||
<button id="php82-clean-error-log" class="layui-btn">清空日志</button>
|
||||
</div>
|
||||
<pre id="php82-error-log" class="layui-code">
|
||||
获取中...
|
||||
</pre>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<div class="layui-btn-container">
|
||||
<button id="php82-clean-slow-log" class="layui-btn">清空日志</button>
|
||||
</div>
|
||||
<pre id="php82-slow-log" class="layui-code">
|
||||
获取中...
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let php82_config_editor;
|
||||
layui.use(['index', 'code', 'table'], function () {
|
||||
let $ = layui.$
|
||||
, admin = layui.admin
|
||||
, element = layui.element
|
||||
, code = layui.code
|
||||
, table = layui.table;
|
||||
|
||||
// 获取php82运行状态并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/php82/status"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:PHP运行状态获取失败,接口返回' + result);
|
||||
return false;
|
||||
}
|
||||
if (result.data) {
|
||||
$('#php82-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
|
||||
} else {
|
||||
$('#php82-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// 获取php82错误日志并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/php82/errorLog"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
$('#php82-error-log').text('PHP日志获取失败,请刷新重试!');
|
||||
code({
|
||||
elem: '#php82-error-log'
|
||||
, title: 'php-fpm.log'
|
||||
, encode: true
|
||||
, about: false
|
||||
|
||||
});
|
||||
return false;
|
||||
}
|
||||
$('#php82-error-log').text(result.data);
|
||||
code({
|
||||
elem: '#php82-error-log'
|
||||
, title: 'php-fpm.log'
|
||||
, encode: true
|
||||
, about: false
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取php82慢日志并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/php82/slowLog"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
$('#php82-slow-log').text('PHP慢日志获取失败,请刷新重试!');
|
||||
code({
|
||||
elem: '#php82-slow-log'
|
||||
, title: 'slow.log'
|
||||
, encode: true
|
||||
, about: false
|
||||
|
||||
});
|
||||
return false;
|
||||
}
|
||||
$('#php82-slow-log').text(result.data);
|
||||
code({
|
||||
elem: '#php82-slow-log'
|
||||
, title: 'slow.log'
|
||||
, encode: true
|
||||
, about: false
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取php82配置并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/php82/config"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
console.log('耗子Linux面板:PHP主配置获取失败,接口返回' + result);
|
||||
return false;
|
||||
}
|
||||
$('#php82-config-editor').text(result.data);
|
||||
php82_config_editor = ace.edit("php82-config-editor", {
|
||||
mode: "ace/mode/ini",
|
||||
selectionStyle: "text"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取php82负载状态并渲染
|
||||
table.render({
|
||||
elem: '#php82-load-status'
|
||||
, url: '/api/plugins/php82/load'
|
||||
, cols: [[
|
||||
{field: 'name', width: '82%', title: '属性',}
|
||||
, {field: 'value', width: '20%', title: '当前值'}
|
||||
]]
|
||||
});
|
||||
element.render();
|
||||
|
||||
// 获取php82扩展并渲染
|
||||
table.render({
|
||||
elem: '#php82-extension'
|
||||
, url: '/api/plugins/php82/extensions'
|
||||
, cols: [[
|
||||
{field: 'slug', hide: true, title: 'Slug', sort: true}
|
||||
, {field: 'name', width: '20%', title: '拓展名'}
|
||||
, {field: 'description', width: '70%', title: '描述'}
|
||||
, {
|
||||
field: 'control',
|
||||
title: '操作',
|
||||
templet: '#php82-extension-control',
|
||||
fixed: 'right',
|
||||
align: 'left'
|
||||
}
|
||||
]]
|
||||
, page: false
|
||||
, text: {
|
||||
none: '暂无拓展'
|
||||
}
|
||||
});
|
||||
// 工具条
|
||||
table.on('tool(php82-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/php82/installExtension',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: data.slug
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.close(index);
|
||||
table.reload('php82-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/php82/uninstallExtension',
|
||||
type: 'POST',
|
||||
data: {
|
||||
slug: data.slug
|
||||
}
|
||||
, success: function (res) {
|
||||
layer.close(index);
|
||||
if (res.code === 0) {
|
||||
table.reload('php82-extension');
|
||||
layer.msg('卸载:' + data.name + ' 成功加入任务队列', {icon: 1, time: 1000});
|
||||
} else {
|
||||
layer.msg(res.msg, {icon: 2, time: 1000});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 事件监听
|
||||
$('#php82-start').click(function () {
|
||||
index = layer.msg('正在启动PHP,请稍后...', {icon: 16, time: 0});
|
||||
layer.confirm('确定要启动PHP吗?', {
|
||||
btn: ['启动', '取消']
|
||||
}, function () {
|
||||
admin.req({
|
||||
url: "/api/plugins/php82/start"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('PHP启动成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#php82-stop').click(function () {
|
||||
layer.confirm('停止PHP将导致使用PHP的网站无法访问,是否继续停止?', {
|
||||
btn: ['停止', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在停止PHP,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php82/stop"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('PHP停止成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#php82-restart').click(function () {
|
||||
layer.confirm('重启PHP将导致使用PHP的网站短时间无法访问,是否继续重启?', {
|
||||
btn: ['重启', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在重启PHP,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php82/restart"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('PHP重启成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#php82-reload').click(function () {
|
||||
index = layer.msg('正在重载PHP,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php82/reload"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.alert('PHP重载成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#php82-config-save').click(function () {
|
||||
index = layer.msg('正在保存配置,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php82/config"
|
||||
, type: 'post'
|
||||
, data: {
|
||||
config: php82_config_editor.getValue()
|
||||
}
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.alert('PHP配置保存成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#php82-clean-error-log').click(function () {
|
||||
index = layer.msg('正在清空错误日志,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php82/clearErrorLog"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.msg('PHP日志已清空!');
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#php82-clean-slow-log').click(function () {
|
||||
index = layer.msg('正在清空慢日志,请稍后...', {icon: 16, time: 0});
|
||||
admin.req({
|
||||
url: "/api/plugins/php82/clearSlowLog"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.msg('PHP慢日志已清空!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,305 @@
|
||||
<!--
|
||||
Name: Pure-Ftpd管理器
|
||||
Author: 耗子
|
||||
Date: 2023-08-03
|
||||
-->
|
||||
<title>Pure-Ftpd</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">Pure-Ftpd 运行状态</div>
|
||||
<div class="layui-card-body">
|
||||
<blockquote id="pureftpd-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="pureftpd-start" class="layui-btn">启动</button>
|
||||
<button id="pureftpd-stop" class="layui-btn layui-btn-danger">停止</button>
|
||||
<button id="pureftpd-restart" class="layui-btn layui-btn-warm">重启</button>
|
||||
<button id="pureftpd-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="pureftpd_setting">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" style="font-size: 13px;">端口</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="pureftpd_port" value="获取中ing..." class="layui-input"
|
||||
disabled>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">设置Pure-Ftpd的访问端口</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn layui-btn-sm" lay-submit lay-filter="pureftpd_setting_submit">
|
||||
确认修改
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">Pure-Ftpd 用户列表</div>
|
||||
<div class="layui-card-body">
|
||||
<table class="layui-hide" id="pureftpd-user-list" lay-filter="pureftpd-user-list"></table>
|
||||
<!-- 顶部工具栏 -->
|
||||
<script type="text/html" id="pureftpd-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="pureftpd-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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
layui.use(['index', 'form', 'table', 'view'], function () {
|
||||
let $ = layui.$
|
||||
, admin = layui.admin
|
||||
, table = layui.table
|
||||
, form = layui.form
|
||||
, view = layui.view;
|
||||
|
||||
// 获取运行状态并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/pureftpd/status"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
if (result.data) {
|
||||
$('#pureftpd-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
|
||||
} else {
|
||||
$('#pureftpd-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 获取端口并渲染
|
||||
admin.req({
|
||||
url: "/api/plugins/pureftpd/port"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
$('input[name=pureftpd_port]').val(result.data);
|
||||
$('input').attr('disabled', false);
|
||||
form.render();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听提交
|
||||
form.on('submit(pureftpd_setting_submit)', function (data) {
|
||||
data.field.port = $('input[name=pureftpd_port]').val();
|
||||
index = layer.msg('请稍候...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/pureftpd/port"
|
||||
, type: 'post'
|
||||
, data: data.field
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.msg('设置成功', {icon: 1});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
// 获取用户列表
|
||||
table.render({
|
||||
elem: '#pureftpd-user-list'
|
||||
, url: '/api/plugins/pureftpd/list'
|
||||
, toolbar: '#pureftpd-user-list-bar'
|
||||
, title: 'Pure-Ftpd 用户列表'
|
||||
, cols: [[
|
||||
{field: 'username', title: '用户名', fixed: 'left', unresize: true, sort: true}
|
||||
, {field: 'path', title: '目录', unresize: true, sort: true}
|
||||
, {fixed: 'right', title: '操作', toolbar: '#pureftpd-user-list-control', width: 150}
|
||||
]]
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.message,
|
||||
"count": res.data.total,
|
||||
"data": res.data.items
|
||||
};
|
||||
}
|
||||
, page: true
|
||||
});
|
||||
// 头工具栏事件
|
||||
table.on('toolbar(pureftpd-user-list)', function (obj) {
|
||||
if (obj.event === 'add_user') {
|
||||
admin.popup({
|
||||
title: '新建Pure-Ftpd用户'
|
||||
, area: ['600px', '400px']
|
||||
, id: 'LAY-popup-pureftpd-user-add'
|
||||
, success: function (layer, index) {
|
||||
view(this.id).render('plugins/pureftpd/add_user', {}).done(function () {
|
||||
form.render(null, 'LAY-popup-pureftpd-user-add');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// 行工具事件
|
||||
table.on('tool(pureftpd-user-list)', function (obj) {
|
||||
let data = obj.data;
|
||||
if (obj.event === 'del') {
|
||||
layer.confirm('确定要删除用户 <b style="color: red;">' + data.username + '</b> 吗?', function (index) {
|
||||
index = layer.msg('请稍等...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
admin.req({
|
||||
url: "/api/plugins/pureftpd/delete"
|
||||
, type: 'post'
|
||||
, data: data
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
layer.msg('Pure-Ftpd用户删除失败,请刷新重试!')
|
||||
return false;
|
||||
}
|
||||
obj.del();
|
||||
layer.alert(data.username + '删除成功!');
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
} else if (obj.event === 'change_password') {
|
||||
// 弹出输入密码框
|
||||
layer.prompt({
|
||||
formType: 1
|
||||
, title: '请输入新密码(6位以上)'
|
||||
}, function (value, index) {
|
||||
layer.close(index);
|
||||
index = layer.msg('请稍等...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
// 发送请求
|
||||
admin.req({
|
||||
url: "/api/plugins/pureftpd/changePassword"
|
||||
, type: 'post'
|
||||
, data: {
|
||||
username: data.username,
|
||||
password: value
|
||||
}
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
layer.msg('密码修改失败,请刷新重试!')
|
||||
return false;
|
||||
}
|
||||
layer.alert('用户' + data.username + '密码修改成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 事件监听
|
||||
$('#pureftpd-start').click(function () {
|
||||
layer.confirm('确定要启动Pure-Ftpd吗?', {
|
||||
btn: ['启动', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('请稍等...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
admin.req({
|
||||
url: "/api/plugins/pureftpd/start"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Pure-Ftpd启动成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#pureftpd-stop').click(function () {
|
||||
layer.confirm('停止Pure-Ftpd将导致FTP无法连接,是否继续停止?', {
|
||||
btn: ['停止', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('请稍等...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
admin.req({
|
||||
url: "/api/plugins/pureftpd/stop"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Pure-Ftpd停止成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#pureftpd-restart').click(function () {
|
||||
layer.confirm('确定要重启Pure-Ftpd吗?', {
|
||||
btn: ['重启', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('请稍等...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
admin.req({
|
||||
url: "/api/plugins/pureftpd/restart"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Pure-Ftpd重启成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#pureftpd-reload').click(function () {
|
||||
index = layer.msg('请稍等...', {
|
||||
icon: 16
|
||||
, time: 0
|
||||
, shade: 0.3
|
||||
});
|
||||
admin.req({
|
||||
url: "/api/plugins/pureftpd/reload"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.alert('Pure-Ftpd重载成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,80 @@
|
||||
<!--
|
||||
Name: Pure-Ftpd管理器 - 新建用户
|
||||
Author: 耗子
|
||||
Date: 2023-08-03
|
||||
-->
|
||||
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
|
||||
<form class="layui-form" action="" lay-filter="add-pureftpd-user-form">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">用户名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="username" id="add-pureftpd-user-username"
|
||||
lay-verify="required" placeholder="请输入用户名" class="layui-input"
|
||||
value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">密码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="password" name="password" id="add-pureftpd-user-password"
|
||||
lay-verify="required" placeholder="请输入密码" class="layui-input"
|
||||
value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">目录</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="path" id="add-pureftpd-user-path"
|
||||
lay-verify="required" placeholder="请输入目录" class="layui-input"
|
||||
value="">
|
||||
</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-pureftpd-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', 'form', 'table'], function () {
|
||||
var admin = layui.admin
|
||||
, layer = layui.layer
|
||||
, form = layui.form
|
||||
, table = layui.table;
|
||||
|
||||
form.render();
|
||||
|
||||
// 提交
|
||||
form.on('submit(add-pureftpd-user-submit)', function (data) {
|
||||
index = layer.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/pureftpd/add"
|
||||
, type: 'post'
|
||||
, data: data.field
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
layer.msg('Pure-Ftpd用户添加失败,请刷新重试!')
|
||||
return false;
|
||||
}
|
||||
table.reload('pureftpd-user-list');
|
||||
layer.alert('Pure-Ftpd用户添加成功!', {
|
||||
icon: 1
|
||||
, title: '提示'
|
||||
, btn: ['确定']
|
||||
, yes: function (index) {
|
||||
layer.closeAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,190 @@
|
||||
<!--
|
||||
Name: Redis管理器
|
||||
Author: 耗子
|
||||
Date: 2023-08-09
|
||||
-->
|
||||
<title>Redis</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">Redis 管理</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">
|
||||
<blockquote id="redis-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="redis-start" class="layui-btn">启动</button>
|
||||
<button id="redis-stop" class="layui-btn layui-btn-danger">停止</button>
|
||||
<button id="redis-restart" class="layui-btn layui-btn-warm">重启</button>
|
||||
<button id="redis-reload" class="layui-btn layui-btn-normal">重载</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<blockquote class="layui-elem-quote">此处修改的是Redis主配置文件,如果你不了解各参数的含义,请不要随意修改!<br>
|
||||
提示:Ctrl+F 搜索关键字,Ctrl+S 保存,Ctrl+H 查找替换!
|
||||
</blockquote>
|
||||
<div id="redis-config-editor"
|
||||
style="height: 600px;"></div>
|
||||
<div class="layui-btn-container" style="padding-top: 30px;">
|
||||
<button id="redis-config-save" class="layui-btn">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<table class="layui-hide" id="redis-load-status"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let redis_config_editor;
|
||||
layui.use(['admin', 'jquery', 'element', 'table'], function () {
|
||||
let $ = layui.$
|
||||
, admin = layui.admin
|
||||
, element = layui.element
|
||||
, table = layui.table;
|
||||
|
||||
admin.req({
|
||||
url: "/api/plugins/redis/status"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
if (result.data) {
|
||||
$('#redis-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
|
||||
} else {
|
||||
$('#redis-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
admin.req({
|
||||
url: "/api/plugins/redis/config"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
$('#redis-config-editor').text(result.data);
|
||||
redis_config_editor = ace.edit("redis-config-editor", {
|
||||
mode: "ace/mode/ini",
|
||||
selectionStyle: "text"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
table.render({
|
||||
elem: '#redis-load-status'
|
||||
, url: '/api/plugins/redis/load'
|
||||
, cols: [[
|
||||
{field: 'name', width: '80%', title: '属性',}
|
||||
, {field: 'value', width: '20%', title: '当前值'}
|
||||
]]
|
||||
});
|
||||
element.render();
|
||||
|
||||
// 事件监听
|
||||
$('#redis-start').click(function () {
|
||||
layer.confirm('确定要启动Redis吗?', {
|
||||
btn: ['启动', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在启动Redis,请稍候...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/redis/start"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Redis启动成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#redis-stop').click(function () {
|
||||
layer.confirm('停止Redis可能导致使用Redis的网站出现异常,是否继续停止?', {
|
||||
btn: ['停止', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在停止Redis,请稍候...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/redis/stop"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Redis停止成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#redis-restart').click(function () {
|
||||
layer.confirm('重启Redis将导致使用Redis的网站短时间出现异常,是否继续重启?', {
|
||||
btn: ['重启', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在重启Redis,请稍候...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/redis/restart"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Redis重启成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#redis-reload').click(function () {
|
||||
index = layer.msg('正在重载Redis,请稍候...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/redis/reload"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.alert('Redis重载成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#redis-config-save').click(function () {
|
||||
index = layer.msg('正在保存Redis配置,请稍候...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/redis/config"
|
||||
, type: 'post'
|
||||
, data: {
|
||||
config: redis_config_editor.getValue()
|
||||
}
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.alert('Redis配置保存成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,451 @@
|
||||
<!--
|
||||
Name: Supervisor
|
||||
Author: 耗子
|
||||
Date: 2023-07-26
|
||||
-->
|
||||
<title>Supervisor</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">Supervisor 管理</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>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
<blockquote id="supervisor-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="supervisor-start" class="layui-btn">启动</button>
|
||||
<button id="supervisor-stop" class="layui-btn layui-btn-danger">停止</button>
|
||||
<button id="supervisor-restart" class="layui-btn layui-btn-warm">重启</button>
|
||||
<button id="supervisor-reload" class="layui-btn layui-btn-normal">重载</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<table class="layui-hide" id="supervisor-list" lay-filter="supervisor-list"></table>
|
||||
<!-- 顶部工具栏 -->
|
||||
<script type="text/html" id="supervisor-list-bar">
|
||||
<div class="layui-btn-container">
|
||||
<button class="layui-btn layui-btn-sm" lay-event="add_process">添加进程</button>
|
||||
</div>
|
||||
</script>
|
||||
<!-- 右侧管理 -->
|
||||
<script type="text/html" id="supervisor-list-control">
|
||||
<a class="layui-btn layui-btn-xs" lay-event="log">日志</a>
|
||||
<a class="layui-btn layui-bg-blue layui-btn-xs" lay-event="config">配置</a>
|
||||
<a class="layui-btn layui-border layui-btn-xs" lay-event="start">启动</a>
|
||||
<a class="layui-btn layui-bg-purple layui-btn-xs" lay-event="restart">重启</a>
|
||||
<a class="layui-btn layui-bg-orange layui-btn-xs" lay-event="stop">停止</a>
|
||||
<a class="layui-btn layui-bg-red layui-btn-xs" lay-event="delete">删除</a>
|
||||
</script>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<blockquote class="layui-elem-quote">此处修改的是Supervisor主配置文件,如果你不了解各参数的含义,请不要随意修改!<br>
|
||||
提示:Ctrl+F 搜索关键字,Ctrl+S 保存,Ctrl+H 查找替换!
|
||||
</blockquote>
|
||||
<div id="supervisor-config-editor"
|
||||
style="height: 600px;"></div>
|
||||
<div class="layui-btn-container" style="padding-top: 30px;">
|
||||
<button id="supervisor-config-save" class="layui-btn">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
<div class="layui-btn-container">
|
||||
<button id="supervisor-clear-log" class="layui-btn">清空日志</button>
|
||||
</div>
|
||||
<pre id="supervisor-log" class="layui-code">
|
||||
获取中...
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let supervisor_config_editor;
|
||||
let supervisor_process_config_editor;
|
||||
layui.use(['jquery', 'admin', 'view', 'form', 'code', 'table'], function () {
|
||||
let $ = layui.$
|
||||
, admin = layui.admin
|
||||
, table = layui.table
|
||||
, form = layui.form
|
||||
, view = layui.view
|
||||
, code = layui.code;
|
||||
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/status"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
if (result.data) {
|
||||
$('#supervisor-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
|
||||
} else {
|
||||
$('#supervisor-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
table.render({
|
||||
elem: '#supervisor-list'
|
||||
, url: '/api/plugins/supervisor/processes'
|
||||
, toolbar: '#supervisor-list-bar'
|
||||
, title: 'Supervisor进程列表'
|
||||
, cols: [[
|
||||
{field: 'name', title: '名称', fixed: 'left', unresize: true, sort: true}
|
||||
, {field: 'status', title: '状态', sort: true}
|
||||
, {field: 'pid', title: 'PID', sort: true}
|
||||
, {field: 'uptime', title: '运行时间(天)', sort: true}
|
||||
, {fixed: 'right', title: '操作', toolbar: '#supervisor-list-control', width: 350}
|
||||
]]
|
||||
, page: true
|
||||
, text: {
|
||||
none: '无进程'
|
||||
}
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.message,
|
||||
"count": res.data.total,
|
||||
"data": res.data.items
|
||||
};
|
||||
}
|
||||
});
|
||||
// 头工具栏事件
|
||||
table.on('toolbar(supervisor-list)', function (obj) {
|
||||
if (obj.event === 'add_process') {
|
||||
admin.popup({
|
||||
title: '添加进程'
|
||||
, area: ['600px', '600px']
|
||||
, id: 'LAY-popup-supervisor-process-add'
|
||||
, success: function (layer, index) {
|
||||
view(this.id).render('plugins/supervisor/add_process', {}).done(function () {
|
||||
form.render(null, 'LAY-popup-supervisor-process-add');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// 行工具事件
|
||||
table.on('tool(supervisor-list)', function (obj) {
|
||||
let data = obj.data;
|
||||
if (obj.event === 'log') {
|
||||
admin.popup({
|
||||
title: '日志 - ' + data.name
|
||||
,
|
||||
area: ['80%', '80%']
|
||||
,
|
||||
id: 'process-log'
|
||||
,
|
||||
content: '<pre id="process-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) {
|
||||
index2 = layer.msg('正在获取日志...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: '/api/plugins/supervisor/processLog?process=' + data.name
|
||||
, type: 'GET'
|
||||
, success: function (res) {
|
||||
layer.close(index2);
|
||||
if (res.code === 0) {
|
||||
$('#process-log-view').html(res.data);
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (obj.event === 'config') {
|
||||
admin.req({
|
||||
url: '/api/plugins/supervisor/processConfig?process=' + data.name
|
||||
, type: 'GET'
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
admin.popup({
|
||||
title: '编辑 - ' + data.name
|
||||
,
|
||||
area: ['80%', '80%']
|
||||
,
|
||||
id: 'process-config'
|
||||
,
|
||||
content: '<div id="process-config-editor" style="height: 80%;">' + res.data + '</div><br><button id="process-config-' + data.name + '" class="layui-btn">保存</button>'
|
||||
,
|
||||
success: function (layero, index) {
|
||||
supervisor_process_config_editor = ace.edit("process-config-editor", {
|
||||
mode: "ace/mode/ini",
|
||||
selectionStyle: "text"
|
||||
});
|
||||
$('#process-config-' + data.name).click(function () {
|
||||
index2 = layer.msg('正在保存配置...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: '/api/plugins/supervisor/processConfig'
|
||||
, type: 'POST'
|
||||
, data: {
|
||||
process: data.name,
|
||||
config: supervisor_process_config_editor.getValue()
|
||||
}
|
||||
, success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('保存成功', {icon: 1, time: 1000});
|
||||
table.reload('supervisor-list');
|
||||
layer.close(index2);
|
||||
layer.close(index);
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
layer.msg(res.message, {icon: 2, time: 1000});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (obj.event === 'start') {
|
||||
index = layer.msg('正在启动进程...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/startProcess"
|
||||
, type: 'post'
|
||||
, data: {
|
||||
process: data.name
|
||||
}
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('进程启动成功!');
|
||||
}
|
||||
});
|
||||
}
|
||||
if (obj.event === 'restart') {
|
||||
layer.confirm('确定要重启进程吗?', function (index) {
|
||||
index = layer.msg('请稍候...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/restartProcess"
|
||||
, type: 'post'
|
||||
, data: {
|
||||
process: data.name
|
||||
}
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.alert('进程重启成功!');
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
if (obj.event === 'stop') {
|
||||
layer.confirm('确定要停止进程吗?', function (index) {
|
||||
index = layer.msg('请稍候...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/stopProcess"
|
||||
, type: 'post'
|
||||
, data: {
|
||||
process: data.name
|
||||
}
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.alert('进程停止成功!');
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
if (obj.event === 'delete') {
|
||||
layer.confirm('确定要删除进程吗?', function (index) {
|
||||
index = layer.msg('请稍候...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/deleteProcess"
|
||||
, type: 'post'
|
||||
, data: {
|
||||
process: data.name
|
||||
}
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
obj.del();
|
||||
layer.alert('进程删除成功!');
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/log"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
$('#supervisor-log').text('Supervisor日志获取失败,请刷新重试!');
|
||||
code({
|
||||
elem: '#supervisor-log'
|
||||
, title: 'supervisor.log'
|
||||
, encode: true
|
||||
, about: false
|
||||
|
||||
});
|
||||
return false;
|
||||
}
|
||||
$('#supervisor-log').text(result.data);
|
||||
code({
|
||||
elem: '#supervisor-log'
|
||||
, title: 'supervisor.log'
|
||||
, encode: true
|
||||
, about: false
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/config"
|
||||
, type: 'get'
|
||||
, success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
$('#supervisor-config-editor').text(result.data);
|
||||
supervisor_config_editor = ace.edit("supervisor-config-editor", {
|
||||
mode: "ace/mode/ini",
|
||||
selectionStyle: "text"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#supervisor-start').click(function () {
|
||||
layer.confirm('确定要启动Supervisor吗?', {
|
||||
btn: ['启动', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在启动Supervisor...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/start"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Supervisor启动成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#supervisor-stop').click(function () {
|
||||
layer.confirm('确定要停止Supervisor吗?', {
|
||||
btn: ['停止', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在停止Supervisor...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/stop"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Supervisor停止成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#supervisor-restart').click(function () {
|
||||
layer.confirm('确定要重启Supervisor吗?', {
|
||||
btn: ['重启', '取消']
|
||||
}, function () {
|
||||
index = layer.msg('正在重启Supervisor...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/restart"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Supervisor重启成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#supervisor-reload').click(function () {
|
||||
index = layer.msg('正在重载Supervisor...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/reload"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
admin.events.refresh();
|
||||
layer.alert('Supervisor重载成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#supervisor-config-save').click(function () {
|
||||
index = layer.msg('正在保存配置...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/config"
|
||||
, type: 'post'
|
||||
, data: {
|
||||
config: supervisor_config_editor.getValue()
|
||||
}
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.alert('Supervisor配置保存成功!');
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#supervisor-clear-log').click(function () {
|
||||
index = layer.msg('正在清空Supervisor日志...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/clearLog"
|
||||
, type: 'post'
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
layer.msg('Supervisor日志已清空!');
|
||||
setTimeout(function () {
|
||||
admin.events.refresh();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!--
|
||||
Name: Supervisor - 添加进程
|
||||
Author: 耗子
|
||||
Date: 2023-07-26
|
||||
-->
|
||||
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
|
||||
<form class="layui-form" action="" lay-filter="add-mount-s3fs-form">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="name" lay-verify="required" placeholder="请输入名称" class="layui-input"
|
||||
value="">
|
||||
</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-block">
|
||||
<input type="text" name="command" lay-verify="required" placeholder="请输入启动命令" class="layui-input"
|
||||
value="">
|
||||
</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-block">
|
||||
<input type="text" name="path" lay-verify="required" placeholder="请输入运行目录" class="layui-input"
|
||||
value="">
|
||||
</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-block">
|
||||
<input type="text" name="user" lay-verify="required" placeholder="请输入启动用户" class="layui-input"
|
||||
value="">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">一般情况下填写www即可</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">进程数量</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="number" name="num" lay-verify="required" placeholder="请输入进程数量" class="layui-input"
|
||||
value="1" min="1" step="1" lay-affix="number">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">一般情况下填写1即可</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-footer">
|
||||
<button class="layui-btn" lay-submit="" lay-filter="supervisor-add-process-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', 'jquery', 'table'], function () {
|
||||
var admin = layui.admin
|
||||
, layer = layui.layer
|
||||
, form = layui.form
|
||||
, table = layui.table;
|
||||
|
||||
form.render();
|
||||
|
||||
form.on('submit(supervisor-add-process-submit)', function (data) {
|
||||
index = layer.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
|
||||
admin.req({
|
||||
url: "/api/plugins/supervisor/addProcess"
|
||||
, type: 'post'
|
||||
, data: data.field
|
||||
, success: function (result) {
|
||||
layer.close(index);
|
||||
if (result.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
table.reload('supervisor-list');
|
||||
layer.alert('Supervisor进程添加成功!', {
|
||||
icon: 1
|
||||
, title: '提示'
|
||||
, btn: ['确定']
|
||||
, yes: function (index) {
|
||||
layer.closeAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--
|
||||
Name: 系统安全
|
||||
Author: 耗子
|
||||
Date: 2023-07-21
|
||||
Date: 2023-08-03
|
||||
-->
|
||||
<title>系统安全</title>
|
||||
|
||||
@@ -218,6 +218,15 @@ Date: 2023-07-21
|
||||
, {field: 'protocol', title: '协议', sort: true}
|
||||
, {fixed: 'right', title: '操作', toolbar: '#safe-port-setting', width: 150}
|
||||
]]
|
||||
, parseData: function (res) {
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.message,
|
||||
"count": res.data.total,
|
||||
"data": res.data.items
|
||||
};
|
||||
}
|
||||
, page: true
|
||||
});
|
||||
table.on('tool(safe-port)', function (obj) {
|
||||
let data = obj.data;
|
||||
|
||||
@@ -80,6 +80,7 @@ Date: 2023-07-25
|
||||
return false;
|
||||
}
|
||||
layer.alert('SSH信息保存成功!')
|
||||
admin.events.refresh()
|
||||
}
|
||||
});
|
||||
return false;
|
||||
|
||||
@@ -94,7 +94,8 @@ Date: 2023-07-22
|
||||
, url: '/api/panel/task/list?status=waiting'
|
||||
, cols: [[
|
||||
{field: 'id', hide: true, title: 'ID', sort: true}
|
||||
, {field: 'name', width: '100%', title: '任务名'}
|
||||
, {field: 'name', fixed: 'left', title: '任务名'}
|
||||
, {field: 'created_at', title: '创建时间', width: 200}
|
||||
]]
|
||||
, page: true
|
||||
, text: {
|
||||
@@ -116,13 +117,15 @@ Date: 2023-07-22
|
||||
, url: '/api/panel/task/list?status=finished'
|
||||
, cols: [[
|
||||
{field: 'id', hide: true, title: 'ID', sort: true}
|
||||
, {field: 'name', width: '80%', title: '任务名'}
|
||||
, {field: 'name', fixed: 'left', title: '任务名'}
|
||||
, {field: 'created_at', title: '创建时间', width: 200}
|
||||
, {field: 'updated_at', title: '完成时间', width: 200}
|
||||
, {
|
||||
field: 'control',
|
||||
title: '操作',
|
||||
templet: '#panel-task-finished-control-tpl',
|
||||
fixed: 'right',
|
||||
align: 'center'
|
||||
field: 'control'
|
||||
, title: '操作'
|
||||
, width: 100
|
||||
, templet: '#panel-task-finished-control-tpl'
|
||||
, align: 'center'
|
||||
}
|
||||
]]
|
||||
, page: true
|
||||
@@ -169,13 +172,16 @@ Date: 2023-07-22
|
||||
, url: '/api/panel/task/list?status=failed'
|
||||
, cols: [[
|
||||
{field: 'id', hide: true, title: 'ID', sort: true}
|
||||
, {field: 'name', width: '80%', title: '任务名'}
|
||||
, {field: 'name', fixed: 'left', title: '任务名'}
|
||||
, {field: 'created_at', title: '创建时间', width: 200}
|
||||
, {field: 'updated_at', title: '完成时间', width: 200}
|
||||
, {
|
||||
field: 'control',
|
||||
title: '操作',
|
||||
templet: '#panel-task-failed-control-tpl',
|
||||
fixed: 'right',
|
||||
align: 'center'
|
||||
field: 'control'
|
||||
, title: '操作'
|
||||
, width: 100
|
||||
, templet: '#panel-task-failed-control-tpl'
|
||||
, fixed: 'right'
|
||||
, align: 'center'
|
||||
}
|
||||
]]
|
||||
, page: true
|
||||
|
||||
@@ -289,7 +289,7 @@ Date: 2023-07-24
|
||||
layer.close(index)
|
||||
layer.load()
|
||||
admin.req({
|
||||
url: '/api/panel/website/clearSiteLog'
|
||||
url: '/api/panel/website/clearLog'
|
||||
, type: 'post'
|
||||
, data: {name: params.config.name}
|
||||
, success: function (res) {
|
||||
@@ -316,6 +316,15 @@ Date: 2023-07-24
|
||||
let domains = $("input[name='domains[]']").map(function () {
|
||||
return $(this).val();
|
||||
}).get()
|
||||
var reg = new RegExp(/\n443.*\n?/);
|
||||
// 如果开启了https,就自动添加443端口
|
||||
if ($('input[name="ssl"]').prop('checked') && !reg.test(ports)) {
|
||||
ports = ports + '\n443 ssl http2';
|
||||
}
|
||||
// 如果关闭了https,就自动删除443端口
|
||||
if (!$('input[name="ssl"]').prop('checked') && reg.test(ports)) {
|
||||
ports = ports.replace(/443.*\n?/, '');
|
||||
}
|
||||
|
||||
admin.req({
|
||||
url: '/api/panel/website/config'
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"labels": [
|
||||
"🤖 Dependencies"
|
||||
],
|
||||
"commitMessagePrefix": "chore(deps): ",
|
||||
"customEnvVariables": {
|
||||
"GOPROXY": "https://goproxy.cn,direct"
|
||||
},
|
||||
"postUpdateOptions": [
|
||||
"gomodTidy"
|
||||
]
|
||||
}
|
||||
@@ -4,13 +4,19 @@ import (
|
||||
"github.com/goravel/framework/contracts/route"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/http/controllers/plugins/fail2ban"
|
||||
"panel/app/http/controllers/plugins/mysql57"
|
||||
"panel/app/http/controllers/plugins/mysql80"
|
||||
"panel/app/http/controllers/plugins/openresty"
|
||||
"panel/app/http/controllers/plugins/php74"
|
||||
"panel/app/http/controllers/plugins/php80"
|
||||
"panel/app/http/controllers/plugins/php81"
|
||||
"panel/app/http/controllers/plugins/php82"
|
||||
"panel/app/http/controllers/plugins/phpmyadmin"
|
||||
"panel/app/http/controllers/plugins/pureftpd"
|
||||
"panel/app/http/controllers/plugins/redis"
|
||||
"panel/app/http/controllers/plugins/s3fs"
|
||||
"panel/app/http/controllers/plugins/supervisor"
|
||||
"panel/app/http/middleware"
|
||||
)
|
||||
|
||||
@@ -125,15 +131,114 @@ func Plugin() {
|
||||
route.Post("installExtension", php80Controller.InstallExtension)
|
||||
route.Post("uninstallExtension", php80Controller.UninstallExtension)
|
||||
})
|
||||
facades.Route().Prefix("api/plugins/php81").Middleware(middleware.Jwt()).Group(func(route route.Route) {
|
||||
php81Controller := php81.NewPhp81Controller()
|
||||
route.Get("status", php81Controller.Status)
|
||||
route.Post("reload", php81Controller.Reload)
|
||||
route.Post("start", php81Controller.Start)
|
||||
route.Post("stop", php81Controller.Stop)
|
||||
route.Post("restart", php81Controller.Restart)
|
||||
route.Get("load", php81Controller.Load)
|
||||
route.Get("config", php81Controller.GetConfig)
|
||||
route.Post("config", php81Controller.SaveConfig)
|
||||
route.Get("errorLog", php81Controller.ErrorLog)
|
||||
route.Get("slowLog", php81Controller.SlowLog)
|
||||
route.Post("clearErrorLog", php81Controller.ClearErrorLog)
|
||||
route.Post("clearSlowLog", php81Controller.ClearSlowLog)
|
||||
route.Get("extensions", php81Controller.GetExtensionList)
|
||||
route.Post("installExtension", php81Controller.InstallExtension)
|
||||
route.Post("uninstallExtension", php81Controller.UninstallExtension)
|
||||
})
|
||||
facades.Route().Prefix("api/plugins/php82").Middleware(middleware.Jwt()).Group(func(route route.Route) {
|
||||
php82Controller := php82.NewPhp82Controller()
|
||||
route.Get("status", php82Controller.Status)
|
||||
route.Post("reload", php82Controller.Reload)
|
||||
route.Post("start", php82Controller.Start)
|
||||
route.Post("stop", php82Controller.Stop)
|
||||
route.Post("restart", php82Controller.Restart)
|
||||
route.Get("load", php82Controller.Load)
|
||||
route.Get("config", php82Controller.GetConfig)
|
||||
route.Post("config", php82Controller.SaveConfig)
|
||||
route.Get("errorLog", php82Controller.ErrorLog)
|
||||
route.Get("slowLog", php82Controller.SlowLog)
|
||||
route.Post("clearErrorLog", php82Controller.ClearErrorLog)
|
||||
route.Post("clearSlowLog", php82Controller.ClearSlowLog)
|
||||
route.Get("extensions", php82Controller.GetExtensionList)
|
||||
route.Post("installExtension", php82Controller.InstallExtension)
|
||||
route.Post("uninstallExtension", php82Controller.UninstallExtension)
|
||||
})
|
||||
facades.Route().Prefix("api/plugins/phpmyadmin").Middleware(middleware.Jwt()).Group(func(route route.Route) {
|
||||
phpMyAdminController := phpmyadmin.NewPhpMyAdminController()
|
||||
route.Get("info", phpMyAdminController.Info)
|
||||
route.Post("port", phpMyAdminController.SetPort)
|
||||
})
|
||||
facades.Route().Prefix("api/plugins/pureftpd").Middleware(middleware.Jwt()).Group(func(route route.Route) {
|
||||
pureFtpdController := pureftpd.NewPureFtpdController()
|
||||
route.Get("status", pureFtpdController.Status)
|
||||
route.Post("reload", pureFtpdController.Reload)
|
||||
route.Post("start", pureFtpdController.Start)
|
||||
route.Post("stop", pureFtpdController.Stop)
|
||||
route.Post("restart", pureFtpdController.Restart)
|
||||
route.Get("list", pureFtpdController.List)
|
||||
route.Post("add", pureFtpdController.Add)
|
||||
route.Post("delete", pureFtpdController.Delete)
|
||||
route.Post("changePassword", pureFtpdController.ChangePassword)
|
||||
route.Get("port", pureFtpdController.GetPort)
|
||||
route.Post("port", pureFtpdController.SetPort)
|
||||
})
|
||||
facades.Route().Prefix("api/plugins/redis").Middleware(middleware.Jwt()).Group(func(route route.Route) {
|
||||
redisController := redis.NewRedisController()
|
||||
route.Get("status", redisController.Status)
|
||||
route.Post("reload", redisController.Reload)
|
||||
route.Post("start", redisController.Start)
|
||||
route.Post("stop", redisController.Stop)
|
||||
route.Post("restart", redisController.Restart)
|
||||
route.Get("load", redisController.Load)
|
||||
route.Get("config", redisController.GetConfig)
|
||||
route.Post("config", redisController.SaveConfig)
|
||||
})
|
||||
facades.Route().Prefix("api/plugins/s3fs").Middleware(middleware.Jwt()).Group(func(route route.Route) {
|
||||
s3fsController := s3fs.NewS3fsController()
|
||||
route.Get("list", s3fsController.List)
|
||||
route.Post("add", s3fsController.Add)
|
||||
route.Post("delete", s3fsController.Delete)
|
||||
})
|
||||
facades.Route().Prefix("api/plugins/supervisor").Middleware(middleware.Jwt()).Group(func(route route.Route) {
|
||||
supervisorController := supervisor.NewSupervisorController()
|
||||
route.Get("status", supervisorController.Status)
|
||||
route.Post("start", supervisorController.Start)
|
||||
route.Post("stop", supervisorController.Stop)
|
||||
route.Post("restart", supervisorController.Restart)
|
||||
route.Post("reload", supervisorController.Reload)
|
||||
route.Get("log", supervisorController.Log)
|
||||
route.Post("clearLog", supervisorController.ClearLog)
|
||||
route.Get("config", supervisorController.Config)
|
||||
route.Post("config", supervisorController.SaveConfig)
|
||||
route.Get("processes", supervisorController.Processes)
|
||||
route.Post("startProcess", supervisorController.StartProcess)
|
||||
route.Post("stopProcess", supervisorController.StopProcess)
|
||||
route.Post("restartProcess", supervisorController.RestartProcess)
|
||||
route.Get("processLog", supervisorController.ProcessLog)
|
||||
route.Post("clearProcessLog", supervisorController.ClearProcessLog)
|
||||
route.Get("processConfig", supervisorController.ProcessConfig)
|
||||
route.Post("processConfig", supervisorController.SaveProcessConfig)
|
||||
route.Post("deleteProcess", supervisorController.DeleteProcess)
|
||||
route.Post("addProcess", supervisorController.AddProcess)
|
||||
|
||||
})
|
||||
facades.Route().Prefix("api/plugins/fail2ban").Middleware(middleware.Jwt()).Group(func(route route.Route) {
|
||||
fail2banController := fail2ban.NewFail2banController()
|
||||
route.Get("status", fail2banController.Status)
|
||||
route.Post("start", fail2banController.Start)
|
||||
route.Post("stop", fail2banController.Stop)
|
||||
route.Post("restart", fail2banController.Restart)
|
||||
route.Post("reload", fail2banController.Reload)
|
||||
route.Get("list", fail2banController.List)
|
||||
route.Post("add", fail2banController.Add)
|
||||
route.Post("delete", fail2banController.Delete)
|
||||
route.Get("ban", fail2banController.BanList)
|
||||
route.Post("unban", fail2banController.Unban)
|
||||
route.Post("whiteList", fail2banController.SetWhiteList)
|
||||
route.Get("whiteList", fail2banController.GetWhiteList)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ fi
|
||||
# 修改 fail2ban 配置文件
|
||||
sed -i 's!# logtarget.*!logtarget = /var/log/fail2ban.log!' /etc/fail2ban/fail2ban.conf
|
||||
sed -i 's!logtarget\s*=.*!logtarget = /var/log/fail2ban.log!' /etc/fail2ban/jail.conf
|
||||
cat >/etc/fail2ban/jail.local <<EOF
|
||||
cat > /etc/fail2ban/jail.local << EOF
|
||||
[DEFAULT]
|
||||
ignoreip = 127.0.0.1/8
|
||||
bantime = 600
|
||||
@@ -78,8 +78,8 @@ if [ "${sshPort}" == "" ]; then
|
||||
sshPort="22"
|
||||
fi
|
||||
sed -i "s/port = 22/port = ${sshPort}/g" /etc/fail2ban/jail.local
|
||||
if [ -f "/etc/pure-ftpd/pure-ftpd.conf" ]; then
|
||||
ftpPort=$(cat /etc/pure-ftpd/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}')
|
||||
if [ -f "/www/server/pure-ftpd/etc/pure-ftpd.conf" ]; then
|
||||
ftpPort=$(cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}')
|
||||
fi
|
||||
if [ "${ftpPort}" == "" ]; then
|
||||
ftpPort="21"
|
||||
@@ -87,10 +87,17 @@ if [ "${ftpPort}" == "" ]; then
|
||||
else
|
||||
sed -i "s/port = 21/port = ${ftpPort}/g" /etc/fail2ban/jail.local
|
||||
fi
|
||||
|
||||
# Debian 的特殊处理
|
||||
if [ "${OS}" == "debian" ]; then
|
||||
sed -i "s/\/var\/log\/secure/\/var\/log\/auth.log/g" /etc/fail2ban/jail.local
|
||||
sed -i "s/banaction = firewallcmd-ipset/banaction = ufw/g" /etc/fail2ban/jail.local
|
||||
fi
|
||||
|
||||
# 启动 fail2ban
|
||||
systemctl unmask fail2ban
|
||||
systemctl daemon-reload
|
||||
systemctl enable fail2ban
|
||||
systemctl restart fail2ban
|
||||
|
||||
panel writePlugin fail2ban
|
||||
panel writePlugin fail2ban 1.0.0
|
||||
|
||||
@@ -20,6 +20,8 @@ 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")
|
||||
|
||||
fail2ban-client unban --all
|
||||
fail2ban-client stop
|
||||
systemctl stop fail2ban
|
||||
systemctl disable fail2ban
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
: '
|
||||
Copyright 2022 HaoZi Technology Co., Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
'
|
||||
|
||||
HR="+----------------------------------------------------"
|
||||
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
|
||||
|
||||
if [ "${OS}" == "centos" ]; then
|
||||
dnf install -y fail2ban
|
||||
elif [ "${OS}" == "debian" ]; then
|
||||
apt install -y fail2ban
|
||||
else
|
||||
echo -e $HR
|
||||
echo "错误:不支持的操作系统"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:fail2ban安装失败,请截图错误信息寻求帮助。"
|
||||
exit 1
|
||||
fi
|
||||
@@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
'
|
||||
|
||||
LOGO="+----------------------------------------------------\n| 耗子面板安装脚本\n+----------------------------------------------------\n| Copyright © 2022-"$(date +%Y)" 耗子科技 All rights reserved.\n+----------------------------------------------------"
|
||||
LOGO="+----------------------------------------------------\n| 耗子Linux面板安装脚本\n+----------------------------------------------------\n| Copyright © 2022-"$(date +%Y)" 耗子科技 All rights reserved.\n+----------------------------------------------------"
|
||||
HR="+----------------------------------------------------"
|
||||
download_Url=""
|
||||
setup_Path="/www"
|
||||
@@ -35,26 +35,26 @@ Prepare_system() {
|
||||
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
|
||||
if [ "${OS}" == "unknown" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:该系统不支持安装耗子面板,请更换Debian12/RHEL9安装。"
|
||||
echo "错误:该系统不支持安装耗子Linux面板,请更换Debian12/RHEL9安装。"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${ARCH}" != "x86_64" ] && [ "${ARCH}" != "aarch64" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:该系统架构不支持安装耗子面板,请更换x86_64/aarch64架构安装。"
|
||||
echo "错误:该系统架构不支持安装耗子Linux面板,请更换x86_64/aarch64架构安装。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
is64bit=$(getconf LONG_BIT)
|
||||
if [ "${is64bit}" != '64' ]; then
|
||||
echo -e $HR
|
||||
echo "错误:32位系统不支持安装耗子面板,请更换64位系统安装。"
|
||||
echo "错误:32位系统不支持安装耗子Linux面板,请更换64位系统安装。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
isInstalled=$(systemctl status panel 2>&1 | grep "Active")
|
||||
if [ "${isInstalled}" != "" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:耗子面板已安装,请勿重复安装。"
|
||||
echo "错误:耗子Linux面板已安装,请勿重复安装。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -130,7 +130,7 @@ Prepare_system() {
|
||||
apt install -y curl wget zip unzip tar git jq git dos2unix
|
||||
else
|
||||
echo -e $HR
|
||||
echo "错误:该系统不支持安装耗子面板,请更换Debian12/RHEL9安装。"
|
||||
echo "错误:该系统不支持安装耗子Linux面板,请更换Debian12/RHEL9安装。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -170,12 +170,12 @@ Init_Panel() {
|
||||
rm -rf ${setup_Path}/panel/*
|
||||
# 下载面板zip包并解压
|
||||
if [ "${ARCH}" == "x86_64" ]; then
|
||||
panelZip=$(curl "https://api.github.com/repos/HaoZi-Team/Panel/releases/latest" | jq -r '.assets[] | select(.name | contains("amd64v3")) | .browser_download_url')
|
||||
panelZip=$(curl "https://api.github.com/repos/haozi-team/panel/releases/latest" | jq -r '.assets[] | select(.name | contains("amd64v2")) | .browser_download_url')
|
||||
elif [ "${ARCH}" == "aarch64" ]; then
|
||||
panelZip=$(curl "https://api.github.com/repos/HaoZi-Team/Panel/releases/latest" | jq -r '.assets[] | select(.name | contains("arm64")) | .browser_download_url')
|
||||
panelZip=$(curl "https://api.github.com/repos/haozi-team/panel/releases/latest" | jq -r '.assets[] | select(.name | contains("arm64")) | .browser_download_url')
|
||||
else
|
||||
echo -e $HR
|
||||
echo "错误:该系统架构不支持安装耗子面板,请更换x86_64/aarch64架构安装。"
|
||||
echo "错误:该系统架构不支持安装耗子Linux面板,请更换x86_64/aarch64架构安装。"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$?" != "0" ] || [ "${panelZip}" == "" ]; then
|
||||
|
||||
@@ -33,7 +33,7 @@ rm -f /usr/lib64/libmysql*
|
||||
userdel -r mysql
|
||||
groupdel mysql
|
||||
|
||||
sed -i '#export PATH=/www/server/mysql#d' /etc/profile
|
||||
sed -i '/export PATH=\/www\/server\/mysql/d' /etc/profile
|
||||
source /etc/profile
|
||||
|
||||
panel deletePlugin mysql${1}
|
||||
|
||||
@@ -23,14 +23,14 @@ OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } ||
|
||||
downloadUrl="https://dl.cdn.haozi.net/panel/openresty"
|
||||
setupPath="/www"
|
||||
openrestyPath="${setupPath}/server/openresty"
|
||||
openrestyVersion="1.21.4.1"
|
||||
openrestyVersion="1.21.4.2"
|
||||
cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l)
|
||||
ipLocation=$(curl -s https://ip.ping0.cc/geo)
|
||||
|
||||
# 安装依赖
|
||||
if [ "${OS}" == "centos" ]; then
|
||||
# EPEL 9 仓库中目前没有 GeoIP-devel 包,暂时使用 Remi 仓库
|
||||
dnf install https://rpms.remirepo.net/enterprise/remi-release-9.rpm
|
||||
dnf install https://rpms.remirepo.net/enterprise/remi-release-9.rpm -y
|
||||
if [[ ${ipLocation} =~ "中国" ]]; then
|
||||
sed -e 's!^#mirrorlist=!mirrorlist=!g' -e 's!^mirrorlist=!#mirrorlist=!g' -e 's!^#baseurl=!baseurl=!g' -e 's!http://rpms.remirepo.net/enterprise!https://mirrors.tuna.tsinghua.edu.cn/remi/enterprise!g' -i /etc/yum.repos.d/remi*
|
||||
fi
|
||||
@@ -133,7 +133,7 @@ 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}/ngx_waf
|
||||
./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="-std=gnu99" --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
|
||||
@@ -320,12 +320,13 @@ chmod -R 644 /www/server/vhost
|
||||
echo "" > ${openrestyPath}/conf/enable-php-0.conf
|
||||
|
||||
# 自动为所有PHP版本创建配置文件
|
||||
cd ${setupPath}/server/php
|
||||
phpList=$(ls -l | grep ^d | awk '{print $NF}')
|
||||
for phpVersion in ${phpList}; do
|
||||
if [ -d "${setupPath}/server/php/${phpVersion}" ]; then
|
||||
# 写入PHP配置文件
|
||||
cat > ${openrestyPath}/conf/enable-php-${phpVersion}.conf << EOF
|
||||
if [ -d "${setupPath}/server/php" ]; then
|
||||
cd ${setupPath}/server/php
|
||||
phpList=$(ls -l | grep ^d | awk '{print $NF}')
|
||||
for phpVersion in ${phpList}; do
|
||||
if [ -d "${setupPath}/server/php/${phpVersion}" ]; then
|
||||
# 写入PHP配置文件
|
||||
cat > ${openrestyPath}/conf/enable-php-${phpVersion}.conf << EOF
|
||||
location ~ \.php$ {
|
||||
try_files \$uri =404;
|
||||
fastcgi_pass unix:/tmp/php-cgi-${phpVersion}.sock;
|
||||
@@ -334,8 +335,9 @@ location ~ \.php$ {
|
||||
include pathinfo.conf;
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# 写入代理默认配置文件
|
||||
cat > ${openrestyPath}/conf/proxy.conf << EOF
|
||||
|
||||
@@ -223,8 +223,8 @@ sed -i 's/display_errors = Off/display_errors = On/g' ${phpPath}/etc/php.ini
|
||||
sed -i 's/error_reporting =.*/error_reporting = E_ALL \& \~E_NOTICE/g' ${phpPath}/etc/php.ini
|
||||
|
||||
# 设置SSL根证书
|
||||
sed -i "s#;openssl.cafile=#openssl.cafile=/etc/pki/tls/certs/ca-bundle.crt#" ${phpPath}/etc/php.ini
|
||||
sed -i "s#;curl.cainfo =#curl.cainfo = /etc/pki/tls/certs/ca-bundle.crt#" ${phpPath}/etc/php.ini
|
||||
#sed -i "s#;openssl.cafile=#openssl.cafile=/etc/pki/tls/certs/ca-bundle.crt#" ${phpPath}/etc/php.ini
|
||||
#sed -i "s#;curl.cainfo =#curl.cainfo = /etc/pki/tls/certs/ca-bundle.crt#" ${phpPath}/etc/php.ini
|
||||
|
||||
# 关闭php外显
|
||||
sed -i 's/expose_php = On/expose_php = Off/g' ${phpPath}/etc/php.ini
|
||||
|
||||
@@ -38,7 +38,7 @@ Install() {
|
||||
if [ "${OS}" == "centos" ]; then
|
||||
dnf install ImageMagick ImageMagick-devel -y
|
||||
elif [ "${OS}" == "debian" ]; then
|
||||
apt install imagemagick libmagickwand-dev
|
||||
apt install imagemagick libmagickwand-dev -y
|
||||
else
|
||||
echo -e $HR
|
||||
echo "错误:耗子Linux面板不支持该系统"
|
||||
|
||||
@@ -36,7 +36,7 @@ Install() {
|
||||
cd /www/server/php/${phpVersion}/src/ext
|
||||
rm -rf phpredis
|
||||
rm -rf phpredis.tar.gz
|
||||
wget -O phpredis.tar.gz ${downloadUrl}/php-ext/phpredis-${phpredisVersion}.tar.gz
|
||||
wget -O phpredis.tar.gz ${downloadUrl}/phpredis-${phpredisVersion}.tar.gz
|
||||
tar -zxvf phpredis.tar.gz
|
||||
mv phpredis-${phpredisVersion} phpredis
|
||||
cd phpredis
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
: '
|
||||
Copyright 2022 HaoZi Technology Co., Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
'
|
||||
|
||||
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/pure-ftpd"
|
||||
setupPath="/www"
|
||||
pureftpdPath="${setupPath}/server/pure-ftpd"
|
||||
pureftpdVersion="1.0.50"
|
||||
|
||||
# 准备安装目录
|
||||
rm -rf ${pureftpdPath}
|
||||
mkdir -p ${pureftpdPath}
|
||||
cd ${pureftpdPath}
|
||||
|
||||
wget -O ${pureftpdPath}/pure-ftpd-${pureftpdVersion}.tar.gz ${downloadUrl}/pure-ftpd-${pureftpdVersion}.tar.gz
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Pure-Ftpd-${pureftpdVersion}下载失败,请检查网络是否正常。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tar -xvf pure-ftpd-${pureftpdVersion}.tar.gz
|
||||
rm -f pure-ftpd-${pureftpdVersion}.tar.gz
|
||||
mv pure-ftpd-${pureftpdVersion} src
|
||||
cd src
|
||||
|
||||
./configure --prefix=${pureftpdPath} CFLAGS=-O2 --with-puredb --with-quotas --with-cookie --with-virtualhosts --with-diraliases --with-sysquotas --with-ratios --with-altlog --with-paranoidmsg --with-shadow --with-welcomemsg --with-throttling --with-uploadscript --with-language=simplified-chinese --with-rfc2640 --with-ftpwho --with-tls
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Pure-Ftpd-${pureftpdVersion}编译配置失败,请截图错误信息寻求帮助。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Pure-Ftpd-${pureftpdVersion}编译失败,请截图错误信息寻求帮助。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make install
|
||||
if [ ! -f "${pureftpdPath}/bin/pure-pw" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Pure-Ftpd-${pureftpdVersion}安装失败,请截图错误信息寻求帮助。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 修改 pure-ftpd 配置文件
|
||||
sed -i "s!# PureDB\s*/etc/pureftpd.pdb!PureDB ${pureftpdPath}/etc/pureftpd.pdb!" ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i 's!# ChrootEveryone\s*yes!ChrootEveryone yes!' ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i 's!NoAnonymous\s*no!NoAnonymous yes!' ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i 's!AnonymousCanCreateDirs\s*yes!AnonymousCanCreateDirs no!' ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i 's!AnonymousCantUpload\s*yes!AnonymousCantUpload no!' ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i 's!PAMAuthentication\s*yes!PAMAuthentication no!' ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i 's!UnixAuthentication\s*yes!UnixAuthentication no!' ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i 's!# PassivePortRange\s*30000 50000!PassivePortRange 39000 40000!' ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i 's!PassivePortRange\s*30000 50000!PassivePortRange 39000 40000!' ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i 's!LimitRecursion\s*10000 8!LimitRecursion 20000 8!' ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i 's!# TLS!TLS!' ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i "s!# CertFile\s*/etc/ssl/private/pure-ftpd.pem!CertFile ${pureftpdPath}/etc/pure-ftpd.pem!" ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
sed -i 's!# Bind\s*127.0.0.1,21!Bind 0.0.0.0,21!' ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
touch ${pureftpdPath}/etc/pureftpd.passwd
|
||||
touch ${pureftpdPath}/etc/pureftpd.pdb
|
||||
|
||||
openssl dhparam -out ${pureftpdPath}/etc/pure-ftpd-dhparams.pem 2048
|
||||
openssl req -x509 -nodes -days 3560 -newkey rsa:2048 -sha256 -keyout ${pureftpdPath}/etc/pure-ftpd.pem -out ${pureftpdPath}/etc/pure-ftpd.pem << EOF
|
||||
CN
|
||||
Beijing
|
||||
Beijing
|
||||
HaoZi Technology Co., Ltd
|
||||
HaoZi Panel
|
||||
github.com/haozi-team/panel
|
||||
panel@haozi.net
|
||||
EOF
|
||||
chmod 600 ${pureftpdPath}/etc/*.pem
|
||||
|
||||
# 添加系统服务
|
||||
ln -sf ${pureftpdPath}/bin/pure-pw /usr/bin/pure-pw
|
||||
|
||||
cat > /etc/systemd/system/pure-ftpd.service << EOF
|
||||
[Unit]
|
||||
Description=Pure-FTPd FTP server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
PIDFile=/var/run/pure-ftpd.pid
|
||||
ExecStart=${pureftpdPath}/sbin/pure-ftpd ${pureftpdPath}/etc/pure-ftpd.conf
|
||||
ExecReload=/bin/kill -HUP \$MAINPID
|
||||
ExecStop=/bin/kill -TERM \$MAINPID
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# 添加防火墙规则
|
||||
if [ "${OS}" == "centos" ]; then
|
||||
firewall-cmd --zone=public --add-port=21/tcp --permanent
|
||||
firewall-cmd --zone=public --add-port=39000-40000/tcp --permanent
|
||||
firewall-cmd --reload
|
||||
elif [ "${OS}" == "debian" ]; then
|
||||
ufw allow 21/tcp
|
||||
ufw allow 39000:40000/tcp
|
||||
ufw reload
|
||||
fi
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable pure-ftpd.service
|
||||
systemctl start pure-ftpd.service
|
||||
|
||||
panel writePlugin pureftpd 1.0.50
|
||||
|
||||
echo -e "${HR}\nPure-Ftpd-${pureftpdVersion} 安装完成\n${HR}"
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
: '
|
||||
Copyright 2022 HaoZi Technology Co., Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
'
|
||||
|
||||
HR="+----------------------------------------------------"
|
||||
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
|
||||
|
||||
systemctl stop pure-ftpd
|
||||
systemctl disable pure-ftpd
|
||||
rm -f /etc/systemd/system/pure-ftpd.service
|
||||
systemctl daemon-reload
|
||||
pkill -9 pure-ftpd
|
||||
rm -rf /www/server/pure-ftpd
|
||||
rm -f /usr/bin/pure-pw
|
||||
|
||||
panel deletePlugin pureftpd
|
||||
|
||||
echo -e "${HR}\nPure-Ftpd 卸载完成\n${HR}"
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
: '
|
||||
Copyright 2022 HaoZi Technology Co., Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
'
|
||||
|
||||
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/pure-ftpd"
|
||||
setupPath="/www"
|
||||
pureftpdPath="${setupPath}/server/pure-ftpd"
|
||||
pureftpdVersion="1.0.50"
|
||||
|
||||
# 准备安装目录
|
||||
cp ${pureftpdPath}/etc/pureftpd.passwd /tmp/pureftpd.passwd
|
||||
cp ${pureftpdPath}/etc/pureftpd.pdb /tmp/pureftpd.pdb
|
||||
cp ${pureftpdPath}/etc/pureftpd.conf /tmp/pureftpd.conf
|
||||
systemctl stop pure-ftpd.service
|
||||
rm -rf ${pureftpdPath}
|
||||
mkdir -p ${pureftpdPath}
|
||||
cd ${pureftpdPath}
|
||||
|
||||
wget -O ${pureftpdPath}/pure-ftpd-${pureftpdVersion}.tar.gz ${downloadUrl}/pure-ftpd-${pureftpdVersion}.tar.gz
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Pure-Ftpd-${pureftpdVersion}下载失败,请检查网络是否正常。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tar -xvf pure-ftpd-${pureftpdVersion}.tar.gz
|
||||
rm -f pure-ftpd-${pureftpdVersion}.tar.gz
|
||||
mv pure-ftpd-${pureftpdVersion} src
|
||||
cd src
|
||||
|
||||
./configure --prefix=${pureftpdPath} CFLAGS=-O2 --with-puredb --with-quotas --with-cookie --with-virtualhosts --with-diraliases --with-sysquotas --with-ratios --with-altlog --with-paranoidmsg --with-shadow --with-welcomemsg --with-throttling --with-uploadscript --with-language=simplified-chinese --with-rfc2640 --with-ftpwho --with-tls
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Pure-Ftpd-${pureftpdVersion}编译配置失败,请截图错误信息寻求帮助。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Pure-Ftpd-${pureftpdVersion}编译失败,请截图错误信息寻求帮助。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make install
|
||||
if [ ! -f "${pureftpdPath}/bin/pure-pw" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Pure-Ftpd-${pureftpdVersion}安装失败,请截图错误信息寻求帮助。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 还原配置
|
||||
cp /tmp/pureftpd.passwd ${pureftpdPath}/etc/pureftpd.passwd
|
||||
cp /tmp/pureftpd.pdb ${pureftpdPath}/etc/pureftpd.pdb
|
||||
cp /tmp/pureftpd.conf ${pureftpdPath}/etc/pureftpd.conf
|
||||
|
||||
systemctl start pure-ftpd.service
|
||||
|
||||
panel writePlugin pureftpd 1.0.50
|
||||
|
||||
echo -e "${HR}\nPure-Ftpd-${pureftpdVersion} 升级完成\n${HR}"
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
: '
|
||||
Copyright 2022 HaoZi Technology Co., Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
'
|
||||
|
||||
HR="+----------------------------------------------------"
|
||||
ARCH=$(uname -m)
|
||||
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/redis"
|
||||
setupPath="/www"
|
||||
redisPath="${setupPath}/server/redis"
|
||||
redisVersion="7.0.12"
|
||||
cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l)
|
||||
|
||||
if ! id -u "redis" > /dev/null 2>&1; then
|
||||
groupadd redis
|
||||
useradd -s /sbin/nologin -g redis redis
|
||||
fi
|
||||
|
||||
# 安装依赖
|
||||
if [ "${OS}" == "centos" ]; then
|
||||
dnf makecache -y
|
||||
dnf groupinstall "Development Tools" -y
|
||||
dnf install systemd-devel openssl-devel -y
|
||||
elif [ "${OS}" == "debian" ]; then
|
||||
apt update
|
||||
apt install build-essential libsystemd-dev libssl-dev -y
|
||||
else
|
||||
echo -e $HR
|
||||
echo "错误:耗子Linux面板不支持该系统"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 准备目录
|
||||
rm -rf ${redisPath}
|
||||
mkdir -p ${redisPath}
|
||||
cd ${redisPath}
|
||||
|
||||
# 下载源码
|
||||
wget -T 120 -O ${redisPath}/redis-${redisVersion}.tar.gz ${downloadUrl}/redis-${redisVersion}.tar.gz
|
||||
tar -zxvf redis-${redisVersion}.tar.gz
|
||||
rm -f redis-${redisVersion}.tar.gz
|
||||
mv redis-${redisVersion}/* ./ && rm -rf redis-${redisVersion}
|
||||
mkdir -p ${redisPath}/bin
|
||||
|
||||
make BUILD_TLS=yes USE_SYSTEMD=yes -j${cpuCore}
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Redis编译失败,请截图错误信息寻求帮助。"
|
||||
rm -rf ${redisPath}
|
||||
exit 1
|
||||
fi
|
||||
make PREFIX=${redisPath} install
|
||||
if [ ! -f "${redisPath}/bin/redis-server" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Redis安装失败,请截图错误信息寻求帮助。"
|
||||
rm -rf ${redisPath}
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 设置软链接
|
||||
ln -sf ${redisPath}/bin/redis-cli /usr/bin/redis-cli
|
||||
ln -sf ${redisPath}/bin/redis-server /usr/bin/redis-server
|
||||
ln -sf ${redisPath}/bin/redis-sentinel /usr/bin/redis-sentinel
|
||||
ln -sf ${redisPath}/bin/redis-benchmark /usr/bin/redis-benchmark
|
||||
ln -sf ${redisPath}/bin/redis-check-aof /usr/bin/redis-check-aof
|
||||
ln -sf ${redisPath}/bin/redis-check-rdb /usr/bin/redis-check-rdb
|
||||
|
||||
# 设置配置文件
|
||||
VM_OVERCOMMIT_MEMORY=$(cat /etc/sysctl.conf|grep vm.overcommit_memory)
|
||||
NET_CORE_SOMAXCONN=$(cat /etc/sysctl.conf|grep net.core.somaxconn)
|
||||
if [ -z "${VM_OVERCOMMIT_MEMORY}" ] && [ -z "${NET_CORE_SOMAXCONN}" ];then
|
||||
echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf
|
||||
echo "net.core.somaxconn = 1024" >> /etc/sysctl.conf
|
||||
sysctl -p
|
||||
fi
|
||||
|
||||
sed -i 's/dir .\//dir \/www\/server\/redis\//g' ${redisPath}/redis.conf
|
||||
sed -i 's/# supervised.*/supervised systemd/g' ${redisPath}/redis.conf
|
||||
sed -i 's/daemonize.*/daemonize no/g' ${redisPath}/redis.conf
|
||||
|
||||
if [ "${ARCH}" == "aarch64" ]; then
|
||||
echo "ignore-warnings ARM64-COW-BUG" >> ${redisPath}/redis.conf
|
||||
fi
|
||||
|
||||
chown -R redis:redis ${redisPath}
|
||||
chmod -R 755 ${redisPath}
|
||||
|
||||
# 设置服务
|
||||
cp -r utils/systemd-redis_server.service /etc/systemd/system/redis.service
|
||||
sed -i "s!ExecStart=.*!ExecStart=${redisPath}/bin/redis-server ${redisPath}/redis.conf!g" /etc/systemd/system/redis.service
|
||||
sed -i "s!#User=.*!User=redis!g" /etc/systemd/system/redis.service
|
||||
sed -i "s!#Group=.*!Group=redis!g" /etc/systemd/system/redis.service
|
||||
sed -i "s!#WorkingDirectory=.*!WorkingDirectory=${redisPath}!g" /etc/systemd/system/redis.service
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable redis
|
||||
systemctl start redis
|
||||
|
||||
panel writePlugin redis ${redisVersion}
|
||||
|
||||
echo -e "${HR}\nRedis 安装完成\n${HR}"
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
: '
|
||||
Copyright 2022 HaoZi Technology Co., Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
'
|
||||
|
||||
HR="+----------------------------------------------------"
|
||||
|
||||
systemctl stop redis
|
||||
systemctl disable redis
|
||||
rm -rf /etc/systemd/system/redis.service
|
||||
systemctl daemon-reload
|
||||
pkill -9 redis
|
||||
rm -rf /www/server/redis
|
||||
|
||||
rm -rf /usr/bin/redis-cli
|
||||
rm -rf /usr/bin/redis-server
|
||||
rm -rf /usr/bin/redis-benchmark
|
||||
rm -rf /usr/bin/redis-check-aof
|
||||
rm -rf /usr/bin/redis-check-rdb
|
||||
rm -rf /usr/bin/redis-sentinel
|
||||
|
||||
panel deletePlugin redis
|
||||
|
||||
echo -e "${HR}\nRedis 卸载完成\n${HR}"
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
: '
|
||||
Copyright 2022 HaoZi Technology Co., Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
'
|
||||
|
||||
HR="+----------------------------------------------------"
|
||||
ARCH=$(uname -m)
|
||||
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/redis"
|
||||
setupPath="/www"
|
||||
redisPath="${setupPath}/server/redis"
|
||||
redisVersion="7.0.12"
|
||||
cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l)
|
||||
|
||||
# 准备目录
|
||||
cd ${redisPath}
|
||||
|
||||
# 下载源码
|
||||
wget -T 120 -O ${redisPath}/redis-${redisVersion}.tar.gz ${downloadUrl}/redis-${redisVersion}.tar.gz
|
||||
tar -zxvf redis-${redisVersion}.tar.gz
|
||||
rm -f redis-${redisVersion}.tar.gz
|
||||
mv redis-${redisVersion}/* ./ && rm -rf redis-${redisVersion}
|
||||
mkdir -p ${redisPath}/bin
|
||||
|
||||
make BUILD_TLS=yes USE_SYSTEMD=yes -j${cpuCore}
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Redis编译失败,请截图错误信息寻求帮助。"
|
||||
rm -rf ${redisPath}
|
||||
exit 1
|
||||
fi
|
||||
make PREFIX=${redisPath} install
|
||||
if [ ! -f "${redisPath}/bin/redis-server" ]; then
|
||||
echo -e $HR
|
||||
echo "错误:Redis安装失败,请截图错误信息寻求帮助。"
|
||||
rm -rf ${redisPath}
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 设置软链接
|
||||
ln -sf ${redisPath}/bin/redis-cli /usr/bin/redis-cli
|
||||
ln -sf ${redisPath}/bin/redis-server /usr/bin/redis-server
|
||||
ln -sf ${redisPath}/bin/redis-sentinel /usr/bin/redis-sentinel
|
||||
ln -sf ${redisPath}/bin/redis-benchmark /usr/bin/redis-benchmark
|
||||
ln -sf ${redisPath}/bin/redis-check-aof /usr/bin/redis-check-aof
|
||||
ln -sf ${redisPath}/bin/redis-check-rdb /usr/bin/redis-check-rdb
|
||||
|
||||
# 设置配置文件
|
||||
sed -i 's/dir .\//dir \/www\/server\/redis\//g' ${redisPath}/redis.conf
|
||||
sed -i 's/# supervised.*/supervised systemd/g' ${redisPath}/redis.conf
|
||||
sed -i 's/daemonize.*/daemonize no/g' ${redisPath}/redis.conf
|
||||
|
||||
if [ "${ARCH}" == "aarch64" ]; then
|
||||
echo "ignore-warnings ARM64-COW-BUG" >> ${redisPath}/redis.conf
|
||||
fi
|
||||
|
||||
chown -R redis:redis ${redisPath}
|
||||
chmod -R 755 ${redisPath}
|
||||
|
||||
# 设置服务
|
||||
cp -r utils/systemd-redis_server.service /etc/systemd/system/redis.service
|
||||
sed -i "s!ExecStart=.*!ExecStart=${redisPath}/bin/redis-server ${redisPath}/redis.conf!g" /etc/systemd/system/redis.service
|
||||
sed -i "s!#User=.*!User=redis!g" /etc/systemd/system/redis.service
|
||||
sed -i "s!#Group=.*!Group=redis!g" /etc/systemd/system/redis.service
|
||||
sed -i "s!#WorkingDirectory=.*!WorkingDirectory=${redisPath}!g" /etc/systemd/system/redis.service
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl restart redis
|
||||
|
||||
panel writePlugin redis ${redisVersion}
|
||||
|
||||
echo -e "${HR}\nRedis 升级完成\n${HR}"
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
: '
|
||||
Copyright 2022 HaoZi Technology Co., Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
'
|
||||
|
||||
HR="+----------------------------------------------------"
|
||||
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
|
||||
|
||||
if [ "${OS}" == "centos" ]; then
|
||||
dnf install -y supervisor
|
||||
sed -i 's#files = supervisord.d/\*.ini#files = supervisord.d/*.conf#g' /etc/supervisord.conf
|
||||
systemctl enable supervisord
|
||||
systemctl start supervisord
|
||||
elif [ "${OS}" == "debian" ]; then
|
||||
apt install -y supervisor
|
||||
systemctl enable supervisor
|
||||
systemctl start supervisor
|
||||
else
|
||||
echo -e $HR
|
||||
echo "错误:不支持的操作系统"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
panel writePlugin supervisor 4.2.5
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
: '
|
||||
Copyright 2022 HaoZi Technology Co., Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
'
|
||||
|
||||
HR="+----------------------------------------------------"
|
||||
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
|
||||
|
||||
if [ "${OS}" == "centos" ]; then
|
||||
dnf remove -y supervisor
|
||||
elif [ "${OS}" == "debian" ]; then
|
||||
apt remove -y supervisor
|
||||
else
|
||||
echo -e $HR
|
||||
echo "错误:不支持的操作系统"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
panel deletePlugin supervisor
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
: '
|
||||
Copyright 2022 HaoZi Technology Co., Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
'
|
||||
|
||||
HR="+----------------------------------------------------"
|
||||
OldVersion=$(panel getSetting version)
|
||||
Reference in New Issue
Block a user