Compare commits

...

77 Commits

Author SHA1 Message Date
耗子 db65a493dc feat: release v2.0.31 2023-08-13 16:01:00 +08:00
耗子 4dd99a149c feat: escape log output 2023-08-13 15:50:22 +08:00
耗子 5d1fbb2e2b feat: postgresql plugin 2023-08-13 15:37:23 +08:00
耗子 94569cca02 fix: mysql check 2023-08-13 11:21:30 +08:00
耗子 905aaf102b feat: update dependence 2023-08-10 02:17:25 +08:00
耗子 d91aa666cc feat: postgresql install script 2023-08-10 01:56:01 +08:00
耗子 4f90bbc177 workflow: optimize auto reply 2023-08-10 01:39:02 +08:00
耗子 f6c3bd27fd feat: release v2.0.30 2023-08-09 02:16:44 +08:00
耗子 137c5ceba4 fix: php redis script 2023-08-09 02:16:22 +08:00
耗子 c6f5b99373 docs: update readme 2023-08-09 01:12:27 +08:00
耗子 d7f647a8db feat: release v2.0.29 2023-08-09 00:55:07 +08:00
耗子 77277a6af6 feat: optimize config save 2023-08-09 00:54:38 +08:00
耗子 d9acaec3c5 feat: redis plugin 2023-08-09 00:50:45 +08:00
耗子 c8692844b6 workflow: delete 2023-08-08 02:31:14 +08:00
耗子 0b49301e9e feat: go mod tidy 2023-08-08 02:30:59 +08:00
耗子 30fded4552 ci: update 2023-08-08 02:00:30 +08:00
耗子 9b3b76d1d0 Merge branch 'renovate/github.com-imroc-req-v3-3.x' into 'main'
fix(deps): update module github.com/imroc/req/v3 to v3.40.1

See merge request haozi-team/panel!4
2023-08-07 14:11:35 +08:00
Renovate f37f22b86b fix(deps): update module github.com/imroc/req/v3 to v3.40.1 2023-08-07 05:20:46 +00:00
耗子 52eedad3da Merge branch 'renovate/github.com-iancoleman-strcase-0.x' into 'main'
fix(deps): update module github.com/iancoleman/strcase to v0.3.0

See merge request haozi-team/panel!3
2023-08-07 13:05:19 +08:00
耗子 0e2dda597f Merge branch 'renovate/golang.org-x-crypto-0.x' into 'main'
fix(deps): update module golang.org/x/crypto to v0.12.0

See merge request haozi-team/panel!5
2023-08-07 13:04:48 +08:00
Renovate 313511b474 fix(deps): update module golang.org/x/crypto to v0.12.0 2023-08-06 19:24:11 +00:00
Renovate d2ed2b5082 fix(deps): update module github.com/iancoleman/strcase to v0.3.0 2023-08-06 18:52:46 +00:00
耗子 cce19e1a64 Merge branch 'renovate/configure' into 'main'
Configure Renovate

