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/restart", Restart)
|
||||||
r.POST("nginx/test", Test)
|
r.POST("nginx/test", Test)
|
||||||
r.GET("nginx/status", Status)
|
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)
|
r.GET("nginx/detail_status", GetDetailStatus)
|
||||||
// 使用SSE推送Nginx详细状态信息
|
// Use SSE to push detailed Nginx status information
|
||||||
r.GET("nginx/detail_status/stream", StreamDetailStatus)
|
r.GET("nginx/detail_status/stream", StreamDetailStatus)
|
||||||
// 获取 stub_status 模块状态
|
// Get stub_status module status
|
||||||
r.GET("nginx/stub_status", CheckStubStatus)
|
r.GET("nginx/stub_status", CheckStubStatus)
|
||||||
// 启用或禁用 stub_status 模块
|
// Enable or disable stub_status module
|
||||||
r.POST("nginx/stub_status", ToggleStubStatus)
|
r.POST("nginx/stub_status", ToggleStubStatus)
|
||||||
r.POST("nginx_log", nginx_log.GetNginxLogPage)
|
r.POST("nginx_log", nginx_log.GetNginxLogPage)
|
||||||
r.GET("nginx/directives", GetDirectives)
|
r.GET("nginx/directives", GetDirectives)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// GetDetailedStatus API 实现
|
// Implementation of GetDetailedStatus API
|
||||||
// 该功能用于解决 Issue #850,提供类似宝塔面板的 Nginx 负载监控功能
|
// This feature is designed to address Issue #850, providing Nginx load monitoring functionality similar to BT Panel
|
||||||
// 返回详细的 Nginx 状态信息,包括请求统计、连接数、工作进程等数据
|
// Returns detailed Nginx status information, including request statistics, connections, worker processes, and other data
|
||||||
package nginx
|
package nginx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -15,79 +15,79 @@ import (
|
||||||
"github.com/uozi-tech/cosy/logger"
|
"github.com/uozi-tech/cosy/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NginxPerformanceInfo 存储 Nginx 性能相关信息
|
// NginxPerformanceInfo stores Nginx performance-related information
|
||||||
type NginxPerformanceInfo struct {
|
type NginxPerformanceInfo struct {
|
||||||
// 基本状态信息
|
// Basic status information
|
||||||
nginx.StubStatusData
|
nginx.StubStatusData
|
||||||
|
|
||||||
// 进程相关信息
|
// Process-related information
|
||||||
nginx.NginxProcessInfo
|
nginx.NginxProcessInfo
|
||||||
|
|
||||||
// 配置信息
|
// Configuration information
|
||||||
nginx.NginxConfigInfo
|
nginx.NginxConfigInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDetailStatus 获取 Nginx 详细状态信息
|
// GetDetailStatus retrieves detailed Nginx status information
|
||||||
func GetDetailStatus(c *gin.Context) {
|
func GetDetailStatus(c *gin.Context) {
|
||||||
response := nginx.GetPerformanceData()
|
response := nginx.GetPerformanceData()
|
||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamDetailStatus 使用 SSE 流式推送 Nginx 详细状态信息
|
// StreamDetailStatus streams Nginx detailed status information using SSE
|
||||||
func StreamDetailStatus(c *gin.Context) {
|
func StreamDetailStatus(c *gin.Context) {
|
||||||
// 设置 SSE 的响应头
|
// Set SSE response headers
|
||||||
c.Header("Content-Type", "text/event-stream")
|
c.Header("Content-Type", "text/event-stream")
|
||||||
c.Header("Cache-Control", "no-cache")
|
c.Header("Cache-Control", "no-cache")
|
||||||
c.Header("Connection", "keep-alive")
|
c.Header("Connection", "keep-alive")
|
||||||
c.Header("Access-Control-Allow-Origin", "*")
|
c.Header("Access-Control-Allow-Origin", "*")
|
||||||
|
|
||||||
// 创建上下文,当客户端断开连接时取消
|
// Create context that cancels when client disconnects
|
||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
// 为防止 goroutine 泄漏,创建一个计时器通道
|
// Create a ticker channel to prevent goroutine leaks
|
||||||
ticker := time.NewTicker(5 * time.Second)
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
// 立即发送一次初始数据
|
// Send initial data immediately
|
||||||
sendPerformanceData(c)
|
sendPerformanceData(c)
|
||||||
|
|
||||||
// 使用 goroutine 定期发送数据
|
// Use goroutine to send data periodically
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
// 发送性能数据
|
// Send performance data
|
||||||
if err := sendPerformanceData(c); err != nil {
|
if err := sendPerformanceData(c); err != nil {
|
||||||
logger.Warn("Error sending SSE data:", err)
|
logger.Warn("Error sending SSE data:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
// 客户端断开连接或请求被取消
|
// Client closed connection or request canceled
|
||||||
logger.Debug("Client closed connection")
|
logger.Debug("Client closed connection")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendPerformanceData 发送一次性能数据
|
// sendPerformanceData sends performance data once
|
||||||
func sendPerformanceData(c *gin.Context) error {
|
func sendPerformanceData(c *gin.Context) error {
|
||||||
response := nginx.GetPerformanceData()
|
response := nginx.GetPerformanceData()
|
||||||
|
|
||||||
// 发送 SSE 事件
|
// Send SSE event
|
||||||
c.SSEvent("message", response)
|
c.SSEvent("message", response)
|
||||||
|
|
||||||
// 刷新缓冲区,确保数据立即发送
|
// Flush buffer to ensure data is sent immediately
|
||||||
c.Writer.Flush()
|
c.Writer.Flush()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckStubStatus 获取 Nginx stub_status 模块状态
|
// CheckStubStatus gets Nginx stub_status module status
|
||||||
func CheckStubStatus(c *gin.Context) {
|
func CheckStubStatus(c *gin.Context) {
|
||||||
stubStatus := nginx.GetStubStatus()
|
stubStatus := nginx.GetStubStatus()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, stubStatus)
|
c.JSON(http.StatusOK, stubStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToggleStubStatus 启用或禁用 stub_status 模块
|
// ToggleStubStatus enables or disables stub_status module
|
||||||
func ToggleStubStatus(c *gin.Context) {
|
func ToggleStubStatus(c *gin.Context) {
|
||||||
var json struct {
|
var json struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
|
@ -99,7 +99,7 @@ func ToggleStubStatus(c *gin.Context) {
|
||||||
|
|
||||||
stubStatus := nginx.GetStubStatus()
|
stubStatus := nginx.GetStubStatus()
|
||||||
|
|
||||||
// 如果当前状态与期望状态相同,则无需操作
|
// If current status matches desired status, no action needed
|
||||||
if stubStatus.Enabled == json.Enable {
|
if stubStatus.Enabled == json.Enable {
|
||||||
c.JSON(http.StatusOK, stubStatus)
|
c.JSON(http.StatusOK, stubStatus)
|
||||||
return
|
return
|
||||||
|
@ -117,7 +117,7 @@ func ToggleStubStatus(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新加载 Nginx 配置
|
// Reload Nginx configuration
|
||||||
reloadOutput := nginx.Reload()
|
reloadOutput := nginx.Reload()
|
||||||
if len(reloadOutput) > 0 && (strings.Contains(strings.ToLower(reloadOutput), "error") ||
|
if len(reloadOutput) > 0 && (strings.Contains(strings.ToLower(reloadOutput), "error") ||
|
||||||
strings.Contains(strings.ToLower(reloadOutput), "failed")) {
|
strings.Contains(strings.ToLower(reloadOutput), "failed")) {
|
||||||
|
@ -125,7 +125,7 @@ func ToggleStubStatus(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查操作后的状态
|
// Check status after operation
|
||||||
newStubStatus := nginx.GetStubStatus()
|
newStubStatus := nginx.GetStubStatus()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, newStubStatus)
|
c.JSON(http.StatusOK, newStubStatus)
|
||||||
|
|
|
@ -71,6 +71,7 @@ func SaveSettings(c *gin.Context) {
|
||||||
Node settings.Node `json:"node"`
|
Node settings.Node `json:"node"`
|
||||||
Openai settings.OpenAI `json:"openai"`
|
Openai settings.OpenAI `json:"openai"`
|
||||||
Logrotate settings.Logrotate `json:"logrotate"`
|
Logrotate settings.Logrotate `json:"logrotate"`
|
||||||
|
Nginx settings.Nginx `json:"nginx"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cosy.BindAndValid(c, &json) {
|
if !cosy.BindAndValid(c, &json) {
|
||||||
|
@ -104,6 +105,7 @@ func SaveSettings(c *gin.Context) {
|
||||||
cSettings.ProtectedFill(settings.NodeSettings, &json.Node)
|
cSettings.ProtectedFill(settings.NodeSettings, &json.Node)
|
||||||
cSettings.ProtectedFill(settings.OpenAISettings, &json.Openai)
|
cSettings.ProtectedFill(settings.OpenAISettings, &json.Openai)
|
||||||
cSettings.ProtectedFill(settings.LogrotateSettings, &json.Logrotate)
|
cSettings.ProtectedFill(settings.LogrotateSettings, &json.Logrotate)
|
||||||
|
cSettings.ProtectedFill(settings.NginxSettings, &json.Nginx)
|
||||||
|
|
||||||
err := settings.Save()
|
err := settings.Save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -63,6 +63,7 @@ export interface NginxSettings {
|
||||||
test_config_cmd: string
|
test_config_cmd: string
|
||||||
reload_cmd: string
|
reload_cmd: string
|
||||||
restart_cmd: string
|
restart_cmd: string
|
||||||
|
stub_status_port: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeSettings {
|
export interface NodeSettings {
|
||||||
|
|
|
@ -6,6 +6,9 @@ const data: Ref<Settings> = inject('data') as Ref<Settings>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AForm layout="vertical">
|
<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')">
|
<AFormItem :label="$gettext('Nginx Access Log Path')">
|
||||||
{{ data.nginx.access_log_path }}
|
{{ data.nginx.access_log_path }}
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
|
|
@ -74,6 +74,7 @@ const data = ref<Settings>({
|
||||||
test_config_cmd: '',
|
test_config_cmd: '',
|
||||||
reload_cmd: '',
|
reload_cmd: '',
|
||||||
restart_cmd: '',
|
restart_cmd: '',
|
||||||
|
stub_status_port: 51820,
|
||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
name: '',
|
name: '',
|
||||||
|
|
|
@ -43,7 +43,7 @@ func GetPerformanceData() NginxPerformanceResponse {
|
||||||
logger.Warn("Failed to get Nginx config info:", err)
|
logger.Warn("Failed to get Nginx config info:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保ProcessMode字段能够正确传递
|
// Ensure ProcessMode field is correctly passed
|
||||||
perfInfo := NginxPerformanceInfo{
|
perfInfo := NginxPerformanceInfo{
|
||||||
StubStatusData: *statusInfo,
|
StubStatusData: *statusInfo,
|
||||||
NginxProcessInfo: *processInfo,
|
NginxProcessInfo: *processInfo,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package nginx
|
package nginx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -8,8 +9,10 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/0xJacky/Nginx-UI/settings"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/uozi-tech/cosy/logger"
|
"github.com/uozi-tech/cosy/logger"
|
||||||
)
|
)
|
||||||
|
@ -31,7 +34,6 @@ type StubStatusData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StubStatusPort = 51828
|
|
||||||
StubStatusPath = "/stub_status"
|
StubStatusPath = "/stub_status"
|
||||||
StubStatusHost = "127.0.0.1"
|
StubStatusHost = "127.0.0.1"
|
||||||
StubStatusProtocol = "http"
|
StubStatusProtocol = "http"
|
||||||
|
@ -54,7 +56,7 @@ func GetStubStatusData() (bool, *StubStatusData, error) {
|
||||||
|
|
||||||
// Get the stub_status status information
|
// Get the stub_status status information
|
||||||
enabled, statusURL := IsStubStatusEnabled()
|
enabled, statusURL := IsStubStatusEnabled()
|
||||||
logger.Info("GetStubStatusData", "enabled", enabled, "statusURL", statusURL)
|
logger.Debug("GetStubStatusData", "enabled", enabled, "statusURL", statusURL)
|
||||||
if !enabled {
|
if !enabled {
|
||||||
return false, result, fmt.Errorf("stub_status is not enabled")
|
return false, result, fmt.Errorf("stub_status is not enabled")
|
||||||
}
|
}
|
||||||
|
@ -131,12 +133,12 @@ func IsStubStatusEnabled() (bool, string) {
|
||||||
for _, server := range ngxConfig.Servers {
|
for _, server := range ngxConfig.Servers {
|
||||||
protocol := StubStatusProtocol
|
protocol := StubStatusProtocol
|
||||||
host := StubStatusHost
|
host := StubStatusHost
|
||||||
port := strconv.Itoa(StubStatusPort)
|
port := settings.NginxSettings.StubStatusPort
|
||||||
|
|
||||||
for _, location := range server.Locations {
|
for _, location := range server.Locations {
|
||||||
// Check if the location content contains stub_status
|
// Check if the location content contains stub_status
|
||||||
if strings.Contains(location.Content, "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
|
return true, stubStatusURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,23 +171,54 @@ func DisableStubStatus() error {
|
||||||
func CreateStubStatusConfig() error {
|
func CreateStubStatusConfig() error {
|
||||||
httpConfPath := GetConfPath("conf.d", StubStatusConfigName)
|
httpConfPath := GetConfPath("conf.d", StubStatusConfigName)
|
||||||
|
|
||||||
stubStatusConfig := `
|
const stubStatusTemplate = `
|
||||||
# DO NOT EDIT THIS FILE, IT IS AUTO GENERATED BY NGINX-UI
|
# DO NOT EDIT THIS FILE, IT IS AUTO GENERATED BY NGINX-UI
|
||||||
# Nginx stub_status configuration for Nginx-UI
|
# Nginx stub_status configuration for Nginx-UI
|
||||||
# Modified at ` + time.Now().Format("2006-01-02 15:04:05") + `
|
# Modified at {{.ModifiedTime}}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 51828; # Use non-standard port to avoid conflicts
|
listen {{.Port}}; # Use non-standard port to avoid conflicts
|
||||||
server_name localhost;
|
server_name {{.ServerName}};
|
||||||
|
|
||||||
# Status monitoring interface
|
# Status monitoring interface
|
||||||
location /stub_status {
|
location {{.StatusPath}} {
|
||||||
stub_status;
|
stub_status;
|
||||||
allow 127.0.0.1; # Only allow local access
|
allow {{.AllowIP}}; # Only allow local access
|
||||||
deny all;
|
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)
|
ngxConfig, err := ParseNgxConfigByContent(stubStatusConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to parse new nginx config")
|
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"`
|
TestConfigCmd string `json:"test_config_cmd" protected:"true"`
|
||||||
ReloadCmd string `json:"reload_cmd" protected:"true"`
|
ReloadCmd string `json:"reload_cmd" protected:"true"`
|
||||||
RestartCmd string `json:"restart_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{}
|
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