diff --git a/app/http/controllers/plugins/postgresql16_controller.go b/app/http/controllers/plugins/postgresql16_controller.go deleted file mode 100644 index 46173a16..00000000 --- a/app/http/controllers/plugins/postgresql16_controller.go +++ /dev/null @@ -1,553 +0,0 @@ -package plugins - -import ( - "strings" - - "github.com/goravel/framework/contracts/http" - "github.com/goravel/framework/support/carbon" - - "github.com/TheTNB/panel/app/http/controllers" - "github.com/TheTNB/panel/app/models" - "github.com/TheTNB/panel/internal" - "github.com/TheTNB/panel/internal/services" - "github.com/TheTNB/panel/pkg/tools" - "github.com/TheTNB/panel/types" -) - -type Postgresql16Controller struct { - setting internal.Setting - backup internal.Backup -} - -func NewPostgresql16Controller() *Postgresql16Controller { - return &Postgresql16Controller{ - setting: services.NewSettingImpl(), - backup: services.NewBackupImpl(), - } -} - -// Status 获取运行状态 -func (r *Postgresql16Controller) Status(ctx http.Context) http.Response { - status, err := tools.ServiceStatus("postgresql") - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败") - } - - return controllers.Success(ctx, status) -} - -// Reload 重载配置 -func (r *Postgresql16Controller) Reload(ctx http.Context) http.Response { - if err := tools.ServiceReload("postgresql"); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "重载PostgreSQL失败") - } - - return controllers.Success(ctx, nil) -} - -// Restart 重启服务 -func (r *Postgresql16Controller) Restart(ctx http.Context) http.Response { - if err := tools.ServiceRestart("postgresql"); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "重启PostgreSQL失败") - } - - return controllers.Success(ctx, nil) -} - -// Start 启动服务 -func (r *Postgresql16Controller) Start(ctx http.Context) http.Response { - if err := tools.ServiceStart("postgresql"); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "启动PostgreSQL失败") - } - - return controllers.Success(ctx, nil) -} - -// Stop 停止服务 -func (r *Postgresql16Controller) Stop(ctx http.Context) http.Response { - if err := tools.ServiceStop("postgresql"); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "停止PostgreSQL失败") - } - - return controllers.Success(ctx, nil) -} - -// GetConfig 获取配置 -func (r *Postgresql16Controller) GetConfig(ctx http.Context) http.Response { - // 获取配置 - config, err := tools.Read("/www/server/postgresql/data/postgresql.conf") - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败") - } - - return controllers.Success(ctx, config) -} - -// GetUserConfig 获取用户配置 -func (r *Postgresql16Controller) GetUserConfig(ctx http.Context) http.Response { - // 获取配置 - config, err := tools.Read("/www/server/postgresql/data/pg_hba.conf") - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败") - } - - return controllers.Success(ctx, config) -} - -// SaveConfig 保存配置 -func (r *Postgresql16Controller) SaveConfig(ctx http.Context) http.Response { - config := ctx.Request().Input("config") - if len(config) == 0 { - return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空") - } - - if err := tools.Write("/www/server/postgresql/data/postgresql.conf", config, 0644); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败") - } - - return r.Restart(ctx) -} - -// SaveUserConfig 保存用户配置 -func (r *Postgresql16Controller) SaveUserConfig(ctx http.Context) http.Response { - config := ctx.Request().Input("config") - if len(config) == 0 { - return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空") - } - - if err := tools.Write("/www/server/postgresql/data/pg_hba.conf", config, 0644); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败") - } - - return r.Restart(ctx) -} - -// Load 获取负载 -func (r *Postgresql16Controller) Load(ctx http.Context) http.Response { - status, err := tools.ServiceStatus("postgresql") - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败") - } - if !status { - return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL已停止运行") - } - - time, err := tools.Exec(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`) - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL启动时间失败") - } - pid, err := tools.Exec(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`) - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程PID失败") - } - process, err := tools.Exec(`ps aux | grep postgres | grep -v grep | wc -l`) - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程数失败") - } - connections, err := tools.Exec(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`) - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL连接数失败") - } - storage, err := tools.Exec(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`) - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL空间占用失败") - } - - data := []types.NV{ - {Name: "启动时间", Value: carbon.Parse(time).ToDateTimeString()}, - {Name: "进程 PID", Value: pid}, - {Name: "进程数", Value: process}, - {Name: "总连接数", Value: connections}, - {Name: "空间占用", Value: storage}, - } - - return controllers.Success(ctx, data) -} - -// Log 获取日志 -func (r *Postgresql16Controller) Log(ctx http.Context) http.Response { - log, err := tools.Exec("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log") - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, log) - } - - return controllers.Success(ctx, log) -} - -// ClearLog 清空日志 -func (r *Postgresql16Controller) ClearLog(ctx http.Context) http.Response { - if out, err := tools.Exec("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log"); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - - return controllers.Success(ctx, nil) -} - -// DatabaseList 获取数据库列表 -func (r *Postgresql16Controller) DatabaseList(ctx http.Context) http.Response { - status, err := tools.ServiceStatus("postgresql") - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败") - } - if !status { - return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL已停止运行") - } - - type database struct { - Name string `json:"name"` - Owner string `json:"owner"` - Encoding string `json:"encoding"` - } - - raw, err := tools.Exec(`echo "\l" | su - postgres -c "psql"`) - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, raw) - } - databases := strings.Split(raw, "\n") - if len(databases) >= 4 { - databases = databases[3 : len(databases)-1] - } else { - return controllers.Success(ctx, http.Json{ - "total": 0, - "items": []database{}, - }) - } - - var databaseList []database - for _, db := range databases { - parts := strings.Split(db, "|") - if len(parts) != 9 || 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 (r *Postgresql16Controller) AddDatabase(ctx http.Context) http.Response { - 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) - } - - database := ctx.Request().Input("database") - user := ctx.Request().Input("user") - password := ctx.Request().Input("password") - - if out, err := tools.Exec(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - if out, err := tools.Exec(`echo "ALTER DATABASE ` + database + ` OWNER TO ` + user + `;" | su - postgres -c "psql"`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - - userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256" - if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - - return r.Reload(ctx) -} - -// DeleteDatabase 删除数据库 -func (r *Postgresql16Controller) DeleteDatabase(ctx http.Context) http.Response { - 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) - } - - database := ctx.Request().Input("database") - if out, err := tools.Exec(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - - return controllers.Success(ctx, nil) -} - -// BackupList 获取备份列表 -func (r *Postgresql16Controller) BackupList(ctx http.Context) http.Response { - backupList, err := r.backup.PostgresqlList() - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败") - } - - page := ctx.Request().QueryInt("page", 1) - limit := ctx.Request().QueryInt("limit", 10) - startIndex := (page - 1) * limit - endIndex := page * limit - if startIndex > len(backupList) { - return controllers.Success(ctx, http.Json{ - "total": 0, - "items": []types.BackupFile{}, - }) - } - if endIndex > len(backupList) { - endIndex = len(backupList) - } - pagedBackupList := backupList[startIndex:endIndex] - if pagedBackupList == nil { - pagedBackupList = []types.BackupFile{} - } - - return controllers.Success(ctx, http.Json{ - "total": len(backupList), - "items": pagedBackupList, - }) -} - -// UploadBackup 上传备份 -func (r *Postgresql16Controller) UploadBackup(ctx http.Context) http.Response { - file, err := ctx.Request().File("file") - if err != nil { - return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败") - } - - backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/postgresql" - if !tools.Exists(backupPath) { - if err = tools.Mkdir(backupPath, 0644); err != nil { - return nil - } - } - - name := file.GetClientOriginalName() - _, err = file.StoreAs(backupPath, name) - if err != nil { - return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败") - } - - return controllers.Success(ctx, nil) -} - -// CreateBackup 创建备份 -func (r *Postgresql16Controller) CreateBackup(ctx http.Context) http.Response { - 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) - } - - database := ctx.Request().Input("database") - err = r.backup.PostgresqlBackup(database) - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) - } - - return controllers.Success(ctx, nil) -} - -// DeleteBackup 删除备份 -func (r *Postgresql16Controller) DeleteBackup(ctx http.Context) http.Response { - validator, err := ctx.Request().Validate(map[string]string{ - "name": "required|min_len:1|max_len:255", - }) - if err != nil { - return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) - } - - backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/postgresql" - fileName := ctx.Request().Input("name") - if err := tools.Remove(backupPath + "/" + fileName); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) - } - - return controllers.Success(ctx, nil) -} - -// RestoreBackup 还原备份 -func (r *Postgresql16Controller) RestoreBackup(ctx http.Context) http.Response { - validator, err := ctx.Request().Validate(map[string]string{ - "backup": "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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) - } - - err = r.backup.PostgresqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup")) - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error()) - } - - return controllers.Success(ctx, nil) -} - -// UserList 用户列表 -func (r *Postgresql16Controller) UserList(ctx http.Context) http.Response { - type user struct { - User string `json:"user"` - Role string `json:"role"` - } - - raw, err := tools.Exec(`echo "\du" | su - postgres -c "psql"`) - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, raw) - } - 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) != 2 { - 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 (r *Postgresql16Controller) AddUser(ctx http.Context) http.Response { - 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) - } - - user := ctx.Request().Input("user") - password := ctx.Request().Input("password") - database := ctx.Request().Input("database") - if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - - userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256" - if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - - return r.Reload(ctx) -} - -// DeleteUser 删除用户 -func (r *Postgresql16Controller) DeleteUser(ctx http.Context) http.Response { - 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) - } - - user := ctx.Request().Input("user") - if out, err := tools.Exec(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - if out, err := tools.Exec(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - - return r.Reload(ctx) -} - -// SetUserPassword 设置用户密码 -func (r *Postgresql16Controller) SetUserPassword(ctx http.Context) http.Response { - 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) - } - - user := ctx.Request().Input("user") - password := ctx.Request().Input("password") - if out, err := tools.Exec(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - - return controllers.Success(ctx, nil) -} diff --git a/app/http/controllers/plugins/postgresql15_controller.go b/app/http/controllers/plugins/postgresql_controller.go similarity index 65% rename from app/http/controllers/plugins/postgresql15_controller.go rename to app/http/controllers/plugins/postgresql_controller.go index 57eee514..26666808 100644 --- a/app/http/controllers/plugins/postgresql15_controller.go +++ b/app/http/controllers/plugins/postgresql_controller.go @@ -1,10 +1,11 @@ package plugins import ( - "strings" + "database/sql" "github.com/goravel/framework/contracts/http" "github.com/goravel/framework/support/carbon" + _ "github.com/lib/pq" "github.com/TheTNB/panel/app/http/controllers" "github.com/TheTNB/panel/app/models" @@ -14,66 +15,20 @@ import ( "github.com/TheTNB/panel/types" ) -type Postgresql15Controller struct { +type PostgreSQLController struct { setting internal.Setting backup internal.Backup } -func NewPostgresql15Controller() *Postgresql15Controller { - return &Postgresql15Controller{ +func NewPostgreSQLController() *PostgreSQLController { + return &PostgreSQLController{ setting: services.NewSettingImpl(), backup: services.NewBackupImpl(), } } -// Status 获取运行状态 -func (r *Postgresql15Controller) Status(ctx http.Context) http.Response { - status, err := tools.ServiceStatus("postgresql") - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败") - } - - return controllers.Success(ctx, status) -} - -// Reload 重载配置 -func (r *Postgresql15Controller) Reload(ctx http.Context) http.Response { - if err := tools.ServiceReload("postgresql"); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "重载PostgreSQL失败") - } - - return controllers.Success(ctx, nil) -} - -// Restart 重启服务 -func (r *Postgresql15Controller) Restart(ctx http.Context) http.Response { - if err := tools.ServiceRestart("postgresql"); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "重启PostgreSQL失败") - } - - return controllers.Success(ctx, nil) -} - -// Start 启动服务 -func (r *Postgresql15Controller) Start(ctx http.Context) http.Response { - if err := tools.ServiceStart("postgresql"); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "启动PostgreSQL失败") - } - - return controllers.Success(ctx, nil) -} - -// Stop 停止服务 -func (r *Postgresql15Controller) Stop(ctx http.Context) http.Response { - if err := tools.ServiceStop("postgresql"); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "停止PostgreSQL失败") - } - - return controllers.Success(ctx, nil) -} - // GetConfig 获取配置 -func (r *Postgresql15Controller) GetConfig(ctx http.Context) http.Response { +func (r *PostgreSQLController) GetConfig(ctx http.Context) http.Response { // 获取配置 config, err := tools.Read("/www/server/postgresql/data/postgresql.conf") if err != nil { @@ -84,7 +39,7 @@ func (r *Postgresql15Controller) GetConfig(ctx http.Context) http.Response { } // GetUserConfig 获取用户配置 -func (r *Postgresql15Controller) GetUserConfig(ctx http.Context) http.Response { +func (r *PostgreSQLController) GetUserConfig(ctx http.Context) http.Response { // 获取配置 config, err := tools.Read("/www/server/postgresql/data/pg_hba.conf") if err != nil { @@ -95,7 +50,7 @@ func (r *Postgresql15Controller) GetUserConfig(ctx http.Context) http.Response { } // SaveConfig 保存配置 -func (r *Postgresql15Controller) SaveConfig(ctx http.Context) http.Response { +func (r *PostgreSQLController) SaveConfig(ctx http.Context) http.Response { config := ctx.Request().Input("config") if len(config) == 0 { return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空") @@ -105,11 +60,15 @@ func (r *Postgresql15Controller) SaveConfig(ctx http.Context) http.Response { return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败") } - return r.Restart(ctx) + if err := tools.ServiceReload("postgresql"); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败") + } + + return controllers.Success(ctx, nil) } // SaveUserConfig 保存用户配置 -func (r *Postgresql15Controller) SaveUserConfig(ctx http.Context) http.Response { +func (r *PostgreSQLController) SaveUserConfig(ctx http.Context) http.Response { config := ctx.Request().Input("config") if len(config) == 0 { return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空") @@ -119,11 +78,15 @@ func (r *Postgresql15Controller) SaveUserConfig(ctx http.Context) http.Response return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败") } - return r.Restart(ctx) + if err := tools.ServiceReload("postgresql"); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败") + } + + return controllers.Success(ctx, nil) } // Load 获取负载 -func (r *Postgresql15Controller) Load(ctx http.Context) http.Response { +func (r *PostgreSQLController) Load(ctx http.Context) http.Response { status, err := tools.ServiceStatus("postgresql") if err != nil { return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败") @@ -165,7 +128,7 @@ func (r *Postgresql15Controller) Load(ctx http.Context) http.Response { } // Log 获取日志 -func (r *Postgresql15Controller) Log(ctx http.Context) http.Response { +func (r *PostgreSQLController) Log(ctx http.Context) http.Response { log, err := tools.Exec("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log") if err != nil { return controllers.Error(ctx, http.StatusInternalServerError, log) @@ -175,7 +138,7 @@ func (r *Postgresql15Controller) Log(ctx http.Context) http.Response { } // ClearLog 清空日志 -func (r *Postgresql15Controller) ClearLog(ctx http.Context) http.Response { +func (r *PostgreSQLController) ClearLog(ctx http.Context) http.Response { if out, err := tools.Exec("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log"); err != nil { return controllers.Error(ctx, http.StatusInternalServerError, out) } @@ -184,82 +147,73 @@ func (r *Postgresql15Controller) ClearLog(ctx http.Context) http.Response { } // DatabaseList 获取数据库列表 -func (r *Postgresql15Controller) DatabaseList(ctx http.Context) http.Response { - status, err := tools.ServiceStatus("postgresql") +func (r *PostgreSQLController) DatabaseList(ctx http.Context) http.Response { + db, err := sql.Open("postgres", "host=localhost port=5432 user=postgres dbname=postgres sslmode=disable") if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败") + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if !status { - return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL已停止运行") + if err = db.Ping(); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } + query := ` + SELECT d.datname, pg_catalog.pg_get_userbyid(d.datdba), pg_catalog.pg_encoding_to_char(d.encoding) + FROM pg_catalog.pg_database d + WHERE datistemplate = false; + ` + rows, err := db.Query(query) + if err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) + } + defer rows.Close() + type database struct { Name string `json:"name"` Owner string `json:"owner"` Encoding string `json:"encoding"` } - raw, err := tools.Exec(`echo "\l" | su - postgres -c "psql"`) - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, raw) - } - databases := strings.Split(raw, "\n") - if len(databases) >= 4 { - databases = databases[3 : len(databases)-1] - } else { - return controllers.Success(ctx, http.Json{ - "total": 0, - "items": []database{}, - }) - } - - var databaseList []database - for _, db := range databases { - parts := strings.Split(db, "|") - if len(parts) != 8 || len(strings.TrimSpace(parts[0])) == 0 { - continue + var databases []database + for rows.Next() { + var db database + if err := rows.Scan(&db.Name, &db.Owner, &db.Encoding); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - - databaseList = append(databaseList, database{ - Name: strings.TrimSpace(parts[0]), - Owner: strings.TrimSpace(parts[1]), - Encoding: strings.TrimSpace(parts[2]), - }) + databases = append(databases, db) + } + if err = rows.Err(); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } page := ctx.Request().QueryInt("page", 1) limit := ctx.Request().QueryInt("limit", 10) startIndex := (page - 1) * limit endIndex := page * limit - if startIndex > len(databaseList) { + if startIndex > len(databases) { return controllers.Success(ctx, http.Json{ "total": 0, "items": []database{}, }) } - if endIndex > len(databaseList) { - endIndex = len(databaseList) + if endIndex > len(databases) { + endIndex = len(databases) } - pagedDatabases := databaseList[startIndex:endIndex] + pagedDatabases := databases[startIndex:endIndex] return controllers.Success(ctx, http.Json{ - "total": len(databaseList), + "total": len(databases), "items": pagedDatabases, }) } // AddDatabase 添加数据库 -func (r *Postgresql15Controller) AddDatabase(ctx http.Context) http.Response { - validator, err := ctx.Request().Validate(map[string]string{ +func (r *PostgreSQLController) AddDatabase(ctx http.Context) http.Response { + if sanitize := controllers.Sanitize(ctx, 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) + }); sanitize != nil { + return sanitize } database := ctx.Request().Input("database") @@ -284,19 +238,19 @@ func (r *Postgresql15Controller) AddDatabase(ctx http.Context) http.Response { return controllers.Error(ctx, http.StatusInternalServerError, out) } - return r.Reload(ctx) + if err := tools.ServiceReload("postgresql"); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败") + } + + return controllers.Success(ctx, nil) } // DeleteDatabase 删除数据库 -func (r *Postgresql15Controller) DeleteDatabase(ctx http.Context) http.Response { - validator, err := ctx.Request().Validate(map[string]string{ +func (r *PostgreSQLController) DeleteDatabase(ctx http.Context) http.Response { + if sanitize := controllers.Sanitize(ctx, 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) + }); sanitize != nil { + return sanitize } database := ctx.Request().Input("database") @@ -308,7 +262,7 @@ func (r *Postgresql15Controller) DeleteDatabase(ctx http.Context) http.Response } // BackupList 获取备份列表 -func (r *Postgresql15Controller) BackupList(ctx http.Context) http.Response { +func (r *PostgreSQLController) BackupList(ctx http.Context) http.Response { backupList, err := r.backup.PostgresqlList() if err != nil { return controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败") @@ -339,7 +293,7 @@ func (r *Postgresql15Controller) BackupList(ctx http.Context) http.Response { } // UploadBackup 上传备份 -func (r *Postgresql15Controller) UploadBackup(ctx http.Context) http.Response { +func (r *PostgreSQLController) UploadBackup(ctx http.Context) http.Response { file, err := ctx.Request().File("file") if err != nil { return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败") @@ -362,20 +316,15 @@ func (r *Postgresql15Controller) UploadBackup(ctx http.Context) http.Response { } // CreateBackup 创建备份 -func (r *Postgresql15Controller) CreateBackup(ctx http.Context) http.Response { - validator, err := ctx.Request().Validate(map[string]string{ +func (r *PostgreSQLController) CreateBackup(ctx http.Context) http.Response { + if sanitize := controllers.Sanitize(ctx, 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) + }); sanitize != nil { + return sanitize } database := ctx.Request().Input("database") - err = r.backup.PostgresqlBackup(database) - if err != nil { + if err := r.backup.PostgresqlBackup(database); err != nil { return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } @@ -383,15 +332,11 @@ func (r *Postgresql15Controller) CreateBackup(ctx http.Context) http.Response { } // DeleteBackup 删除备份 -func (r *Postgresql15Controller) DeleteBackup(ctx http.Context) http.Response { - validator, err := ctx.Request().Validate(map[string]string{ +func (r *PostgreSQLController) DeleteBackup(ctx http.Context) http.Response { + if sanitize := controllers.Sanitize(ctx, map[string]string{ "name": "required|min_len:1|max_len:255", - }) - if err != nil { - return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) + }); sanitize != nil { + return sanitize } backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/postgresql" @@ -404,89 +349,112 @@ func (r *Postgresql15Controller) DeleteBackup(ctx http.Context) http.Response { } // RestoreBackup 还原备份 -func (r *Postgresql15Controller) RestoreBackup(ctx http.Context) http.Response { - validator, err := ctx.Request().Validate(map[string]string{ +func (r *PostgreSQLController) RestoreBackup(ctx http.Context) http.Response { + if sanitize := controllers.Sanitize(ctx, map[string]string{ "backup": "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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) + }); sanitize != nil { + return sanitize } - err = r.backup.PostgresqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup")) - if err != nil { + if err := r.backup.PostgresqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup")); err != nil { return controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error()) } return controllers.Success(ctx, nil) } -// UserList 用户列表 -func (r *Postgresql15Controller) UserList(ctx http.Context) http.Response { - type user struct { - User string `json:"user"` - Role string `json:"role"` - } - - raw, err := tools.Exec(`echo "\du" | su - postgres -c "psql"`) +// RoleList 角色列表 +func (r *PostgreSQLController) RoleList(ctx http.Context) http.Response { + db, err := sql.Open("postgres", "host=localhost port=5432 user=postgres dbname=postgres sslmode=disable") if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, raw) + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - users := strings.Split(raw, "\n") - if len(users) < 4 { - return controllers.Error(ctx, http.StatusInternalServerError, "用户列表为空") + if err = db.Ping(); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - users = users[3:] - var userList []user - for _, u := range users { - userInfo := strings.Split(u, "|") - if len(userInfo) != 3 { - continue + query := ` + SELECT rolname, + rolsuper, + rolcreaterole, + rolcreatedb, + rolreplication, + rolbypassrls + FROM pg_roles + WHERE rolcanlogin = true; + ` + rows, err := db.Query(query) + if err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) + } + defer rows.Close() + + type role struct { + Role string `json:"role"` + Attributes []string `json:"attributes"` + } + + var roles []role + for rows.Next() { + var r role + var super, canCreateRole, canCreateDb, replication, bypassRls bool + if err = rows.Scan(&r.Role, &super, &canCreateRole, &canCreateDb, &replication, &bypassRls); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - userList = append(userList, user{ - User: strings.TrimSpace(userInfo[0]), - Role: strings.TrimSpace(userInfo[1]), - }) + permissions := map[string]bool{ + "超级用户": super, + "创建角色": canCreateRole, + "创建数据库": canCreateDb, + "可以复制": replication, + "绕过行级安全": bypassRls, + } + for perm, enabled := range permissions { + if enabled { + r.Attributes = append(r.Attributes, perm) + } + } + + if len(r.Attributes) == 0 { + r.Attributes = append(r.Attributes, "无") + } + + roles = append(roles, r) + } + if err = rows.Err(); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } page := ctx.Request().QueryInt("page", 1) limit := ctx.Request().QueryInt("limit", 10) startIndex := (page - 1) * limit endIndex := page * limit - if startIndex > len(userList) { + if startIndex > len(roles) { return controllers.Success(ctx, http.Json{ "total": 0, - "items": []user{}, + "items": []role{}, }) } - if endIndex > len(userList) { - endIndex = len(userList) + if endIndex > len(roles) { + endIndex = len(roles) } - pagedUsers := userList[startIndex:endIndex] + pagedRoles := roles[startIndex:endIndex] return controllers.Success(ctx, http.Json{ - "total": len(userList), - "items": pagedUsers, + "total": len(roles), + "items": pagedRoles, }) } -// AddUser 添加用户 -func (r *Postgresql15Controller) AddUser(ctx http.Context) http.Response { - validator, err := ctx.Request().Validate(map[string]string{ +// AddRole 添加角色 +func (r *PostgreSQLController) AddRole(ctx http.Context) http.Response { + if sanitize := controllers.Sanitize(ctx, 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) + }); sanitize != nil { + return sanitize } user := ctx.Request().Input("user") @@ -504,19 +472,19 @@ func (r *Postgresql15Controller) AddUser(ctx http.Context) http.Response { return controllers.Error(ctx, http.StatusInternalServerError, out) } - return r.Reload(ctx) + if err := tools.ServiceReload("postgresql"); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败") + } + + return controllers.Success(ctx, nil) } -// DeleteUser 删除用户 -func (r *Postgresql15Controller) DeleteUser(ctx http.Context) http.Response { - validator, err := ctx.Request().Validate(map[string]string{ +// DeleteRole 删除角色 +func (r *PostgreSQLController) DeleteRole(ctx http.Context) http.Response { + if sanitize := controllers.Sanitize(ctx, 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) + }); sanitize != nil { + return sanitize } user := ctx.Request().Input("user") @@ -527,20 +495,20 @@ func (r *Postgresql15Controller) DeleteUser(ctx http.Context) http.Response { return controllers.Error(ctx, http.StatusInternalServerError, out) } - return r.Reload(ctx) + if err := tools.ServiceReload("postgresql"); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败") + } + + return controllers.Success(ctx, nil) } -// SetUserPassword 设置用户密码 -func (r *Postgresql15Controller) SetUserPassword(ctx http.Context) http.Response { - validator, err := ctx.Request().Validate(map[string]string{ +// SetRolePassword 设置用户密码 +func (r *PostgreSQLController) SetRolePassword(ctx http.Context) http.Response { + if sanitize := controllers.Sanitize(ctx, 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.StatusUnprocessableEntity, err.Error()) - } - if validator.Fails() { - return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One()) + }); sanitize != nil { + return sanitize } user := ctx.Request().Input("user") diff --git a/routes/plugin.go b/routes/plugin.go index d7af9c7c..3a39caf0 100644 --- a/routes/plugin.go +++ b/routes/plugin.go @@ -44,59 +44,27 @@ func Plugin() { route.Post("users/password", mySQLController.SetUserPassword) route.Post("users/privileges", mySQLController.SetUserPrivileges) }) - r.Prefix("postgresql15").Group(func(route route.Router) { - postgresql15Controller := plugins.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("databases", postgresql15Controller.DatabaseList) - route.Post("databases", postgresql15Controller.AddDatabase) - route.Delete("databases", postgresql15Controller.DeleteDatabase) - route.Get("backups", postgresql15Controller.BackupList) - route.Post("backups", postgresql15Controller.CreateBackup) - route.Put("backups", postgresql15Controller.UploadBackup) - route.Delete("backups", postgresql15Controller.DeleteBackup) - route.Post("backups/restore", postgresql15Controller.RestoreBackup) - route.Get("users", postgresql15Controller.UserList) - route.Post("users", postgresql15Controller.AddUser) - route.Delete("users", postgresql15Controller.DeleteUser) - route.Post("users/password", postgresql15Controller.SetUserPassword) - }) - r.Prefix("postgresql16").Group(func(route route.Router) { - postgresql16Controller := plugins.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("databases", postgresql16Controller.DatabaseList) - route.Post("databases", postgresql16Controller.AddDatabase) - route.Delete("databases", postgresql16Controller.DeleteDatabase) - route.Get("backups", postgresql16Controller.BackupList) - route.Post("backups", postgresql16Controller.CreateBackup) - route.Put("backups", postgresql16Controller.UploadBackup) - route.Delete("backups", postgresql16Controller.DeleteBackup) - route.Post("backups/restore", postgresql16Controller.RestoreBackup) - route.Get("users", postgresql16Controller.UserList) - route.Post("users", postgresql16Controller.AddUser) - route.Delete("users", postgresql16Controller.DeleteUser) - route.Post("users/password", postgresql16Controller.SetUserPassword) + r.Prefix("postgresql").Group(func(route route.Router) { + controller := plugins.NewPostgreSQLController() + route.Get("load", controller.Load) + route.Get("config", controller.GetConfig) + route.Post("config", controller.SaveConfig) + route.Get("userConfig", controller.GetUserConfig) + route.Post("userConfig", controller.SaveUserConfig) + route.Get("log", controller.Log) + route.Post("clearLog", controller.ClearLog) + route.Get("databases", controller.DatabaseList) + route.Post("databases", controller.AddDatabase) + route.Delete("databases", controller.DeleteDatabase) + route.Get("backups", controller.BackupList) + route.Post("backups", controller.CreateBackup) + route.Put("backups", controller.UploadBackup) + route.Delete("backups", controller.DeleteBackup) + route.Post("backups/restore", controller.RestoreBackup) + route.Get("roles", controller.RoleList) + route.Post("roles", controller.AddRole) + route.Delete("roles", controller.DeleteRole) + route.Post("roles/password", controller.SetRolePassword) }) r.Prefix("php").Group(func(route route.Router) { phpController := plugins.NewPHPController()