mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
feat: add stub_status_port configuration for Nginx settings
This commit is contained in:
parent
2d56914af1
commit
509443a6e7
9 changed files with 88 additions and 40 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -63,6 +63,7 @@ export interface NginxSettings {
|
|||
test_config_cmd: string
|
||||
reload_cmd: string
|
||||
restart_cmd: string
|
||||
stub_status_port: number
|
||||
}
|
||||
|
||||
export interface NodeSettings {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -74,6 +74,7 @@ const data = ref<Settings>({
|
|||
test_config_cmd: '',
|
||||
reload_cmd: '',
|
||||
restart_cmd: '',
|
||||
stub_status_port: 51820,
|
||||
},
|
||||
node: {
|
||||
name: '',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue