Jelajahi Sumber

refactor: nodes analytics (#847)

* refactor: nodes analytics

* feat(debug): add pprof in debug mode

* refactor: websocket error handler
Jacky 2 bulan lalu
induk
melakukan
cb4977e5ab

+ 9 - 6
api/analytic/analytic.go

@@ -2,6 +2,10 @@ package analytic
 
 import (
 	"fmt"
+	"net/http"
+	"runtime"
+	"time"
+
 	"github.com/0xJacky/Nginx-UI/internal/analytic"
 	"github.com/0xJacky/Nginx-UI/internal/helper"
 	"github.com/shirou/gopsutil/v4/cpu"
@@ -10,9 +14,6 @@ import (
 	"github.com/shirou/gopsutil/v4/net"
 	"github.com/spf13/cast"
 	"github.com/uozi-tech/cosy/logger"
-	"net/http"
-	"runtime"
-	"time"
 
 	"github.com/gin-gonic/gin"
 	"github.com/gorilla/websocket"
@@ -86,12 +87,14 @@ func Analytic(c *gin.Context) {
 
 		// write
 		err = ws.WriteJSON(stat)
-		if helper.IsUnexpectedWebsocketError(err) {
-			logger.Error(err)
+		if err != nil {
+			if helper.IsUnexpectedWebsocketError(err) {
+				logger.Error(err)
+			}
 			break
 		}
 
-		time.Sleep(1000 * time.Microsecond)
+		time.Sleep(1 * time.Second)
 	}
 }
 

+ 11 - 6
api/analytic/nodes.go

@@ -1,13 +1,14 @@
 package analytic
 
 import (
+	"net/http"
+	"time"
+
 	"github.com/0xJacky/Nginx-UI/internal/analytic"
 	"github.com/0xJacky/Nginx-UI/internal/helper"
 	"github.com/gin-gonic/gin"
 	"github.com/gorilla/websocket"
 	"github.com/uozi-tech/cosy/logger"
-	"net/http"
-	"time"
 )
 
 func GetNodeStat(c *gin.Context) {
@@ -28,8 +29,10 @@ func GetNodeStat(c *gin.Context) {
 	for {
 		// write
 		err = ws.WriteJSON(analytic.GetNodeStat())
-		if helper.IsUnexpectedWebsocketError(err) {
-			logger.Error(err)
+		if err != nil {
+			if helper.IsUnexpectedWebsocketError(err) {
+				logger.Error(err)
+			}
 			break
 		}
 
@@ -55,8 +58,10 @@ func GetNodesAnalytic(c *gin.Context) {
 	for {
 		// write
 		err = ws.WriteJSON(analytic.NodeMap)
-		if helper.IsUnexpectedWebsocketError(err) {
-			logger.Error(err)
+		if err != nil {
+			if helper.IsUnexpectedWebsocketError(err) {
+				logger.Error(err)
+			}
 			break
 		}
 

+ 5 - 3
api/nginx_log/nginx_log.go

@@ -2,6 +2,7 @@ package nginx_log
 
 import (
 	"encoding/json"
+	"github.com/0xJacky/Nginx-UI/internal/helper"
 	"github.com/0xJacky/Nginx-UI/internal/nginx"
 	"github.com/0xJacky/Nginx-UI/internal/nginx_log"
 	"github.com/gin-gonic/gin"
@@ -252,9 +253,10 @@ func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan ch
 				}
 
 				err = ws.WriteMessage(websocket.TextMessage, []byte(line.Text))
-
-				if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {
-					errChan <- errors.Wrap(err, "error tailNginxLog write message")
+				if err != nil {
+					if helper.IsUnexpectedWebsocketError(err) {
+						errChan <- errors.Wrap(err, "error tailNginxLog write message")
+					}
 					return
 				}
 			case control = <-controlChan:

+ 4 - 2
api/upstream/upstream.go

@@ -36,8 +36,10 @@ func AvailabilityTest(c *gin.Context) {
 
 	for {
 		err = ws.WriteJSON(upstream.AvailabilityTest(body))
-		if helper.IsUnexpectedWebsocketError(err) {
-			logger.Error(err)
+		if err != nil {
+			if helper.IsUnexpectedWebsocketError(err) {
+				logger.Error(err)
+			}
 			break
 		}
 

+ 0 - 4
app/src/views/dashboard/ServerAnalytic.vue

@@ -90,10 +90,6 @@ onMounted(() => {
   })
 })
 
-onUnmounted(() => {
-  websocket.close()
-})
-
 function handle_uptime(t: number) {
   // uptime
   let _uptime = Math.floor(t)

+ 6 - 1
app/src/views/dashboard/components/NodeAnalyticItem.vue

@@ -31,7 +31,7 @@ defineProps<{
     <div class="hardware-monitor-item longer">
       <div class="mb-1">
         <LineChartOutlined class="mr-1" />
-        <span class="load-avg-describe">1min:</span>{{ ` ${item.avg_load?.load1?.toFixed(2)}` }} ·
+        <span class="load-avg-describe">1min:</span>{{ item.avg_load?.load1?.toFixed(2) }} ·
         <span class="load-avg-describe">5min:</span>{{ item.avg_load?.load5?.toFixed(2) }} ·
         <span class="load-avg-describe">15min:</span>{{ item.avg_load?.load15?.toFixed(2) }}
       </div>
@@ -123,6 +123,11 @@ defineProps<{
   .longer {
     width: 180px;
   }
+
+  .load-avg-describe {
+    margin-right: 2px;
+  }
+
   @media (max-width: 400px) {
     .longer {
       width: 180px;

+ 6 - 9
app/src/views/environment/Environment.vue

@@ -8,10 +8,15 @@ import { message } from 'ant-design-vue'
 
 const route = useRoute()
 const curd = ref()
+const loadingFromSettings = ref(false)
+
 function loadFromSettings() {
+  loadingFromSettings.value = true
   environment.load_from_settings().then(() => {
     curd.value.get_list()
     message.success($gettext('Load successfully'))
+  }).finally(() => {
+    loadingFromSettings.value = false
   })
 }
 const selectedNodeIds = ref([])
@@ -25,14 +30,6 @@ function batchUpgrade() {
 const inTrash = computed(() => {
   return route.query.trash === 'true'
 })
-
-// const timer = setInterval(() => {
-//   curd.value.get_list()
-// }, 10000)
-
-// onUnmounted(() => {
-//   clearInterval(timer)
-// })
 </script>
 
 <template>
@@ -48,7 +45,7 @@ const inTrash = computed(() => {
       :columns="envColumns"
     >
       <template #beforeAdd>
-        <AButton size="small" type="link" @click="loadFromSettings">
+        <AButton size="small" type="link" :loading="loadingFromSettings" @click="loadFromSettings">
           {{ $gettext('Load from settings') }}
         </AButton>
       </template>

+ 31 - 30
go.mod

@@ -11,6 +11,7 @@ require (
 	github.com/dgraph-io/ristretto/v2 v2.1.0
 	github.com/dustin/go-humanize v1.0.1
 	github.com/elliotchance/orderedmap/v3 v3.1.0
+	github.com/gin-contrib/pprof v1.5.2
 	github.com/gin-contrib/static v1.1.3
 	github.com/gin-gonic/gin v1.10.0
 	github.com/go-acme/lego/v4 v4.21.0
@@ -30,12 +31,12 @@ require (
 	github.com/pretty66/websocketproxy v0.0.0-20220507015215-930b3a686308
 	github.com/samber/lo v1.49.1
 	github.com/sashabaranov/go-openai v1.36.1
-	github.com/shirou/gopsutil/v4 v4.24.12
+	github.com/shirou/gopsutil/v4 v4.25.1
 	github.com/spf13/cast v1.7.1
 	github.com/stretchr/testify v1.10.0
 	github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a
 	github.com/uozi-tech/cosy v1.14.3
-	github.com/uozi-tech/cosy-driver-sqlite v0.2.0
+	github.com/uozi-tech/cosy-driver-sqlite v0.2.1
 	github.com/urfave/cli/v3 v3.0.0-beta1
 	golang.org/x/crypto v0.32.0
 	golang.org/x/net v0.34.0
@@ -74,20 +75,20 @@ require (
 	github.com/StackExchange/wmi v1.2.1 // indirect
 	github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
 	github.com/aliyun/alibaba-cloud-sdk-go v1.63.84 // indirect
-	github.com/aws/aws-sdk-go-v2 v1.35.0 // indirect
-	github.com/aws/aws-sdk-go-v2/config v1.29.3 // indirect
-	github.com/aws/aws-sdk-go-v2/credentials v1.17.56 // indirect
-	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.26 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.30 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.30 // indirect
+	github.com/aws/aws-sdk-go-v2 v1.36.0 // indirect
+	github.com/aws/aws-sdk-go-v2/config v1.29.5 // indirect
+	github.com/aws/aws-sdk-go-v2/credentials v1.17.58 // indirect
+	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.11 // indirect
-	github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.13 // indirect
-	github.com/aws/aws-sdk-go-v2/service/route53 v1.48.4 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sso v1.24.13 // indirect
-	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.12 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sts v1.33.11 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 // indirect
+	github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.14 // indirect
+	github.com/aws/aws-sdk-go-v2/service/route53 v1.48.6 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 // indirect
+	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 // indirect
 	github.com/aws/smithy-go v1.22.2 // indirect
 	github.com/benbjohnson/clock v1.3.5 // indirect
 	github.com/boombuler/barcode v1.0.2 // indirect
@@ -194,7 +195,7 @@ require (
 	github.com/nxadm/tail v1.4.11 // indirect
 	github.com/nzdjb/go-metaname v1.0.0 // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
-	github.com/oracle/oci-go-sdk/v65 v65.82.0 // indirect
+	github.com/oracle/oci-go-sdk/v65 v65.83.0 // indirect
 	github.com/ovh/go-ovh v1.6.0 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
 	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
@@ -211,7 +212,7 @@ require (
 	github.com/sacloud/packages-go v0.0.11 // indirect
 	github.com/sagikazarmark/locafero v0.7.0 // indirect
 	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
-	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.31 // indirect
+	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 // indirect
 	github.com/selectel/domains-go v1.1.0 // indirect
 	github.com/selectel/go-selvpcclient/v3 v3.2.1 // indirect
 	github.com/shopspring/decimal v1.4.0 // indirect
@@ -226,8 +227,8 @@ require (
 	github.com/spf13/pflag v1.0.6 // indirect
 	github.com/spf13/viper v1.19.0 // indirect
 	github.com/subosito/gotenv v1.6.0 // indirect
-	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1090 // indirect
-	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1090 // indirect
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1091 // indirect
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1091 // indirect
 	github.com/tjfoc/gmsm v1.4.1 // indirect
 	github.com/tklauser/go-sysconf v0.3.14 // indirect
 	github.com/tklauser/numcpus v0.9.0 // indirect
@@ -241,8 +242,8 @@ require (
 	github.com/volcengine/volc-sdk-golang v1.0.194 // indirect
 	github.com/vultr/govultr/v3 v3.14.1 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
-	github.com/yandex-cloud/go-genproto v0.0.0-20250127124313-5be1a2cc06d4 // indirect
-	github.com/yandex-cloud/go-sdk v0.0.0-20250127132311-016f84adc072 // indirect
+	github.com/yandex-cloud/go-genproto v0.0.0-20250203115010-0bcba64c41f6 // indirect
+	github.com/yandex-cloud/go-sdk v0.0.0-20250203123950-24786ecffd92 // indirect
 	github.com/yusufpapurcu/wmi v1.2.4 // indirect
 	go.mongodb.org/mongo-driver v1.17.2 // indirect
 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
@@ -254,19 +255,19 @@ require (
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/ratelimit v0.3.1 // indirect
 	go.uber.org/zap v1.27.0 // indirect
-	golang.org/x/arch v0.13.0 // indirect
+	golang.org/x/arch v0.14.0 // indirect
 	golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
-	golang.org/x/mod v0.22.0 // indirect
-	golang.org/x/oauth2 v0.25.0 // indirect
-	golang.org/x/sync v0.10.0 // indirect
-	golang.org/x/sys v0.29.0 // indirect
-	golang.org/x/text v0.21.0 // indirect
-	golang.org/x/time v0.9.0 // indirect
+	golang.org/x/mod v0.23.0 // indirect
+	golang.org/x/oauth2 v0.26.0 // indirect
+	golang.org/x/sync v0.11.0 // indirect
+	golang.org/x/sys v0.30.0 // indirect
+	golang.org/x/text v0.22.0 // indirect
+	golang.org/x/time v0.10.0 // indirect
 	golang.org/x/tools v0.29.0 // indirect
 	google.golang.org/api v0.219.0 // indirect
-	google.golang.org/genproto v0.0.0-20250127172529-29210b9bc287 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 // indirect
+	google.golang.org/genproto v0.0.0-20250204164813-702378808489 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489 // indirect
 	google.golang.org/grpc v1.70.0 // indirect
 	google.golang.org/protobuf v1.36.4 // indirect
 	gopkg.in/fsnotify.v1 v1.4.7 // indirect

+ 63 - 0
go.sum

@@ -714,16 +714,28 @@ github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm
 github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
 github.com/aws/aws-sdk-go-v2 v1.35.0 h1:jTPxEJyzjSuuz0wB+302hr8Eu9KUI+Zv8zlujMGJpVI=
 github.com/aws/aws-sdk-go-v2 v1.35.0/go.mod h1:JgstGg0JjWU1KpVJjD5H0y0yyAIpSdKEq556EI6yOOM=
+github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk=
+github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM=
 github.com/aws/aws-sdk-go-v2/config v1.29.3 h1:a5Ucjxe6iV+LHEBmYA9w40rT5aGxWybx/4l/O/fvJlE=
 github.com/aws/aws-sdk-go-v2/config v1.29.3/go.mod h1:pt9z1x12zDiDb4iFLrxoeAKLVCU/Gp9DL/5BnwlY77o=
+github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k=
+github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg=
 github.com/aws/aws-sdk-go-v2/credentials v1.17.56 h1:JKMBreKudV+ozx6rZJLvEtiexv48aEdhdC7mXUw9MLs=
 github.com/aws/aws-sdk-go-v2/credentials v1.17.56/go.mod h1:S3xRjIHD8HHFgMTz4L56q/7IldfNtGL9JjH/vP3U6DA=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.26 h1:XMBqBEuZLf8yxtH+mU/uUDyQbN4iD/xv9h6he2+lzhw=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.26/go.mod h1:d0+wQ/3CYGPuHEfBTPpQdfUX7gjk0/Lxs5Q6KzdEGY8=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.30 h1:+7AzSGNhHoY53di13lvztf9Dyd/9ofzoYGBllkWp3a0=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.30/go.mod h1:Jxd/FrCny99yURiQiMywgXvBhd7tmgdv6KdlUTNzMSo=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.30 h1:Ex06eY6I5rO7IX0HalGfa5nGjpBoOsS1Qm3xfjkuszs=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.30/go.mod h1:AvyEMA9QcX59kFhVizBpIBpEMThUTXssuJe+emBdcGM=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
 github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
@@ -731,16 +743,28 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/C
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.11 h1:5JKQ2J3BBW4ovy6A/5Lwx9SpA6IzgH8jB3bquGZ1NUw=
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.11/go.mod h1:VShCk7rfCzK/b9U1aSkzLwcOoaDlYna16482QqEavis=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk=
 github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.13 h1:yukUijkcclShNo3QXry+udZDyDQOy8siCjqNfpRKuf8=
 github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.13/go.mod h1:Ka+a4bm2nmtvk+Ql1K2Bmr7MrJCs8qz4UDmaLQs1daY=
+github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.14 h1:GH3vnPsdH2sTkZRBPnAeMqwkJXdwPNrEh9nI+DEdD0o=
+github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.14/go.mod h1:fHFrxpH3kA2iK2NBg/jj3jxgVVfNS7WaKYC5axxr/PY=
 github.com/aws/aws-sdk-go-v2/service/route53 v1.48.4 h1:qajhoD/ElVskbXAJfgljClGj7DGME0uoDGUMVjFTkNs=
 github.com/aws/aws-sdk-go-v2/service/route53 v1.48.4/go.mod h1:kDfNqSNtcqB8aNUJClykJ+xLILNoYAaUIo72A2uR73Y=
+github.com/aws/aws-sdk-go-v2/service/route53 v1.48.6 h1:O7L9iEodiF07vJoXShMrw2XyeAqZhLUIXqWEitCq6EE=
+github.com/aws/aws-sdk-go-v2/service/route53 v1.48.6/go.mod h1:E93uWfli9RToQzVA7+bYnynKOFcYOhNWqhY1hWSMZRc=
 github.com/aws/aws-sdk-go-v2/service/sso v1.24.13 h1:q4pOAKxypbFoUJzOpgo939bF50qb4DgYshiDfcsdN0M=
 github.com/aws/aws-sdk-go-v2/service/sso v1.24.13/go.mod h1:G/0PTg7+vQT42ictQGjJhixzTcVZtHFvrN/OeTXrRfQ=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs=
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.12 h1:4sGSGshSSfO1vrcXruPick3ioSf8nhhD6nuB2ni37P4=
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.12/go.mod h1:NHpu/pLOelViA4qxkAFH10VLqh+XeLhZfXDaFyMVgSs=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U=
 github.com/aws/aws-sdk-go-v2/service/sts v1.33.11 h1:RIXOjp7Dp4siCYJRwBHUcBdVgOWflSJGlq4ZhMI5Ta0=
 github.com/aws/aws-sdk-go-v2/service/sts v1.33.11/go.mod h1:ZR17k9bPKPR8u0IkyA6xVsjr56doNQ4ZB1fs7abYBfE=
+github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc=
+github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w=
 github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
 github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
 github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
@@ -782,6 +806,7 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -909,6 +934,8 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G
 github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gin-contrib/pprof v1.5.2 h1:Kcq5W2bA2PBcVtF0MqkQjpvCpwJr+pd7zxcQh2csg7E=
+github.com/gin-contrib/pprof v1.5.2/go.mod h1:a1W4CDXwAPm2zql2AKdnT7OVCJdV/oFPhJXVOrDs5Ns=
 github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
 github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
 github.com/gin-contrib/static v1.1.3 h1:WLOpkBtMDJ3gATFZgNJyVibFMio/UHonnueqJsQ0w4U=
@@ -1533,6 +1560,8 @@ github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mo
 github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
 github.com/oracle/oci-go-sdk/v65 v65.82.0 h1:42fSqE847E95ICfVPcKhRmzkvM6tucwbPdUMQydfWGc=
 github.com/oracle/oci-go-sdk/v65 v65.82.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
+github.com/oracle/oci-go-sdk/v65 v65.83.0 h1:KFI0oyyCTPmgevHF+QlN02Zdf23Jx1p1X+4KPyH14H8=
+github.com/oracle/oci-go-sdk/v65 v65.83.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
 github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI=
 github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -1651,6 +1680,8 @@ github.com/sashabaranov/go-openai v1.36.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adO
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.31 h1:Fj7jPyu9TQjqfXcLylINK5PANSzOWXIX4QtGmfp67AY=
 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.31/go.mod h1:kzh+BSAvpoyHHdHBCDhmSWtBc1NbLMZ2lWHqnBoxFks=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 h1:4+LP7qmsLSGbmc66m1s5dKRMBwztRppfxFKlYqYte/c=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32/go.mod h1:kzh+BSAvpoyHHdHBCDhmSWtBc1NbLMZ2lWHqnBoxFks=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo=
 github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA=
@@ -1658,6 +1689,8 @@ github.com/selectel/go-selvpcclient/v3 v3.2.1 h1:ny6WIAMiHzKxOgOEnwcWE79wIQij1AH
 github.com/selectel/go-selvpcclient/v3 v3.2.1/go.mod h1:3EfSf8aEWyhspOGbvZ6mvnFg7JN5uckxNyBFPGWsXNQ=
 github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4=
 github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o=
+github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
+github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@@ -1749,8 +1782,12 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1090 h1:0fZ+FZE7ZvqxGdYbtQW8OyPXGD1qGPmg4wT+Tjkv+1s=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1090/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1091 h1:RxogX8ZCPBmZ6PY7DjnWnwGRkAkYEEinT5WNNxbLVeo=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1091/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1090 h1:8AXFluT9RV4EeWC7kfJUWjnFQlIJ4pBVC/+Qtqgg0hM=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1090/go.mod h1:/XMAs17Sih+pqp/Pxy0WpmdZE/CychzXEnW/tTrCujk=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1091 h1:36WwNgrtoGKszQovUj3+0CjNsM1gMQ3a5lvZx2bkjng=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1091/go.mod h1:cWRGvOUnMQMky4oliMX1dXT6Z4CbsSGOIxaUcrD5Zvw=
 github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
 github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
 github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
@@ -1781,6 +1818,8 @@ github.com/uozi-tech/cosy-driver-postgres v0.2.1 h1:OICakGuT+omva6QOJCxTJ5Lfr7CG
 github.com/uozi-tech/cosy-driver-postgres v0.2.1/go.mod h1:eAy1A89yHbAEfjkhNAifaJQk172NqrNoRyRtFcZc9Go=
 github.com/uozi-tech/cosy-driver-sqlite v0.2.0 h1:eTpIMyGoFUK4JcaiKfJHD5AyiM6vtCwN98c7Bz5n25o=
 github.com/uozi-tech/cosy-driver-sqlite v0.2.0/go.mod h1:87a6mzn5IuEtIR4z7U4Ey8eKLGfNEOSkv7kPQlbNQgM=
+github.com/uozi-tech/cosy-driver-sqlite v0.2.1 h1:W+Z4pY25PSJCeReqroG7LIBeffsqotbpHzgqSMqZDIM=
+github.com/uozi-tech/cosy-driver-sqlite v0.2.1/go.mod h1:2ya7Z5P3HzFi1ktfL8gvwaAGx0DDV0bmWxNSNpaLlwo=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
 github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
 github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
@@ -1803,8 +1842,12 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/yandex-cloud/go-genproto v0.0.0-20250127124313-5be1a2cc06d4 h1:3N8k0k2YikzqQUUAFqPhbhiLEodQrRKIvlsUuJ09DYo=
 github.com/yandex-cloud/go-genproto v0.0.0-20250127124313-5be1a2cc06d4/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo=
+github.com/yandex-cloud/go-genproto v0.0.0-20250203115010-0bcba64c41f6 h1:CHYGew+KO1JaK5sx/N2ApgVCTGCKvfSl0sSPplTyCog=
+github.com/yandex-cloud/go-genproto v0.0.0-20250203115010-0bcba64c41f6/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo=
 github.com/yandex-cloud/go-sdk v0.0.0-20250127132311-016f84adc072 h1:s2wfllm5Z32Sl1TktCxHXMKJ9yQXpdFUuN5CFA/8qJY=
 github.com/yandex-cloud/go-sdk v0.0.0-20250127132311-016f84adc072/go.mod h1:/7UdvQNU5/ISIOPHcj0S4lUcp/KejW2LJQhZGt9tdMU=
+github.com/yandex-cloud/go-sdk v0.0.0-20250203123950-24786ecffd92 h1:UTcY1921ZXBABB5JpSWxccyZaCjMuSbniyifW6nmLZ4=
+github.com/yandex-cloud/go-sdk v0.0.0-20250203123950-24786ecffd92/go.mod h1:MQm5WxsYpQRdUklz2C8q3uDhuIaDzzV7seLErAILLBE=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1890,6 +1933,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
 go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
 golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
+golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -1990,6 +2035,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
 golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
+golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -2102,6 +2149,8 @@ golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw
 golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
 golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
 golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
+golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -2120,6 +2169,8 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
+golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -2244,6 +2295,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
 golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -2284,6 +2337,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -2294,6 +2349,8 @@ golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
 golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
+golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -2590,10 +2647,16 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
 google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
 google.golang.org/genproto v0.0.0-20250127172529-29210b9bc287 h1:WoUI1G0DQ648FKvSl756SKxHQR/bI+y4HyyIQfxMWI8=
 google.golang.org/genproto v0.0.0-20250127172529-29210b9bc287/go.mod h1:wkQ2Aj/xvshAUDtO/JHvu9y+AaN9cqs28QuSVSHtZSY=
+google.golang.org/genproto v0.0.0-20250204164813-702378808489 h1:nQcbCCOg2h2CQ0yA8SY3AHqriNKDvsetuq9mE/HFjtc=
+google.golang.org/genproto v0.0.0-20250204164813-702378808489/go.mod h1:wkQ2Aj/xvshAUDtO/JHvu9y+AaN9cqs28QuSVSHtZSY=
 google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 h1:A2ni10G3UlplFrWdCDJTl7D7mJ7GSRm37S+PDimaKRw=
 google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4=
+google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 h1:fCuMM4fowGzigT89NCIsW57Pk9k2D12MMi2ODn+Nk+o=
+google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489 h1:5bKytslY8ViY0Cj/ewmRtrWHW64bNF03cAatUUFCdFI=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=

+ 4 - 8
internal/analytic/node.go

@@ -2,6 +2,7 @@ package analytic
 
 import (
 	"encoding/json"
+	"errors"
 	"github.com/0xJacky/Nginx-UI/internal/transport"
 	"github.com/0xJacky/Nginx-UI/internal/upgrader"
 	"github.com/0xJacky/Nginx-UI/model"
@@ -69,15 +70,14 @@ func GetNode(env *model.Environment) (n *Node) {
 	return n
 }
 
-func InitNode(env *model.Environment) (n *Node) {
+func InitNode(env *model.Environment) (n *Node, err error) {
 	n = &Node{
 		Environment: env,
 	}
 
 	u, err := url.JoinPath(env.URL, "/api/node")
 	if err != nil {
-		logger.Error(err)
-		return
+		return 
 	}
 
 	t, err := transport.NewTransport()
@@ -90,7 +90,6 @@ func InitNode(env *model.Environment) (n *Node) {
 
 	req, err := http.NewRequest("GET", u, nil)
 	if err != nil {
-		logger.Error(err)
 		return
 	}
 
@@ -98,7 +97,6 @@ func InitNode(env *model.Environment) (n *Node) {
 
 	resp, err := client.Do(req)
 	if err != nil {
-		logger.Error(err)
 		return
 	}
 
@@ -106,13 +104,11 @@ func InitNode(env *model.Environment) (n *Node) {
 	bytes, _ := io.ReadAll(resp.Body)
 
 	if resp.StatusCode != http.StatusOK {
-		logger.Error(string(bytes))
-		return
+		return n, errors.New(string(bytes))
 	}
 
 	err = json.Unmarshal(bytes, &n.NodeInfo)
 	if err != nil {
-		logger.Error(err)
 		return
 	}
 

+ 80 - 57
internal/analytic/node_record.go

@@ -2,81 +2,112 @@ package analytic
 
 import (
 	"context"
-	"encoding/json"
+	"net/http"
+	"sync"
+	"time"
+
 	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/0xJacky/Nginx-UI/query"
 	"github.com/gorilla/websocket"
 	"github.com/uozi-tech/cosy/logger"
-	"net/http"
-	"time"
 )
 
-var stopNodeRecordChan = make(chan struct{})
+var (
+	ctx, cancel = context.WithCancel(context.Background())
+	wg          sync.WaitGroup
+	restartMu   sync.Mutex // Add mutex to prevent concurrent restarts
+)
 
 func RestartRetrieveNodesStatus() {
-	stopNodeRecordChan <- struct{}{}
-	time.Sleep(10 * time.Second)
-	go RetrieveNodesStatus()
+	restartMu.Lock() // Acquire lock before modifying shared resources
+	defer restartMu.Unlock()
+
+	// Cancel previous context to stop all operations
+	cancel()
+
+	// Wait for previous goroutines to finish
+	wg.Wait()
+
+	// Create new context for this run
+	ctx, cancel = context.WithCancel(context.Background())
+
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		RetrieveNodesStatus()
+	}()
 }
 
 func RetrieveNodesStatus() {
-	NodeMap = make(TNodeMap)
-	errChan := make(chan error)
-
-	ctx, cancel := context.WithCancel(context.Background())
+	logger.Info("RetrieveNodesStatus start")
+	defer logger.Info("RetrieveNodesStatus exited")
 
-	defer cancel()
+	mutex.Lock()
+	if NodeMap == nil {
+		NodeMap = make(TNodeMap)
+	}
+	mutex.Unlock()
 
 	env := query.Environment
-
 	envs, err := env.Where(env.Enabled.Is(true)).Find()
 	if err != nil {
 		logger.Error(err)
 		return
 	}
 
-	for _, v := range envs {
-		go nodeAnalyticLive(v, errChan, ctx)
+	var wg sync.WaitGroup
+	defer wg.Wait()
+
+	for _, env := range envs {
+		wg.Add(1)
+		go func(e *model.Environment) {
+			defer wg.Done()
+			retryTicker := time.NewTicker(5 * time.Second)
+			defer retryTicker.Stop()
+
+			for {
+				select {
+				case <-ctx.Done():
+					return
+				default:
+					if err := nodeAnalyticRecord(e, ctx); err != nil {
+						logger.Error(err)
+						if NodeMap[env.ID] != nil {
+							mutex.Lock()
+							NodeMap[env.ID].Status = false
+							mutex.Unlock()
+						}
+						select {
+						case <-retryTicker.C:
+						case <-ctx.Done():
+							return
+						}
+					}
+				}
+			}
+		}(env)
 	}
 
-	for {
-		select {
-		case err = <-errChan:
-			logger.Error(err)
-		case <-stopNodeRecordChan:
-			logger.Info("RetrieveNodesStatus exited normally")
-			return // will execute defer cancel()
-		}
-	}
+	<-ctx.Done()
 }
 
-func nodeAnalyticLive(env *model.Environment, errChan chan error, ctx context.Context) {
-	for {
-		err := nodeAnalyticRecord(env, ctx)
+func nodeAnalyticRecord(env *model.Environment, ctx context.Context) error {
+	scopeCtx, cancel := context.WithCancel(ctx)
+	defer cancel()
 
-		if err != nil {
-			// set node offline
-			if NodeMap[env.ID] != nil {
-				mutex.Lock()
-				NodeMap[env.ID].Status = false
-				mutex.Unlock()
-			}
-			logger.Error(err)
-			errChan <- err
-			// wait 5s then reconnect
-			time.Sleep(5 * time.Second)
-		}
-	}
-}
+	node, err := InitNode(env)
 
-func nodeAnalyticRecord(env *model.Environment, ctx context.Context) (err error) {
 	mutex.Lock()
-	NodeMap[env.ID] = InitNode(env)
+	NodeMap[env.ID] = node
 	mutex.Unlock()
 
+	if err != nil {
+		return err
+	}
+
 	u, err := env.GetWebSocketURL("/api/analytic/intro")
 	if err != nil {
-		return
+		return err
 	}
 
 	header := http.Header{}
@@ -90,28 +121,20 @@ func nodeAnalyticRecord(env *model.Environment, ctx context.Context) (err error)
 
 	c, _, err := dial.Dial(u, header)
 	if err != nil {
-		return
+		return err
 	}
 
 	defer c.Close()
 
-	var nodeStat NodeStat
-
 	go func() {
-		// shutdown
-		<-ctx.Done()
+		<-scopeCtx.Done()
 		_ = c.Close()
 	}()
 
-	for {
-		_, message, err := c.ReadMessage()
-		if err != nil || websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNoStatusReceived,
-			websocket.CloseNormalClosure) {
-			return err
-		}
-
-		err = json.Unmarshal(message, &nodeStat)
+	var nodeStat NodeStat
 
+	for {
+		err = c.ReadJSON(&nodeStat)
 		if err != nil {
 			return err
 		}

+ 0 - 3
internal/analytic/node_stat.go

@@ -26,21 +26,18 @@ func GetNodeStat() (data NodeStat) {
 	cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
 
 	loadAvg, err := load.Avg()
-
 	if err != nil {
 		logger.Error(err)
 		return
 	}
 
 	diskStat, err := GetDiskStat()
-
 	if err != nil {
 		logger.Error(err)
 		return
 	}
 
 	netIO, err := net.IOCounters(false)
-
 	if err != nil {
 		logger.Error(err)
 		return

+ 4 - 7
internal/helper/websocket_error.go

@@ -1,24 +1,21 @@
 package helper
 
 import (
-	"strings"
-	"github.com/gorilla/websocket"
 	"errors"
+	"github.com/gorilla/websocket"
+	"strings"
 	"syscall"
 )
 
+// IsUnexpectedWebsocketError checks if the error is an unexpected websocket error
 func IsUnexpectedWebsocketError(err error) bool {
-	// nil error is an expected error
-	if err == nil {
-		return false
-	}
 	// ignore: write: broken pipe
 	if errors.Is(err, syscall.EPIPE) {
 		return false
 	}
 	// client closed error: *net.OpErr
 	if strings.Contains(err.Error(), "An existing connection was forcibly closed by the remote host") {
-		return true
+		return false
 	}
 
 	return websocket.IsUnexpectedCloseError(err,

+ 37 - 13
internal/middleware/middleware.go

@@ -12,6 +12,34 @@ import (
 	"github.com/uozi-tech/cosy/logger"
 )
 
+// getToken from header, cookie or query
+func getToken(c *gin.Context) (token string) {
+	if token = c.GetHeader("Authorization"); token != "" {
+		return
+	}
+
+	if token, _ = c.Cookie("token"); token != "" {
+		return token
+	}
+
+	if token = c.Query("token"); token != "" {
+		tokenBytes, _ := base64.StdEncoding.DecodeString(token)
+		return string(tokenBytes)
+	}
+
+	return ""
+}
+
+// getXNodeID from header or query
+func getXNodeID(c *gin.Context) (xNodeID string) {
+	if xNodeID = c.GetHeader("X-Node-ID"); xNodeID != "" {
+		return xNodeID
+	}
+
+	return c.Query("x_node_id")
+}
+
+// AuthRequired is a middleware that checks if the user is authenticated
 func AuthRequired() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		abortWithAuthFailure := func() {
@@ -20,20 +48,20 @@ func AuthRequired() gin.HandlerFunc {
 			})
 		}
 
-		token := c.GetHeader("Authorization")
+		xNodeID := getXNodeID(c)
+		if xNodeID != "" {
+			c.Set("ProxyNodeID", xNodeID)
+		}
+
+		token := getToken(c)
 		if token == "" {
 			if token = c.GetHeader("X-Node-Secret"); token != "" && token == settings.NodeSettings.Secret {
 				c.Set("Secret", token)
 				c.Next()
 				return
 			} else {
-				c.Set("ProxyNodeID", c.Query("x_node_id"))
-				tokenBytes, _ := base64.StdEncoding.DecodeString(c.Query("token"))
-				token = string(tokenBytes)
-				if token == "" {
-					abortWithAuthFailure()
-					return
-				}
+				abortWithAuthFailure()
+				return
 			}
 		}
 
@@ -44,11 +72,6 @@ func AuthRequired() gin.HandlerFunc {
 		}
 
 		c.Set("user", u)
-
-		if nodeID := c.GetHeader("X-Node-ID"); nodeID != "" {
-			c.Set("ProxyNodeID", nodeID)
-		}
-
 		c.Next()
 	}
 }
@@ -70,6 +93,7 @@ func (f ServerFileSystemType) Exists(prefix string, _path string) bool {
 	return err == nil
 }
 
+// CacheJs is a middleware that send header to client to cache js file
 func CacheJs() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		if strings.Contains(c.Request.URL.String(), "js") {

+ 9 - 5
internal/pty/pipeline.go

@@ -2,6 +2,7 @@ package pty
 
 import (
 	"encoding/json"
+	"github.com/0xJacky/Nginx-UI/internal/helper"
 	"github.com/0xJacky/Nginx-UI/settings"
 	"github.com/creack/pty"
 	"github.com/gorilla/websocket"
@@ -46,9 +47,10 @@ func NewPipeLine(conn *websocket.Conn) (p *Pipeline, err error) {
 func (p *Pipeline) ReadWsAndWritePty(errorChan chan error) {
 	for {
 		msgType, payload, err := p.ws.ReadMessage()
-		if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNoStatusReceived,
-			websocket.CloseNormalClosure) {
-			errorChan <- errors.Wrap(err, "Error ReadWsAndWritePty unexpected close")
+		if err != nil {
+			if helper.IsUnexpectedWebsocketError(err) {
+				errorChan <- errors.Wrap(err, "Error ReadWsAndWritePty unexpected close")
+			}
 			return
 		}
 		if msgType != websocket.TextMessage {
@@ -117,8 +119,10 @@ func (p *Pipeline) ReadPtyAndWriteWs(errorChan chan error) {
 		}
 		processedOutput := validString(string(buf[:n]))
 		err = p.ws.WriteMessage(websocket.TextMessage, []byte(processedOutput))
-		if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {
-			errorChan <- errors.Wrap(err, "Error ReadPtyAndWriteWs websocket write")
+		if err != nil {
+			if helper.IsUnexpectedWebsocketError(err) {
+				errorChan <- errors.Wrap(err, "Error ReadPtyAndWriteWs websocket write")
+			}
 			return
 		}
 	}

+ 1 - 1
model/model.go

@@ -12,7 +12,7 @@ type Model struct {
 	ID        uint64          `gorm:"primary_key" json:"id"`
 	CreatedAt time.Time       `json:"created_at"`
 	UpdatedAt time.Time       `json:"updated_at"`
-	DeletedAt *gorm.DeletedAt `gorm:"index" json:"deleted_at"`
+	DeletedAt *gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
 }
 
 func GenerateAllModel() []any {

+ 6 - 0
router/routers.go

@@ -3,6 +3,8 @@ package router
 import (
 	"net/http"
 
+	"github.com/gin-contrib/pprof"
+
 	"github.com/0xJacky/Nginx-UI/api/analytic"
 	"github.com/0xJacky/Nginx-UI/api/certificate"
 	"github.com/0xJacky/Nginx-UI/api/cluster"
@@ -23,6 +25,7 @@ import (
 	"github.com/0xJacky/Nginx-UI/internal/middleware"
 	"github.com/gin-gonic/gin"
 	"github.com/uozi-tech/cosy"
+	cSettings "github.com/uozi-tech/cosy/settings"
 )
 
 func InitRouter() {
@@ -45,6 +48,9 @@ func InitRouter() {
 		// Authorization required and not websocket request
 		g := root.Group("/", middleware.AuthRequired(), middleware.Proxy())
 		{
+			if cSettings.ServerSettings.RunMode == gin.DebugMode {
+				pprof.Register(g)
+			}
 			user.InitUserRouter(g)
 			analytic.InitRouter(g)
 			user.InitManageUserRouter(g)