See merge request haozi-team/panel!1
2023-08-07 02:13:45 +08:00
Renovate 62b84a8909 Add renovate.json 2023-08-06 17:58:04 +00:00
耗子 ff7b4f5f3b ci: delete 2023-08-07 01:26:40 +08:00
耗子 43a8d8cc2a ci: fix 2023-08-07 01:01:10 +08:00
耗子 bf09ffd316 workflow: delete unnecessary workflows 2023-08-07 00:30:16 +08:00
耗子 b0d08e2901 feat: panel update script 2023-08-05 16:10:24 +08:00
耗子 62684ccfda feat: release v2.0.28 2023-08-05 14:19:59 +08:00
耗子 6353231f4a feat: update openresty 1.21.4.2 2023-08-05 14:19:31 +08:00
耗子 6abd6b139f docs: Update README.md 2023-08-05 01:51:22 +08:00
耗子 40358b2551 fix(website): reload openresty after save raw config 2023-08-04 22:53:33 +08:00
耗子 6e6342f0a6 feat: release v2.0.27 2023-08-04 22:45:32 +08:00
耗子 cea485133f fix: website ssl and php 2023-08-04 22:44:58 +08:00
耗子 08e14a84a5 fix: lint 2023-08-04 02:38:44 +08:00
耗子 bc179923a0 feat: optimize readme 2023-08-04 02:31:14 +08:00
耗子 67ef7ccd7a feat: optimize readme 2023-08-04 02:23:54 +08:00
耗子 1abb9691f4 fix(safe): null rule 2023-08-04 00:33:27 +08:00
耗子 4dc2895781 feat: release v2.0.26 2023-08-03 23:39:02 +08:00
耗子 e7635d4b36 fix: lint 2023-08-03 23:34:18 +08:00
耗子 e731c025b5 feat: pure-ftpd plugin 2023-08-03 23:33:31 +08:00
耗子 8234a16f9e feat: release v2.0.25 2023-07-30 03:52:56 +08:00
耗子 0e3dba7dc8 fix: lint 2023-07-30 03:51:46 +08:00
耗子 7e8ef74bee feat: fail2ban plugin 2023-07-30 03:50:55 +08:00
耗子 26ef34ef9b refactor: rename 2023-07-29 02:39:32 +08:00
耗子 4aa6dfa1ee feat(ssh): auto refresh when save 2023-07-29 02:35:21 +08:00
耗子 51989254d6 feat: release v2.0.24 2023-07-28 04:12:34 +08:00
耗子 5cfeb49468 fix(website): open_basedir 2023-07-28 04:12:06 +08:00
耗子 614a5f15b5 fix(cron): backup path 2023-07-28 03:47:43 +08:00
耗子 f5a6f17108 fix(cron): frontend bug 2023-07-27 17:15:41 +08:00
耗子 6356b82a21 feat: release v2.0.23 2023-07-27 17:03:06 +08:00
耗子 b3b1afe77e feat(task): add time 2023-07-27 16:45:07 +08:00
耗子 f9628643be feat(cron): optimize frontend 2023-07-27 16:29:47 +08:00
耗子 c8352d567d fix: php imagemagick install 2023-07-27 16:12:09 +08:00
耗子 2b92a0c693 fix(info): php version check 2023-07-27 16:09:07 +08:00
耗子 8fec8af0d7 fix(ssl): read cert 2023-07-27 16:06:45 +08:00
耗子 5b83e042c2 fix(mysql): path 2023-07-27 15:52:57 +08:00
耗子 525cb154ca fix(website): ssl problem 2023-07-27 15:52:25 +08:00
耗子 1cbd282e40 fix(safe): ping status 2023-07-27 15:14:45 +08:00
耗子 9ffa32160b feat: release v2.0.22 2023-07-27 13:09:08 +08:00
耗子 872da5f8e1 fix(safe): ping status 2023-07-27 13:08:10 +08:00
耗子 eed3651cb1 fix(cron): editor space 2023-07-27 02:31:46 +08:00
耗子 653900fd73 fix(website): root setting and clean log 2023-07-27 02:29:00 +08:00
耗子 ff1f2569dd fix: login 2023-07-27 01:04:29 +08:00
耗子 0c6354fc6e feat: release v2.0.20 2023-07-27 00:57:58 +08:00
耗子 2ed5c36890 feat: add php 81 82 2023-07-27 00:57:13 +08:00
耗子 ecaf8edf14 feat: fix status 2023-07-27 00:47:54 +08:00
耗子 2429cf023a feat: release v2.0.19 2023-07-26 19:06:32 +08:00
耗子 c7adc39887 feat: update framework 2023-07-26 18:13:09 +08:00
耗子 f4e267ef1b feat: optimize openresty install 2023-07-26 15:26:22 +08:00
耗子 98bb24d97a fix: build 2023-07-26 15:22:38 +08:00
耗子 9e603a3003 revert: use v2 version 2023-07-26 15:16:24 +08:00
耗子 7164541415 feat: use v2 version 2023-07-26 15:15:47 +08:00
耗子 a51b391634 feat: use v2 version 2023-07-26 15:15:34 +08:00
耗子 edd3b2a622 fix: openresty install in rhel9 2023-07-26 14:59:42 +08:00
耗子 c55ef5c1f2 feat: release v2.0.18 2023-07-26 02:19:20 +08:00
耗子 65a46796ea feat: supervisor plugin and optimize code 2023-07-26 02:14:26 +08:00
88 changed files with 7807 additions and 347 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ body:
attributes:
label: 耗子面板版本 (HaoZi Panel Version)
description: |
请提供耗子面板的版本号。
请提供面板的版本号。
Please provide the version number of the panel.
placeholder: "2.0.0"
validations:
-25
View File
@@ -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
-22
View File
@@ -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: "
+2 -2
View File
@@ -27,7 +27,7 @@ jobs:
我们认为您的建议非常有价值!欢迎提交 PR,请包含相应的测试用例、文档等,并确保 CI 通过,感谢和期待您的贡献!
We think your suggestion is very valuable! Welcome to submit a PR, please include test cases, documentation, etc., and ensure that the CI is passed, thank you and look forward to your contribution!
"Talk is cheap, Show me the Code." - Linus Torvalds
![aoligei](https://github.com/haozi-team/panel/assets/115467771/fb04debf-3f4c-4fac-a0b8-c3455f8e57a0)
- name: ☢️ Bug
if: github.event.label.name == '☢️ Bug'
uses: actions-cool/issues-helper@v3
@@ -41,4 +41,4 @@ jobs:
我们认为您的反馈非常有价值!欢迎提交 PR,请包含相应的测试用例、文档等,并确保 CI 通过,感谢和期待您的贡献!
We think your feedback is very valuable! Welcome to submit a PR, please include test cases, documentation, etc., and ensure that the CI is passed, thank you and look forward to your contribution!
"Talk is cheap, Show me the Code." - Linus Torvalds
![aoligei](https://github.com/haozi-team/panel/assets/115467771/fb04debf-3f4c-4fac-a0b8-c3455f8e57a0)
-11
View File
@@ -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
View File
@@ -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
+72 -15
View File
@@ -1,35 +1,41 @@
# 耗子Linux面板
[![Release](https://img.shields.io/github/release/HaoZi-Team/Panel.svg)](https://github.com/HaoZi-Team/Panel/releases)
[![Test](https://github.com/HaoZi-Team/Panel/actions/workflows/test.yml/badge.svg)](https://github.com/HaoZi-Team/Panel/actions)
[![Report Card](https://goreportcard.com/badge/github.com/HaoZi-Team/Panel)](https://goreportcard.com/report/github.com/HaoZi-Team/Panel)
[![Codecov](https://codecov.io/gh/HaoZi-Team/Panel/branch/main/graph/badge.svg?token=XFT5NGNSRG)](https://codecov.io/gh/HaoZi-Team/Panel)
![License](https://img.shields.io/github/license/HaoZi-Team/Panel)
[![Release](https://img.shields.io/github/release/haozi-team/panel.svg)](https://github.com/haozi-team/panel/releases)
[![Test](https://github.com/haozi-team/panel/actions/workflows/test.yml/badge.svg)](https://github.com/haozi-team/panel/actions)
[![Report Card](https://goreportcard.com/badge/github.com/haozi-team/panel)](https://goreportcard.com/report/github.com/haozi-team/panel)
[![Codecov](https://codecov.io/gh/haozi-team/panel/branch/main/graph/badge.svg?token=XFT5NGNSRG)](https://codecov.io/gh/haozi-team/panel)
![License](https://img.shields.io/github/license/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 趋势
## 行为准则
[![Star 趋势](https://starchart.cc/HaoZi-Team/Panel.svg)](https://starchart.cc/HaoZi-Team/Panel)
耗子Linux面板遵守业界通用的行为准则。任何违反行为准则的行为都可以报告给我们:
- 参与者将容忍反对意见。
- 参与者必须确保他们的语言和行为没有人身攻击和贬低个人言论。
- 在解释他人的言行时,参与者应始终保持良好的意图。
- 不能容忍可合理视为骚扰的行为。
## 安全说明
安全性是我们最关心的问题之一,我们已在生产环境广泛应用耗子Linux面板。
耗子Linux面板采用业界多种方案尽可能保证面板的安全性,但是我们不能保证面板的绝对安全性,**因此我们不对面板的安全性做任何保证**。
如果您在使用面板的过程中发现任何安全问题,请勿提交 Issue,可通过以下方式直接联系我们:
- 邮箱:i@haozi.net
- QQ826896000
## 贡献者
这个项目的存在要归功于所有做出贡献的人,参与贡献请先查看贡献代码部分。
<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>
+17 -1
View File
@@ -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} 删除面板设置数据")
}
+6 -3
View File
@@ -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) + `
+6
View File
@@ -2,6 +2,7 @@ package controllers
import (
"fmt"
"regexp"
"strings"
"github.com/goravel/framework/contracts/http"
@@ -125,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
@@ -247,7 +247,7 @@ func (c *Mysql57Controller) ErrorLog(ctx http.Context) {
return
}
log := tools.ExecShell("tail -n 100 /www/server/mysql/mysql-error.log")
log := tools.Escape(tools.ExecShell("tail -n 100 /www/server/mysql/mysql-error.log"))
controllers.Success(ctx, log)
}
@@ -267,7 +267,7 @@ func (c *Mysql57Controller) SlowLog(ctx http.Context) {
return
}
log := tools.ExecShell("tail -n 100 /www/server/mysql/mysql-slow.log")
log := tools.Escape(tools.ExecShell("tail -n 100 /www/server/mysql/mysql-slow.log"))
controllers.Success(ctx, log)
}
@@ -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,13 +453,17 @@ 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, "删除数据库成功")
}
// BackupList 获取备份列表
func (c *Mysql57Controller) BackupList(ctx http.Context) {
if !controllers.Check(ctx, "mysql57") {
return
}
backupList, err := c.backup.MysqlList()
if err != nil {
facades.Log().Error("[MySQL57] 获取备份列表失败:" + err.Error())
@@ -626,7 +649,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 +695,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 +722,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 +749,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 +777,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
@@ -247,7 +247,7 @@ func (c *Mysql80Controller) ErrorLog(ctx http.Context) {
return
}
log := tools.ExecShell("tail -n 100 /www/server/mysql/mysql-error.log")
log := tools.Escape(tools.ExecShell("tail -n 100 /www/server/mysql/mysql-error.log"))
controllers.Success(ctx, log)
}
@@ -267,7 +267,7 @@ func (c *Mysql80Controller) SlowLog(ctx http.Context) {
return
}
log := tools.ExecShell("tail -n 100 /www/server/mysql/mysql-slow.log")
log := tools.Escape(tools.ExecShell("tail -n 100 /www/server/mysql/mysql-slow.log"))
controllers.Success(ctx, log)
}
@@ -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,13 +453,17 @@ 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, "删除数据库成功")
}
// BackupList 获取备份列表
func (c *Mysql80Controller) BackupList(ctx http.Context) {
if !controllers.Check(ctx, "mysql80") {
return
}
backupList, err := c.backup.MysqlList()
if err != nil {
facades.Log().Error("[MySQL80] 获取备份列表失败:" + err.Error())
@@ -626,7 +649,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 +695,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 +722,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 +749,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 +777,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
@@ -189,7 +188,7 @@ func (c *Php74Controller) ErrorLog(ctx http.Context) {
return
}
log := tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/php-fpm.log")
log := tools.Escape(tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/php-fpm.log"))
controllers.Success(ctx, log)
}
@@ -198,7 +197,7 @@ func (c *Php74Controller) SlowLog(ctx http.Context) {
return
}
log := tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/slow.log")
log := tools.Escape(tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/slow.log"))
controllers.Success(ctx, log)
}
@@ -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
@@ -189,7 +188,7 @@ func (c *Php80Controller) ErrorLog(ctx http.Context) {
return
}
log := tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/php-fpm.log")
log := tools.Escape(tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/php-fpm.log"))
controllers.Success(ctx, log)
}
@@ -198,7 +197,7 @@ func (c *Php80Controller) SlowLog(ctx http.Context) {
return
}
log := tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/slow.log")
log := tools.Escape(tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/slow.log"))
controllers.Success(ctx, log)
}
@@ -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.Escape(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.Escape(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: "(需先安装PostgreSQLpdo_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.Escape(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.Escape(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: "(需先安装PostgreSQLpdo_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,624 @@
package postgresql15
import (
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/carbon"
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/pkg/tools"
)
type Postgresql15Controller struct {
setting services.Setting
backup services.Backup
}
type Info struct {
Name string `json:"name"`
Value string `json:"value"`
}
func NewPostgresql15Controller() *Postgresql15Controller {
return &Postgresql15Controller{
setting: services.NewSettingImpl(),
backup: services.NewBackupImpl(),
}
}
// Status 获取运行状态
func (c *Postgresql15Controller) Status(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
status := tools.ExecShell("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Reload 重载配置
func (c *Postgresql15Controller) Reload(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
tools.ExecShell("systemctl reload postgresql")
status := tools.ExecShell("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Restart 重启服务
func (c *Postgresql15Controller) Restart(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
tools.ExecShell("systemctl restart postgresql")
status := tools.ExecShell("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Start 启动服务
func (c *Postgresql15Controller) Start(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
tools.ExecShell("systemctl start postgresql")
status := tools.ExecShell("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Stop 停止服务
func (c *Postgresql15Controller) Stop(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
tools.ExecShell("systemctl stop postgresql")
status := tools.ExecShell("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
return
}
if status != "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// GetConfig 获取配置
func (c *Postgresql15Controller) GetConfig(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
// 获取配置
config := tools.ReadFile("/www/server/postgresql/data/postgresql.conf")
if len(config) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
return
}
controllers.Success(ctx, config)
}
// GetUserConfig 获取用户配置
func (c *Postgresql15Controller) GetUserConfig(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
// 获取配置
config := tools.ReadFile("/www/server/postgresql/data/pg_hba.conf")
if len(config) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
return
}
controllers.Success(ctx, config)
}
// SaveConfig 保存配置
func (c *Postgresql15Controller) SaveConfig(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
config := ctx.Request().Input("config")
if len(config) == 0 {
controllers.Error(ctx, http.StatusBadRequest, "配置不能为空")
return
}
if !tools.WriteFile("/www/server/postgresql/data/postgresql.conf", config, 0644) {
controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
return
}
c.Restart(ctx)
}
// SaveUserConfig 保存用户配置
func (c *Postgresql15Controller) SaveUserConfig(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
config := ctx.Request().Input("config")
if len(config) == 0 {
controllers.Error(ctx, http.StatusBadRequest, "配置不能为空")
return
}
if !tools.WriteFile("/www/server/postgresql/data/pg_hba.conf", config, 0644) {
controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
return
}
c.Restart(ctx)
}
// Load 获取负载
func (c *Postgresql15Controller) Load(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
status := tools.ExecShell("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if status != "active" {
controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL 已停止运行")
return
}
data := []Info{
{"启动时间", carbon.Parse(tools.ExecShell(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)).ToDateTimeString()},
{"进程 PID", tools.ExecShell(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)},
{"进程数", tools.ExecShell(`ps aux | grep postgres | grep -v grep | wc -l`)},
{"总连接数", tools.ExecShell(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)},
{"空间占用", tools.ExecShell(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)},
}
controllers.Success(ctx, data)
}
// Log 获取日志
func (c *Postgresql15Controller) Log(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
log := tools.ExecShell("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
controllers.Success(ctx, log)
}
// ClearLog 清空日志
func (c *Postgresql15Controller) ClearLog(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
tools.ExecShell("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (c *Postgresql15Controller) DatabaseList(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
status := tools.ExecShell("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if status != "active" {
controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL 已停止运行")
return
}
raw := tools.ExecShell(`echo "\l" | su - postgres -c "psql"`)
databases := strings.Split(raw, "\n")
databases = databases[3 : len(databases)-1]
type database struct {
Name string `json:"name"`
Owner string `json:"owner"`
Encoding string `json:"encoding"`
}
var databaseList []database
for _, db := range databases {
parts := strings.Split(db, "|")
if len(parts) != 8 || len(strings.TrimSpace(parts[0])) == 0 {
continue
}
databaseList = append(databaseList, database{
Name: strings.TrimSpace(parts[0]),
Owner: strings.TrimSpace(parts[1]),
Encoding: strings.TrimSpace(parts[2]),
})
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(databaseList) {
controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
return
}
if endIndex > len(databaseList) {
endIndex = len(databaseList)
}
pagedDatabases := databaseList[startIndex:endIndex]
controllers.Success(ctx, http.Json{
"total": len(databaseList),
"items": pagedDatabases,
})
}
// AddDatabase 添加数据库
func (c *Postgresql15Controller) AddDatabase(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
database := ctx.Request().Input("database")
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.ExecShell(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`)
tools.ExecShell(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
tools.ExecShell(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`)
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
tools.ExecShell(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`)
c.Reload(ctx)
}
// DeleteDatabase 删除数据库
func (c *Postgresql15Controller) DeleteDatabase(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:postgres,template0,template1",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
database := ctx.Request().Input("database")
tools.ExecShell(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`)
controllers.Success(ctx, nil)
}
// BackupList 获取备份列表
func (c *Postgresql15Controller) BackupList(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
backupList, err := c.backup.PostgresqlList()
if err != nil {
facades.Log().Error("[PostgreSQL] 获取备份列表失败:" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败")
return
}
controllers.Success(ctx, backupList)
}
// UploadBackup 上传备份
func (c *Postgresql15Controller) UploadBackup(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
file, err := ctx.Request().File("file")
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, "上传文件失败")
return
}
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
name := file.GetClientOriginalName()
_, err = file.StoreAs(backupPath, name)
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, "上传文件失败")
return
}
controllers.Success(ctx, nil)
}
// CreateBackup 创建备份
func (c *Postgresql15Controller) CreateBackup(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
database := ctx.Request().Input("database")
err = c.backup.PostgresqlBackup(database)
if err != nil {
facades.Log().Error("[PostgreSQL] 创建备份失败:" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "创建备份失败")
return
}
controllers.Success(ctx, nil)
}
// DeleteBackup 删除备份
func (c *Postgresql15Controller) DeleteBackup(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|min_len:1|max_len:255",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
fileName := ctx.Request().Input("name")
tools.RemoveFile(backupPath + "/" + fileName)
controllers.Success(ctx, nil)
}
// RestoreBackup 还原备份
func (c *Postgresql15Controller) RestoreBackup(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|min_len:1|max_len:255",
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
err = c.backup.PostgresqlRestore(ctx.Request().Input("database"), ctx.Request().Input("name"))
if err != nil {
facades.Log().Error("[PostgreSQL] 还原失败:" + err.Error())
controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error())
return
}
controllers.Success(ctx, nil)
}
// UserList 用户列表
func (c *Postgresql15Controller) UserList(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
type user struct {
User string `json:"user"`
Role string `json:"role"`
}
raw := tools.ExecShell(`echo "\du" | su - postgres -c "psql"`)
users := strings.Split(raw, "\n")
if len(users) < 4 {
controllers.Error(ctx, http.StatusInternalServerError, "用户列表为空")
return
}
users = users[3:]
var userList []user
for _, u := range users {
userInfo := strings.Split(u, "|")
if len(userInfo) != 3 {
continue
}
userList = append(userList, user{
User: strings.TrimSpace(userInfo[0]),
Role: strings.TrimSpace(userInfo[1]),
})
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(userList) {
controllers.Success(ctx, http.Json{
"total": 0,
"items": []user{},
})
return
}
if endIndex > len(userList) {
endIndex = len(userList)
}
pagedUsers := userList[startIndex:endIndex]
controllers.Success(ctx, http.Json{
"total": len(userList),
"items": pagedUsers,
})
}
// AddUser 添加用户
func (c *Postgresql15Controller) AddUser(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
tools.ExecShell(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
tools.ExecShell(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`)
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
tools.ExecShell(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`)
c.Reload(ctx)
}
// DeleteUser 删除用户
func (c *Postgresql15Controller) DeleteUser(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
user := ctx.Request().Input("user")
tools.ExecShell(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`)
tools.ExecShell(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`)
c.Reload(ctx)
}
// SetUserPassword 设置用户密码
func (c *Postgresql15Controller) SetUserPassword(ctx http.Context) {
if !controllers.Check(ctx, "postgresql15") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.ExecShell(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
controllers.Success(ctx, nil)
}
@@ -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)
}
+54 -12
View File
@@ -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)
+1 -1
View File
@@ -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
}
+13 -11
View File
@@ -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重定向标记位结束
`
}
+2 -2
View File
@@ -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": "登录已过期",
})
+1
View File
@@ -4,6 +4,7 @@ import "github.com/goravel/framework/support/carbon"
const (
SettingKeyName = "name"
SettingKeyVersion = "version"
SettingKeyMonitor = "monitor"
SettingKeyMonitorDays = "monitor_days"
SettingKeyBackupPath = "backup_path"
+13
View File
@@ -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`
)
+1 -1
View File
@@ -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"
+13
View File
@@ -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`
)
+13
View File
@@ -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`
)
+13
View File
@@ -0,0 +1,13 @@
package postgresql15
var (
Name = "PostgreSQL-15"
Description = "PostgreSQL 是开源的对象 - 关系数据库数据库管理系统,在类似 BSD 许可与 MIT 许可的 PostgreSQL 许可下发行。"
Slug = "postgresql15"
Version = "15.3"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/postgresql/install.sh 15`
Uninstall = `bash /www/panel/scripts/postgresql/uninstall.sh 15`
Update = `bash /www/panel/scripts/postgresql/update.sh 15`
)
+13
View File
@@ -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`
)
+13
View File
@@ -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`
)
+13
View File
@@ -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`
)
+94 -2
View File
@@ -20,6 +20,9 @@ type Backup interface {
MysqlList() ([]BackupFile, error)
MysqlBackup(database string) error
MysqlRestore(database string, backupFile string) error
PostgresqlList() ([]BackupFile, error)
PostgresqlBackup(database string) error
PostgresqlRestore(database string, backupFile string) error
}
type BackupFile struct {
@@ -155,7 +158,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,8 +210,97 @@ 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
}
// PostgresqlList PostgreSQL备份列表
func (s *BackupImpl) PostgresqlList() ([]BackupFile, error) {
backupPath := s.setting.Get(models.SettingKeyBackupPath)
if len(backupPath) == 0 {
return []BackupFile{}, nil
}
backupPath += "/postgresql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
files, err := os.ReadDir(backupPath)
if err != nil {
return []BackupFile{}, err
}
var backupList []BackupFile
for _, file := range files {
info, err := file.Info()
if err != nil {
continue
}
backupList = append(backupList, BackupFile{
Name: file.Name(),
Size: tools.FormatBytes(float64(info.Size())),
})
}
return backupList, nil
}
// PostgresqlBackup PostgreSQL备份
func (s *BackupImpl) PostgresqlBackup(database string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
backupFile := database + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
tools.ExecShell(`su - postgres -c "pg_dump ` + database + `" > ` + backupPath + "/" + backupFile)
tools.ExecShell("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile)
tools.RemoveFile(backupPath + "/" + backupFile)
return nil
}
// PostgresqlRestore PostgreSQL恢复
func (s *BackupImpl) PostgresqlRestore(database string, backupFile string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
ext := filepath.Ext(backupFile)
backupFile = backupPath + "/" + backupFile
if !tools.Exists(backupFile) {
return errors.New("备份文件不存在")
}
switch ext {
case ".zip":
tools.ExecShell("unzip -o " + backupFile + " -d " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ext)
case ".gz":
if strings.HasSuffix(backupFile, ".tar.gz") {
// 解压.tar.gz文件
tools.ExecShell("tar -zxvf " + backupFile + " -C " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ".tar.gz")
} else {
// 解压.gz文件
tools.ExecShell("gzip -d " + backupFile)
backupFile = strings.TrimSuffix(backupFile, ext)
}
case ".bz2":
tools.ExecShell("bzip2 -d " + backupFile)
backupFile = strings.TrimSuffix(backupFile, ext)
case ".tar":
tools.ExecShell("tar -xvf " + backupFile + " -C " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ext)
case ".rar":
tools.ExecShell("unrar x " + backupFile + " " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ext)
}
if !tools.Exists(backupFile) {
return errors.New("自动解压失败,请手动解压")
}
tools.ExecShell(`su - postgres -c "psql ` + database + `" < ` + backupFile)
return nil
}
+84
View File
@@ -5,13 +5,20 @@ 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/postgresql15"
"panel/app/plugins/pureftpd"
"panel/app/plugins/redis"
"panel/app/plugins/s3fs"
"panel/app/plugins/supervisor"
)
// PanelPlugin 插件元数据结构
@@ -88,6 +95,17 @@ func (r *PluginImpl) All() []PanelPlugin {
Uninstall: mysql80.Uninstall,
Update: mysql80.Update,
})
p = append(p, PanelPlugin{
Name: postgresql15.Name,
Description: postgresql15.Description,
Slug: postgresql15.Slug,
Version: postgresql15.Version,
Requires: postgresql15.Requires,
Excludes: postgresql15.Excludes,
Install: postgresql15.Install,
Uninstall: postgresql15.Uninstall,
Update: postgresql15.Update,
})
p = append(p, PanelPlugin{
Name: php74.Name,
Description: php74.Description,
@@ -110,6 +128,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 +161,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 +194,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
}
+7 -15
View File
@@ -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
}
@@ -348,7 +340,7 @@ func (r *WebsiteImpl) GetConfig(id int) (WebsiteSetting, error) {
}
setting.Rewrite = tools.ReadFile("/www/server/vhost/rewrite/" + website.Name + ".conf")
setting.Log = tools.ExecShell(`tail -n 100 '/www/wwwlogs/` + website.Name + `.log'`)
setting.Log = tools.Escape(tools.ExecShell(`tail -n 100 '/www/wwwlogs/` + website.Name + `.log'`))
return setting, nil
}
+1 -1
View File
@@ -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
View File
@@ -7,7 +7,7 @@ import (
func init() {
config := facades.Config()
config.Add("panel", map[string]any{
"name": "耗子面板",
"version": "v2.0.17",
"name": "耗子Linux面板",
"version": "v2.0.31",
})
}
+26 -26
View File
@@ -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.20230809103226-03eb8ad21087
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-20230809150735-7b3493d9a819
)
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-20230808223545-4887780b67fb // 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.1 // 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.2 // indirect
github.com/quic-go/quic-go v0.37.4 // 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
@@ -162,21 +162,21 @@ require (
go.mongodb.org/mongo-driver v1.7.5 // indirect
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/arch v0.4.0 // indirect
golang.org/x/image v0.11.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.14.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.12.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
google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a // indirect
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/grpc v1.56.2 // indirect
google.golang.org/grpc v1.57.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
@@ -185,7 +185,7 @@ require (
gorm.io/driver/postgres v1.5.2 // indirect
gorm.io/driver/sqlserver v1.5.1 // indirect
gorm.io/gorm v1.25.2 // indirect
gorm.io/plugin/dbresolver v1.4.1 // indirect
gorm.io/plugin/dbresolver v1.4.2 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
+54 -62
View File
@@ -85,7 +85,7 @@ github.com/aws/aws-sdk-go v1.37.16 h1:Q4YOP2s00NpB9wfmTDZArdcLRuG9ijbnoAwTW3ivle
github.com/aws/aws-sdk-go v1.37.16/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/brianvoe/gofakeit/v6 v6.23.0 h1:pgVhyWpYq4e0GEVCh2gdZnS/nBX+8SnyTBliHg5xjks=
github.com/brianvoe/gofakeit/v6 v6.23.1 h1:k2gX0hQpJStvixDbbw8oJOvPBg0XmHJWbSOF5JkiUHw=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
@@ -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-20230808223545-4887780b67fb h1:oqpb3Cwpc7EOml5PVGMYbSGmwNui2R7i8IW83gs4W0c=
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb/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.1 h1:rPp64QZQJM+fysGFAhKpvekQAav4Ok6sjfTs9ZtxcpA=
github.com/gookit/validate v1.5.1/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.20230809103226-03eb8ad21087 h1:YOKs5GFzbWOZz3ojiX5jl082Y1BRGOCig3ldnNBTrRs=
github.com/goravel/framework v1.12.1-0.20230809103226-03eb8ad21087/go.mod h1:qc8dK2vVVVdTSashuby7+KZZ2CfGztNgMaoDGWtvtVI=
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.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI=
github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.37.4 h1:ke8B73yMCWGq9MfrCCAw0Uzdm7GaViC3i39dsIdDlH4=
github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
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=
@@ -673,8 +669,8 @@ go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
@@ -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-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U=
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/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.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
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.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
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=
@@ -1043,10 +1036,10 @@ google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20230525234025-438c736192d0 h1:x1vNwUhVOcsYoKyEGCZBH694SBmmBjA2EfauFVEI2+M=
google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a h1:HiYVD+FGJkTo+9zj1gqz0anapsa1JxjiSrN+BJKyUmE=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8=
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M=
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -1068,8 +1061,8 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1118,12 +1111,11 @@ gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBp
gorm.io/driver/sqlserver v1.5.1 h1:wpyW/pR26U94uaujltiFGXY7fd2Jw5hC9PB1ZF/Y5s4=
gorm.io/driver/sqlserver v1.5.1/go.mod h1:AYHzzte2msKTmYBYsSIq8ZUsznLJwBdkB2wpI+kt0nM=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/plugin/dbresolver v1.4.1 h1:Ug4LcoPhrvqq71UhxtF346f+skTYoCa/nEsdjvHwEzk=
gorm.io/plugin/dbresolver v1.4.1/go.mod h1:CTbCtMWhsjXSiJqiW2R8POvJ2cq18RVOl4WGyT5nhNc=
gorm.io/plugin/dbresolver v1.4.2 h1:IeLSH20ayxbo4rN6HMIQ0ccdsh/fkLK23pp6ivZrqBI=
gorm.io/plugin/dbresolver v1.4.2/go.mod h1:l4Cn87EHLEYuqUncpEeTC2tTJQkjngPSD+lo8hIvcT0=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+6
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"strings"
"text/template"
"unicode/utf8"
)
@@ -78,3 +79,8 @@ func Cut(str, begin, end string) string {
return string([]rune(str)[b:e])
}
// Escape 转义字符串
func Escape(str string) string {
return template.HTMLEscapeString(str)
}
+5 -3
View File
@@ -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("重启完成")
+1 -1
View File
@@ -37,7 +37,7 @@ layui.define(['laytpl', 'layer'], function (exports) {
})
//跳转到登入页
location.hash = '/user/login'
location.hash = '/login'
}
//Ajax请求
+10 -5
View File
@@ -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) {
+335
View File
@@ -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>
+18
View File
@@ -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) {
+18
View File
@@ -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) {
+381
View File
@@ -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>
+381
View File
@@ -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,524 @@
<!--
Name: PostgreSQL管理器
Author: 耗子
Date: 2023-08-13
-->
<title>PostgreSQL</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">PostgreSQL管理</div>
<div class="layui-card-body">
<div class="layui-tab">
<ul class="layui-tab-title">
<li class="layui-this">基本信息</li>
<li>管理</li>
<li>主配置</li>
<li>用户配置</li>
<li>负载状态</li>
<li>日志</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>运行状态</legend>
</fieldset>
<blockquote id="postgresql-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="postgresql-start" class="layui-btn">启动</button>
<button id="postgresql-stop" class="layui-btn layui-btn-danger">停止</button>
<button id="postgresql-restart" class="layui-btn layui-btn-warm">重启</button>
<button id="postgresql-reload" class="layui-btn layui-btn-normal">重载</button>
</div>
</div>
<div class="layui-tab-item">
<blockquote class="layui-elem-quote">面板仅集成了部分常用功能,如需更多功能,请使用
pgAdmin 客户端。
</blockquote>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>数据库列表</legend>
</fieldset>
<table class="layui-hide" id="postgresql-database-list"
lay-filter="postgresql-database-list"></table>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>用户列表</legend>
</fieldset>
<table class="layui-hide" id="postgresql-user-list"
lay-filter="postgresql-user-list"></table>
<!-- 数据库顶部工具栏 -->
<script type="text/html" id="postgresql-database-list-bar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add_database">新建数据库
</button>
</div>
</script>
<!-- 用户顶部工具栏 -->
<script type="text/html" id="postgresql-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="postgresql-database-list-control">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="backup">备份</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<!-- 用户右侧管理 -->
<script type="text/html" id="postgresql-user-list-control">
<a class="layui-btn layui-btn-normal layui-btn-xs"
lay-event="change_password">改密</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
</div>
<div class="layui-tab-item">
<blockquote class="layui-elem-quote">此处修改的是PostgreSQL主配置文件,如果你不了解各参数的含义,请不要随意修改!<br>
提示:Ctrl+F 搜索关键字,Ctrl+S 保存,Ctrl+H 查找替换!
</blockquote>
<div id="postgresql-config-editor"
style="height: 600px;"></div>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="postgresql-config-save" class="layui-btn">保存</button>
</div>
</div>
<div class="layui-tab-item">
<blockquote class="layui-elem-quote">此处修改的是PostgreSQL用户配置文件,如果你不了解各参数的含义,请不要随意修改!<br>
提示:Ctrl+F 搜索关键字,Ctrl+S 保存,Ctrl+H 查找替换!
</blockquote>
<div id="postgresql-user-config-editor"
style="height: 600px;"></div>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="postgresql-user-config-save" class="layui-btn">保存</button>
</div>
</div>
<div class="layui-tab-item">
<table class="layui-hide" id="postgresql-load-status"></table>
</div>
<div class="layui-tab-item">
<pre id="postgresql-log" class="layui-code">
获取中...
</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let postgresql_config_editor;
let postgresql_user_config_editor;
layui.use(['index', 'admin', 'element', 'form', 'view', 'code', 'table'], function () {
let $ = layui.$
, admin = layui.admin
, element = layui.element
, code = layui.code
, table = layui.table
, form = layui.form
, view = layui.view;
form.render();
admin.req({
url: "/api/plugins/postgresql15/status"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
return false;
}
if (result.data) {
$('#postgresql-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
} else {
$('#postgresql-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
}
}
});
// 获取数据库列表
table.render({
elem: '#postgresql-database-list'
, url: '/api/plugins/postgresql15/database'
, toolbar: '#postgresql-database-list-bar'
, title: '数据库列表'
, cols: [[
{field: 'name', title: '库名', fixed: 'left', unresize: true, sort: true}
, {field: 'owner', title: '所有者', unresize: true, sort: true}
, {field: 'encoding', title: '编码', unresize: true, sort: true}
, {fixed: 'right', title: '操作', toolbar: '#postgresql-database-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(postgresql-database-list)', function (obj) {
if (obj.event === 'add_database') {
admin.popup({
title: '新建数据库'
, area: ['600px', '300px']
, id: 'LAY-popup-postgresql-database-add'
, success: function (layer, index) {
view(this.id).render('plugins/postgresql15/add_database', {}).done(function () {
form.render(null, 'LAY-popup-postgresql-database-add');
});
}
});
}
});
// 行工具事件
table.on('tool(postgresql-database-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('高风险操作,确定要删除数据库 <b style="color: red;">' + data.name + '</b> 吗?', function (index) {
index = layer.msg('正在删除数据库 <b style="color: red;">' + data.name + '</b> ...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/postgresql15/deleteDatabase"
, type: 'post'
, data: {
database: data.name
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
obj.del();
layer.alert('数据库' + data.name + '删除成功!');
}
});
layer.close(index);
});
} else if (obj.event === 'backup') {
// 打开备份页面
admin.popup({
title: '备份管理 - ' + data.name
, area: ['70%', '80%']
, id: 'LAY-popup-postgresql-backup'
, success: function (layero, index) {
view(this.id).render('plugins/postgresql15/backup', {
data: data
}).done(function () {
form.render(null, 'LAY-popup-postgresql-backup');
});
}
});
}
});
// 获取数据库用户列表
table.render({
elem: '#postgresql-user-list'
, url: '/api/plugins/postgresql15/user'
, toolbar: '#postgresql-user-list-bar'
, title: '用户列表'
, cols: [[
{field: 'user', title: '用户名', fixed: 'left', width: 300, sort: true}
, {field: 'role', title: '权限'}
, {fixed: 'right', title: '操作', toolbar: '#postgresql-user-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(postgresql-user-list)', function (obj) {
if (obj.event === 'add_user') {
admin.popup({
title: '新建用户'
, area: ['600px', '300px']
, id: 'LAY-popup-postgresql-user-add'
, success: function (layer, index) {
view(this.id).render('plugins/postgresql15/add_user', {}).done(function () {
form.render(null, 'LAY-popup-postgresql-user-add');
});
}
});
}
});
// 行工具事件
table.on('tool(postgresql-user-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('高风险操作,确定要删除用户 <b style="color: red;">' + data.user + '</b> 吗?', function (index) {
index = layer.msg('正在删除用户 <b style="color: red;">' + data.user + '</b> ...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/postgresql15/deleteUser"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
obj.del();
layer.alert('用户' + data.user + '删除成功!');
}
});
layer.close(index);
});
} else if (obj.event === 'change_password') {
// 弹出输入密码框
layer.prompt({
formType: 1
, title: '请输入新密码(建议8位以上大小写数字特殊符号混合)'
}, function (value, index) {
layer.close(index);
layer.msg('正在修改密码 ...', {
icon: 16
, time: 0
, shade: 0.3
});
// 发送请求
admin.req({
url: "/api/plugins/postgresql15/changePassword"
, type: 'post'
, data: {
user: data.user,
password: value
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
layer.alert('用户' + data.user + '密码修改成功!');
}
});
});
}
});
// 获取postgresql日志并渲染
admin.req({
url: "/api/plugins/postgresql15/log"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
$('#postgresql-log').text('PostgreSQL日志获取失败,请刷新重试!');
code({
elem: '#postgresql-log'
, title: 'error.log'
, encode: true
, about: false
});
return false;
}
$('#postgresql-log').text(result.data);
code({
elem: '#postgresql-log'
, title: 'postgresql.log'
, encode: true
, about: false
});
}
});
// 获取postgresql配置并渲染
admin.req({
url: "/api/plugins/postgresql15/config"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
return false;
}
$('#postgresql-config-editor').text(result.data);
postgresql_config_editor = ace.edit("postgresql-config-editor", {
mode: "ace/mode/ini",
selectionStyle: "text"
});
}
});
// 获取postgresql用户配置并渲染
admin.req({
url: "/api/plugins/postgresql15/userConfig"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
return false;
}
$('#postgresql-user-config-editor').text(result.data);
postgresql_user_config_editor = ace.edit("postgresql-user-config-editor", {
mode: "ace/mode/ini",
selectionStyle: "text"
});
}
});
// 获取postgresql负载状态并渲染
table.render({
elem: '#postgresql-load-status'
, url: '/api/plugins/postgresql15/load'
, cols: [[
{field: 'name', width: '80%', title: '属性',}
, {field: 'value', width: '20%', title: '当前值'}
]]
});
element.render();
// 事件监听
$('#postgresql-start').click(function () {
layer.confirm('确定要启动PostgreSQL吗?', {
btn: ['启动', '取消']
}, function () {
index = layer.msg('正在启动PostgreSQL...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/postgresql15/start"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('PostgreSQL启动成功!');
}
});
});
});
$('#postgresql-stop').click(function () {
layer.confirm('停止PostgreSQL将导致使用PostgreSQL的网站无法访问,是否继续停止?', {
btn: ['停止', '取消']
}, function () {
index = layer.msg('正在停止PostgreSQL...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/postgresql15/stop"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('PostgreSQL停止成功!');
}
});
});
});
$('#postgresql-restart').click(function () {
layer.confirm('重启PostgreSQL将导致使用PostgreSQL的网站短时间无法访问,是否继续重启?', {
btn: ['重启', '取消']
}, function () {
index = layer.msg('正在重启PostgreSQL...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/postgresql15/restart"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('PostgreSQL重启成功!');
}
});
});
});
$('#postgresql-reload').click(function () {
index = layer.msg('正在重载PostgreSQL...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/postgresql15/reload"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('PostgreSQL重载成功!');
}
});
});
$('#postgresql-config-save').click(function () {
index = layer.msg('正在保存配置...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/postgresql15/config"
, type: 'post'
, data: {
config: postgresql_config_editor.getValue()
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
layer.alert('PostgreSQL配置保存成功!');
}
});
});
$('#postgresql-user-config-save').click(function () {
index = layer.msg('正在保存配置...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/postgresql15/userConfig"
, type: 'post'
, data: {
config: postgresql_user_config_editor.getValue()
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
layer.alert('PostgreSQL用户配置保存成功!');
}
});
});
});
</script>
@@ -0,0 +1,77 @@
<!--
Name: PostgreSQL管理器 - 添加数据库
Author: 耗子
Date: 2023-08-13
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<form class="layui-form" action="" lay-filter="add-postgresql-database-form">
<div class="layui-form-item">
<label class="layui-form-label">数据库名</label>
<div class="layui-input-block">
<input type="text" name="database" lay-verify="required" placeholder="请输入数据库名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="user" lay-verify="required" placeholder="请输入用户名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="text" name="password" lay-verify="required" placeholder="请输入密码(8位以上大小写数字特殊符号混合)"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<div class="layui-footer">
<button class="layui-btn" lay-submit="" lay-filter="add-postgresql-database-submit">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</div>
</form>
</script>
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form', 'table'], function () {
var admin = layui.admin
, layer = layui.layer
, form = layui.form
, table = layui.table
form.render();
// 提交
form.on('submit(add-postgresql-database-submit)', function (data) {
index = layer.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/postgresql15/addDatabase"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
table.reload('postgresql-database-list');
table.reload('postgresql-user-list');
layer.alert('数据库添加成功!', {
icon: 1
, title: '提示'
, btn: ['确定']
, yes: function (index) {
layer.closeAll();
}
});
}
});
return false;
});
});
};
</script>
@@ -0,0 +1,79 @@
<!--
Name: PostgreSQL管理器 - 添加用户
Author: 耗子
Date: 2023-08-13
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<form class="layui-form" action="" lay-filter="add-postgresql-user-form">
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="user" lay-verify="required" placeholder="请输入用户名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="text" name="password" lay-verify="required"
placeholder="请输入密码(8位以上大小写数字特殊符号混合)"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数据库</label>
<div class="layui-input-block">
<input type="text" name="database" lay-verify="required" placeholder="输入授权给该用户的数据库名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<div class="layui-footer">
<button class="layui-btn" lay-submit="" lay-filter="add-postgresql-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', 'table'], function () {
var $ = layui.$
, admin = layui.admin
, layer = layui.layer
, form = layui.form
, table = layui.table
form.render();
// 提交
form.on('submit(add-postgresql-user-submit)', function (data) {
index = layer.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/postgresql15/addUser"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
table.reload('postgresql-database-list');
table.reload('postgresql-user-list');
layer.alert('用户添加成功!', {
icon: 1
, title: '提示'
, btn: ['确定']
, yes: function (index) {
layer.closeAll();
}
});
}
});
return false;
});
});
};
</script>
@@ -0,0 +1,141 @@
<!--
Name: PostgreSQL管理器 - 数据库备份
Author: 耗子
Date: 2023-08-13
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<div class="layui-row">
<div class="layui-col-xs12 layui-col-sm12 layui-col-md12">
<table class="layui-hide" id="postgresql-backup-list" lay-filter="postgresql-backup-list"></table>
</div>
</div>
</script>
<!-- 备份顶部工具栏 -->
<script type="text/html" id="postgresql-database-backup-bar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="backup_database">备份数据库</button>
<button class="layui-btn layui-btn-sm" id="upload_postgresql_backup">上传备份</button>
</div>
</script>
<!-- 备份右侧管理 -->
<script type="text/html" id="postgresql-database-backup-control">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="restore">恢复</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form', 'laydate', 'code'], function () {
var admin = layui.admin
, layer = layui.layer
, table = layui.table
, upload = layui.upload;
// 渲染表格
table.render({
elem: '#postgresql-backup-list'
, url: '/api/plugins/postgresql15/backup'
, toolbar: '#postgresql-database-backup-bar'
, title: '备份列表'
, cols: [[
{field: 'name', title: '备份名称', width: 500}
, {field: 'size', title: '文件大小'}
, {field: 'right', title: '操作', width: 150, toolbar: '#postgresql-database-backup-control'}
]]
, text: {
none: '无备份数据'
}
, done: function (res, curr, count) {
upload.render({
elem: '#upload_postgresql_backup'
, url: '/api/plugins/postgresql15/uploadBackup'
, accept: 'file'
, ext: 'sql|zip|rar|tar|gz|bz2'
, before: function (obj) {
index = layer.msg('正在上传备份文件,可能需要较长时间,请勿操作...', {
icon: 16
, time: 0
});
}
, done: function (res) {
layer.close(index);
layer.msg('上传成功!', {icon: 1});
table.reload('postgresql-backup-list');
}
});
}
});
// 头工具栏事件
table.on('toolbar(postgresql-backup-list)', function (obj) {
if (obj.event === 'backup_database') {
index = layer.msg('正在备份数据库,请稍等...', {
icon: 16
, time: 0
});
admin.req({
url: '/api/plugins/postgresql15/createBackup'
, type: 'post'
, data: {
database: params.data.name
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
layer.alert('备份失败!');
return false;
}
table.reload('postgresql-backup-list');
layer.msg('备份成功!', {icon: 1});
}
});
}
});
// 行工具事件
table.on('tool(postgresql-backup-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
});
admin.req({
url: "/api/plugins/postgresql15/deleteBackup"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
layer.msg('数据库备份删除失败,请刷新重试!')
return false;
}
obj.del();
layer.alert('数据库备份' + data.name + '删除成功!');
}
});
});
} else if (obj.event === 'restore') {
layer.confirm('高风险操作,确定要恢复数据库备份 <b style="color: red;">' + data.name + '</b> 吗?', function (index) {
index = layer.msg('正在恢复数据库备份,可能需要较长时间,请勿操作...', {
icon: 16
, time: 0
});
data.database = params.data.name;
admin.req({
url: "/api/plugins/postgresql15/restoreBackup"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
layer.msg('数据库备份恢复失败,请刷新重试!')
return false;
}
layer.alert('数据库备份' + data.name + '恢复成功!');
}
});
});
}
});
});
};
</script>
+305
View File
@@ -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>
+190
View File
@@ -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>
+451
View File
@@ -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>
+10 -1
View File
@@ -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;
+1
View File
@@ -80,6 +80,7 @@ Date: 2023-07-25
return false;
}
layer.alert('SSH信息保存成功!')
admin.events.refresh()
}
});
return false;
+19 -13
View File
@@ -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
+10 -1
View File
@@ -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'
+16
View File
@@ -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"
]
}
+133
View File
@@ -4,13 +4,20 @@ 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/postgresql15"
"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"
)
@@ -89,6 +96,33 @@ func Plugin() {
route.Post("userPassword", mysql80Controller.SetUserPassword)
route.Post("userPrivileges", mysql80Controller.SetUserPrivileges)
})
facades.Route().Prefix("api/plugins/postgresql15").Middleware(middleware.Jwt()).Group(func(route route.Route) {
postgresql15Controller := postgresql15.NewPostgresql15Controller()
route.Get("status", postgresql15Controller.Status)
route.Post("reload", postgresql15Controller.Reload)
route.Post("start", postgresql15Controller.Start)
route.Post("stop", postgresql15Controller.Stop)
route.Post("restart", postgresql15Controller.Restart)
route.Get("load", postgresql15Controller.Load)
route.Get("config", postgresql15Controller.GetConfig)
route.Post("config", postgresql15Controller.SaveConfig)
route.Get("userConfig", postgresql15Controller.GetUserConfig)
route.Post("userConfig", postgresql15Controller.SaveUserConfig)
route.Get("log", postgresql15Controller.Log)
route.Post("clearLog", postgresql15Controller.ClearLog)
route.Get("database", postgresql15Controller.DatabaseList)
route.Post("addDatabase", postgresql15Controller.AddDatabase)
route.Post("deleteDatabase", postgresql15Controller.DeleteDatabase)
route.Get("backup", postgresql15Controller.BackupList)
route.Post("createBackup", postgresql15Controller.CreateBackup)
route.Post("uploadBackup", postgresql15Controller.UploadBackup)
route.Post("deleteBackup", postgresql15Controller.DeleteBackup)
route.Post("restoreBackup", postgresql15Controller.RestoreBackup)
route.Get("user", postgresql15Controller.UserList)
route.Post("addUser", postgresql15Controller.AddUser)
route.Post("deleteUser", postgresql15Controller.DeleteUser)
route.Post("userPassword", postgresql15Controller.SetUserPassword)
})
facades.Route().Prefix("api/plugins/php74").Middleware(middleware.Jwt()).Group(func(route route.Route) {
php74Controller := php74.NewPhp74Controller()
route.Get("status", php74Controller.Status)
@@ -125,15 +159,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)
})
}
+11 -4
View File
@@ -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
+2
View File
@@ -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
+37
View File
@@ -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
+9 -9
View File
@@ -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
+1 -1
View File
@@ -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}
+14 -12
View File
@@ -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
@@ -378,7 +380,7 @@ EOF
systemctl daemon-reload
systemctl enable openresty.service
systemctl start openresty.service
systemctl restart openresty.service
panel writePlugin openresty ${openrestyVersion}
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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面板不支持该系统"
+1 -1
View File
@@ -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
+150 -6
View File
@@ -18,10 +18,154 @@ limitations under the License.
'
HR="+----------------------------------------------------"
postgresqlVersion="$1"
setup_Path="/www"
postgresqlPath="${setup_Path}/server/postgresql"
os_Version=$(cat /etc/redhat-release | sed -r 's/.* ([0-9]+)\.?.*/\1/')
ipLocation=$(curl -s https://ip.ping0.cc/geo)
ARCH=$(uname -m)
memTotal=$(LC_ALL=C free -m | grep Mem | awk '{print $2}')
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/postgresql"
setupPath="/www"
postgresqlPath="${setupPath}/server/postgresql"
postgresqlVersion=""
postgresqlPassword=$(cat /dev/urandom | head -n 16 | md5sum | head -c 16)
cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l)
# TODO
if [[ "${1}" == "15" ]]; then
postgresqlVersion="15.3"
else
echo -e $HR
echo "错误:不支持的 PostgreSQL 版本!"
exit 1
fi
# 安装依赖
if [ "${OS}" == "centos" ]; then
dnf makecache -y
dnf groupinstall "Development Tools" -y
dnf install make gettext zlib-devel readline-devel libicu-devel libxml2-devel libxslt-devel openssl-devel systemd-devel -y
elif [ "${OS}" == "debian" ]; then
apt update
apt install build-essential make gettext zlib1g-dev libreadline-dev libicu-dev libxml2-dev libxslt-dev libssl-dev libsystemd-dev -y
else
echo -e $HR
echo "错误:耗子Linux面板不支持该系统"
exit 1
fi
postgresqlUserCheck=$(cat /etc/passwd | grep postgres)
if [ "${postgresqlUserCheck}" == "" ]; then
groupadd postgres
useradd -g postgres postgres
fi
# 准备目录
rm -rf ${postgresqlPath}
mkdir -p ${postgresqlPath}
cd ${postgresqlPath}
# 下载源码
wget -T 120 -O ${postgresqlPath}/postgresql-${postgresqlVersion}.tar.gz ${downloadUrl}/postgresql-${postgresqlVersion}.tar.gz
tar -zxvf postgresql-${postgresqlVersion}.tar.gz
rm -f postgresql-${postgresqlVersion}.tar.gz
mv postgresql-${postgresqlVersion} src
# 编译
cd src
./configure --prefix=${postgresqlPath} --enable-nls='zh_CN en' --with-icu --with-ssl=openssl --with-systemd --with-libxml --with-libxslt
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误:PostgreSQL 编译初始化失败,请截图错误信息寻求帮助。"
rm -rf ${postgresqlPath}
exit 1
fi
make -j${cpuCore}
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误:PostgreSQL 编译失败,请截图错误信息寻求帮助。"
rm -rf ${postgresqlPath}
exit 1
fi
make install
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误:PostgreSQL 安装失败,请截图错误信息寻求帮助。"
rm -rf ${postgresqlPath}
exit 1
fi
rm -rf ${postgresqlPath}/src
# 配置
mkdir -p ${postgresqlPath}/data
mkdir -p ${postgresqlPath}/logs
chown -R postgres:postgres ${postgresqlPath}
chmod -R 700 ${postgresqlPath}
echo "export PATH=${postgresqlPath}/bin:\$PATH" >> /etc/profile
source /etc/profile
mkdir -p /home/postgres
cd /home/postgres
if [ -f /home/postgres/.bash_profile ]; then
echo "export PGHOME=${postgresqlPath}" >> /home/postgres/.bash_profile
echo "export PGDATA=${postgresqlPath}/data" >> /home/postgres/.bash_profile
echo "export PATH=${postgresqlPath}/bin:\$PATH " >> /home/postgres/.bash_profile
echo "MANPATH=$PGHOME/share/man:$MANPATH" >> /home/postgres/.bash_profile
echo "LD_LIBRARY_PATH=$PGHOME/lib:$LD_LIBRARY_PATH" >> /home/postgres/.bash_profile
source /home/postgres/.bash_profile
fi
if [ -f /home/postgres/.profile ]; then
echo "export PGHOME=${postgresqlPath}" >> /home/postgres/.profile
echo "export PGDATA=${postgresqlPath}/data" >> /home/postgres/.profile
echo "export PATH=${postgresqlPath}/bin:\$PATH " >> /home/postgres/.profile
echo "MANPATH=$PGHOME/share/man:$MANPATH" >> /home/postgres/.profile
echo "LD_LIBRARY_PATH=$PGHOME/lib:$LD_LIBRARY_PATH" >> /home/postgres/.profile
source /home/postgres/.profile
fi
# 初始化
su - postgres -c "${postgresqlPath}/bin/initdb -D ${postgresqlPath}/data"
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误:PostgreSQL 初始化失败,请截图错误信息寻求帮助。"
rm -rf ${postgresqlPath}
exit 1
fi
# 配置慢查询日志
cat >> ${postgresqlPath}/data/postgresql.conf << EOF
logging_collector = on
log_destination = 'stderr'
log_directory = '${postgresqlPath}/logs'
log_filename = 'postgresql-%Y-%m-%d.log'
log_statement = all
log_min_duration_statement = 5000
EOF
# 写入服务
cat > /etc/systemd/system/postgresql.service << EOF
[Unit]
Description=PostgreSQL database server
Documentation=man:postgres(1)
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
User=postgres
ExecStart=${postgresqlPath}/bin/postgres -D ${postgresqlPath}/data
ExecReload=/bin/kill -HUP \$MAINPID
KillMode=mixed
KillSignal=SIGINT
TimeoutSec=infinity
[Install]
WantedBy=multi-user.target
EOF
# 启动服务
systemctl daemon-reload
systemctl enable postgresql
systemctl start postgresql
panel writePlugin postgresql${1} ${postgresqlVersion}
echo -e "${HR}\nPostgreSQL-${1} 安装完成\n${HR}"
+37
View File
@@ -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="+----------------------------------------------------"
systemctl stop postgresql
systemctl disable postgresql
rm -rf /etc/systemd/system/postgresql.service
systemctl daemon-reload
pkill -9 postgresql
rm -rf /www/server/postgresql
userdel -r postgres
groupdel postgres
sed -i '/export PATH=\/www\/server\/postgresql/d' /etc/profile
source /etc/profile
panel deletePlugin postgresql${1}
echo -e "${HR}\nPostgreSQL-${1} 卸载完成\n${HR}"
+130
View File
@@ -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}"
+33
View File
@@ -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}"
+78
View File
@@ -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}"
+116
View File
@@ -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}"
+38
View File
@@ -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}"
+86
View File
@@ -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}"
+38
View File
@@ -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
+33
View File
@@ -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
+21
View File
@@ -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)