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

feat: 更新 PostgreSQL 16.0 和 15.4

This commit is contained in:
耗子
2023-09-25 23:59:24 +08:00
parent 4950b48fc9
commit 198c56cb25
10 changed files with 1490 additions and 3 deletions

View File

@@ -0,0 +1,611 @@
package postgresql16
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 Postgresql16Controller struct {
setting services.Setting
backup services.Backup
}
type Info struct {
Name string `json:"name"`
Value string `json:"value"`
}
func NewPostgresql16Controller() *Postgresql16Controller {
return &Postgresql16Controller{
setting: services.NewSettingImpl(),
backup: services.NewBackupImpl(),
}
}
// Status 获取运行状态
func (c *Postgresql16Controller) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
}
// Reload 重载配置
func (c *Postgresql16Controller) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
tools.Exec("systemctl reload postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
}
// Restart 重启服务
func (c *Postgresql16Controller) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
tools.Exec("systemctl restart postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
}
// Start 启动服务
func (c *Postgresql16Controller) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
tools.Exec("systemctl start postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
}
// Stop 停止服务
func (c *Postgresql16Controller) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
tools.Exec("systemctl stop postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
}
// GetConfig 获取配置
func (c *Postgresql16Controller) GetConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
// 获取配置
config := tools.Read("/www/server/postgresql/data/postgresql.conf")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
return controllers.Success(ctx, config)
}
// GetUserConfig 获取用户配置
func (c *Postgresql16Controller) GetUserConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
// 获取配置
config := tools.Read("/www/server/postgresql/data/pg_hba.conf")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
return controllers.Success(ctx, config)
}
// SaveConfig 保存配置
func (c *Postgresql16Controller) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusBadRequest, "配置不能为空")
}
if !tools.Write("/www/server/postgresql/data/postgresql.conf", config, 0644) {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
return c.Restart(ctx)
}
// SaveUserConfig 保存用户配置
func (c *Postgresql16Controller) SaveUserConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusBadRequest, "配置不能为空")
}
if !tools.Write("/www/server/postgresql/data/pg_hba.conf", config, 0644) {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
return c.Restart(ctx)
}
// Load 获取负载
func (c *Postgresql16Controller) Load(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if status != "active" {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL 已停止运行")
}
data := []Info{
{"启动时间", carbon.Parse(tools.Exec(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)).ToDateTimeString()},
{"进程 PID", tools.Exec(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)},
{"进程数", tools.Exec(`ps aux | grep postgres | grep -v grep | wc -l`)},
{"总连接数", tools.Exec(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)},
{"空间占用", tools.Exec(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)},
}
return controllers.Success(ctx, data)
}
// Log 获取日志
func (c *Postgresql16Controller) Log(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
log := tools.Exec("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
return controllers.Success(ctx, log)
}
// ClearLog 清空日志
func (c *Postgresql16Controller) ClearLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
tools.Exec("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
return controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (c *Postgresql16Controller) DatabaseList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if status != "active" {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL 已停止运行")
}
raw := tools.Exec(`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) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
}
if endIndex > len(databaseList) {
endIndex = len(databaseList)
}
pagedDatabases := databaseList[startIndex:endIndex]
return controllers.Success(ctx, http.Json{
"total": len(databaseList),
"items": pagedDatabases,
})
}
// AddDatabase 添加数据库
func (c *Postgresql16Controller) AddDatabase(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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 {
return controllers.Error(ctx, http.StatusBadRequest, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
}
database := ctx.Request().Input("database")
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.Exec(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`)
tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
tools.Exec(`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.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`)
return c.Reload(ctx)
}
// DeleteDatabase 删除数据库
func (c *Postgresql16Controller) DeleteDatabase(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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 {
return controllers.Error(ctx, http.StatusBadRequest, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
}
database := ctx.Request().Input("database")
tools.Exec(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`)
return controllers.Success(ctx, nil)
}
// BackupList 获取备份列表
func (c *Postgresql16Controller) BackupList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
backupList, err := c.backup.PostgresqlList()
if err != nil {
facades.Log().Error("[PostgreSQL] 获取备份列表失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败")
}
return controllers.Success(ctx, backupList)
}
// UploadBackup 上传备份
func (c *Postgresql16Controller) UploadBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
file, err := ctx.Request().File("file")
if err != nil {
return controllers.Error(ctx, http.StatusBadRequest, "上传文件失败")
}
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 {
return controllers.Error(ctx, http.StatusBadRequest, "上传文件失败")
}
return controllers.Success(ctx, nil)
}
// CreateBackup 创建备份
func (c *Postgresql16Controller) CreateBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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 {
return controllers.Error(ctx, http.StatusBadRequest, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
}
database := ctx.Request().Input("database")
err = c.backup.PostgresqlBackup(database)
if err != nil {
facades.Log().Error("[PostgreSQL] 创建备份失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "创建备份失败")
}
return controllers.Success(ctx, nil)
}
// DeleteBackup 删除备份
func (c *Postgresql16Controller) DeleteBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|min_len:1|max_len:255",
})
if err != nil {
return controllers.Error(ctx, http.StatusBadRequest, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
}
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
fileName := ctx.Request().Input("name")
tools.Remove(backupPath + "/" + fileName)
return controllers.Success(ctx, nil)
}
// RestoreBackup 还原备份
func (c *Postgresql16Controller) RestoreBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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 {
return controllers.Error(ctx, http.StatusBadRequest, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
}
err = c.backup.PostgresqlRestore(ctx.Request().Input("database"), ctx.Request().Input("name"))
if err != nil {
facades.Log().Error("[PostgreSQL] 还原失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error())
}
return controllers.Success(ctx, nil)
}
// UserList 用户列表
func (c *Postgresql16Controller) UserList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
type user struct {
User string `json:"user"`
Role string `json:"role"`
}
raw := tools.Exec(`echo "\du" | su - postgres -c "psql"`)
users := strings.Split(raw, "\n")
if len(users) < 4 {
return controllers.Error(ctx, http.StatusInternalServerError, "用户列表为空")
}
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) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []user{},
})
}
if endIndex > len(userList) {
endIndex = len(userList)
}
pagedUsers := userList[startIndex:endIndex]
return controllers.Success(ctx, http.Json{
"total": len(userList),
"items": pagedUsers,
})
}
// AddUser 添加用户
func (c *Postgresql16Controller) AddUser(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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 {
return controllers.Error(ctx, http.StatusBadRequest, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
}
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
tools.Exec(`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.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`)
return c.Reload(ctx)
}
// DeleteUser 删除用户
func (c *Postgresql16Controller) DeleteUser(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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 {
return controllers.Error(ctx, http.StatusBadRequest, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
}
user := ctx.Request().Input("user")
tools.Exec(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`)
tools.Exec(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`)
return c.Reload(ctx)
}
// SetUserPassword 设置用户密码
func (c *Postgresql16Controller) SetUserPassword(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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 {
return controllers.Error(ctx, http.StatusBadRequest, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
}
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.Exec(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
return controllers.Success(ctx, nil)
}

View File

@@ -4,9 +4,9 @@ var (
Name = "PostgreSQL-15"
Description = "PostgreSQL 是开源的对象 - 关系数据库数据库管理系统,在类似 BSD 许可与 MIT 许可的 PostgreSQL 许可下发行。"
Slug = "postgresql15"
Version = "15.3"
Version = "15.4"
Requires = []string{}
Excludes = []string{}
Excludes = []string{"postgresql16"}
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`

View File

@@ -0,0 +1,13 @@
package postgresql16
var (
Name = "PostgreSQL-16"
Description = "PostgreSQL 是开源的对象 - 关系数据库数据库管理系统,在类似 BSD 许可与 MIT 许可的 PostgreSQL 许可下发行。"
Slug = "postgresql16"
Version = "16.0"
Requires = []string{}
Excludes = []string{"postgresql15"}
Install = `bash /www/panel/scripts/postgresql/install.sh 16`
Uninstall = `bash /www/panel/scripts/postgresql/uninstall.sh 16`
Update = `bash /www/panel/scripts/postgresql/update.sh 16`
)

View File

@@ -15,6 +15,7 @@ import (
"panel/app/plugins/php82"
"panel/app/plugins/phpmyadmin"
"panel/app/plugins/postgresql15"
"panel/app/plugins/postgresql16"
"panel/app/plugins/pureftpd"
"panel/app/plugins/redis"
"panel/app/plugins/s3fs"
@@ -107,6 +108,17 @@ func (r *PluginImpl) All() []PanelPlugin {
Uninstall: postgresql15.Uninstall,
Update: postgresql15.Update,
})
p = append(p, PanelPlugin{
Name: postgresql16.Name,
Description: postgresql16.Description,
Slug: postgresql16.Slug,
Version: postgresql16.Version,
Requires: postgresql16.Requires,
Excludes: postgresql16.Excludes,
Install: postgresql16.Install,
Uninstall: postgresql16.Uninstall,
Update: postgresql16.Update,
})
p = append(p, PanelPlugin{
Name: php74.Name,
Description: php74.Description,

View File

@@ -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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/backup', {
data: data
}).done(function () {
form.render(null, 'LAY-popup-postgresql-backup');
});
}
});
}
});
// 获取数据库用户列表
table.render({
elem: '#postgresql-user-list'
, url: '/api/plugins/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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>

View File

@@ -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/postgresql16/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>

View File

@@ -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/postgresql16/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>

View File

@@ -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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/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/postgresql16/restoreBackup"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
layer.msg('数据库备份恢复失败,请刷新重试!')
return false;
}
layer.alert('数据库备份' + data.name + '恢复成功!');
}
});
});
}
});
});
};
</script>

