feat: add stub_status_port configuration for Nginx settings

This commit is contained in:
Jacky 2025-04-11 08:32:02 +00:00
parent 2d56914af1
commit 509443a6e7
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
9 changed files with 88 additions and 40 deletions

View file

@ -13,13 +13,13 @@ func InitRouter(r *gin.RouterGroup) {
r.POST("nginx/restart", Restart)
r.POST("nginx/test", Test)
r.GET("nginx/status", Status)
// 获取 Nginx 详细状态信息包括连接数、进程信息等Issue #850
// Get detailed Nginx status information, including connection count, process information, etc. (Issue #850)
r.GET("nginx/detail_status", GetDetailStatus)
// 使用SSE推送Nginx详细状态信息
// Use SSE to push detailed Nginx status information
r.GET("nginx/detail_status/stream", StreamDetailStatus)
// 获取 stub_status 模块状态
// Get stub_status module status
r.GET("nginx/stub_status", CheckStubStatus)
// 启用或禁用 stub_status 模块
// Enable or disable stub_status module
r.POST("nginx/stub_status", ToggleStubStatus)
r.POST("nginx_log", nginx_log.GetNginxLogPage)
r.GET("nginx/directives", GetDirectives)

View file

@ -1,6 +1,6 @@
// GetDetailedStatus API 实现
// 该功能用于解决 Issue #850提供类似宝塔面板的 Nginx 负载监控功能
// 返回详细的 Nginx 状态信息,包括请求统计、连接数、工作进程等数据
// Implementation of GetDetailedStatus API
// This feature is designed to address Issue #850, providing Nginx load monitoring functionality similar to BT Panel
// Returns detailed Nginx status information, including request statistics, connections, worker processes, and other data
package nginx
import (
@ -15,79 +15,79 @@ import (
"github.com/uozi-tech/cosy/logger"
)
// NginxPerformanceInfo 存储 Nginx 性能相关信息
// NginxPerformanceInfo stores Nginx performance-related information
type NginxPerformanceInfo struct {
// 基本状态信息
// Basic status information
nginx.StubStatusData
// 进程相关信息
// Process-related information
nginx.NginxProcessInfo
// 配置信息
// Configuration information
nginx.NginxConfigInfo
}
// GetDetailStatus 获取 Nginx 详细状态信息
// GetDetailStatus retrieves detailed Nginx status information
func GetDetailStatus(c *gin.Context) {
response := nginx.GetPerformanceData()
c.JSON(http.StatusOK, response)
}
// StreamDetailStatus 使用 SSE 流式推送 Nginx 详细状态信息
// StreamDetailStatus streams Nginx detailed status information using SSE
func StreamDetailStatus(c *gin.Context) {
// 设置 SSE 的响应头
// Set SSE response headers
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("Access-Control-Allow-Origin", "*")
// 创建上下文,当客户端断开连接时取消
// Create context that cancels when client disconnects
ctx := c.Request.Context()
// 为防止 goroutine 泄漏,创建一个计时器通道
// Create a ticker channel to prevent goroutine leaks
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
// 立即发送一次初始数据
// Send initial data immediately
sendPerformanceData(c)
// 使用 goroutine 定期发送数据
// Use goroutine to send data periodically
for {
select {
case <-ticker.C:
// 发送性能数据
// Send performance data
if err := sendPerformanceData(c); err != nil {
logger.Warn("Error sending SSE data:", err)
return
}
case <-ctx.Done():
// 客户端断开连接或请求被取消
// Client closed connection or request canceled
logger.Debug("Client closed connection")
return
}
}
}
// sendPerformanceData 发送一次性能数据
// sendPerformanceData sends performance data once
func sendPerformanceData(c *gin.Context) error {
response := nginx.GetPerformanceData()
// 发送 SSE 事件
// Send SSE event
c.SSEvent("message", response)
// 刷新缓冲区,确保数据立即发送
// Flush buffer to ensure data is sent immediately
c.Writer.Flush()
return nil
}
// CheckStubStatus 获取 Nginx stub_status 模块状态
// CheckStubStatus gets Nginx stub_status module status
func CheckStubStatus(c *gin.Context) {
stubStatus := nginx.GetStubStatus()
c.JSON(http.StatusOK, stubStatus)
}
// ToggleStubStatus 启用或禁用 stub_status 模块
// ToggleStubStatus enables or disables stub_status module
func ToggleStubStatus(c *gin.Context) {
var json struct {
Enable bool `json:"enable"`
@ -99,7 +99,7 @@ func ToggleStubStatus(c *gin.Context) {
stubStatus := nginx.GetStubStatus()
// 如果当前状态与期望状态相同,则无需操作
// If current status matches desired status, no action needed
if stubStatus.Enabled == json.Enable {
c.JSON(http.StatusOK, stubStatus)
return
@ -117,7 +117,7 @@ func ToggleStubStatus(c *gin.Context) {
return
}
// 重新加载 Nginx 配置
// Reload Nginx configuration
reloadOutput := nginx.Reload()
if len(reloadOutput) > 0 && (strings.Contains(strings.ToLower(reloadOutput), "error") ||
strings.Contains(strings.ToLower(reloadOutput), "failed")) {
@ -125,7 +125,7 @@ func ToggleStubStatus(c *gin.Context) {
return
}
// 检查操作后的状态
// Check status after operation
newStubStatus := nginx.GetStubStatus()
c.JSON(http.StatusOK, newStubStatus)

View file

@ -71,6 +71,7 @@ func SaveSettings(c *gin.Context) {
Node settings.Node `json:"node"`
Openai settings.OpenAI `json:"openai"`
Logrotate settings.Logrotate `json:"logrotate"`
Nginx settings.Nginx `json:"nginx"`
}
if !cosy.BindAndValid(c, &json) {
@ -104,6 +105,7 @@ func SaveSettings(c *gin.Context) {
cSettings.ProtectedFill(settings.NodeSettings, &json.Node)
cSettings.ProtectedFill(settings.OpenAISettings, &json.Openai)
cSettings.ProtectedFill(settings.LogrotateSettings, &json.Logrotate)
cSettings.ProtectedFill(settings.NginxSettings, &json.Nginx)
err := settings.Save()
if err != nil {

View file

@ -63,6 +63,7 @@ export interface NginxSettings {
test_config_cmd: string
reload_cmd: string
restart_cmd: string
stub_status_port: number
}
export interface NodeSettings {

View file

@ -6,6 +6,9 @@ const data: Ref<Settings> = inject('data') as Ref<Settings>
<template>
<AForm layout="vertical">
<AFormItem :label="$gettext('Stub Status Port')">
<AInputNumber v-model:value="data.nginx.stub_status_port" />
</AFormItem>
<AFormItem :label="$gettext('Nginx Access Log Path')">
{{ data.nginx.access_log_path }}
</AFormItem>

View file

@ -74,6 +74,7 @@ const data = ref<Settings>({
test_config_cmd: '',
reload_cmd: '',
restart_cmd: '',
stub_status_port: 51820,
},
node: {
name: '',

View file

@ -43,7 +43,7 @@ func GetPerformanceData() NginxPerformanceResponse {
logger.Warn("Failed to get Nginx config info:", err)
}
// 确保ProcessMode字段能够正确传递
// Ensure ProcessMode field is correctly passed
perfInfo := NginxPerformanceInfo{
StubStatusData: *statusInfo,
NginxProcessInfo: *processInfo,

View file

@ -1,6 +1,7 @@
package nginx
import (
"bytes"
"fmt"
"io"
"net/http"
@ -8,8 +9,10 @@ import (
"regexp"
"strconv"
"strings"
"text/template"
"time"
"github.com/0xJacky/Nginx-UI/settings"
"github.com/pkg/errors"
"github.com/uozi-tech/cosy/logger"
)
@ -31,7 +34,6 @@ type StubStatusData struct {
}
const (
StubStatusPort = 51828
StubStatusPath = "/stub_status"
StubStatusHost = "127.0.0.1"
StubStatusProtocol = "http"
@ -54,7 +56,7 @@ func GetStubStatusData() (bool, *StubStatusData, error) {
// Get the stub_status status information
enabled, statusURL := IsStubStatusEnabled()
logger.Info("GetStubStatusData", "enabled", enabled, "statusURL", statusURL)
logger.Debug("GetStubStatusData", "enabled", enabled, "statusURL", statusURL)
if !enabled {
return false, result, fmt.Errorf("stub_status is not enabled")
}
@ -131,12 +133,12 @@ func IsStubStatusEnabled() (bool, string) {
for _, server := range ngxConfig.Servers {
protocol := StubStatusProtocol
host := StubStatusHost
port := strconv.Itoa(StubStatusPort)
port := settings.NginxSettings.StubStatusPort
for _, location := range server.Locations {
// Check if the location content contains stub_status
if strings.Contains(location.Content, "stub_status") {
stubStatusURL := fmt.Sprintf("%s://%s:%s%s", protocol, host, port, StubStatusPath)
stubStatusURL := fmt.Sprintf("%s://%s:%d%s", protocol, host, port, StubStatusPath)
return true, stubStatusURL
}
}
@ -169,23 +171,54 @@ func DisableStubStatus() error {
func CreateStubStatusConfig() error {
httpConfPath := GetConfPath("conf.d", StubStatusConfigName)
stubStatusConfig := `
const stubStatusTemplate = `
# DO NOT EDIT THIS FILE, IT IS AUTO GENERATED BY NGINX-UI
# Nginx stub_status configuration for Nginx-UI
# Modified at ` + time.Now().Format("2006-01-02 15:04:05") + `
# Modified at {{.ModifiedTime}}
server {
listen 51828; # Use non-standard port to avoid conflicts
server_name localhost;
listen {{.Port}}; # Use non-standard port to avoid conflicts
server_name {{.ServerName}};
# Status monitoring interface
location /stub_status {
location {{.StatusPath}} {
stub_status;
allow 127.0.0.1; # Only allow local access
deny all;
allow {{.AllowIP}}; # Only allow local access
deny {{.DenyAccess}};
}
}
`
type StubStatusTemplateData struct {
ModifiedTime string
Port uint
ServerName string
StatusPath string
AllowIP string
DenyAccess string
}
data := StubStatusTemplateData{
ModifiedTime: time.Now().Format(time.DateTime),
Port: settings.NginxSettings.StubStatusPort,
ServerName: "localhost",
StatusPath: StubStatusPath,
AllowIP: StubStatusAllow,
DenyAccess: StubStatusDeny,
}
tmpl, err := template.New("stub_status").Parse(stubStatusTemplate)
if err != nil {
return errors.Wrap(err, "failed to parse template")
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return errors.Wrap(err, "failed to execute template")
}
stubStatusConfig := buf.String()
ngxConfig, err := ParseNgxConfigByContent(stubStatusConfig)
if err != nil {
return errors.Wrap(err, "failed to parse new nginx config")

View file

@ -10,6 +10,14 @@ type Nginx struct {
TestConfigCmd string `json:"test_config_cmd" protected:"true"`
ReloadCmd string `json:"reload_cmd" protected:"true"`
RestartCmd string `json:"restart_cmd" protected:"true"`
StubStatusPort uint `json:"stub_status_port" binding:"omitempty,min=1,max=65535"`
}
var NginxSettings = &Nginx{}
func (n *Nginx) GetStubStatusPort() uint {
if n.StubStatusPort == 0 {
return 51820
}
return n.StubStatusPort
}