mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-06-19 07:36:59 +00:00
feat: allow disabling proxy targets availability test #1327
This commit is contained in:
@@ -15,7 +15,9 @@
|
||||
"Bash(find:*)",
|
||||
"Bash(sed:*)",
|
||||
"Bash(cp:*)",
|
||||
"mcp__eslint__lint-files"
|
||||
"mcp__eslint__lint-files",
|
||||
"Bash(go generate:*)",
|
||||
"Bash(pnpm eslint:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/internal/upstream"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register the disabled sockets checker callback
|
||||
service := upstream.GetUpstreamService()
|
||||
service.SetDisabledSocketsChecker(getDisabledSockets)
|
||||
}
|
||||
|
||||
// getDisabledSockets queries the database for disabled sockets
|
||||
func getDisabledSockets() map[string]bool {
|
||||
disabled := make(map[string]bool)
|
||||
|
||||
db := model.UseDB()
|
||||
if db == nil {
|
||||
return disabled
|
||||
}
|
||||
|
||||
var configs []model.UpstreamConfig
|
||||
if err := db.Where("enabled = ?", false).Find(&configs).Error; err != nil {
|
||||
logger.Error("Failed to query disabled sockets:", err)
|
||||
return disabled
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
disabled[config.Socket] = true
|
||||
}
|
||||
|
||||
return disabled
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/0xJacky/Nginx-UI/internal/upstream"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/0xJacky/Nginx-UI/query"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/uozi-tech/cosy"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
)
|
||||
|
||||
// UpstreamInfo represents an upstream with its configuration and health status
|
||||
type UpstreamInfo struct {
|
||||
Name string `json:"name"`
|
||||
Servers []upstream.ProxyTarget `json:"servers"`
|
||||
ConfigPath string `json:"config_path"`
|
||||
LastSeen string `json:"last_seen"`
|
||||
Status map[string]*upstream.Status `json:"status"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// GetUpstreamList returns all upstreams with their configuration and health status
|
||||
func GetUpstreamList(c *gin.Context) {
|
||||
service := upstream.GetUpstreamService()
|
||||
|
||||
// Get all upstream definitions
|
||||
upstreams := service.GetAllUpstreamDefinitions()
|
||||
|
||||
// Get availability map
|
||||
availabilityMap := service.GetAvailabilityMap()
|
||||
|
||||
// Get all upstream configurations from database
|
||||
u := query.UpstreamConfig
|
||||
configs, err := u.Find()
|
||||
if err != nil {
|
||||
cosy.ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a map for quick lookup of enabled status by upstream name
|
||||
configMap := make(map[string]bool)
|
||||
for _, config := range configs {
|
||||
configMap[config.Socket] = config.Enabled
|
||||
}
|
||||
|
||||
// Build response
|
||||
result := make([]UpstreamInfo, 0, len(upstreams))
|
||||
for name, def := range upstreams {
|
||||
// Get enabled status from database, default to true if not found
|
||||
enabled := true
|
||||
if val, exists := configMap[name]; exists {
|
||||
enabled = val
|
||||
}
|
||||
|
||||
// Get status for each server in this upstream
|
||||
serverStatus := make(map[string]*upstream.Status)
|
||||
for _, server := range def.Servers {
|
||||
key := formatSocketAddress(server.Host, server.Port)
|
||||
if status, exists := availabilityMap[key]; exists {
|
||||
serverStatus[key] = status
|
||||
}
|
||||
}
|
||||
|
||||
info := UpstreamInfo{
|
||||
Name: name,
|
||||
Servers: def.Servers,
|
||||
ConfigPath: def.ConfigPath,
|
||||
LastSeen: def.LastSeen.Format("2006-01-02 15:04:05"),
|
||||
Status: serverStatus,
|
||||
Enabled: enabled,
|
||||
}
|
||||
result = append(result, info)
|
||||
}
|
||||
|
||||
// Sort by name for stable ordering
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Name < result[j].Name
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": result,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUpstreamConfigRequest represents the request body for updating upstream config
|
||||
type UpdateUpstreamConfigRequest struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// UpdateUpstreamConfig updates the enabled status of an upstream
|
||||
func UpdateUpstreamConfig(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
|
||||
var req UpdateUpstreamConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
cosy.ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
u := query.UpstreamConfig
|
||||
|
||||
// Check if config exists
|
||||
config, err := u.Where(u.Socket.Eq(name)).First()
|
||||
if err != nil {
|
||||
// Create new config if not found
|
||||
config = &model.UpstreamConfig{
|
||||
Socket: name,
|
||||
Enabled: req.Enabled,
|
||||
}
|
||||
if err := u.Create(config); err != nil {
|
||||
logger.Error("Failed to create upstream config:", err)
|
||||
cosy.ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Update existing config
|
||||
if _, err := u.Where(u.Socket.Eq(name)).Update(u.Enabled, req.Enabled); err != nil {
|
||||
logger.Error("Failed to update upstream config:", err)
|
||||
cosy.ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Upstream config updated successfully",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,4 +5,6 @@ import "github.com/gin-gonic/gin"
|
||||
func InitRouter(r *gin.RouterGroup) {
|
||||
r.GET("/upstream/availability", GetAvailability)
|
||||
r.GET("/upstream/availability_ws", AvailabilityWebSocket)
|
||||
r.GET("/upstream/sockets", GetSocketList)
|
||||
r.PUT("/upstream/socket/:socket", UpdateSocketConfig)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/0xJacky/Nginx-UI/internal/upstream"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/0xJacky/Nginx-UI/query"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/uozi-tech/cosy"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
)
|
||||
|
||||
// SocketInfo represents a socket with its configuration and health status
|
||||
type SocketInfo struct {
|
||||
Socket string `json:"socket"` // host:port
|
||||
Host string `json:"host"` // hostname/IP
|
||||
Port string `json:"port"` // port number
|
||||
Type string `json:"type"` // proxy_pass, grpc_pass, or upstream
|
||||
IsConsul bool `json:"is_consul"` // whether this is a consul service
|
||||
UpstreamName string `json:"upstream_name"` // which upstream this belongs to (if any)
|
||||
LastCheck string `json:"last_check"` // last time health check was performed
|
||||
Status *upstream.Status `json:"status"` // health check status
|
||||
Enabled bool `json:"enabled"` // whether health check is enabled
|
||||
}
|
||||
|
||||
// GetSocketList returns all sockets with their configuration and health status
|
||||
func GetSocketList(c *gin.Context) {
|
||||
service := upstream.GetUpstreamService()
|
||||
|
||||
// Get all target infos
|
||||
targets := service.GetTargetInfos()
|
||||
|
||||
// Get availability map
|
||||
availabilityMap := service.GetAvailabilityMap()
|
||||
|
||||
// Get all socket configurations from database
|
||||
u := query.UpstreamConfig
|
||||
configs, err := u.Find()
|
||||
if err != nil {
|
||||
cosy.ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a map for quick lookup of enabled status
|
||||
configMap := make(map[string]bool)
|
||||
for _, config := range configs {
|
||||
configMap[config.Socket] = config.Enabled
|
||||
}
|
||||
|
||||
// Build response
|
||||
result := make([]SocketInfo, 0, len(targets))
|
||||
for _, target := range targets {
|
||||
socketAddr := formatSocketAddress(target.Host, target.Port)
|
||||
|
||||
// Get enabled status from database, default to true if not found
|
||||
enabled := true
|
||||
if val, exists := configMap[socketAddr]; exists {
|
||||
enabled = val
|
||||
}
|
||||
|
||||
// Get health status
|
||||
var status *upstream.Status
|
||||
if s, exists := availabilityMap[socketAddr]; exists {
|
||||
status = s
|
||||
}
|
||||
|
||||
// Find which upstream this belongs to
|
||||
upstreamName := findUpstreamForSocket(service, target.ProxyTarget)
|
||||
|
||||
info := SocketInfo{
|
||||
Socket: socketAddr,
|
||||
Host: target.Host,
|
||||
Port: target.Port,
|
||||
Type: target.Type,
|
||||
IsConsul: target.IsConsul,
|
||||
UpstreamName: upstreamName,
|
||||
LastCheck: target.LastSeen.Format("2006-01-02 15:04:05"),
|
||||
Status: status,
|
||||
Enabled: enabled,
|
||||
}
|
||||
result = append(result, info)
|
||||
}
|
||||
|
||||
// Sort by socket address for stable ordering
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Socket < result[j].Socket
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": result,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSocketConfigRequest represents the request body for updating socket config
|
||||
type UpdateSocketConfigRequest struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// UpdateSocketConfig updates the enabled status of a socket
|
||||
func UpdateSocketConfig(c *gin.Context) {
|
||||
socket := c.Param("socket")
|
||||
|
||||
var req UpdateSocketConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
cosy.ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
u := query.UpstreamConfig
|
||||
|
||||
// Check if config exists
|
||||
config, err := u.Where(u.Socket.Eq(socket)).First()
|
||||
if err != nil {
|
||||
// Create new config if not found
|
||||
config = &model.UpstreamConfig{
|
||||
Socket: socket,
|
||||
Enabled: req.Enabled,
|
||||
}
|
||||
if err := u.Create(config); err != nil {
|
||||
logger.Error("Failed to create socket config:", err)
|
||||
cosy.ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Update existing config
|
||||
if _, err := u.Where(u.Socket.Eq(socket)).Update(u.Enabled, req.Enabled); err != nil {
|
||||
logger.Error("Failed to update socket config:", err)
|
||||
cosy.ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Socket config updated successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// findUpstreamForSocket finds which upstream a socket belongs to
|
||||
func findUpstreamForSocket(service *upstream.Service, target upstream.ProxyTarget) string {
|
||||
socketAddr := formatSocketAddress(target.Host, target.Port)
|
||||
upstreams := service.GetAllUpstreamDefinitions()
|
||||
|
||||
for name, upstream := range upstreams {
|
||||
for _, server := range upstream.Servers {
|
||||
serverAddr := formatSocketAddress(server.Host, server.Port)
|
||||
if serverAddr == socketAddr {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package upstream
|
||||
|
||||
// formatSocketAddress formats a host:port combination into a proper socket address
|
||||
// For IPv6 addresses, it adds brackets around the host if they're not already present
|
||||
func formatSocketAddress(host, port string) string {
|
||||
// Reuse the logic from service package
|
||||
if len(host) > 0 && host[0] != '[' && containsColon(host) {
|
||||
return "[" + host + "]:" + port
|
||||
}
|
||||
return host + ":" + port
|
||||
}
|
||||
|
||||
// containsColon checks if string contains a colon
|
||||
func containsColon(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == ':' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Vendored
-30
@@ -10,29 +10,16 @@ declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AAlert: typeof import('ant-design-vue/es')['Alert']
|
||||
AApp: typeof import('ant-design-vue/es')['App']
|
||||
AAutoComplete: typeof import('ant-design-vue/es')['AutoComplete']
|
||||
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
||||
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||
ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
|
||||
ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem']
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
ACard: typeof import('ant-design-vue/es')['Card']
|
||||
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
||||
ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
|
||||
ACol: typeof import('ant-design-vue/es')['Col']
|
||||
ACollapse: typeof import('ant-design-vue/es')['Collapse']
|
||||
ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
|
||||
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||
ADrawer: typeof import('ant-design-vue/es')['Drawer']
|
||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||
AEmpty: typeof import('ant-design-vue/es')['Empty']
|
||||
AForm: typeof import('ant-design-vue/es')['Form']
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
|
||||
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
ALayout: typeof import('ant-design-vue/es')['Layout']
|
||||
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
||||
ALayoutFooter: typeof import('ant-design-vue/es')['LayoutFooter']
|
||||
@@ -43,34 +30,17 @@ declare module 'vue' {
|
||||
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||
APopover: typeof import('ant-design-vue/es')['Popover']
|
||||
AppProvider: typeof import('./src/components/AppProvider.vue')['default']
|
||||
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
||||
AResult: typeof import('ant-design-vue/es')['Result']
|
||||
ARow: typeof import('ant-design-vue/es')['Row']
|
||||
ASegmented: typeof import('ant-design-vue/es')['Segmented']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
||||
AStep: typeof import('ant-design-vue/es')['Step']
|
||||
ASteps: typeof import('ant-design-vue/es')['Steps']
|
||||
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
|
||||
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||
ATable: typeof import('ant-design-vue/es')['Table']
|
||||
ATabPane: typeof import('ant-design-vue/es')['TabPane']
|
||||
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
ATypographyText: typeof import('ant-design-vue/es')['TypographyText']
|
||||
ATypographyTitle: typeof import('ant-design-vue/es')['TypographyTitle']
|
||||
AutoCertFormAutoCertForm: typeof import('./src/components/AutoCertForm/AutoCertForm.vue')['default']
|
||||
AutoCertFormDNSChallenge: typeof import('./src/components/AutoCertForm/DNSChallenge.vue')['default']
|
||||
BaseEditorBaseEditor: typeof import('./src/components/BaseEditor/BaseEditor.vue')['default']
|
||||
|
||||
@@ -19,6 +19,26 @@ export interface UpstreamAvailabilityResponse {
|
||||
target_count: number
|
||||
}
|
||||
|
||||
export interface SocketInfo {
|
||||
socket: string
|
||||
host: string
|
||||
port: string
|
||||
type: string
|
||||
is_consul: boolean
|
||||
upstream_name: string
|
||||
last_check: string
|
||||
status: UpstreamStatus | null
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface SocketListResponse {
|
||||
data: SocketInfo[]
|
||||
}
|
||||
|
||||
export interface UpdateSocketConfigRequest {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
const upstream = {
|
||||
// HTTP GET interface to get all upstream availability results
|
||||
getAvailability(): Promise<UpstreamAvailabilityResponse> {
|
||||
@@ -29,6 +49,16 @@ const upstream = {
|
||||
availabilityWebSocket() {
|
||||
return ws('/api/upstream/availability_ws')
|
||||
},
|
||||
|
||||
// Get all sockets with their configuration and health status
|
||||
getSocketList(): Promise<SocketListResponse> {
|
||||
return http.get('/upstream/sockets')
|
||||
},
|
||||
|
||||
// Update socket configuration
|
||||
updateSocketConfig(socket: string, data: UpdateSocketConfigRequest) {
|
||||
return http.put(`/upstream/socket/${encodeURIComponent(socket)}`, data)
|
||||
},
|
||||
}
|
||||
|
||||
export default upstream
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
54001: () => $gettext('Node analytics failed: {0}'),
|
||||
}
|
||||
@@ -22,4 +22,5 @@ export default {
|
||||
50021: () => $gettext('Write private.key error: {0}'),
|
||||
50022: () => $gettext('Obtain cert error: {0}'),
|
||||
50023: () => $gettext('Revoke cert error: {0}'),
|
||||
50031: () => $gettext('No certificate available'),
|
||||
}
|
||||
|
||||
@@ -12,4 +12,6 @@ export default {
|
||||
500011: () => $gettext('Failed to inspect current container: {0}'),
|
||||
500012: () => $gettext('Failed to create temp container: {0}'),
|
||||
500013: () => $gettext('Failed to start temp container: {0}'),
|
||||
500014: () => $gettext('Could not find old container name'),
|
||||
500015: () => $gettext('Could not find temp container'),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
50201: () => $gettext('Log parser is not initialized; call indexer.InitLogParser() before use'),
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
50101: () => $gettext('Empty log line'),
|
||||
50102: () => $gettext('Log line exceeds maximum length'),
|
||||
50103: () => $gettext('Unsupported log format'),
|
||||
50104: () => $gettext('Invalid timestamp format'),
|
||||
}
|
||||
@@ -6,9 +6,6 @@ export default {
|
||||
50005: () => $gettext('Directive params is empty'),
|
||||
50006: () => $gettext('Settings.NginxLogSettings.ErrorLogPath is empty, refer to https://nginxui.com/guide/config-nginx.html for more information'),
|
||||
50007: () => $gettext('Settings.NginxLogSettings.AccessLogPath is empty, refer to https://nginxui.com/guide/config-nginx.html for more information'),
|
||||
50008: () => $gettext('Empty log line'),
|
||||
50009: () => $gettext('Invalid timestamp format'),
|
||||
50010: () => $gettext('Unsupported log format'),
|
||||
50011: () => $gettext('Log indexer not available'),
|
||||
50012: () => $gettext('Analytics service not available'),
|
||||
50013: () => $gettext('Log file does not exist'),
|
||||
|
||||
@@ -3,4 +3,5 @@ export default {
|
||||
400001: () => $gettext('Invalid notifier config'),
|
||||
400002: () => $gettext('Invalid notification ID'),
|
||||
404002: () => $gettext('External notification configuration not found'),
|
||||
400003: () => $gettext('Invalid Telegram Chat ID: cannot be zero'),
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@ export default {
|
||||
51004: () => $gettext('Failed to execute template: {0}'),
|
||||
51005: () => $gettext('Failed to parse nginx config: {0}'),
|
||||
51006: () => $gettext('Failed to build nginx config: {0}'),
|
||||
51007: () => $gettext('Failed to get nginx.conf path'),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
52001: () => $gettext('Upgrader core downloadUrl is empty'),
|
||||
52002: () => $gettext('Upgrader core digest is empty'),
|
||||
52003: () => $gettext('Digest file content is empty'),
|
||||
52004: () => $gettext('Executable binary file is empty'),
|
||||
52005: () => $gettext('Update already in progress'),
|
||||
}
|
||||
@@ -12,4 +12,7 @@ export default {
|
||||
40401: () => $gettext('Session not found'),
|
||||
40402: () => $gettext('Token is empty'),
|
||||
50005: () => $gettext('Invalid claims type'),
|
||||
50006: () => $gettext('Config not found'),
|
||||
50007: () => $gettext('Db file not found'),
|
||||
50008: () => $gettext('Init user not exists'),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
53001: () => $gettext('Invalid commit SHA'),
|
||||
53002: () => $gettext('Release API request failed: {0}'),
|
||||
}
|
||||
+289
-209
File diff suppressed because it is too large
Load Diff
+345
-287
File diff suppressed because it is too large
Load Diff
+125
-24
@@ -297,7 +297,7 @@ msgid ""
|
||||
"certificate application will fail."
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:13
|
||||
#: src/constants/errors/nginx_log.ts:10
|
||||
msgid "Analytics service not available"
|
||||
msgstr ""
|
||||
|
||||
@@ -543,7 +543,7 @@ msgid ""
|
||||
"ready."
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:17
|
||||
#: src/constants/errors/nginx_log.ts:14
|
||||
msgid "Background log service not available"
|
||||
msgstr ""
|
||||
|
||||
@@ -748,7 +748,7 @@ msgstr ""
|
||||
msgid "Cannot access backup path {0}: {1}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:16
|
||||
#: src/constants/errors/nginx_log.ts:13
|
||||
msgid "Cannot access log file"
|
||||
msgstr ""
|
||||
|
||||
@@ -1197,6 +1197,10 @@ msgstr ""
|
||||
msgid "Config entry file not exist"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/user.ts:15
|
||||
msgid "Config not found"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/backup.ts:14
|
||||
msgid "Config path is empty"
|
||||
msgstr ""
|
||||
@@ -1294,6 +1298,14 @@ msgstr ""
|
||||
msgid "Core Upgrade"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/docker.ts:15
|
||||
msgid "Could not find old container name"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/docker.ts:16
|
||||
msgid "Could not find temp container"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/nginx_log/dashboard/components/BrowserStatsTable.vue:18
|
||||
#: src/views/nginx_log/dashboard/components/DailyTrendsChart.vue:98
|
||||
#: src/views/nginx_log/dashboard/components/DeviceStatsTable.vue:17
|
||||
@@ -1480,6 +1492,10 @@ msgstr ""
|
||||
msgid "Days"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/user.ts:16
|
||||
msgid "Db file not found"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/middleware.ts:3
|
||||
msgid "Decryption failed"
|
||||
msgstr ""
|
||||
@@ -1633,10 +1649,18 @@ msgstr ""
|
||||
msgid "Device Type"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/upgrader.ts:4
|
||||
msgid "Digest file content is empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/preference/components/ExternalNotify/dingtalk.ts:5
|
||||
msgid "DingTalk"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:31
|
||||
msgid "Direct"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:72
|
||||
msgid "Directive"
|
||||
msgstr ""
|
||||
@@ -1924,7 +1948,7 @@ msgstr ""
|
||||
msgid "Email (*)"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:9
|
||||
#: src/constants/errors/nginx_log.parser.ts:2
|
||||
msgid "Empty log line"
|
||||
msgstr ""
|
||||
|
||||
@@ -2153,6 +2177,10 @@ msgstr ""
|
||||
msgid "Error processing content"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/upgrader.ts:5
|
||||
msgid "Executable binary file is empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/system/Upgrade.vue:195
|
||||
msgid "Executable Path"
|
||||
msgstr ""
|
||||
@@ -2369,7 +2397,7 @@ msgstr ""
|
||||
msgid "Failed to decrypt Nginx UI directory: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:22
|
||||
#: src/constants/errors/nginx_log.ts:19
|
||||
msgid "Failed to delete all indexes"
|
||||
msgstr ""
|
||||
|
||||
@@ -2381,7 +2409,7 @@ msgstr ""
|
||||
msgid "Failed to delete certificate from database: %{error}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:21
|
||||
#: src/constants/errors/nginx_log.ts:18
|
||||
msgid "Failed to delete file index"
|
||||
msgstr ""
|
||||
|
||||
@@ -2455,7 +2483,7 @@ msgstr ""
|
||||
msgid "Failed to get container id: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:23
|
||||
#: src/constants/errors/nginx_log.ts:20
|
||||
msgid "Failed to get index status"
|
||||
msgstr ""
|
||||
|
||||
@@ -2463,11 +2491,15 @@ msgstr ""
|
||||
msgid "Failed to get Nginx performance settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/performance.ts:9
|
||||
msgid "Failed to get nginx.conf path"
|
||||
msgstr ""
|
||||
|
||||
#: src/composables/useNginxPerformance.ts:49
|
||||
msgid "Failed to get performance data"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:24
|
||||
#: src/constants/errors/nginx_log.ts:21
|
||||
msgid "Failed to get persistence stats"
|
||||
msgstr ""
|
||||
|
||||
@@ -2547,11 +2579,11 @@ msgstr ""
|
||||
msgid "Failed to read symlink: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:20
|
||||
#: src/constants/errors/nginx_log.ts:17
|
||||
msgid "Failed to rebuild file index"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:19
|
||||
#: src/constants/errors/nginx_log.ts:16
|
||||
msgid "Failed to rebuild index"
|
||||
msgstr ""
|
||||
|
||||
@@ -2651,7 +2683,7 @@ msgstr ""
|
||||
msgid "File or directory not found: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:18
|
||||
#: src/constants/errors/nginx_log.ts:15
|
||||
msgid "File path is required"
|
||||
msgstr ""
|
||||
|
||||
@@ -2857,6 +2889,10 @@ msgstr ""
|
||||
msgid "GZIP Min Length"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:61
|
||||
msgid "Health Check"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/dashboard/components/SiteHealthCheckModal.vue:365
|
||||
msgid "Health Check Configuration"
|
||||
msgstr ""
|
||||
@@ -2869,6 +2905,10 @@ msgstr ""
|
||||
msgid "Health check configuration saved successfully"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:37
|
||||
msgid "Health Status"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/SensitiveString/SensitiveString.vue:40
|
||||
msgid "Hide"
|
||||
msgstr ""
|
||||
@@ -2893,7 +2933,7 @@ msgstr ""
|
||||
msgid "History"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/index.ts:50
|
||||
#: src/routes/index.ts:52
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -3032,6 +3072,10 @@ msgstr ""
|
||||
msgid "Info"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/user.ts:17
|
||||
msgid "Init user not exists"
|
||||
msgstr ""
|
||||
|
||||
#: src/language/constants.ts:25
|
||||
msgid "Initial core upgrader error"
|
||||
msgstr ""
|
||||
@@ -3102,6 +3146,10 @@ msgstr ""
|
||||
msgid "Invalid claims type"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/version.ts:2
|
||||
msgid "Invalid commit SHA"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/SystemRestore/SystemRestoreContent.vue:121
|
||||
msgid "Invalid file object"
|
||||
msgstr ""
|
||||
@@ -3160,11 +3208,15 @@ msgstr ""
|
||||
msgid "Invalid security token format"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:10
|
||||
#: src/constants/errors/notification.ts:6
|
||||
msgid "Invalid Telegram Chat ID: cannot be zero"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.parser.ts:5
|
||||
msgid "Invalid timestamp format"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:26
|
||||
#: src/constants/errors/nginx_log.ts:23
|
||||
msgid "Invalid websocket message type"
|
||||
msgstr ""
|
||||
|
||||
@@ -3279,6 +3331,10 @@ msgstr ""
|
||||
msgid "Last Backup Time"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:52
|
||||
msgid "Last Check"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/system/Upgrade.vue:197
|
||||
msgid "Last checked at"
|
||||
msgstr ""
|
||||
@@ -3437,11 +3493,11 @@ msgid ""
|
||||
"nginx-log.html for more information."
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:14
|
||||
#: src/constants/errors/nginx_log.ts:11
|
||||
msgid "Log file does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:25
|
||||
#: src/constants/errors/nginx_log.ts:22
|
||||
msgid "Log file is not a regular file"
|
||||
msgstr ""
|
||||
|
||||
@@ -3453,7 +3509,7 @@ msgstr ""
|
||||
msgid "Log file not indexed yet"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:12
|
||||
#: src/constants/errors/nginx_log.ts:9
|
||||
msgid "Log indexer not available"
|
||||
msgstr ""
|
||||
|
||||
@@ -3461,11 +3517,19 @@ msgstr ""
|
||||
msgid "Log indexing completed! Loading updated data..."
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.parser.ts:3
|
||||
msgid "Log line exceeds maximum length"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:430
|
||||
msgid "Log List"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:15
|
||||
#: src/constants/errors/nginx_log.indexer.ts:2
|
||||
msgid "Log parser is not initialized; call indexer.InitLogParser() before use"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:12
|
||||
msgid "Log path is not under whitelist"
|
||||
msgstr ""
|
||||
|
||||
@@ -3687,15 +3751,15 @@ msgstr ""
|
||||
msgid "Model"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:28
|
||||
#: src/constants/errors/nginx_log.ts:25
|
||||
msgid "Modern analytics service not available"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:29
|
||||
#: src/constants/errors/nginx_log.ts:26
|
||||
msgid "Modern indexer service not available"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:27
|
||||
#: src/constants/errors/nginx_log.ts:24
|
||||
msgid "Modern searcher service not available"
|
||||
msgstr ""
|
||||
|
||||
@@ -4081,6 +4145,10 @@ msgstr ""
|
||||
msgid "No Action"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/cert.ts:25
|
||||
msgid "No certificate available"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/nginx_log/dashboard/components/ChinaMapChart/ChinaMapChart.vue:196
|
||||
#: src/views/nginx_log/dashboard/components/ChinaMapChart/ChinaMapChart.vue:232
|
||||
msgid "No China geographic data available"
|
||||
@@ -4093,6 +4161,10 @@ msgstr ""
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:42
|
||||
msgid "No Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/nginx_log/structured/StructuredLogViewer.vue:820
|
||||
msgid "No entries in current page"
|
||||
msgstr ""
|
||||
@@ -4140,6 +4212,10 @@ msgstr ""
|
||||
msgid "Node"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/analytic.ts:2
|
||||
msgid "Node analytics failed: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/preference/tabs/NodeSettings.vue:15
|
||||
msgid "Node name"
|
||||
msgstr ""
|
||||
@@ -4969,6 +5045,10 @@ msgstr ""
|
||||
msgid "Reinstall"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/version.ts:3
|
||||
msgid "Release API request failed: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/system/Upgrade.vue:270
|
||||
msgid "Release Note"
|
||||
msgstr ""
|
||||
@@ -5774,6 +5854,10 @@ msgstr ""
|
||||
msgid "Sleep time between cache manager iterations"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:18
|
||||
msgid "Socket"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/nginx_log/structured/StructuredLogViewer.vue:735
|
||||
msgid "Sorted by"
|
||||
msgstr ""
|
||||
@@ -6614,10 +6698,14 @@ msgstr ""
|
||||
msgid "Unsupported backup type: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:11
|
||||
#: src/constants/errors/nginx_log.parser.ts:4
|
||||
msgid "Unsupported log format"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/upgrader.ts:6
|
||||
msgid "Update already in progress"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/user/UserProfile.vue:218
|
||||
msgid "Update Password"
|
||||
msgstr ""
|
||||
@@ -6657,6 +6745,14 @@ msgstr ""
|
||||
msgid "Upgraded successfully"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/upgrader.ts:3
|
||||
msgid "Upgrader core digest is empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/upgrader.ts:2
|
||||
msgid "Upgrader core downloadUrl is empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/node/BatchUpgrader.vue:88 src/views/system/Upgrade.vue:80
|
||||
msgid "Upgrading Nginx UI, please wait..."
|
||||
msgstr ""
|
||||
@@ -6673,7 +6769,8 @@ msgstr ""
|
||||
msgid "Upload Folders"
|
||||
msgstr ""
|
||||
|
||||
#: src/composables/useUpstreamStatus.ts:132
|
||||
#: src/composables/useUpstreamStatus.ts:132 src/routes/modules/upstream.ts:10
|
||||
#: src/views/upstream/SocketList.vue:25
|
||||
msgid "Upstream"
|
||||
msgstr ""
|
||||
|
||||
@@ -6681,6 +6778,10 @@ msgstr ""
|
||||
msgid "Upstream Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:134
|
||||
msgid "Upstream Sockets"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/namespace/columns.ts:59
|
||||
msgid "Upstream Test Type"
|
||||
msgstr ""
|
||||
@@ -6935,7 +7036,7 @@ msgstr ""
|
||||
msgid "Workers"
|
||||
msgstr ""
|
||||
|
||||
#: src/layouts/HeaderLayout.vue:61 src/routes/index.ts:59
|
||||
#: src/layouts/HeaderLayout.vue:61 src/routes/index.ts:61
|
||||
#: src/views/workspace/WorkSpace.vue:51
|
||||
msgid "Workspace"
|
||||
msgstr ""
|
||||
|
||||
+334
-262
File diff suppressed because it is too large
Load Diff
+332
-268
File diff suppressed because it is too large
Load Diff
+365
-446
File diff suppressed because it is too large
Load Diff
+340
-376
File diff suppressed because it is too large
Load Diff
+125
-23
@@ -302,7 +302,7 @@ msgstr ""
|
||||
msgid "All selected subdomains must belong to the same DNS Provider, otherwise the certificate application will fail."
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:13
|
||||
#: src/constants/errors/nginx_log.ts:10
|
||||
msgid "Analytics service not available"
|
||||
msgstr ""
|
||||
|
||||
@@ -549,7 +549,7 @@ msgstr ""
|
||||
msgid "Background indexing in progress. Data will be updated automatically when ready."
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:17
|
||||
#: src/constants/errors/nginx_log.ts:14
|
||||
msgid "Background log service not available"
|
||||
msgstr ""
|
||||
|
||||
@@ -750,7 +750,7 @@ msgstr ""
|
||||
msgid "Cannot access backup path {0}: {1}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:16
|
||||
#: src/constants/errors/nginx_log.ts:13
|
||||
msgid "Cannot access log file"
|
||||
msgstr ""
|
||||
|
||||
@@ -1170,6 +1170,10 @@ msgstr ""
|
||||
msgid "Config entry file not exist"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/user.ts:15
|
||||
msgid "Config not found"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/backup.ts:14
|
||||
msgid "Config path is empty"
|
||||
msgstr ""
|
||||
@@ -1267,6 +1271,14 @@ msgstr ""
|
||||
msgid "Core Upgrade"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/docker.ts:15
|
||||
msgid "Could not find old container name"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/docker.ts:16
|
||||
msgid "Could not find temp container"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/nginx_log/dashboard/components/BrowserStatsTable.vue:18
|
||||
#: src/views/nginx_log/dashboard/components/DailyTrendsChart.vue:98
|
||||
#: src/views/nginx_log/dashboard/components/DeviceStatsTable.vue:17
|
||||
@@ -1451,6 +1463,10 @@ msgstr ""
|
||||
msgid "Days"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/user.ts:16
|
||||
msgid "Db file not found"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/middleware.ts:3
|
||||
msgid "Decryption failed"
|
||||
msgstr ""
|
||||
@@ -1608,10 +1624,18 @@ msgstr ""
|
||||
msgid "Device Type"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/upgrader.ts:4
|
||||
msgid "Digest file content is empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/preference/components/ExternalNotify/dingtalk.ts:5
|
||||
msgid "DingTalk"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:31
|
||||
msgid "Direct"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:72
|
||||
msgid "Directive"
|
||||
msgstr ""
|
||||
@@ -1900,7 +1924,7 @@ msgstr ""
|
||||
msgid "Email (*)"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:9
|
||||
#: src/constants/errors/nginx_log.parser.ts:2
|
||||
msgid "Empty log line"
|
||||
msgstr ""
|
||||
|
||||
@@ -2128,6 +2152,10 @@ msgstr ""
|
||||
msgid "Error processing content"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/upgrader.ts:5
|
||||
msgid "Executable binary file is empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/system/Upgrade.vue:195
|
||||
msgid "Executable Path"
|
||||
msgstr ""
|
||||
@@ -2340,7 +2368,7 @@ msgstr ""
|
||||
msgid "Failed to decrypt Nginx UI directory: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:22
|
||||
#: src/constants/errors/nginx_log.ts:19
|
||||
msgid "Failed to delete all indexes"
|
||||
msgstr ""
|
||||
|
||||
@@ -2352,7 +2380,7 @@ msgstr ""
|
||||
msgid "Failed to delete certificate from database: %{error}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:21
|
||||
#: src/constants/errors/nginx_log.ts:18
|
||||
msgid "Failed to delete file index"
|
||||
msgstr ""
|
||||
|
||||
@@ -2426,7 +2454,7 @@ msgstr ""
|
||||
msgid "Failed to get container id: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:23
|
||||
#: src/constants/errors/nginx_log.ts:20
|
||||
msgid "Failed to get index status"
|
||||
msgstr ""
|
||||
|
||||
@@ -2434,11 +2462,15 @@ msgstr ""
|
||||
msgid "Failed to get Nginx performance settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/performance.ts:9
|
||||
msgid "Failed to get nginx.conf path"
|
||||
msgstr ""
|
||||
|
||||
#: src/composables/useNginxPerformance.ts:49
|
||||
msgid "Failed to get performance data"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:24
|
||||
#: src/constants/errors/nginx_log.ts:21
|
||||
msgid "Failed to get persistence stats"
|
||||
msgstr ""
|
||||
|
||||
@@ -2518,11 +2550,11 @@ msgstr ""
|
||||
msgid "Failed to read symlink: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:20
|
||||
#: src/constants/errors/nginx_log.ts:17
|
||||
msgid "Failed to rebuild file index"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:19
|
||||
#: src/constants/errors/nginx_log.ts:16
|
||||
msgid "Failed to rebuild index"
|
||||
msgstr ""
|
||||
|
||||
@@ -2622,7 +2654,7 @@ msgstr ""
|
||||
msgid "File or directory not found: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:18
|
||||
#: src/constants/errors/nginx_log.ts:15
|
||||
msgid "File path is required"
|
||||
msgstr ""
|
||||
|
||||
@@ -2820,6 +2852,10 @@ msgstr ""
|
||||
msgid "GZIP Min Length"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:61
|
||||
msgid "Health Check"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/dashboard/components/SiteHealthCheckModal.vue:365
|
||||
msgid "Health Check Configuration"
|
||||
msgstr ""
|
||||
@@ -2832,6 +2868,10 @@ msgstr ""
|
||||
msgid "Health check configuration saved successfully"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:37
|
||||
msgid "Health Status"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/SensitiveString/SensitiveString.vue:40
|
||||
msgid "Hide"
|
||||
msgstr ""
|
||||
@@ -2856,7 +2896,7 @@ msgstr ""
|
||||
msgid "History"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/index.ts:50
|
||||
#: src/routes/index.ts:52
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -2986,6 +3026,10 @@ msgstr ""
|
||||
msgid "Info"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/user.ts:17
|
||||
msgid "Init user not exists"
|
||||
msgstr ""
|
||||
|
||||
#: src/language/constants.ts:25
|
||||
msgid "Initial core upgrader error"
|
||||
msgstr ""
|
||||
@@ -3054,6 +3098,10 @@ msgstr ""
|
||||
msgid "Invalid claims type"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/version.ts:2
|
||||
msgid "Invalid commit SHA"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/SystemRestore/SystemRestoreContent.vue:121
|
||||
msgid "Invalid file object"
|
||||
msgstr ""
|
||||
@@ -3112,11 +3160,15 @@ msgstr ""
|
||||
msgid "Invalid security token format"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:10
|
||||
#: src/constants/errors/notification.ts:6
|
||||
msgid "Invalid Telegram Chat ID: cannot be zero"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.parser.ts:5
|
||||
msgid "Invalid timestamp format"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:26
|
||||
#: src/constants/errors/nginx_log.ts:23
|
||||
msgid "Invalid websocket message type"
|
||||
msgstr ""
|
||||
|
||||
@@ -3229,6 +3281,10 @@ msgstr ""
|
||||
msgid "Last Backup Time"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:52
|
||||
msgid "Last Check"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/system/Upgrade.vue:197
|
||||
msgid "Last checked at"
|
||||
msgstr ""
|
||||
@@ -3387,11 +3443,11 @@ msgstr ""
|
||||
msgid "Log file %{log_path} is not a regular file. If you are using nginx-ui in docker container, please refer to https://nginxui.com/zh_CN/guide/config-nginx-log.html for more information."
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:14
|
||||
#: src/constants/errors/nginx_log.ts:11
|
||||
msgid "Log file does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:25
|
||||
#: src/constants/errors/nginx_log.ts:22
|
||||
msgid "Log file is not a regular file"
|
||||
msgstr ""
|
||||
|
||||
@@ -3403,7 +3459,7 @@ msgstr ""
|
||||
msgid "Log file not indexed yet"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:12
|
||||
#: src/constants/errors/nginx_log.ts:9
|
||||
msgid "Log indexer not available"
|
||||
msgstr ""
|
||||
|
||||
@@ -3411,12 +3467,20 @@ msgstr ""
|
||||
msgid "Log indexing completed! Loading updated data..."
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.parser.ts:3
|
||||
msgid "Log line exceeds maximum length"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/modules/nginx_log.ts:39
|
||||
#: src/views/nginx_log/NginxLogList.vue:430
|
||||
msgid "Log List"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:15
|
||||
#: src/constants/errors/nginx_log.indexer.ts:2
|
||||
msgid "Log parser is not initialized; call indexer.InitLogParser() before use"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:12
|
||||
msgid "Log path is not under whitelist"
|
||||
msgstr ""
|
||||
|
||||
@@ -3634,15 +3698,15 @@ msgstr ""
|
||||
msgid "Model"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:28
|
||||
#: src/constants/errors/nginx_log.ts:25
|
||||
msgid "Modern analytics service not available"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:29
|
||||
#: src/constants/errors/nginx_log.ts:26
|
||||
msgid "Modern indexer service not available"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:27
|
||||
#: src/constants/errors/nginx_log.ts:24
|
||||
msgid "Modern searcher service not available"
|
||||
msgstr ""
|
||||
|
||||
@@ -4039,6 +4103,10 @@ msgstr ""
|
||||
msgid "No Action"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/cert.ts:25
|
||||
msgid "No certificate available"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/nginx_log/dashboard/components/ChinaMapChart/ChinaMapChart.vue:196
|
||||
#: src/views/nginx_log/dashboard/components/ChinaMapChart/ChinaMapChart.vue:232
|
||||
msgid "No China geographic data available"
|
||||
@@ -4051,6 +4119,10 @@ msgstr ""
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:42
|
||||
msgid "No Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/nginx_log/structured/StructuredLogViewer.vue:820
|
||||
msgid "No entries in current page"
|
||||
msgstr ""
|
||||
@@ -4096,6 +4168,10 @@ msgstr ""
|
||||
msgid "Node"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/analytic.ts:2
|
||||
msgid "Node analytics failed: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/preference/tabs/NodeSettings.vue:15
|
||||
msgid "Node name"
|
||||
msgstr ""
|
||||
@@ -4911,6 +4987,10 @@ msgstr ""
|
||||
msgid "Reinstall"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/version.ts:3
|
||||
msgid "Release API request failed: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/system/Upgrade.vue:270
|
||||
msgid "Release Note"
|
||||
msgstr ""
|
||||
@@ -5713,6 +5793,10 @@ msgstr ""
|
||||
msgid "Sleep time between cache manager iterations"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:18
|
||||
msgid "Socket"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/nginx_log/structured/StructuredLogViewer.vue:735
|
||||
msgid "Sorted by"
|
||||
msgstr ""
|
||||
@@ -6505,10 +6589,14 @@ msgstr ""
|
||||
msgid "Unsupported backup type: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/nginx_log.ts:11
|
||||
#: src/constants/errors/nginx_log.parser.ts:4
|
||||
msgid "Unsupported log format"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/upgrader.ts:6
|
||||
msgid "Update already in progress"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/user/UserProfile.vue:218
|
||||
msgid "Update Password"
|
||||
msgstr ""
|
||||
@@ -6552,6 +6640,14 @@ msgstr ""
|
||||
msgid "Upgraded successfully"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/upgrader.ts:3
|
||||
msgid "Upgrader core digest is empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/errors/upgrader.ts:2
|
||||
msgid "Upgrader core downloadUrl is empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/node/BatchUpgrader.vue:88
|
||||
#: src/views/system/Upgrade.vue:80
|
||||
msgid "Upgrading Nginx UI, please wait..."
|
||||
@@ -6570,6 +6666,8 @@ msgid "Upload Folders"
|
||||
msgstr ""
|
||||
|
||||
#: src/composables/useUpstreamStatus.ts:132
|
||||
#: src/routes/modules/upstream.ts:10
|
||||
#: src/views/upstream/SocketList.vue:25
|
||||
msgid "Upstream"
|
||||
msgstr ""
|
||||
|
||||
@@ -6577,6 +6675,10 @@ msgstr ""
|
||||
msgid "Upstream Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/upstream/SocketList.vue:134
|
||||
msgid "Upstream Sockets"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/namespace/columns.ts:59
|
||||
msgid "Upstream Test Type"
|
||||
msgstr ""
|
||||
@@ -6823,7 +6925,7 @@ msgid "Workers"
|
||||
msgstr ""
|
||||
|
||||
#: src/layouts/HeaderLayout.vue:61
|
||||
#: src/routes/index.ts:59
|
||||
#: src/routes/index.ts:61
|
||||
#: src/views/workspace/WorkSpace.vue:51
|
||||
msgid "Workspace"
|
||||
msgstr ""
|
||||
|
||||
+297
-224
File diff suppressed because it is too large
Load Diff
+309
-230
File diff suppressed because it is too large
Load Diff
+313
-243
File diff suppressed because it is too large
Load Diff
+315
-232
File diff suppressed because it is too large
Load Diff
+321
-242
File diff suppressed because it is too large
Load Diff
+300
-296
File diff suppressed because it is too large
Load Diff
+302
-298
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ import { sitesRoutes } from './modules/sites'
|
||||
import { streamsRoutes } from './modules/streams'
|
||||
import { systemRoutes } from './modules/system'
|
||||
import { terminalRoutes } from './modules/terminal'
|
||||
import { upstreamRoutes } from './modules/upstream'
|
||||
import { userRoutes } from './modules/user'
|
||||
import 'nprogress/nprogress.css'
|
||||
|
||||
@@ -26,6 +27,7 @@ const mainLayoutChildren: RouteRecordRaw[] = [
|
||||
...dashboardRoutes,
|
||||
...sitesRoutes,
|
||||
...streamsRoutes,
|
||||
...upstreamRoutes,
|
||||
...configRoutes,
|
||||
...certificatesRoutes,
|
||||
...terminalRoutes,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { ClusterOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
export const upstreamRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: 'upstream',
|
||||
name: 'Upstream Management',
|
||||
component: () => import('@/views/upstream/SocketList.vue'),
|
||||
meta: {
|
||||
name: () => $gettext('Upstream'),
|
||||
icon: ClusterOutlined,
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,193 @@
|
||||
<script setup lang="ts">
|
||||
import type { ColumnsType } from 'ant-design-vue/es/table'
|
||||
import type { SocketInfo } from '@/api/upstream'
|
||||
import { ReloadOutlined } from '@ant-design/icons-vue'
|
||||
import { message, Tag } from 'ant-design-vue'
|
||||
import upstream from '@/api/upstream'
|
||||
import { formatDateTime } from '@/lib/helper'
|
||||
import { useProxyAvailabilityStore } from '@/pinia/moudule/proxyAvailability'
|
||||
|
||||
const dataSource = ref<SocketInfo[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
// Initialize proxy availability store
|
||||
const proxyAvailabilityStore = useProxyAvailabilityStore()
|
||||
|
||||
const columns: ColumnsType<SocketInfo> = [
|
||||
{
|
||||
title: () => $gettext('Socket'),
|
||||
dataIndex: 'socket',
|
||||
key: 'socket',
|
||||
width: 200,
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: () => $gettext('Upstream'),
|
||||
dataIndex: 'upstream_name',
|
||||
key: 'upstream_name',
|
||||
width: 150,
|
||||
customRender: ({ record }) => {
|
||||
if (!record.upstream_name) {
|
||||
return $gettext('Direct')
|
||||
}
|
||||
return record.upstream_name
|
||||
},
|
||||
},
|
||||
{
|
||||
title: () => $gettext('Health Status'),
|
||||
key: 'status',
|
||||
width: 180,
|
||||
customRender: ({ record }) => {
|
||||
if (!record.status) {
|
||||
return $gettext('No Data')
|
||||
}
|
||||
const status = record.status
|
||||
return h('div', { class: 'flex items-center' }, [
|
||||
h(Tag, { color: status.online ? 'success' : 'error', class: 'mr-2' }, () => status.online ? 'Online' : 'Offline'),
|
||||
status.online ? h('span', `${status.latency.toFixed(2)}ms`) : null,
|
||||
])
|
||||
},
|
||||
},
|
||||
{
|
||||
title: () => $gettext('Last Check'),
|
||||
dataIndex: 'last_check',
|
||||
key: 'last_check',
|
||||
width: 180,
|
||||
customRender: ({ text }) => {
|
||||
return text ? formatDateTime(text) : '-'
|
||||
},
|
||||
},
|
||||
{
|
||||
title: () => $gettext('Health Check'),
|
||||
key: 'enabled',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
|
||||
// Merge socket list with real-time availability data
|
||||
function mergeSocketData(sockets: SocketInfo[]): SocketInfo[] {
|
||||
return sockets.map(socket => {
|
||||
// Get real-time status from availability store
|
||||
const availabilityResult = proxyAvailabilityStore.availabilityResults[socket.socket]
|
||||
|
||||
if (availabilityResult) {
|
||||
return {
|
||||
...socket,
|
||||
status: {
|
||||
online: availabilityResult.online,
|
||||
latency: availabilityResult.latency,
|
||||
},
|
||||
last_check: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
return socket
|
||||
})
|
||||
}
|
||||
|
||||
// Computed data source that combines socket list with real-time availability
|
||||
const enrichedDataSource = computed(() => {
|
||||
return mergeSocketData(dataSource.value)
|
||||
})
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await upstream.getSocketList()
|
||||
dataSource.value = res.data
|
||||
}
|
||||
catch {
|
||||
message.error('Failed to load socket data')
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleToggleEnabled(socket: string, enabled: boolean | string | number) {
|
||||
const isEnabled = typeof enabled === 'boolean' ? enabled : Boolean(enabled)
|
||||
try {
|
||||
await upstream.updateSocketConfig(socket, { enabled: isEnabled })
|
||||
message.success(`Health check ${isEnabled ? 'enabled' : 'disabled'} for ${socket}`)
|
||||
await loadData()
|
||||
}
|
||||
catch {
|
||||
message.error('Failed to update socket configuration')
|
||||
}
|
||||
}
|
||||
|
||||
// Start monitoring when component mounts
|
||||
onMounted(async () => {
|
||||
await loadData()
|
||||
// Start real-time monitoring for availability updates
|
||||
proxyAvailabilityStore.startMonitoring()
|
||||
})
|
||||
|
||||
// Clean up WebSocket connections when component unmounts
|
||||
onUnmounted(() => {
|
||||
proxyAvailabilityStore.stopMonitoring()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ACard :title="$gettext('Upstream Sockets')">
|
||||
<template #extra>
|
||||
<AButton :loading @click="loadData">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
</AButton>
|
||||
</template>
|
||||
|
||||
<ATable
|
||||
:columns="columns"
|
||||
:data-source="enrichedDataSource"
|
||||
:loading="loading"
|
||||
:pagination="{
|
||||
pageSize: 20,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total: number) => `Total ${total} items`,
|
||||
}"
|
||||
:scroll="{ x: 1400 }"
|
||||
row-key="socket"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'socket'">
|
||||
<ATag color="default" :bordered="false" class="socket-tag">
|
||||
<template #icon>
|
||||
<span v-if="record.type === 'upstream'" class="target-type-icon">U</span>
|
||||
<span v-else class="target-type-icon">P</span>
|
||||
</template>
|
||||
{{ record.socket }}
|
||||
</ATag>
|
||||
</template>
|
||||
<template v-if="column.key === 'enabled'">
|
||||
<ASwitch
|
||||
v-model:checked="record.enabled"
|
||||
@change="handleToggleEnabled(record.socket, $event)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</ATable>
|
||||
</ACard>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.socket-tag {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 12px;
|
||||
|
||||
.target-type-icon {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
line-height: 12px;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
Binary file not shown.
@@ -1212,6 +1212,30 @@
|
||||
"https://nginx.org/en/docs/stream/ngx_stream_js_module.html#js_fetch_ciphers"
|
||||
]
|
||||
},
|
||||
"js_fetch_keepalive": {
|
||||
"links": [
|
||||
"https://nginx.org/en/docs/http/ngx_http_js_module.html#js_fetch_keepalive",
|
||||
"https://nginx.org/en/docs/stream/ngx_stream_js_module.html#js_fetch_keepalive"
|
||||
]
|
||||
},
|
||||
"js_fetch_keepalive_requests": {
|
||||
"links": [
|
||||
"https://nginx.org/en/docs/http/ngx_http_js_module.html#js_fetch_keepalive_requests",
|
||||
"https://nginx.org/en/docs/stream/ngx_stream_js_module.html#js_fetch_keepalive_requests"
|
||||
]
|
||||
},
|
||||
"js_fetch_keepalive_time": {
|
||||
"links": [
|
||||
"https://nginx.org/en/docs/http/ngx_http_js_module.html#js_fetch_keepalive_time",
|
||||
"https://nginx.org/en/docs/stream/ngx_stream_js_module.html#js_fetch_keepalive_time"
|
||||
]
|
||||
},
|
||||
"js_fetch_keepalive_timeout": {
|
||||
"links": [
|
||||
"https://nginx.org/en/docs/http/ngx_http_js_module.html#js_fetch_keepalive_timeout",
|
||||
"https://nginx.org/en/docs/stream/ngx_stream_js_module.html#js_fetch_keepalive_timeout"
|
||||
]
|
||||
},
|
||||
"js_fetch_max_response_buffer_size": {
|
||||
"links": [
|
||||
"https://nginx.org/en/docs/http/ngx_http_js_module.html#js_fetch_max_response_buffer_size",
|
||||
|
||||
@@ -32,12 +32,13 @@ type Service struct {
|
||||
availabilityMap map[string]*Status // key: host:port
|
||||
configTargets map[string][]string // configPath -> []targetKeys
|
||||
// Public upstream definitions storage
|
||||
Upstreams map[string]*Definition // key: upstream name
|
||||
upstreamsMutex sync.RWMutex
|
||||
targetsMutex sync.RWMutex
|
||||
lastUpdateTime time.Time
|
||||
testInProgress bool
|
||||
testMutex sync.Mutex
|
||||
Upstreams map[string]*Definition // key: upstream name
|
||||
upstreamsMutex sync.RWMutex
|
||||
targetsMutex sync.RWMutex
|
||||
lastUpdateTime time.Time
|
||||
testInProgress bool
|
||||
testMutex sync.Mutex
|
||||
disabledSocketsChecker func() map[string]bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -236,18 +237,30 @@ func (s *Service) PerformAvailabilityTest() {
|
||||
|
||||
// logger.Debug("Performing availability test for", targetCount, "unique targets")
|
||||
|
||||
// Get disabled sockets from database
|
||||
disabledSockets := make(map[string]bool)
|
||||
if s.disabledSocketsChecker != nil {
|
||||
disabledSockets = s.disabledSocketsChecker()
|
||||
}
|
||||
|
||||
// Separate targets into traditional and consul groups from the start
|
||||
s.targetsMutex.RLock()
|
||||
regularTargetKeys := make([]string, 0, len(s.targets))
|
||||
consulTargets := make([]ProxyTarget, 0, len(s.targets))
|
||||
|
||||
for _, targetInfo := range s.targets {
|
||||
// Check if this socket is disabled
|
||||
socketAddr := formatSocketAddress(targetInfo.ProxyTarget.Host, targetInfo.ProxyTarget.Port)
|
||||
if disabledSockets[socketAddr] {
|
||||
// logger.Debug("Skipping disabled socket:", socketAddr)
|
||||
continue
|
||||
}
|
||||
|
||||
if targetInfo.ProxyTarget.IsConsul {
|
||||
consulTargets = append(consulTargets, targetInfo.ProxyTarget)
|
||||
} else {
|
||||
// Traditional target - use properly formatted socket address
|
||||
key := formatSocketAddress(targetInfo.ProxyTarget.Host, targetInfo.ProxyTarget.Port)
|
||||
regularTargetKeys = append(regularTargetKeys, key)
|
||||
regularTargetKeys = append(regularTargetKeys, socketAddr)
|
||||
}
|
||||
}
|
||||
s.targetsMutex.RUnlock()
|
||||
@@ -277,6 +290,28 @@ func (s *Service) PerformAvailabilityTest() {
|
||||
// logger.Debug("Availability test completed for", len(results), "targets")
|
||||
}
|
||||
|
||||
// findUpstreamNameForTarget finds which upstream a target belongs to
|
||||
func (s *Service) findUpstreamNameForTarget(target ProxyTarget) string {
|
||||
s.upstreamsMutex.RLock()
|
||||
defer s.upstreamsMutex.RUnlock()
|
||||
|
||||
targetKey := formatSocketAddress(target.Host, target.Port)
|
||||
for name, upstream := range s.Upstreams {
|
||||
for _, server := range upstream.Servers {
|
||||
serverKey := formatSocketAddress(server.Host, server.Port)
|
||||
if serverKey == targetKey {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetDisabledSocketsChecker sets a callback function to check disabled sockets
|
||||
func (s *Service) SetDisabledSocketsChecker(checker func() map[string]bool) {
|
||||
s.disabledSocketsChecker = checker
|
||||
}
|
||||
|
||||
// ClearTargets clears all targets (useful for testing or reloading)
|
||||
func (s *Service) ClearTargets() {
|
||||
s.targetsMutex.Lock()
|
||||
|
||||
@@ -53,6 +53,7 @@ func GenerateAllModel() []any {
|
||||
AutoBackup{},
|
||||
SiteConfig{},
|
||||
NginxLogIndex{},
|
||||
UpstreamConfig{},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package model
|
||||
|
||||
type UpstreamConfig struct {
|
||||
Model
|
||||
Socket string `json:"socket" gorm:"uniqueIndex"` // host:port address
|
||||
Enabled bool `json:"enabled" gorm:"default:true"`
|
||||
}
|
||||
@@ -35,6 +35,7 @@ var (
|
||||
Site *site
|
||||
SiteConfig *siteConfig
|
||||
Stream *stream
|
||||
UpstreamConfig *upstreamConfig
|
||||
User *user
|
||||
)
|
||||
|
||||
@@ -58,6 +59,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
||||
Site = &Q.Site
|
||||
SiteConfig = &Q.SiteConfig
|
||||
Stream = &Q.Stream
|
||||
UpstreamConfig = &Q.UpstreamConfig
|
||||
User = &Q.User
|
||||
}
|
||||
|
||||
@@ -82,6 +84,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
||||
Site: newSite(db, opts...),
|
||||
SiteConfig: newSiteConfig(db, opts...),
|
||||
Stream: newStream(db, opts...),
|
||||
UpstreamConfig: newUpstreamConfig(db, opts...),
|
||||
User: newUser(db, opts...),
|
||||
}
|
||||
}
|
||||
@@ -107,6 +110,7 @@ type Query struct {
|
||||
Site site
|
||||
SiteConfig siteConfig
|
||||
Stream stream
|
||||
UpstreamConfig upstreamConfig
|
||||
User user
|
||||
}
|
||||
|
||||
@@ -133,6 +137,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
|
||||
Site: q.Site.clone(db),
|
||||
SiteConfig: q.SiteConfig.clone(db),
|
||||
Stream: q.Stream.clone(db),
|
||||
UpstreamConfig: q.UpstreamConfig.clone(db),
|
||||
User: q.User.clone(db),
|
||||
}
|
||||
}
|
||||
@@ -166,6 +171,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
||||
Site: q.Site.replaceDB(db),
|
||||
SiteConfig: q.SiteConfig.replaceDB(db),
|
||||
Stream: q.Stream.replaceDB(db),
|
||||
UpstreamConfig: q.UpstreamConfig.replaceDB(db),
|
||||
User: q.User.replaceDB(db),
|
||||
}
|
||||
}
|
||||
@@ -189,6 +195,7 @@ type queryCtx struct {
|
||||
Site *siteDo
|
||||
SiteConfig *siteConfigDo
|
||||
Stream *streamDo
|
||||
UpstreamConfig *upstreamConfigDo
|
||||
User *userDo
|
||||
}
|
||||
|
||||
@@ -212,6 +219,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
||||
Site: q.Site.WithContext(ctx),
|
||||
SiteConfig: q.SiteConfig.WithContext(ctx),
|
||||
Stream: q.Stream.WithContext(ctx),
|
||||
UpstreamConfig: q.UpstreamConfig.WithContext(ctx),
|
||||
User: q.User.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ func newLLMSession(db *gorm.DB, opts ...gen.DOOption) lLMSession {
|
||||
_lLMSession.SessionID = field.NewString(tableName, "session_id")
|
||||
_lLMSession.Title = field.NewString(tableName, "title")
|
||||
_lLMSession.Path = field.NewString(tableName, "path")
|
||||
_lLMSession.SessionType = field.NewString(tableName, "session_type")
|
||||
_lLMSession.Messages = field.NewField(tableName, "messages")
|
||||
_lLMSession.MessageCount = field.NewInt(tableName, "message_count")
|
||||
_lLMSession.IsActive = field.NewBool(tableName, "is_active")
|
||||
@@ -53,7 +52,6 @@ type lLMSession struct {
|
||||
SessionID field.String
|
||||
Title field.String
|
||||
Path field.String
|
||||
SessionType field.String
|
||||
Messages field.Field
|
||||
MessageCount field.Int
|
||||
IsActive field.Bool
|
||||
@@ -80,7 +78,6 @@ func (l *lLMSession) updateTableName(table string) *lLMSession {
|
||||
l.SessionID = field.NewString(table, "session_id")
|
||||
l.Title = field.NewString(table, "title")
|
||||
l.Path = field.NewString(table, "path")
|
||||
l.SessionType = field.NewString(table, "session_type")
|
||||
l.Messages = field.NewField(table, "messages")
|
||||
l.MessageCount = field.NewInt(table, "message_count")
|
||||
l.IsActive = field.NewBool(table, "is_active")
|
||||
@@ -103,12 +100,11 @@ func (l *lLMSession) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||
}
|
||||
|
||||
func (l *lLMSession) fillFieldMap() {
|
||||
l.fieldMap = make(map[string]field.Expr, 11)
|
||||
l.fieldMap = make(map[string]field.Expr, 10)
|
||||
l.fieldMap["id"] = l.ID
|
||||
l.fieldMap["session_id"] = l.SessionID
|
||||
l.fieldMap["title"] = l.Title
|
||||
l.fieldMap["path"] = l.Path
|
||||
l.fieldMap["session_type"] = l.SessionType
|
||||
l.fieldMap["messages"] = l.Messages
|
||||
l.fieldMap["message_count"] = l.MessageCount
|
||||
l.fieldMap["is_active"] = l.IsActive
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"gorm.io/gorm/schema"
|
||||
|
||||
"gorm.io/gen"
|
||||
"gorm.io/gen/field"
|
||||
|
||||
"gorm.io/plugin/dbresolver"
|
||||
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
)
|
||||
|
||||
func newUpstreamConfig(db *gorm.DB, opts ...gen.DOOption) upstreamConfig {
|
||||
_upstreamConfig := upstreamConfig{}
|
||||
|
||||
_upstreamConfig.upstreamConfigDo.UseDB(db, opts...)
|
||||
_upstreamConfig.upstreamConfigDo.UseModel(&model.UpstreamConfig{})
|
||||
|
||||
tableName := _upstreamConfig.upstreamConfigDo.TableName()
|
||||
_upstreamConfig.ALL = field.NewAsterisk(tableName)
|
||||
_upstreamConfig.ID = field.NewUint64(tableName, "id")
|
||||
_upstreamConfig.CreatedAt = field.NewTime(tableName, "created_at")
|
||||
_upstreamConfig.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||
_upstreamConfig.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||
_upstreamConfig.Socket = field.NewString(tableName, "socket")
|
||||
_upstreamConfig.Enabled = field.NewBool(tableName, "enabled")
|
||||
|
||||
_upstreamConfig.fillFieldMap()
|
||||
|
||||
return _upstreamConfig
|
||||
}
|
||||
|
||||
type upstreamConfig struct {
|
||||
upstreamConfigDo
|
||||
|
||||
ALL field.Asterisk
|
||||
ID field.Uint64
|
||||
CreatedAt field.Time
|
||||
UpdatedAt field.Time
|
||||
DeletedAt field.Field
|
||||
Socket field.String
|
||||
Enabled field.Bool
|
||||
|
||||
fieldMap map[string]field.Expr
|
||||
}
|
||||
|
||||
func (u upstreamConfig) Table(newTableName string) *upstreamConfig {
|
||||
u.upstreamConfigDo.UseTable(newTableName)
|
||||
return u.updateTableName(newTableName)
|
||||
}
|
||||
|
||||
func (u upstreamConfig) As(alias string) *upstreamConfig {
|
||||
u.upstreamConfigDo.DO = *(u.upstreamConfigDo.As(alias).(*gen.DO))
|
||||
return u.updateTableName(alias)
|
||||
}
|
||||
|
||||
func (u *upstreamConfig) updateTableName(table string) *upstreamConfig {
|
||||
u.ALL = field.NewAsterisk(table)
|
||||
u.ID = field.NewUint64(table, "id")
|
||||
u.CreatedAt = field.NewTime(table, "created_at")
|
||||
u.UpdatedAt = field.NewTime(table, "updated_at")
|
||||
u.DeletedAt = field.NewField(table, "deleted_at")
|
||||
u.Socket = field.NewString(table, "socket")
|
||||
u.Enabled = field.NewBool(table, "enabled")
|
||||
|
||||
u.fillFieldMap()
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *upstreamConfig) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||
_f, ok := u.fieldMap[fieldName]
|
||||
if !ok || _f == nil {
|
||||
return nil, false
|
||||
}
|
||||
_oe, ok := _f.(field.OrderExpr)
|
||||
return _oe, ok
|
||||
}
|
||||
|
||||
func (u *upstreamConfig) fillFieldMap() {
|
||||
u.fieldMap = make(map[string]field.Expr, 6)
|
||||
u.fieldMap["id"] = u.ID
|
||||
u.fieldMap["created_at"] = u.CreatedAt
|
||||
u.fieldMap["updated_at"] = u.UpdatedAt
|
||||
u.fieldMap["deleted_at"] = u.DeletedAt
|
||||
u.fieldMap["socket"] = u.Socket
|
||||
u.fieldMap["enabled"] = u.Enabled
|
||||
}
|
||||
|
||||
func (u upstreamConfig) clone(db *gorm.DB) upstreamConfig {
|
||||
u.upstreamConfigDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||
return u
|
||||
}
|
||||
|
||||
func (u upstreamConfig) replaceDB(db *gorm.DB) upstreamConfig {
|
||||
u.upstreamConfigDo.ReplaceDB(db)
|
||||
return u
|
||||
}
|
||||
|
||||
type upstreamConfigDo struct{ gen.DO }
|
||||
|
||||
// FirstByID Where("id=@id")
|
||||
func (u upstreamConfigDo) FirstByID(id uint64) (result *model.UpstreamConfig, err error) {
|
||||
var params []interface{}
|
||||
|
||||
var generateSQL strings.Builder
|
||||
params = append(params, id)
|
||||
generateSQL.WriteString("id=? ")
|
||||
|
||||
var executeSQL *gorm.DB
|
||||
executeSQL = u.UnderlyingDB().Where(generateSQL.String(), params...).Take(&result) // ignore_security_alert
|
||||
err = executeSQL.Error
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteByID update @@table set deleted_at=strftime('%Y-%m-%d %H:%M:%S','now') where id=@id
|
||||
func (u upstreamConfigDo) DeleteByID(id uint64) (err error) {
|
||||
var params []interface{}
|
||||
|
||||
var generateSQL strings.Builder
|
||||
params = append(params, id)
|
||||
generateSQL.WriteString("update upstream_configs set deleted_at=strftime('%Y-%m-%d %H:%M:%S','now') where id=? ")
|
||||
|
||||
var executeSQL *gorm.DB
|
||||
executeSQL = u.UnderlyingDB().Exec(generateSQL.String(), params...) // ignore_security_alert
|
||||
err = executeSQL.Error
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Debug() *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Debug())
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) WithContext(ctx context.Context) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.WithContext(ctx))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) ReadDB() *upstreamConfigDo {
|
||||
return u.Clauses(dbresolver.Read)
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) WriteDB() *upstreamConfigDo {
|
||||
return u.Clauses(dbresolver.Write)
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Session(config *gorm.Session) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Session(config))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Clauses(conds ...clause.Expression) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Clauses(conds...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Returning(value interface{}, columns ...string) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Returning(value, columns...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Not(conds ...gen.Condition) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Not(conds...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Or(conds ...gen.Condition) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Or(conds...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Select(conds ...field.Expr) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Select(conds...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Where(conds ...gen.Condition) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Where(conds...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Order(conds ...field.Expr) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Order(conds...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Distinct(cols ...field.Expr) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Distinct(cols...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Omit(cols ...field.Expr) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Omit(cols...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Join(table schema.Tabler, on ...field.Expr) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Join(table, on...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) LeftJoin(table schema.Tabler, on ...field.Expr) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.LeftJoin(table, on...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) RightJoin(table schema.Tabler, on ...field.Expr) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.RightJoin(table, on...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Group(cols ...field.Expr) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Group(cols...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Having(conds ...gen.Condition) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Having(conds...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Limit(limit int) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Limit(limit))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Offset(offset int) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Offset(offset))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Scopes(funcs...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Unscoped() *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Unscoped())
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Create(values ...*model.UpstreamConfig) error {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
return u.DO.Create(values)
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) CreateInBatches(values []*model.UpstreamConfig, batchSize int) error {
|
||||
return u.DO.CreateInBatches(values, batchSize)
|
||||
}
|
||||
|
||||
// Save : !!! underlying implementation is different with GORM
|
||||
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
|
||||
func (u upstreamConfigDo) Save(values ...*model.UpstreamConfig) error {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
return u.DO.Save(values)
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) First() (*model.UpstreamConfig, error) {
|
||||
if result, err := u.DO.First(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*model.UpstreamConfig), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Take() (*model.UpstreamConfig, error) {
|
||||
if result, err := u.DO.Take(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*model.UpstreamConfig), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Last() (*model.UpstreamConfig, error) {
|
||||
if result, err := u.DO.Last(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*model.UpstreamConfig), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Find() ([]*model.UpstreamConfig, error) {
|
||||
result, err := u.DO.Find()
|
||||
return result.([]*model.UpstreamConfig), err
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UpstreamConfig, err error) {
|
||||
buf := make([]*model.UpstreamConfig, 0, batchSize)
|
||||
err = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
|
||||
defer func() { results = append(results, buf...) }()
|
||||
return fc(tx, batch)
|
||||
})
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) FindInBatches(result *[]*model.UpstreamConfig, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||
return u.DO.FindInBatches(result, batchSize, fc)
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Attrs(attrs ...field.AssignExpr) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Attrs(attrs...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Assign(attrs ...field.AssignExpr) *upstreamConfigDo {
|
||||
return u.withDO(u.DO.Assign(attrs...))
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Joins(fields ...field.RelationField) *upstreamConfigDo {
|
||||
for _, _f := range fields {
|
||||
u = *u.withDO(u.DO.Joins(_f))
|
||||
}
|
||||
return &u
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Preload(fields ...field.RelationField) *upstreamConfigDo {
|
||||
for _, _f := range fields {
|
||||
u = *u.withDO(u.DO.Preload(_f))
|
||||
}
|
||||
return &u
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) FirstOrInit() (*model.UpstreamConfig, error) {
|
||||
if result, err := u.DO.FirstOrInit(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*model.UpstreamConfig), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) FirstOrCreate() (*model.UpstreamConfig, error) {
|
||||
if result, err := u.DO.FirstOrCreate(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*model.UpstreamConfig), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) FindByPage(offset int, limit int) (result []*model.UpstreamConfig, count int64, err error) {
|
||||
result, err = u.Offset(offset).Limit(limit).Find()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if size := len(result); 0 < limit && 0 < size && size < limit {
|
||||
count = int64(size + offset)
|
||||
return
|
||||
}
|
||||
|
||||
count, err = u.Offset(-1).Limit(-1).Count()
|
||||
return
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||
count, err = u.Count()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = u.Offset(offset).Limit(limit).Scan(result)
|
||||
return
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Scan(result interface{}) (err error) {
|
||||
return u.DO.Scan(result)
|
||||
}
|
||||
|
||||
func (u upstreamConfigDo) Delete(models ...*model.UpstreamConfig) (result gen.ResultInfo, err error) {
|
||||
return u.DO.Delete(models)
|
||||
}
|
||||
|
||||
func (u *upstreamConfigDo) withDO(do gen.Dao) *upstreamConfigDo {
|
||||
u.DO = *do.(*gen.DO)
|
||||
return u
|
||||
}
|
||||
Reference in New Issue
Block a user