View File

@@ -14,6 +14,7 @@ import (
"panel/app/http/controllers/plugins/php82"
"panel/app/http/controllers/plugins/phpmyadmin"
"panel/app/http/controllers/plugins/postgresql15"
"panel/app/http/controllers/plugins/postgresql16"
"panel/app/http/controllers/plugins/pureftpd"
"panel/app/http/controllers/plugins/redis"
"panel/app/http/controllers/plugins/s3fs"
@@ -124,6 +125,33 @@ func Plugin() {
route.Post("deleteUser", postgresql15Controller.DeleteUser)
route.Post("userPassword", postgresql15Controller.SetUserPassword)
})
facades.Route().Prefix("api/plugins/postgresql16").Middleware(middleware.Jwt()).Group(func(route route.Router) {
postgresql16Controller := postgresql16.NewPostgresql16Controller()
route.Get("status", postgresql16Controller.Status)
route.Post("reload", postgresql16Controller.Reload)
route.Post("start", postgresql16Controller.Start)
route.Post("stop", postgresql16Controller.Stop)
route.Post("restart", postgresql16Controller.Restart)
route.Get("load", postgresql16Controller.Load)
route.Get("config", postgresql16Controller.GetConfig)
route.Post("config", postgresql16Controller.SaveConfig)
route.Get("userConfig", postgresql16Controller.GetUserConfig)
route.Post("userConfig", postgresql16Controller.SaveUserConfig)
route.Get("log", postgresql16Controller.Log)
route.Post("clearLog", postgresql16Controller.ClearLog)
route.Get("database", postgresql16Controller.DatabaseList)
route.Post("addDatabase", postgresql16Controller.AddDatabase)
route.Post("deleteDatabase", postgresql16Controller.DeleteDatabase)
route.Get("backup", postgresql16Controller.BackupList)
route.Post("createBackup", postgresql16Controller.CreateBackup)
route.Post("uploadBackup", postgresql16Controller.UploadBackup)
route.Post("deleteBackup", postgresql16Controller.DeleteBackup)
route.Post("restoreBackup", postgresql16Controller.RestoreBackup)
route.Get("user", postgresql16Controller.UserList)
route.Post("addUser", postgresql16Controller.AddUser)
route.Post("deleteUser", postgresql16Controller.DeleteUser)
route.Post("userPassword", postgresql16Controller.SetUserPassword)
})
facades.Route().Prefix("api/plugins/php74").Middleware(middleware.Jwt()).Group(func(route route.Router) {
php74Controller := php74.NewPhp74Controller()
route.Get("status", php74Controller.Status)

View File

@@ -29,7 +29,9 @@ postgresqlPassword=$(cat /dev/urandom | head -n 16 | md5sum | head -c 16)
cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l)
if [[ "${1}" == "15" ]]; then
postgresqlVersion="15.3"
postgresqlVersion="15.4"
elif [[ "${1}" == "16" ]]; then
postgresqlVersion="16.0"
else
echo -e $HR
echo "错误:不支持的 PostgreSQL 版本!"