瀏覽代碼

Merge pull request #826 from 0xJacky/feat/self-check

Self Check
Jacky 3 月之前
父節點
當前提交
987a7397af
共有 42 個文件被更改,包括 1551 次插入296 次删除
  1. 1 0
      README-es.md
  2. 1 0
      README-vi_VN.md
  3. 1 0
      README-zh_CN.md
  4. 1 0
      README-zh_TW.md
  5. 1 0
      README.md
  6. 3 0
      api/system/router.go
  7. 41 0
      api/system/self_check.go
  8. 0 1
      app/package.json
  9. 0 226
      app/pnpm-lock.yaml
  10. 22 0
      app/src/api/self_check.ts
  11. 65 6
      app/src/language/ar/app.po
  12. 64 6
      app/src/language/en/app.po
  13. 65 6
      app/src/language/es/app.po
  14. 64 6
      app/src/language/fr_FR/app.po
  15. 65 6
      app/src/language/ko_KR/app.po
  16. 55 5
      app/src/language/messages.pot
  17. 65 6
      app/src/language/ru_RU/app.po
  18. 65 6
      app/src/language/tr_TR/app.po
  19. 64 6
      app/src/language/vi_VN/app.po
  20. 65 6
      app/src/language/zh_CN/app.po
  21. 65 6
      app/src/language/zh_TW/app.po
  22. 59 1
      app/src/lib/http/index.ts
  23. 7 0
      app/src/routes/index.ts
  24. 99 0
      app/src/views/system/SelfCheck/SelfCheck.vue
  25. 20 0
      app/src/views/system/SelfCheck/tasks.ts
  26. 1 0
      docs/guide/about.md
  27. 1 0
      docs/zh_CN/guide/about.md
  28. 1 0
      docs/zh_TW/guide/about.md
  29. 20 3
      internal/nginx/config_args.go
  30. 67 0
      internal/self_check/directory.go
  31. 19 0
      internal/self_check/errors.go
  32. 168 0
      internal/self_check/nginx_conf.go
  33. 123 0
      internal/self_check/nginx_conf_test.go
  34. 74 0
      internal/self_check/self_check.go
  35. 1 0
      internal/self_check/test_cases/5001.conf
  36. 14 0
      internal/self_check/test_cases/no-http-block-fixed.conf
  37. 10 0
      internal/self_check/test_cases/no-http-block.conf
  38. 23 0
      internal/self_check/test_cases/no-http-sites-enabled-fixed.conf
  39. 34 0
      internal/self_check/test_cases/no-http-sites-enabled.conf
  40. 35 0
      internal/self_check/test_cases/ok.conf
  41. 1 0
      internal/self_check/websocket.go
  42. 1 0
      settings/nginx.go

+ 1 - 0
README-es.md

@@ -109,6 +109,7 @@ Aceptamos traducciones a cualquier idioma.
 - [vue3-gettext](https://github.com/jshmrtn/vue3-gettext)
 - [vue3-ace-editor](https://github.com/CarterLi/vue3-ace-editor)
 - [Gonginx](https://github.com/tufanbarisyildirim/gonginx)
+- [lego](https://github.com/go-acme/lego)
 
 ## Cómo empezar
 

+ 1 - 0
README-vi_VN.md

@@ -121,6 +121,7 @@ Chúng tôi hoan nghênh bản dịch sang bất kỳ ngôn ngữ nào.
 - [vue3-gettext](https://github.com/jshmrtn/vue3-gettext)
 - [vue3-ace-editor](https://github.com/CarterLi/vue3-ace-editor)
 - [Gonginx](https://github.com/tufanbarisyildirim/gonginx)
+- [lego](https://github.com/go-acme/lego)
 
 ## Bắt đầu
 

+ 1 - 0
README-zh_CN.md

@@ -102,6 +102,7 @@ Nginx 网络管理界面,由  [0xJacky](https://jackyu.cn/) 与 [Hintay](https
 - [vue3-gettext](https://github.com/jshmrtn/vue3-gettext)
 - [vue3-ace-editor](https://github.com/CarterLi/vue3-ace-editor)
 - [Gonginx](https://github.com/tufanbarisyildirim/gonginx)
+- [lego](https://github.com/go-acme/lego)
 
 ## 入门指南
 

+ 1 - 0
README-zh_TW.md

@@ -105,6 +105,7 @@ Nginx 網路管理介面,由 [0xJacky](https://jackyu.cn/) 與 [Hintay](https:
 - [vue3-gettext](https://github.com/jshmrtn/vue3-gettext)
 - [vue3-ace-editor](https://github.com/CarterLi/vue3-ace-editor)
 - [Gonginx](https://github.com/tufanbarisyildirim/gonginx)
+- [lego](https://github.com/go-acme/lego)
 
 ## 入門指南
 

+ 1 - 0
README.md

@@ -120,6 +120,7 @@ We welcome translations into any language.
 - [vue3-gettext](https://github.com/jshmrtn/vue3-gettext)
 - [vue3-ace-editor](https://github.com/CarterLi/vue3-ace-editor)
 - [Gonginx](https://github.com/tufanbarisyildirim/gonginx)
+- [lego](https://github.com/go-acme/lego)
 
 ## Getting Started
 

+ 3 - 0
api/system/router.go

@@ -13,6 +13,9 @@ func InitPublicRouter(r *gin.RouterGroup) {
 func InitPrivateRouter(r *gin.RouterGroup) {
 	r.GET("upgrade/release", GetRelease)
 	r.GET("upgrade/current", GetCurrentVersion)
+	r.GET("self_check", SelfCheck)
+	r.POST("self_check/:name/fix", SelfCheckFix)
+	r.GET("self_check/websocket", CheckWebSocket)
 }
 
 func InitWebSocketRouter(r *gin.RouterGroup) {

+ 41 - 0
api/system/self_check.go

@@ -0,0 +1,41 @@
+package system
+
+import (
+	"github.com/gorilla/websocket"
+	"github.com/uozi-tech/cosy/logger"
+	"net/http"
+
+	"github.com/0xJacky/Nginx-UI/internal/self_check"
+	"github.com/gin-gonic/gin"
+)
+
+func SelfCheck(c *gin.Context) {
+	report := self_check.Run()
+	c.JSON(http.StatusOK, report)
+}
+
+func SelfCheckFix(c *gin.Context) {
+	result := self_check.AttemptFix(c.Param("name"))
+	c.JSON(http.StatusOK, result)
+}
+
+func CheckWebSocket(c *gin.Context) {
+	var upgrader = websocket.Upgrader{
+		CheckOrigin: func(r *http.Request) bool {
+			return true
+		},
+	}
+	ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
+	if err != nil {
+		logger.Error(err)
+		return
+	}
+	defer ws.Close()
+	err = ws.WriteJSON(gin.H{
+		"message": "ok",
+	})
+	if err != nil {
+		logger.Error(err)
+		return
+	}
+}

+ 0 - 1
app/package.json

@@ -72,7 +72,6 @@
     "eslint-plugin-sonarjs": "^3.0.1",
     "less": "^4.2.1",
     "postcss": "^8.5.1",
-    "tailwindcss": "^3.4.17",
     "typescript": "5.7.3",
     "unplugin-auto-import": "^19.0.0",
     "unplugin-vue-components": "^28.0.0",

+ 0 - 226
app/pnpm-lock.yaml

@@ -180,9 +180,6 @@ importers:
       postcss:
         specifier: ^8.5.1
         version: 8.5.1
-      tailwindcss:
-        specifier: ^3.4.17
-        version: 3.4.17
       typescript:
         specifier: 5.7.3
         version: 5.7.3
@@ -210,10 +207,6 @@ packages:
   '@0xjacky/vue-github-button@3.1.1':
     resolution: {integrity: sha512-fD9IWHGe0V6+7SR/5rbL/mrq+B6u3tDktZrAFYfKNgS9JUM/OBQAvKG+i8e39iq5invDhzHyLHAkzff9rT0j5g==}
 
-  '@alloc/quick-lru@5.2.0':
-    resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
-    engines: {node: '>=10'}
-
   '@ampproject/remapping@2.3.0':
     resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
     engines: {node: '>=6.0.0'}
@@ -1892,9 +1885,6 @@ packages:
     peerDependencies:
       vue: '>=3.2.0'
 
-  any-promise@1.3.0:
-    resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
-
   anymatch@3.1.3:
     resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
     engines: {node: '>= 8'}
@@ -1906,9 +1896,6 @@ packages:
     resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==}
     engines: {node: '>=14'}
 
-  arg@5.0.2:
-    resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
-
   argparse@2.0.1:
     resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
 
@@ -2051,10 +2038,6 @@ packages:
     resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
     engines: {node: '>=6'}
 
-  camelcase-css@2.0.1:
-    resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
-    engines: {node: '>= 6'}
-
   caniuse-lite@1.0.30001688:
     resolution: {integrity: sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==}
 
@@ -2123,10 +2106,6 @@ packages:
     resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==}
     engines: {node: '>=4.0.0'}
 
-  commander@4.1.1:
-    resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
-    engines: {node: '>= 6'}
-
   commander@7.2.0:
     resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
     engines: {node: '>= 10'}
@@ -2296,15 +2275,9 @@ packages:
   devlop@1.1.0:
     resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
 
-  didyoumean@1.2.2:
-    resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
-
   diff3@0.0.3:
     resolution: {integrity: sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==}
 
-  dlv@1.1.3:
-    resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
-
   doctrine@3.0.0:
     resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
     engines: {node: '>=6.0.0'}
@@ -2654,10 +2627,6 @@ packages:
   fast-deep-equal@3.1.3:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
 
-  fast-glob@3.3.2:
-    resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
-    engines: {node: '>=8.6.0'}
-
   fast-glob@3.3.3:
     resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
     engines: {node: '>=8.6.0'}
@@ -3094,10 +3063,6 @@ packages:
   jackspeak@3.4.3:
     resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
 
-  jiti@1.21.6:
-    resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
-    hasBin: true
-
   jiti@2.4.1:
     resolution: {integrity: sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g==}
     hasBin: true
@@ -3177,10 +3142,6 @@ packages:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
 
-  lilconfig@3.1.3:
-    resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
-    engines: {node: '>=14'}
-
   lines-and-columns@1.2.4:
     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
 
@@ -3480,9 +3441,6 @@ packages:
   muggle-string@0.4.1:
     resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
 
-  mz@2.7.0:
-    resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
-
   nanoid@3.3.8:
     resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -3539,14 +3497,6 @@ packages:
     engines: {node: ^14.16.0 || >=16.10.0}
     hasBin: true
 
-  object-assign@4.1.1:
-    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
-    engines: {node: '>=0.10.0'}
-
-  object-hash@3.0.0:
-    resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
-    engines: {node: '>= 6'}
-
   object-inspect@1.13.3:
     resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==}
     engines: {node: '>= 0.4'}
@@ -3700,10 +3650,6 @@ packages:
     resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
     engines: {node: '>=12'}
 
-  pify@2.3.0:
-    resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
-    engines: {node: '>=0.10.0'}
-
   pify@4.0.1:
     resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
     engines: {node: '>=6'}
@@ -3728,10 +3674,6 @@ packages:
       typescript:
         optional: true
 
-  pirates@4.0.6:
-    resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
-    engines: {node: '>= 6'}
-
   pkg-types@1.2.1:
     resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==}
 
@@ -3752,36 +3694,6 @@ packages:
     resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
     engines: {node: '>= 0.4'}
 
-  postcss-import@15.1.0:
-    resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
-    engines: {node: '>=14.0.0'}
-    peerDependencies:
-      postcss: ^8.0.0
-
-  postcss-js@4.0.1:
-    resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
-    engines: {node: ^12 || ^14 || >= 16}
-    peerDependencies:
-      postcss: ^8.4.21
-
-  postcss-load-config@4.0.2:
-    resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
-    engines: {node: '>= 14'}
-    peerDependencies:
-      postcss: '>=8.0.9'
-      ts-node: '>=9.0.0'
-    peerDependenciesMeta:
-      postcss:
-        optional: true
-      ts-node:
-        optional: true
-
-  postcss-nested@6.2.0:
-    resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
-    engines: {node: '>=12.0'}
-    peerDependencies:
-      postcss: ^8.2.14
-
   postcss-selector-parser@6.1.2:
     resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
     engines: {node: '>=4'}
@@ -3816,9 +3728,6 @@ packages:
   rc9@2.1.2:
     resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
 
-  read-cache@1.0.0:
-    resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
-
   read-pkg-up@7.0.1:
     resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
     engines: {node: '>=8'}
@@ -4110,11 +4019,6 @@ packages:
   stylis@4.3.4:
     resolution: {integrity: sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==}
 
-  sucrase@3.35.0:
-    resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
-    engines: {node: '>=16 || 14 >=14.17'}
-    hasBin: true
-
   supports-color@7.2.0:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
     engines: {node: '>=8'}
@@ -4139,11 +4043,6 @@ packages:
     resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==}
     engines: {node: ^14.18.0 || >=16.0.0}
 
-  tailwindcss@3.4.17:
-    resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
-    engines: {node: '>=14.0.0'}
-    hasBin: true
-
   tapable@2.2.1:
     resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
     engines: {node: '>=6'}
@@ -4152,13 +4051,6 @@ packages:
     resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
     engines: {node: '>=10'}
 
-  thenify-all@1.6.0:
-    resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
-    engines: {node: '>=0.8'}
-
-  thenify@3.3.1:
-    resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
-
   throttle-debounce@5.0.2:
     resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
     engines: {node: '>=12.22'}
@@ -4191,9 +4083,6 @@ packages:
     peerDependencies:
       typescript: '>=4.8.4'
 
-  ts-interface-checker@0.1.13:
-    resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
-
   tslib@2.8.1:
     resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
 
@@ -4601,8 +4490,6 @@ snapshots:
     dependencies:
       github-buttons: 2.29.1
 
-  '@alloc/quick-lru@5.2.0': {}
-
   '@ampproject/remapping@2.3.0':
     dependencies:
       '@jridgewell/gen-mapping': 0.3.8
@@ -6474,8 +6361,6 @@ snapshots:
       vue-types: 3.0.2(vue@3.5.13(typescript@5.7.3))
       warning: 4.0.3
 
-  any-promise@1.3.0: {}
-
   anymatch@3.1.3:
     dependencies:
       normalize-path: 3.0.0
@@ -6492,8 +6377,6 @@ snapshots:
 
   are-docs-informative@0.0.2: {}
 
-  arg@5.0.2: {}
-
   argparse@2.0.1: {}
 
   array-back@3.1.0: {}
@@ -6667,8 +6550,6 @@ snapshots:
 
   callsites@3.1.0: {}
 
-  camelcase-css@2.0.1: {}
-
   caniuse-lite@1.0.30001688: {}
 
   ccount@2.0.1: {}
@@ -6758,8 +6639,6 @@ snapshots:
       lodash.camelcase: 4.3.0
       typical: 4.0.0
 
-  commander@4.1.1: {}
-
   commander@7.2.0: {}
 
   comment-parser@1.4.1: {}
@@ -6908,12 +6787,8 @@ snapshots:
     dependencies:
       dequal: 2.0.3
 
-  didyoumean@1.2.2: {}
-
   diff3@0.0.3: {}
 
-  dlv@1.1.3: {}
-
   doctrine@3.0.0:
     dependencies:
       esutils: 2.0.3
@@ -7473,14 +7348,6 @@ snapshots:
 
   fast-deep-equal@3.1.3: {}
 
-  fast-glob@3.3.2:
-    dependencies:
-      '@nodelib/fs.stat': 2.0.5
-      '@nodelib/fs.walk': 1.2.8
-      glob-parent: 5.1.2
-      merge2: 1.4.1
-      micromatch: 4.0.8
-
   fast-glob@3.3.3:
     dependencies:
       '@nodelib/fs.stat': 2.0.5
@@ -7924,8 +7791,6 @@ snapshots:
     optionalDependencies:
       '@pkgjs/parseargs': 0.11.0
 
-  jiti@1.21.6: {}
-
   jiti@2.4.1: {}
 
   js-tokens@4.0.0: {}
@@ -7997,8 +7862,6 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
-  lilconfig@3.1.3: {}
-
   lines-and-columns@1.2.4: {}
 
   load-tsconfig@0.2.5: {}
@@ -8440,12 +8303,6 @@ snapshots:
 
   muggle-string@0.4.1: {}
 
-  mz@2.7.0:
-    dependencies:
-      any-promise: 1.3.0
-      object-assign: 4.1.1
-      thenify-all: 1.6.0
-
   nanoid@3.3.8: {}
 
   nanopop@2.4.2: {}
@@ -8496,10 +8353,6 @@ snapshots:
       pkg-types: 1.3.0
       ufo: 1.5.4
 
-  object-assign@4.1.1: {}
-
-  object-hash@3.0.0: {}
-
   object-inspect@1.13.3: {}
 
   object-keys@1.1.1: {}
@@ -8640,8 +8493,6 @@ snapshots:
 
   picomatch@4.0.2: {}
 
-  pify@2.3.0: {}
-
   pify@4.0.1: {}
 
   pinia-plugin-persistedstate@4.2.0(pinia@2.3.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)))(rollup@4.28.1):
@@ -8667,8 +8518,6 @@ snapshots:
     transitivePeerDependencies:
       - '@vue/composition-api'
 
-  pirates@4.0.6: {}
-
   pkg-types@1.2.1:
     dependencies:
       confbox: 0.1.8
@@ -8689,30 +8538,6 @@ snapshots:
 
   possible-typed-array-names@1.0.0: {}
 
-  postcss-import@15.1.0(postcss@8.5.1):
-    dependencies:
-      postcss: 8.5.1
-      postcss-value-parser: 4.2.0
-      read-cache: 1.0.0
-      resolve: 1.22.9
-
-  postcss-js@4.0.1(postcss@8.5.1):
-    dependencies:
-      camelcase-css: 2.0.1
-      postcss: 8.5.1
-
-  postcss-load-config@4.0.2(postcss@8.5.1):
-    dependencies:
-      lilconfig: 3.1.3
-      yaml: 2.6.1
-    optionalDependencies:
-      postcss: 8.5.1
-
-  postcss-nested@6.2.0(postcss@8.5.1):
-    dependencies:
-      postcss: 8.5.1
-      postcss-selector-parser: 6.1.2
-
   postcss-selector-parser@6.1.2:
     dependencies:
       cssesc: 3.0.0
@@ -8747,10 +8572,6 @@ snapshots:
       defu: 6.1.4
       destr: 2.0.3
 
-  read-cache@1.0.0:
-    dependencies:
-      pify: 2.3.0
-
   read-pkg-up@7.0.1:
     dependencies:
       find-up: 4.1.0
@@ -9096,16 +8917,6 @@ snapshots:
 
   stylis@4.3.4: {}
 
-  sucrase@3.35.0:
-    dependencies:
-      '@jridgewell/gen-mapping': 0.3.8
-      commander: 4.1.1
-      glob: 10.4.5
-      lines-and-columns: 1.2.4
-      mz: 2.7.0
-      pirates: 4.0.6
-      ts-interface-checker: 0.1.13
-
   supports-color@7.2.0:
     dependencies:
       has-flag: 4.0.0
@@ -9133,33 +8944,6 @@ snapshots:
       '@pkgr/core': 0.1.1
       tslib: 2.8.1
 
-  tailwindcss@3.4.17:
-    dependencies:
-      '@alloc/quick-lru': 5.2.0
-      arg: 5.0.2
-      chokidar: 3.6.0
-      didyoumean: 1.2.2
-      dlv: 1.1.3
-      fast-glob: 3.3.2
-      glob-parent: 6.0.2
-      is-glob: 4.0.3
-      jiti: 1.21.6
-      lilconfig: 3.1.3
-      micromatch: 4.0.8
-      normalize-path: 3.0.0
-      object-hash: 3.0.0
-      picocolors: 1.1.1
-      postcss: 8.5.1
-      postcss-import: 15.1.0(postcss@8.5.1)
-      postcss-js: 4.0.1(postcss@8.5.1)
-      postcss-load-config: 4.0.2(postcss@8.5.1)
-      postcss-nested: 6.2.0(postcss@8.5.1)
-      postcss-selector-parser: 6.1.2
-      resolve: 1.22.9
-      sucrase: 3.35.0
-    transitivePeerDependencies:
-      - ts-node
-
   tapable@2.2.1: {}
 
   tar@6.2.1:
@@ -9171,14 +8955,6 @@ snapshots:
       mkdirp: 1.0.4
       yallist: 4.0.0
 
-  thenify-all@1.6.0:
-    dependencies:
-      thenify: 3.3.1
-
-  thenify@3.3.1:
-    dependencies:
-      any-promise: 1.3.0
-
   throttle-debounce@5.0.2: {}
 
   tinyexec@0.3.1: {}
@@ -9204,8 +8980,6 @@ snapshots:
     dependencies:
       typescript: 5.7.3
 
-  ts-interface-checker@0.1.13: {}
-
   tslib@2.8.1: {}
 
   tsx@4.19.2:

+ 22 - 0
app/src/api/self_check.ts

@@ -0,0 +1,22 @@
+import type { CosyError } from '@/lib/http'
+import http from '@/lib/http'
+import ws from '@/lib/websocket'
+
+export interface Report {
+  name: string
+  err?: CosyError
+}
+
+const selfCheck = {
+  run(): Promise<Report[]> {
+    return http.get('/self_check')
+  },
+  fix(taskName: string) {
+    return http.post(`/self_check/${taskName}/fix`)
+  },
+  websocket() {
+    return ws('/api/self_check/websocket', false)
+  },
+}
+
+export default selfCheck

+ 65 - 6
app/src/language/ar/app.po

@@ -21,7 +21,7 @@ msgstr "المصادقة الثنائية"
 msgid "2FA Settings"
 msgstr "إعدادات المصادقة الثنائية"
 
-#: src/routes/index.ts:283
+#: src/routes/index.ts:290
 msgid "About"
 msgstr "عن"
 
@@ -199,6 +199,11 @@ msgstr "اطلب المساعدة من ChatGPT"
 msgid "Assistant"
 msgstr "المساعد"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:54
+#, fuzzy
+msgid "Attempt to fix"
+msgstr "محاولات"
+
 #: src/views/preference/AuthSettings.vue:17
 msgid "Attempts"
 msgstr "محاولات"
@@ -389,6 +394,26 @@ msgstr "قناة"
 msgid "Check again"
 msgstr "تحقق مرة أخرى"
 
+#: src/views/system/SelfCheck/tasks.ts:12
+msgid "Check if the nginx.conf includes the sites-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:16
+msgid "Check if the nginx.conf includes the streams-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:4
+msgid ""
+"Check if the sites-available and sites-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:8
+msgid ""
+"Check if the streams-available and streams-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
 #: src/language/constants.ts:13
 msgid "Cleaning environment variables"
 msgstr "تنظيف متغيرات البيئة"
@@ -1132,7 +1157,7 @@ msgstr "أدخل الرمز من التطبيق:"
 msgid "Input the recovery code:"
 msgstr "أدخل رمز الاسترداد:"
 
-#: src/routes/index.ts:305 src/views/other/Install.vue:136
+#: src/routes/index.ts:312 src/views/other/Install.vue:136
 msgid "Install"
 msgstr "تثبيت"
 
@@ -1269,7 +1294,7 @@ msgstr "أماكن"
 msgid "Log"
 msgstr "سجل"
 
-#: src/routes/index.ts:311 src/views/other/Login.vue:246
+#: src/routes/index.ts:318 src/views/other/Login.vue:246
 msgid "Login"
 msgstr "تسجيل الدخول"
 
@@ -1434,6 +1459,14 @@ msgstr "Nginx"
 msgid "Nginx Access Log Path"
 msgstr "مسار سجل الوصول لـ Nginx"
 
+#: src/views/system/SelfCheck/tasks.ts:11
+msgid "Nginx Conf Include Sites Enabled"
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:15
+msgid "Nginx Conf Include Streams Enabled"
+msgstr ""
+
 #: src/views/site/site_edit/SiteEdit.vue:223
 #: src/views/stream/StreamEdit.vue:207
 msgid "Nginx Configuration Parse Error"
@@ -1512,7 +1545,7 @@ msgstr "سر العقدة"
 msgid "Not After"
 msgstr "ليس بعد"
 
-#: src/routes/index.ts:317
+#: src/routes/index.ts:324
 msgid "Not Found"
 msgstr "غير موجود"
 
@@ -1813,6 +1846,10 @@ msgstr "يقرأ"
 msgid "Receive"
 msgstr "يستقبل"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:47
+msgid "Recheck"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:43
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
 msgid "Recover"
@@ -2102,6 +2139,10 @@ msgstr "تم نسخ السر"
 msgid "Selector"
 msgstr "المحدد"
 
+#: src/routes/index.ts:283 src/views/system/SelfCheck/SelfCheck.vue:42
+msgid "Self Check"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:348
 #: src/views/dashboard/ServerAnalytic.vue:35
 msgid "Send"
@@ -2114,7 +2155,7 @@ msgstr "إرسال"
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:53
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:70
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:194
-#: src/views/config/components/Mkdir.vue:38
+#: src/lib/http/index.ts:128 src/views/config/components/Mkdir.vue:38
 #: src/views/config/components/Rename.vue:45
 #: src/views/config/ConfigEditor.vue:111
 #: src/views/environment/BatchUpgrader.vue:59
@@ -2184,6 +2225,11 @@ msgstr "فئات الموقع"
 msgid "Site Logs"
 msgstr "سجلات الموقع"
 
+#: src/views/system/SelfCheck/tasks.ts:3
+#, fuzzy
+msgid "Sites Directory"
+msgstr "مجلد"
+
 #: src/routes/index.ts:56
 msgid "Sites List"
 msgstr "قائمة المواقع"
@@ -2230,10 +2276,23 @@ msgstr "متوقف"
 msgid "Storage"
 msgstr "تخزين"
 
+#: src/views/system/SelfCheck/tasks.ts:7
+#, fuzzy
+msgid "Streams Directory"
+msgstr "مجلد"
+
 #: src/constants/index.ts:19 src/views/notification/notificationColumns.tsx:36
 msgid "Success"
 msgstr "نجاح"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:78
+msgid ""
+"Support communication with the backend through the WebSocket protocol. If "
+"your Nginx UI is being used via an Nginx reverse proxy, please refer to this "
+"link to write the corresponding configuration file: https://nginxui.com/"
+"guide/nginx-proxy-example.html"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:240
 #: src/views/dashboard/ServerAnalytic.vue:241
 msgid "Swap"
@@ -2590,7 +2649,7 @@ msgstr "محدث في"
 msgid "Updated successfully"
 msgstr "تم التحديث بنجاح"
 
-#: src/routes/index.ts:290 src/views/environment/Environment.vue:55
+#: src/routes/index.ts:297 src/views/environment/Environment.vue:55
 #: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "ترقية"

+ 64 - 6
app/src/language/en/app.po

@@ -17,7 +17,7 @@ msgstr ""
 msgid "2FA Settings"
 msgstr ""
 
-#: src/routes/index.ts:283
+#: src/routes/index.ts:290
 msgid "About"
 msgstr "About"
 
@@ -209,6 +209,10 @@ msgstr ""
 msgid "Assistant"
 msgstr ""
 
+#: src/views/system/SelfCheck/SelfCheck.vue:54
+msgid "Attempt to fix"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:17
 msgid "Attempts"
 msgstr ""
@@ -401,6 +405,26 @@ msgstr ""
 msgid "Check again"
 msgstr ""
 
+#: src/views/system/SelfCheck/tasks.ts:12
+msgid "Check if the nginx.conf includes the sites-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:16
+msgid "Check if the nginx.conf includes the streams-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:4
+msgid ""
+"Check if the sites-available and sites-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:8
+msgid ""
+"Check if the streams-available and streams-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
 #: src/language/constants.ts:13
 msgid "Cleaning environment variables"
 msgstr ""
@@ -1175,7 +1199,7 @@ msgstr ""
 msgid "Input the recovery code:"
 msgstr ""
 
-#: src/routes/index.ts:305 src/views/other/Install.vue:136
+#: src/routes/index.ts:312 src/views/other/Install.vue:136
 msgid "Install"
 msgstr "Install"
 
@@ -1327,7 +1351,7 @@ msgstr "Locations"
 msgid "Log"
 msgstr "Login"
 
-#: src/routes/index.ts:311 src/views/other/Login.vue:246
+#: src/routes/index.ts:318 src/views/other/Login.vue:246
 msgid "Login"
 msgstr "Login"
 
@@ -1496,6 +1520,14 @@ msgstr ""
 msgid "Nginx Access Log Path"
 msgstr ""
 
+#: src/views/system/SelfCheck/tasks.ts:11
+msgid "Nginx Conf Include Sites Enabled"
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:15
+msgid "Nginx Conf Include Streams Enabled"
+msgstr ""
+
 #: src/views/site/site_edit/SiteEdit.vue:223
 #: src/views/stream/StreamEdit.vue:207
 #, fuzzy
@@ -1579,7 +1611,7 @@ msgstr ""
 msgid "Not After"
 msgstr ""
 
-#: src/routes/index.ts:317
+#: src/routes/index.ts:324
 msgid "Not Found"
 msgstr "Not Found"
 
@@ -1878,6 +1910,10 @@ msgstr "Reads"
 msgid "Receive"
 msgstr "Receive"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:47
+msgid "Recheck"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:43
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
 msgid "Recover"
@@ -2193,6 +2229,10 @@ msgstr ""
 msgid "Selector"
 msgstr ""
 
+#: src/routes/index.ts:283 src/views/system/SelfCheck/SelfCheck.vue:42
+msgid "Self Check"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:348
 #: src/views/dashboard/ServerAnalytic.vue:35
 msgid "Send"
@@ -2205,7 +2245,7 @@ msgstr "Send"
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:53
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:70
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:194
-#: src/views/config/components/Mkdir.vue:38
+#: src/lib/http/index.ts:128 src/views/config/components/Mkdir.vue:38
 #: src/views/config/components/Rename.vue:45
 #: src/views/config/ConfigEditor.vue:111
 #: src/views/environment/BatchUpgrader.vue:59
@@ -2274,6 +2314,11 @@ msgstr ""
 msgid "Site Logs"
 msgstr "Sites List"
 
+#: src/views/system/SelfCheck/tasks.ts:3
+#, fuzzy
+msgid "Sites Directory"
+msgstr "Directive"
+
 #: src/routes/index.ts:56
 msgid "Sites List"
 msgstr "Sites List"
@@ -2326,10 +2371,23 @@ msgstr ""
 msgid "Storage"
 msgstr "Storage"
 
+#: src/views/system/SelfCheck/tasks.ts:7
+#, fuzzy
+msgid "Streams Directory"
+msgstr "Directive"
+
 #: src/constants/index.ts:19 src/views/notification/notificationColumns.tsx:36
 msgid "Success"
 msgstr ""
 
+#: src/views/system/SelfCheck/SelfCheck.vue:78
+msgid ""
+"Support communication with the backend through the WebSocket protocol. If "
+"your Nginx UI is being used via an Nginx reverse proxy, please refer to this "
+"link to write the corresponding configuration file: https://nginxui.com/"
+"guide/nginx-proxy-example.html"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:240
 #: src/views/dashboard/ServerAnalytic.vue:241
 msgid "Swap"
@@ -2671,7 +2729,7 @@ msgstr "Updated at"
 msgid "Updated successfully"
 msgstr "Saved successfully"
 
-#: src/routes/index.ts:290 src/views/environment/Environment.vue:55
+#: src/routes/index.ts:297 src/views/environment/Environment.vue:55
 #: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr ""

+ 65 - 6
app/src/language/es/app.po

@@ -24,7 +24,7 @@ msgstr "2FA"
 msgid "2FA Settings"
 msgstr "Configuración de 2FA"
 
-#: src/routes/index.ts:283
+#: src/routes/index.ts:290
 msgid "About"
 msgstr "Acerca de"
 
@@ -204,6 +204,11 @@ msgstr "Preguntar por ayuda a ChatGPT"
 msgid "Assistant"
 msgstr "Asistente"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:54
+#, fuzzy
+msgid "Attempt to fix"
+msgstr "Intentos"
+
 #: src/views/preference/AuthSettings.vue:17
 msgid "Attempts"
 msgstr "Intentos"
@@ -388,6 +393,26 @@ msgstr "Canal"
 msgid "Check again"
 msgstr "Intentar nuevamente"
 
+#: src/views/system/SelfCheck/tasks.ts:12
+msgid "Check if the nginx.conf includes the sites-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:16
+msgid "Check if the nginx.conf includes the streams-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:4
+msgid ""
+"Check if the sites-available and sites-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:8
+msgid ""
+"Check if the streams-available and streams-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
 #: src/language/constants.ts:13
 msgid "Cleaning environment variables"
 msgstr "Borrar las variables de entorno"
@@ -1139,7 +1164,7 @@ msgstr "Ingrese el código de la aplicación:"
 msgid "Input the recovery code:"
 msgstr "Ingrese el código de recuperación:"
 
-#: src/routes/index.ts:305 src/views/other/Install.vue:136
+#: src/routes/index.ts:312 src/views/other/Install.vue:136
 msgid "Install"
 msgstr "Instalar"
 
@@ -1278,7 +1303,7 @@ msgstr "Ubicaciones"
 msgid "Log"
 msgstr "Registro"
 
-#: src/routes/index.ts:311 src/views/other/Login.vue:246
+#: src/routes/index.ts:318 src/views/other/Login.vue:246
 msgid "Login"
 msgstr "Acceso"
 
@@ -1444,6 +1469,14 @@ msgstr "Nginx"
 msgid "Nginx Access Log Path"
 msgstr "Ruta de registro de acceso de Nginx"
 
+#: src/views/system/SelfCheck/tasks.ts:11
+msgid "Nginx Conf Include Sites Enabled"
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:15
+msgid "Nginx Conf Include Streams Enabled"
+msgstr ""
+
 #: src/views/site/site_edit/SiteEdit.vue:223
 #: src/views/stream/StreamEdit.vue:207
 msgid "Nginx Configuration Parse Error"
@@ -1526,7 +1559,7 @@ msgstr "Secreto del nodo"
 msgid "Not After"
 msgstr "No después de"
 
-#: src/routes/index.ts:317
+#: src/routes/index.ts:324
 msgid "Not Found"
 msgstr "No encontrado"
 
@@ -1845,6 +1878,10 @@ msgstr "Lecturas"
 msgid "Receive"
 msgstr "Recibido"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:47
+msgid "Recheck"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:43
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
 msgid "Recover"
@@ -2150,6 +2187,10 @@ msgstr "El secreto ha sido copiado"
 msgid "Selector"
 msgstr "Seleccionador"
 
+#: src/routes/index.ts:283 src/views/system/SelfCheck/SelfCheck.vue:42
+msgid "Self Check"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:348
 #: src/views/dashboard/ServerAnalytic.vue:35
 msgid "Send"
@@ -2162,7 +2203,7 @@ msgstr "Enviado"
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:53
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:70
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:194
-#: src/views/config/components/Mkdir.vue:38
+#: src/lib/http/index.ts:128 src/views/config/components/Mkdir.vue:38
 #: src/views/config/components/Rename.vue:45
 #: src/views/config/ConfigEditor.vue:111
 #: src/views/environment/BatchUpgrader.vue:59
@@ -2232,6 +2273,11 @@ msgstr "Categorías del sitio"
 msgid "Site Logs"
 msgstr "Registros del sitio"
 
+#: src/views/system/SelfCheck/tasks.ts:3
+#, fuzzy
+msgid "Sites Directory"
+msgstr "Directorio"
+
 #: src/routes/index.ts:56
 msgid "Sites List"
 msgstr "Lista de sitios"
@@ -2278,10 +2324,23 @@ msgstr "Detenido"
 msgid "Storage"
 msgstr "Almacenamiento"
 
+#: src/views/system/SelfCheck/tasks.ts:7
+#, fuzzy
+msgid "Streams Directory"
+msgstr "Directorio"
+
 #: src/constants/index.ts:19 src/views/notification/notificationColumns.tsx:36
 msgid "Success"
 msgstr "Éxito"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:78
+msgid ""
+"Support communication with the backend through the WebSocket protocol. If "
+"your Nginx UI is being used via an Nginx reverse proxy, please refer to this "
+"link to write the corresponding configuration file: https://nginxui.com/"
+"guide/nginx-proxy-example.html"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:240
 #: src/views/dashboard/ServerAnalytic.vue:241
 msgid "Swap"
@@ -2658,7 +2717,7 @@ msgstr "Actualizado a"
 msgid "Updated successfully"
 msgstr "Actualización exitosa"
 
-#: src/routes/index.ts:290 src/views/environment/Environment.vue:55
+#: src/routes/index.ts:297 src/views/environment/Environment.vue:55
 #: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "Actualizar"

+ 64 - 6
app/src/language/fr_FR/app.po

@@ -19,7 +19,7 @@ msgstr ""
 msgid "2FA Settings"
 msgstr ""
 
-#: src/routes/index.ts:283
+#: src/routes/index.ts:290
 msgid "About"
 msgstr "À propos"
 
@@ -212,6 +212,10 @@ msgstr "Modèle ChatGPT"
 msgid "Assistant"
 msgstr ""
 
+#: src/views/system/SelfCheck/SelfCheck.vue:54
+msgid "Attempt to fix"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:17
 msgid "Attempts"
 msgstr ""
@@ -404,6 +408,26 @@ msgstr ""
 msgid "Check again"
 msgstr "Revérifier"
 
+#: src/views/system/SelfCheck/tasks.ts:12
+msgid "Check if the nginx.conf includes the sites-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:16
+msgid "Check if the nginx.conf includes the streams-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:4
+msgid ""
+"Check if the sites-available and sites-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:8
+msgid ""
+"Check if the streams-available and streams-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
 #: src/language/constants.ts:13
 msgid "Cleaning environment variables"
 msgstr "Nettoyage des variables d'environnement"
@@ -1180,7 +1204,7 @@ msgstr ""
 msgid "Input the recovery code:"
 msgstr ""
 
-#: src/routes/index.ts:305 src/views/other/Install.vue:136
+#: src/routes/index.ts:312 src/views/other/Install.vue:136
 msgid "Install"
 msgstr "Installer"
 
@@ -1331,7 +1355,7 @@ msgstr "Localisations"
 msgid "Log"
 msgstr "Connexion"
 
-#: src/routes/index.ts:311 src/views/other/Login.vue:246
+#: src/routes/index.ts:318 src/views/other/Login.vue:246
 msgid "Login"
 msgstr "Connexion"
 
@@ -1499,6 +1523,14 @@ msgstr "Journal Nginx"
 msgid "Nginx Access Log Path"
 msgstr "Chemin du journal d'accès Nginx"
 
+#: src/views/system/SelfCheck/tasks.ts:11
+msgid "Nginx Conf Include Sites Enabled"
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:15
+msgid "Nginx Conf Include Streams Enabled"
+msgstr ""
+
 #: src/views/site/site_edit/SiteEdit.vue:223
 #: src/views/stream/StreamEdit.vue:207
 msgid "Nginx Configuration Parse Error"
@@ -1582,7 +1614,7 @@ msgstr "Secret Jwt"
 msgid "Not After"
 msgstr ""
 
-#: src/routes/index.ts:317
+#: src/routes/index.ts:324
 msgid "Not Found"
 msgstr "Introuvable"
 
@@ -1892,6 +1924,10 @@ msgstr "Lectures"
 msgid "Receive"
 msgstr "Recevoir"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:47
+msgid "Recheck"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:43
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
 msgid "Recover"
@@ -2208,6 +2244,10 @@ msgstr ""
 msgid "Selector"
 msgstr "Sélecteur"
 
+#: src/routes/index.ts:283 src/views/system/SelfCheck/SelfCheck.vue:42
+msgid "Self Check"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:348
 #: src/views/dashboard/ServerAnalytic.vue:35
 msgid "Send"
@@ -2220,7 +2260,7 @@ msgstr "Envoyer"
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:53
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:70
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:194
-#: src/views/config/components/Mkdir.vue:38
+#: src/lib/http/index.ts:128 src/views/config/components/Mkdir.vue:38
 #: src/views/config/components/Rename.vue:45
 #: src/views/config/ConfigEditor.vue:111
 #: src/views/environment/BatchUpgrader.vue:59
@@ -2290,6 +2330,11 @@ msgstr ""
 msgid "Site Logs"
 msgstr "Journaux du site"
 
+#: src/views/system/SelfCheck/tasks.ts:3
+#, fuzzy
+msgid "Sites Directory"
+msgstr "Directive"
+
 #: src/routes/index.ts:56
 msgid "Sites List"
 msgstr "Liste des sites"
@@ -2340,10 +2385,23 @@ msgstr "Arrêté"
 msgid "Storage"
 msgstr "Stockage"
 
+#: src/views/system/SelfCheck/tasks.ts:7
+#, fuzzy
+msgid "Streams Directory"
+msgstr "Directive"
+
 #: src/constants/index.ts:19 src/views/notification/notificationColumns.tsx:36
 msgid "Success"
 msgstr ""
 
+#: src/views/system/SelfCheck/SelfCheck.vue:78
+msgid ""
+"Support communication with the backend through the WebSocket protocol. If "
+"your Nginx UI is being used via an Nginx reverse proxy, please refer to this "
+"link to write the corresponding configuration file: https://nginxui.com/"
+"guide/nginx-proxy-example.html"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:240
 #: src/views/dashboard/ServerAnalytic.vue:241
 #, fuzzy
@@ -2693,7 +2751,7 @@ msgstr "Mis à jour le"
 msgid "Updated successfully"
 msgstr "Mis à jour avec succés"
 
-#: src/routes/index.ts:290 src/views/environment/Environment.vue:55
+#: src/routes/index.ts:297 src/views/environment/Environment.vue:55
 #: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "Mettre à niveau"

+ 65 - 6
app/src/language/ko_KR/app.po

@@ -22,7 +22,7 @@ msgstr "2FA"
 msgid "2FA Settings"
 msgstr "2FA 설정"
 
-#: src/routes/index.ts:283
+#: src/routes/index.ts:290
 msgid "About"
 msgstr "대하여"
 
@@ -201,6 +201,11 @@ msgstr "ChatGPT에게 도움 요청"
 msgid "Assistant"
 msgstr "조수"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:54
+#, fuzzy
+msgid "Attempt to fix"
+msgstr "시도 횟수"
+
 #: src/views/preference/AuthSettings.vue:17
 msgid "Attempts"
 msgstr "시도 횟수"
@@ -386,6 +391,26 @@ msgstr "채널"
 msgid "Check again"
 msgstr "다시 확인"
 
+#: src/views/system/SelfCheck/tasks.ts:12
+msgid "Check if the nginx.conf includes the sites-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:16
+msgid "Check if the nginx.conf includes the streams-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:4
+msgid ""
+"Check if the sites-available and sites-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:8
+msgid ""
+"Check if the streams-available and streams-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
 #: src/language/constants.ts:13
 msgid "Cleaning environment variables"
 msgstr "환경 변수 정리"
@@ -1140,7 +1165,7 @@ msgstr ""
 msgid "Input the recovery code:"
 msgstr ""
 
-#: src/routes/index.ts:305 src/views/other/Install.vue:136
+#: src/routes/index.ts:312 src/views/other/Install.vue:136
 msgid "Install"
 msgstr "설치"
 
@@ -1292,7 +1317,7 @@ msgstr "위치들"
 msgid "Log"
 msgstr "로그인"
 
-#: src/routes/index.ts:311 src/views/other/Login.vue:246
+#: src/routes/index.ts:318 src/views/other/Login.vue:246
 msgid "Login"
 msgstr "로그인"
 
@@ -1466,6 +1491,14 @@ msgstr "Nginx"
 msgid "Nginx Access Log Path"
 msgstr "Nginx 접근 로그 경로"
 
+#: src/views/system/SelfCheck/tasks.ts:11
+msgid "Nginx Conf Include Sites Enabled"
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:15
+msgid "Nginx Conf Include Streams Enabled"
+msgstr ""
+
 #: src/views/site/site_edit/SiteEdit.vue:223
 #: src/views/stream/StreamEdit.vue:207
 #, fuzzy
@@ -1551,7 +1584,7 @@ msgstr "노드 시크릿"
 msgid "Not After"
 msgstr "만료일"
 
-#: src/routes/index.ts:317
+#: src/routes/index.ts:324
 msgid "Not Found"
 msgstr "찾을 수 없음"
 
@@ -1855,6 +1888,10 @@ msgstr "읽기"
 msgid "Receive"
 msgstr "수신"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:47
+msgid "Recheck"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:43
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
 msgid "Recover"
@@ -2174,6 +2211,10 @@ msgstr ""
 msgid "Selector"
 msgstr "선택"
 
+#: src/routes/index.ts:283 src/views/system/SelfCheck/SelfCheck.vue:42
+msgid "Self Check"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:348
 #: src/views/dashboard/ServerAnalytic.vue:35
 msgid "Send"
@@ -2186,7 +2227,7 @@ msgstr "보내기"
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:53
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:70
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:194
-#: src/views/config/components/Mkdir.vue:38
+#: src/lib/http/index.ts:128 src/views/config/components/Mkdir.vue:38
 #: src/views/config/components/Rename.vue:45
 #: src/views/config/ConfigEditor.vue:111
 #: src/views/environment/BatchUpgrader.vue:59
@@ -2255,6 +2296,11 @@ msgstr ""
 msgid "Site Logs"
 msgstr "사이트 로그"
 
+#: src/views/system/SelfCheck/tasks.ts:3
+#, fuzzy
+msgid "Sites Directory"
+msgstr "디렉토리"
+
 #: src/routes/index.ts:56
 msgid "Sites List"
 msgstr "사이트 목록"
@@ -2306,10 +2352,23 @@ msgstr "정지됨"
 msgid "Storage"
 msgstr "저장소"
 
+#: src/views/system/SelfCheck/tasks.ts:7
+#, fuzzy
+msgid "Streams Directory"
+msgstr "디렉토리"
+
 #: src/constants/index.ts:19 src/views/notification/notificationColumns.tsx:36
 msgid "Success"
 msgstr "성공"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:78
+msgid ""
+"Support communication with the backend through the WebSocket protocol. If "
+"your Nginx UI is being used via an Nginx reverse proxy, please refer to this "
+"link to write the corresponding configuration file: https://nginxui.com/"
+"guide/nginx-proxy-example.html"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:240
 #: src/views/dashboard/ServerAnalytic.vue:241
 msgid "Swap"
@@ -2658,7 +2717,7 @@ msgstr "업데이트됨"
 msgid "Updated successfully"
 msgstr "성공적으로 저장되었습니다"
 
-#: src/routes/index.ts:290 src/views/environment/Environment.vue:55
+#: src/routes/index.ts:297 src/views/environment/Environment.vue:55
 #: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "업그레이드"

+ 55 - 5
app/src/language/messages.pot

@@ -10,7 +10,7 @@ msgstr ""
 msgid "2FA Settings"
 msgstr ""
 
-#: src/routes/index.ts:283
+#: src/routes/index.ts:290
 msgid "About"
 msgstr ""
 
@@ -190,6 +190,10 @@ msgstr ""
 msgid "Assistant"
 msgstr ""
 
+#: src/views/system/SelfCheck/SelfCheck.vue:54
+msgid "Attempt to fix"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:17
 msgid "Attempts"
 msgstr ""
@@ -374,6 +378,22 @@ msgstr ""
 msgid "Check again"
 msgstr ""
 
+#: src/views/system/SelfCheck/tasks.ts:12
+msgid "Check if the nginx.conf includes the sites-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:16
+msgid "Check if the nginx.conf includes the streams-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:4
+msgid "Check if the sites-available and sites-enabled directories are under the nginx configuration directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:8
+msgid "Check if the streams-available and streams-enabled directories are under the nginx configuration directory."
+msgstr ""
+
 #: src/language/constants.ts:13
 msgid "Cleaning environment variables"
 msgstr ""
@@ -1105,7 +1125,7 @@ msgstr ""
 msgid "Input the recovery code:"
 msgstr ""
 
-#: src/routes/index.ts:305
+#: src/routes/index.ts:312
 #: src/views/other/Install.vue:136
 msgid "Install"
 msgstr ""
@@ -1243,7 +1263,7 @@ msgstr ""
 msgid "Log"
 msgstr ""
 
-#: src/routes/index.ts:311
+#: src/routes/index.ts:318
 #: src/views/other/Login.vue:246
 msgid "Login"
 msgstr ""
@@ -1403,6 +1423,14 @@ msgstr ""
 msgid "Nginx Access Log Path"
 msgstr ""
 
+#: src/views/system/SelfCheck/tasks.ts:11
+msgid "Nginx Conf Include Sites Enabled"
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:15
+msgid "Nginx Conf Include Streams Enabled"
+msgstr ""
+
 #: src/views/site/site_edit/SiteEdit.vue:223
 #: src/views/stream/StreamEdit.vue:207
 msgid "Nginx Configuration Parse Error"
@@ -1482,7 +1510,7 @@ msgstr ""
 msgid "Not After"
 msgstr ""
 
-#: src/routes/index.ts:317
+#: src/routes/index.ts:324
 msgid "Not Found"
 msgstr ""
 
@@ -1762,6 +1790,10 @@ msgstr ""
 msgid "Receive"
 msgstr ""
 
+#: src/views/system/SelfCheck/SelfCheck.vue:47
+msgid "Recheck"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:43
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
 msgid "Recover"
@@ -2045,6 +2077,11 @@ msgstr ""
 msgid "Selector"
 msgstr ""
 
+#: src/routes/index.ts:283
+#: src/views/system/SelfCheck/SelfCheck.vue:42
+msgid "Self Check"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:348
 #: src/views/dashboard/ServerAnalytic.vue:35
 msgid "Send"
@@ -2057,6 +2094,7 @@ msgstr ""
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:53
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:70
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:194
+#: src/lib/http/index.ts:128
 #: src/views/config/components/Mkdir.vue:38
 #: src/views/config/components/Rename.vue:45
 #: src/views/config/ConfigEditor.vue:111
@@ -2127,6 +2165,10 @@ msgstr ""
 msgid "Site Logs"
 msgstr ""
 
+#: src/views/system/SelfCheck/tasks.ts:3
+msgid "Sites Directory"
+msgstr ""
+
 #: src/routes/index.ts:56
 msgid "Sites List"
 msgstr ""
@@ -2175,11 +2217,19 @@ msgstr ""
 msgid "Storage"
 msgstr ""
 
+#: src/views/system/SelfCheck/tasks.ts:7
+msgid "Streams Directory"
+msgstr ""
+
 #: src/constants/index.ts:19
 #: src/views/notification/notificationColumns.tsx:36
 msgid "Success"
 msgstr ""
 
+#: src/views/system/SelfCheck/SelfCheck.vue:78
+msgid "Support communication with the backend through the WebSocket protocol. If your Nginx UI is being used via an Nginx reverse proxy, please refer to this link to write the corresponding configuration file: https://nginxui.com/guide/nginx-proxy-example.html"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:240
 #: src/views/dashboard/ServerAnalytic.vue:241
 msgid "Swap"
@@ -2468,7 +2518,7 @@ msgstr ""
 msgid "Updated successfully"
 msgstr ""
 
-#: src/routes/index.ts:290
+#: src/routes/index.ts:297
 #: src/views/environment/Environment.vue:55
 #: src/views/system/Upgrade.vue:145
 #: src/views/system/Upgrade.vue:228

+ 65 - 6
app/src/language/ru_RU/app.po

@@ -23,7 +23,7 @@ msgstr "2FA"
 msgid "2FA Settings"
 msgstr "Настройки 2FA"
 
-#: src/routes/index.ts:283
+#: src/routes/index.ts:290
 msgid "About"
 msgstr "О проекте"
 
@@ -203,6 +203,11 @@ msgstr "Обратитесь за помощью к ChatGPT"
 msgid "Assistant"
 msgstr "Ассистент"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:54
+#, fuzzy
+msgid "Attempt to fix"
+msgstr "Попытки"
+
 #: src/views/preference/AuthSettings.vue:17
 msgid "Attempts"
 msgstr "Попытки"
@@ -387,6 +392,26 @@ msgstr "Канал"
 msgid "Check again"
 msgstr "Проверить повторно"
 
+#: src/views/system/SelfCheck/tasks.ts:12
+msgid "Check if the nginx.conf includes the sites-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:16
+msgid "Check if the nginx.conf includes the streams-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:4
+msgid ""
+"Check if the sites-available and sites-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:8
+msgid ""
+"Check if the streams-available and streams-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
 #: src/language/constants.ts:13
 msgid "Cleaning environment variables"
 msgstr "Очистка переменных среды"
@@ -1146,7 +1171,7 @@ msgstr "Введите код из приложения:"
 msgid "Input the recovery code:"
 msgstr "Введите код восстановления:"
 
-#: src/routes/index.ts:305 src/views/other/Install.vue:136
+#: src/routes/index.ts:312 src/views/other/Install.vue:136
 msgid "Install"
 msgstr "Установить"
 
@@ -1285,7 +1310,7 @@ msgstr "Локации"
 msgid "Log"
 msgstr "Журнал"
 
-#: src/routes/index.ts:311 src/views/other/Login.vue:246
+#: src/routes/index.ts:318 src/views/other/Login.vue:246
 msgid "Login"
 msgstr "Логин"
 
@@ -1450,6 +1475,14 @@ msgstr "Nginx"
 msgid "Nginx Access Log Path"
 msgstr "Путь для Nginx Access Log"
 
+#: src/views/system/SelfCheck/tasks.ts:11
+msgid "Nginx Conf Include Sites Enabled"
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:15
+msgid "Nginx Conf Include Streams Enabled"
+msgstr ""
+
 #: src/views/site/site_edit/SiteEdit.vue:223
 #: src/views/stream/StreamEdit.vue:207
 msgid "Nginx Configuration Parse Error"
@@ -1532,7 +1565,7 @@ msgstr ""
 msgid "Not After"
 msgstr "Не позднее"
 
-#: src/routes/index.ts:317
+#: src/routes/index.ts:324
 msgid "Not Found"
 msgstr "Не найден"
 
@@ -1840,6 +1873,10 @@ msgstr "Чтение"
 msgid "Receive"
 msgstr "Принято"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:47
+msgid "Recheck"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:43
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
 msgid "Recover"
@@ -2144,6 +2181,10 @@ msgstr ""
 msgid "Selector"
 msgstr "Выбор"
 
+#: src/routes/index.ts:283 src/views/system/SelfCheck/SelfCheck.vue:42
+msgid "Self Check"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:348
 #: src/views/dashboard/ServerAnalytic.vue:35
 msgid "Send"
@@ -2156,7 +2197,7 @@ msgstr "Отправлено"
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:53
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:70
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:194
-#: src/views/config/components/Mkdir.vue:38
+#: src/lib/http/index.ts:128 src/views/config/components/Mkdir.vue:38
 #: src/views/config/components/Rename.vue:45
 #: src/views/config/ConfigEditor.vue:111
 #: src/views/environment/BatchUpgrader.vue:59
@@ -2226,6 +2267,11 @@ msgstr ""
 msgid "Site Logs"
 msgstr "Журналы сайта"
 
+#: src/views/system/SelfCheck/tasks.ts:3
+#, fuzzy
+msgid "Sites Directory"
+msgstr "Каталог"
+
 #: src/routes/index.ts:56
 msgid "Sites List"
 msgstr "Список сайтов"
@@ -2272,10 +2318,23 @@ msgstr "Остановлен"
 msgid "Storage"
 msgstr "Хранилище"
 
+#: src/views/system/SelfCheck/tasks.ts:7
+#, fuzzy
+msgid "Streams Directory"
+msgstr "Каталог"
+
 #: src/constants/index.ts:19 src/views/notification/notificationColumns.tsx:36
 msgid "Success"
 msgstr "Успех"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:78
+msgid ""
+"Support communication with the backend through the WebSocket protocol. If "
+"your Nginx UI is being used via an Nginx reverse proxy, please refer to this "
+"link to write the corresponding configuration file: https://nginxui.com/"
+"guide/nginx-proxy-example.html"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:240
 #: src/views/dashboard/ServerAnalytic.vue:241
 msgid "Swap"
@@ -2642,7 +2701,7 @@ msgstr "Обновлено в"
 msgid "Updated successfully"
 msgstr "Успешно обновлено"
 
-#: src/routes/index.ts:290 src/views/environment/Environment.vue:55
+#: src/routes/index.ts:297 src/views/environment/Environment.vue:55
 #: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "Обновление"

+ 65 - 6
app/src/language/tr_TR/app.po

@@ -20,7 +20,7 @@ msgstr "İki aşamalı kimlik doğrulaması(2FA)"
 msgid "2FA Settings"
 msgstr "2FA Ayarları"
 
-#: src/routes/index.ts:283
+#: src/routes/index.ts:290
 msgid "About"
 msgstr "Hakkında"
 
@@ -199,6 +199,11 @@ msgstr "ChatGPT'den Yardım İsteyin"
 msgid "Assistant"
 msgstr "Asistan"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:54
+#, fuzzy
+msgid "Attempt to fix"
+msgstr "Girişimler"
+
 #: src/views/preference/AuthSettings.vue:17
 msgid "Attempts"
 msgstr "Girişimler"
@@ -381,6 +386,26 @@ msgstr "Kanal"
 msgid "Check again"
 msgstr "Tekrar kontrol et"
 
+#: src/views/system/SelfCheck/tasks.ts:12
+msgid "Check if the nginx.conf includes the sites-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:16
+msgid "Check if the nginx.conf includes the streams-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:4
+msgid ""
+"Check if the sites-available and sites-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:8
+msgid ""
+"Check if the streams-available and streams-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
 #: src/language/constants.ts:13
 msgid "Cleaning environment variables"
 msgstr "Ortam değişkenlerini temizleme"
@@ -1148,7 +1173,7 @@ msgstr "Uygulamadan kodu girin:"
 msgid "Input the recovery code:"
 msgstr "Kurtarma kodunu girin:"
 
-#: src/routes/index.ts:305 src/views/other/Install.vue:136
+#: src/routes/index.ts:312 src/views/other/Install.vue:136
 msgid "Install"
 msgstr "Yükle"
 
@@ -1286,7 +1311,7 @@ msgstr "Konumlar"
 msgid "Log"
 msgstr "Günlük"
 
-#: src/routes/index.ts:311 src/views/other/Login.vue:246
+#: src/routes/index.ts:318 src/views/other/Login.vue:246
 msgid "Login"
 msgstr "Giriş"
 
@@ -1478,6 +1503,14 @@ msgstr "Nginx"
 msgid "Nginx Access Log Path"
 msgstr "Nginx Erişim Günlüğü Yolu"
 
+#: src/views/system/SelfCheck/tasks.ts:11
+msgid "Nginx Conf Include Sites Enabled"
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:15
+msgid "Nginx Conf Include Streams Enabled"
+msgstr ""
+
 #: src/views/site/site_edit/SiteEdit.vue:223
 #: src/views/stream/StreamEdit.vue:207
 #, fuzzy
@@ -1570,7 +1603,7 @@ msgstr "Düğüm Sırrı"
 msgid "Not After"
 msgstr "Sonra değil"
 
-#: src/routes/index.ts:317
+#: src/routes/index.ts:324
 #, fuzzy
 msgid "Not Found"
 msgstr "Bulunamadı"
@@ -1935,6 +1968,10 @@ msgstr "Okumalar"
 msgid "Receive"
 msgstr "Teslim almak"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:47
+msgid "Recheck"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:43
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
 #, fuzzy
@@ -2289,6 +2326,10 @@ msgstr "Sır kopyalandı"
 msgid "Selector"
 msgstr "Selektör"
 
+#: src/routes/index.ts:283 src/views/system/SelfCheck/SelfCheck.vue:42
+msgid "Self Check"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:348
 #: src/views/dashboard/ServerAnalytic.vue:35
 #, fuzzy
@@ -2302,7 +2343,7 @@ msgstr "Gönder"
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:53
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:70
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:194
-#: src/views/config/components/Mkdir.vue:38
+#: src/lib/http/index.ts:128 src/views/config/components/Mkdir.vue:38
 #: src/views/config/components/Rename.vue:45
 #: src/views/config/ConfigEditor.vue:111
 #: src/views/environment/BatchUpgrader.vue:59
@@ -2384,6 +2425,11 @@ msgstr ""
 msgid "Site Logs"
 msgstr "Site Günlükleri"
 
+#: src/views/system/SelfCheck/tasks.ts:3
+#, fuzzy
+msgid "Sites Directory"
+msgstr "Dizin"
+
 #: src/routes/index.ts:56
 #, fuzzy
 msgid "Sites List"
@@ -2440,11 +2486,24 @@ msgstr "Durduruldu"
 msgid "Storage"
 msgstr "Depolama"
 
+#: src/views/system/SelfCheck/tasks.ts:7
+#, fuzzy
+msgid "Streams Directory"
+msgstr "Dizin"
+
 #: src/constants/index.ts:19 src/views/notification/notificationColumns.tsx:36
 #, fuzzy
 msgid "Success"
 msgstr "Başarılı"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:78
+msgid ""
+"Support communication with the backend through the WebSocket protocol. If "
+"your Nginx UI is being used via an Nginx reverse proxy, please refer to this "
+"link to write the corresponding configuration file: https://nginxui.com/"
+"guide/nginx-proxy-example.html"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:240
 #: src/views/dashboard/ServerAnalytic.vue:241
 #, fuzzy
@@ -2867,7 +2926,7 @@ msgstr "Güncelleme"
 msgid "Updated successfully"
 msgstr "Başarıyla güncellendi"
 
-#: src/routes/index.ts:290 src/views/environment/Environment.vue:55
+#: src/routes/index.ts:297 src/views/environment/Environment.vue:55
 #: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 #, fuzzy
 msgid "Upgrade"

+ 64 - 6
app/src/language/vi_VN/app.po

@@ -17,7 +17,7 @@ msgstr ""
 msgid "2FA Settings"
 msgstr ""
 
-#: src/routes/index.ts:283
+#: src/routes/index.ts:290
 msgid "About"
 msgstr "Tác giả"
 
@@ -210,6 +210,10 @@ msgstr "Hỏi ChatGPT"
 msgid "Assistant"
 msgstr "Trợ lý"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:54
+msgid "Attempt to fix"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:17
 msgid "Attempts"
 msgstr ""
@@ -405,6 +409,26 @@ msgstr "Kênh"
 msgid "Check again"
 msgstr "Kiểm tra lại"
 
+#: src/views/system/SelfCheck/tasks.ts:12
+msgid "Check if the nginx.conf includes the sites-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:16
+msgid "Check if the nginx.conf includes the streams-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:4
+msgid ""
+"Check if the sites-available and sites-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:8
+msgid ""
+"Check if the streams-available and streams-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
 #: src/language/constants.ts:13
 msgid "Cleaning environment variables"
 msgstr "Xoá các biến môi trường"
@@ -1186,7 +1210,7 @@ msgstr ""
 msgid "Input the recovery code:"
 msgstr ""
 
-#: src/routes/index.ts:305 src/views/other/Install.vue:136
+#: src/routes/index.ts:312 src/views/other/Install.vue:136
 msgid "Install"
 msgstr "Cài đặt"
 
@@ -1339,7 +1363,7 @@ msgstr "Locations"
 msgid "Log"
 msgstr "Log"
 
-#: src/routes/index.ts:311 src/views/other/Login.vue:246
+#: src/routes/index.ts:318 src/views/other/Login.vue:246
 msgid "Login"
 msgstr "Đăng nhập"
 
@@ -1507,6 +1531,14 @@ msgstr ""
 msgid "Nginx Access Log Path"
 msgstr "Vị trí lưu log truy cập (Access log) của Nginx"
 
+#: src/views/system/SelfCheck/tasks.ts:11
+msgid "Nginx Conf Include Sites Enabled"
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:15
+msgid "Nginx Conf Include Streams Enabled"
+msgstr ""
+
 #: src/views/site/site_edit/SiteEdit.vue:223
 #: src/views/stream/StreamEdit.vue:207
 #, fuzzy
@@ -1591,7 +1623,7 @@ msgstr ""
 msgid "Not After"
 msgstr "Không phải sau khi"
 
-#: src/routes/index.ts:317
+#: src/routes/index.ts:324
 msgid "Not Found"
 msgstr "Không tìm thấy"
 
@@ -1896,6 +1928,10 @@ msgstr "Đọc"
 msgid "Receive"
 msgstr "Nhận"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:47
+msgid "Recheck"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:43
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
 msgid "Recover"
@@ -2215,6 +2251,10 @@ msgstr ""
 msgid "Selector"
 msgstr "Bộ chọn"
 
+#: src/routes/index.ts:283 src/views/system/SelfCheck/SelfCheck.vue:42
+msgid "Self Check"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:348
 #: src/views/dashboard/ServerAnalytic.vue:35
 msgid "Send"
@@ -2227,7 +2267,7 @@ msgstr "Gửi"
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:53
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:70
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:194
-#: src/views/config/components/Mkdir.vue:38
+#: src/lib/http/index.ts:128 src/views/config/components/Mkdir.vue:38
 #: src/views/config/components/Rename.vue:45
 #: src/views/config/ConfigEditor.vue:111
 #: src/views/environment/BatchUpgrader.vue:59
@@ -2297,6 +2337,11 @@ msgstr ""
 msgid "Site Logs"
 msgstr "Logs"
 
+#: src/views/system/SelfCheck/tasks.ts:3
+#, fuzzy
+msgid "Sites Directory"
+msgstr "Thư mục"
+
 #: src/routes/index.ts:56
 msgid "Sites List"
 msgstr "Danh sách Website"
@@ -2344,10 +2389,23 @@ msgstr "Đã dừng"
 msgid "Storage"
 msgstr "Storage"
 
+#: src/views/system/SelfCheck/tasks.ts:7
+#, fuzzy
+msgid "Streams Directory"
+msgstr "Thư mục"
+
 #: src/constants/index.ts:19 src/views/notification/notificationColumns.tsx:36
 msgid "Success"
 msgstr "Thành công"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:78
+msgid ""
+"Support communication with the backend through the WebSocket protocol. If "
+"your Nginx UI is being used via an Nginx reverse proxy, please refer to this "
+"link to write the corresponding configuration file: https://nginxui.com/"
+"guide/nginx-proxy-example.html"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:240
 #: src/views/dashboard/ServerAnalytic.vue:241
 msgid "Swap"
@@ -2692,7 +2750,7 @@ msgstr "Ngày cập nhật"
 msgid "Updated successfully"
 msgstr "Cập nhật thành công"
 
-#: src/routes/index.ts:290 src/views/environment/Environment.vue:55
+#: src/routes/index.ts:297 src/views/environment/Environment.vue:55
 #: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "Cập nhật"

+ 65 - 6
app/src/language/zh_CN/app.po

@@ -21,7 +21,7 @@ msgstr "2FA"
 msgid "2FA Settings"
 msgstr "2FA 设置"
 
-#: src/routes/index.ts:283
+#: src/routes/index.ts:290
 msgid "About"
 msgstr "关于"
 
@@ -196,6 +196,10 @@ msgstr "与ChatGPT聊天"
 msgid "Assistant"
 msgstr "助手"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:54
+msgid "Attempt to fix"
+msgstr "尝试修复"
+
 #: src/views/preference/AuthSettings.vue:17
 msgid "Attempts"
 msgstr "尝试次数"
@@ -375,6 +379,27 @@ msgstr "通道"
 msgid "Check again"
 msgstr "重新检查"
 
+#: src/views/system/SelfCheck/tasks.ts:12
+msgid "Check if the nginx.conf includes the sites-enabled directory."
+msgstr "检查 nginx.conf 是否包含 sites-enabled 的目录。"
+
+#: src/views/system/SelfCheck/tasks.ts:16
+msgid "Check if the nginx.conf includes the streams-enabled directory."
+msgstr "检查 nginx.conf 是否包含 streams-enabled 的目录。"
+
+#: src/views/system/SelfCheck/tasks.ts:4
+msgid ""
+"Check if the sites-available and sites-enabled directories are under the "
+"nginx configuration directory."
+msgstr "检查 sites-available 和 sites-enabled 目录是否位于 nginx 配置目录下。"
+
+#: src/views/system/SelfCheck/tasks.ts:8
+msgid ""
+"Check if the streams-available and streams-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+"检查 nginx 配置目录下是否有 streams-available 和 streams-enabled 目录。"
+
 #: src/language/constants.ts:13
 msgid "Cleaning environment variables"
 msgstr "正在清理环境变量"
@@ -1102,7 +1127,7 @@ msgstr "输入应用程序中的代码:"
 msgid "Input the recovery code:"
 msgstr "输入恢复代码:"
 
-#: src/routes/index.ts:305 src/views/other/Install.vue:136
+#: src/routes/index.ts:312 src/views/other/Install.vue:136
 msgid "Install"
 msgstr "安装"
 
@@ -1239,7 +1264,7 @@ msgstr "Locations"
 msgid "Log"
 msgstr "日志"
 
-#: src/routes/index.ts:311 src/views/other/Login.vue:246
+#: src/routes/index.ts:318 src/views/other/Login.vue:246
 msgid "Login"
 msgstr "登录"
 
@@ -1402,6 +1427,14 @@ msgstr "Nginx"
 msgid "Nginx Access Log Path"
 msgstr "Nginx 访问日志路径"
 
+#: src/views/system/SelfCheck/tasks.ts:11
+msgid "Nginx Conf Include Sites Enabled"
+msgstr "Nginx Conf 中引用启用站点目录"
+
+#: src/views/system/SelfCheck/tasks.ts:15
+msgid "Nginx Conf Include Streams Enabled"
+msgstr "Nginx Conf 中引用启用 Streams 目录"
+
 #: src/views/site/site_edit/SiteEdit.vue:223
 #: src/views/stream/StreamEdit.vue:207
 msgid "Nginx Configuration Parse Error"
@@ -1480,7 +1513,7 @@ msgstr "节点密钥"
 msgid "Not After"
 msgstr "有效期"
 
-#: src/routes/index.ts:317
+#: src/routes/index.ts:324
 msgid "Not Found"
 msgstr "找不到页面"
 
@@ -1775,6 +1808,10 @@ msgstr "读"
 msgid "Receive"
 msgstr "下载"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:47
+msgid "Recheck"
+msgstr "重新检查"
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:43
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
 msgid "Recover"
@@ -2061,6 +2098,10 @@ msgstr "密钥已复制"
 msgid "Selector"
 msgstr "选择器"
 
+#: src/routes/index.ts:283 src/views/system/SelfCheck/SelfCheck.vue:42
+msgid "Self Check"
+msgstr "自我检查"
+
 #: src/views/dashboard/ServerAnalytic.vue:348
 #: src/views/dashboard/ServerAnalytic.vue:35
 msgid "Send"
@@ -2073,7 +2114,7 @@ msgstr "上传"
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:53
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:70
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:194
-#: src/views/config/components/Mkdir.vue:38
+#: src/lib/http/index.ts:128 src/views/config/components/Mkdir.vue:38
 #: src/views/config/components/Rename.vue:45
 #: src/views/config/ConfigEditor.vue:111
 #: src/views/environment/BatchUpgrader.vue:59
@@ -2141,6 +2182,10 @@ msgstr "网站分类"
 msgid "Site Logs"
 msgstr "站点列表"
 
+#: src/views/system/SelfCheck/tasks.ts:3
+msgid "Sites Directory"
+msgstr "网站目录"
+
 #: src/routes/index.ts:56
 msgid "Sites List"
 msgstr "站点列表"
@@ -2187,10 +2232,24 @@ msgstr "已停止"
 msgid "Storage"
 msgstr "存储"
 
+#: src/views/system/SelfCheck/tasks.ts:7
+msgid "Streams Directory"
+msgstr "Streams 目录"
+
 #: src/constants/index.ts:19 src/views/notification/notificationColumns.tsx:36
 msgid "Success"
 msgstr "成功"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:78
+msgid ""
+"Support communication with the backend through the WebSocket protocol. If "
+"your Nginx UI is being used via an Nginx reverse proxy, please refer to this "
+"link to write the corresponding configuration file: https://nginxui.com/"
+"guide/nginx-proxy-example.html"
+msgstr ""
+"支持通过 WebSocket 协议与后端通信,如果您正在使用 Nginx 反向代理了 Nginx UI "
+"请参考:https://nginxui.com/guide/nginx-proxy-example.html 编写配置文件"
+
 #: src/views/dashboard/ServerAnalytic.vue:240
 #: src/views/dashboard/ServerAnalytic.vue:241
 msgid "Swap"
@@ -2523,7 +2582,7 @@ msgstr "修改时间"
 msgid "Updated successfully"
 msgstr "更新成功"
 
-#: src/routes/index.ts:290 src/views/environment/Environment.vue:55
+#: src/routes/index.ts:297 src/views/environment/Environment.vue:55
 #: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "升级"

+ 65 - 6
app/src/language/zh_TW/app.po

@@ -25,7 +25,7 @@ msgstr "多重要素驗證"
 msgid "2FA Settings"
 msgstr "多重要素驗證設定"
 
-#: src/routes/index.ts:283
+#: src/routes/index.ts:290
 msgid "About"
 msgstr "關於"
 
@@ -204,6 +204,11 @@ msgstr "向 ChatGPT 尋求幫助"
 msgid "Assistant"
 msgstr "助理"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:54
+#, fuzzy
+msgid "Attempt to fix"
+msgstr "嘗試次數"
+
 #: src/views/preference/AuthSettings.vue:17
 msgid "Attempts"
 msgstr "嘗試次數"
@@ -385,6 +390,26 @@ msgstr "通道"
 msgid "Check again"
 msgstr "再次檢查"
 
+#: src/views/system/SelfCheck/tasks.ts:12
+msgid "Check if the nginx.conf includes the sites-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:16
+msgid "Check if the nginx.conf includes the streams-enabled directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:4
+msgid ""
+"Check if the sites-available and sites-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:8
+msgid ""
+"Check if the streams-available and streams-enabled directories are under the "
+"nginx configuration directory."
+msgstr ""
+
 #: src/language/constants.ts:13
 msgid "Cleaning environment variables"
 msgstr "清理環境變數"
@@ -1125,7 +1150,7 @@ msgstr "請輸入應用程式中的代碼:"
 msgid "Input the recovery code:"
 msgstr "輸入恢復碼:"
 
-#: src/routes/index.ts:305 src/views/other/Install.vue:136
+#: src/routes/index.ts:312 src/views/other/Install.vue:136
 msgid "Install"
 msgstr "安裝"
 
@@ -1265,7 +1290,7 @@ msgstr "Locations"
 msgid "Log"
 msgstr "日誌"
 
-#: src/routes/index.ts:311 src/views/other/Login.vue:246
+#: src/routes/index.ts:318 src/views/other/Login.vue:246
 msgid "Login"
 msgstr "登入"
 
@@ -1427,6 +1452,14 @@ msgstr "Nginx"
 msgid "Nginx Access Log Path"
 msgstr "Nginx 存取日誌路徑"
 
+#: src/views/system/SelfCheck/tasks.ts:11
+msgid "Nginx Conf Include Sites Enabled"
+msgstr ""
+
+#: src/views/system/SelfCheck/tasks.ts:15
+msgid "Nginx Conf Include Streams Enabled"
+msgstr ""
+
 #: src/views/site/site_edit/SiteEdit.vue:223
 #: src/views/stream/StreamEdit.vue:207
 msgid "Nginx Configuration Parse Error"
@@ -1509,7 +1542,7 @@ msgstr "Node Secret"
 msgid "Not After"
 msgstr "不晚於"
 
-#: src/routes/index.ts:317
+#: src/routes/index.ts:324
 msgid "Not Found"
 msgstr "找不到頁面"
 
@@ -1808,6 +1841,10 @@ msgstr "讀取"
 msgid "Receive"
 msgstr "接收"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:47
+msgid "Recheck"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:43
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
 msgid "Recover"
@@ -2107,6 +2144,10 @@ msgstr ""
 msgid "Selector"
 msgstr "選擇器"
 
+#: src/routes/index.ts:283 src/views/system/SelfCheck/SelfCheck.vue:42
+msgid "Self Check"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:348
 #: src/views/dashboard/ServerAnalytic.vue:35
 msgid "Send"
@@ -2119,7 +2160,7 @@ msgstr "傳送"
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:53
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:70
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:194
-#: src/views/config/components/Mkdir.vue:38
+#: src/lib/http/index.ts:128 src/views/config/components/Mkdir.vue:38
 #: src/views/config/components/Rename.vue:45
 #: src/views/config/ConfigEditor.vue:111
 #: src/views/environment/BatchUpgrader.vue:59
@@ -2187,6 +2228,11 @@ msgstr ""
 msgid "Site Logs"
 msgstr "網站日誌"
 
+#: src/views/system/SelfCheck/tasks.ts:3
+#, fuzzy
+msgid "Sites Directory"
+msgstr "目錄"
+
 #: src/routes/index.ts:56
 msgid "Sites List"
 msgstr "網站列表"
@@ -2233,10 +2279,23 @@ msgstr "已停止"
 msgid "Storage"
 msgstr "儲存空間"
 
+#: src/views/system/SelfCheck/tasks.ts:7
+#, fuzzy
+msgid "Streams Directory"
+msgstr "目錄"
+
 #: src/constants/index.ts:19 src/views/notification/notificationColumns.tsx:36
 msgid "Success"
 msgstr "成功"
 
+#: src/views/system/SelfCheck/SelfCheck.vue:78
+msgid ""
+"Support communication with the backend through the WebSocket protocol. If "
+"your Nginx UI is being used via an Nginx reverse proxy, please refer to this "
+"link to write the corresponding configuration file: https://nginxui.com/"
+"guide/nginx-proxy-example.html"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:240
 #: src/views/dashboard/ServerAnalytic.vue:241
 msgid "Swap"
@@ -2574,7 +2633,7 @@ msgstr "更新時間"
 msgid "Updated successfully"
 msgstr "更新成功"
 
-#: src/routes/index.ts:290 src/views/environment/Environment.vue:55
+#: src/routes/index.ts:297 src/views/environment/Environment.vue:55
 #: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "升級"

+ 59 - 1
app/src/lib/http/index.ts

@@ -3,8 +3,8 @@ import use2FAModal from '@/components/TwoFA/use2FAModal'
 import { useNProgress } from '@/lib/nprogress/nprogress'
 import { useSettingsStore, useUserStore } from '@/pinia'
 import router from '@/routes'
+import { message } from 'ant-design-vue'
 import axios from 'axios'
-
 import { storeToRefs } from 'pinia'
 import 'nprogress/nprogress.css'
 
@@ -12,6 +12,23 @@ const user = useUserStore()
 const settings = useSettingsStore()
 const { token, secureSessionId } = storeToRefs(user)
 
+// server response
+export interface CosyError {
+  scope?: string
+  code: string
+  message: string
+  params?: string[]
+}
+
+// code, message translation
+export type CosyErrorRecord = Record<number, () => string>
+
+const errors: Record<string, CosyErrorRecord> = {}
+
+function registerError(scope: string, record: CosyErrorRecord) {
+  errors[scope] = record
+}
+
 const instance = axios.create({
   baseURL: import.meta.env.VITE_API_ROOT,
   timeout: 50000,
@@ -59,6 +76,7 @@ instance.interceptors.response.use(
 
     return Promise.resolve(response.data)
   },
+  // eslint-disable-next-line sonarjs/cognitive-complexity
   async error => {
     nprogress.done()
 
@@ -74,6 +92,46 @@ instance.interceptors.response.use(
         break
     }
 
+    const err = error.response.data as CosyError
+
+    if (err?.scope) {
+      // check if already register
+      if (!errors[err.scope]) {
+        try {
+          const error = await import(`@/constants/errors/${err.scope}.ts`)
+
+          registerError(err.scope, error.default)
+        }
+        catch {
+          /* empty */
+        }
+      }
+
+      const msg = errors?.[err.scope]?.[err.code]
+
+      if (msg) {
+        // if err has parmas
+        if (err?.params && err.params.length > 0) {
+          let res = msg()
+
+          err.params.forEach((param, index) => {
+            res = res.replaceAll(`{${index}}`, param)
+          })
+
+          message.error(res, 5)
+        }
+        else {
+          message.error(msg(), 5)
+        }
+      }
+      else {
+        message.error($gettext('Server error'))
+      }
+    }
+    else {
+      message.error($gettext(err?.message ?? 'Server error'))
+    }
+
     return Promise.reject(error.response.data)
   },
 )

+ 7 - 0
app/src/routes/index.ts

@@ -276,6 +276,13 @@ export const routes: RouteRecordRaw[] = [
           icon: InfoCircleOutlined,
         },
         children: [{
+          path: 'self_check',
+          name: 'Self Check',
+          component: () => import('@/views/system/SelfCheck/SelfCheck.vue'),
+          meta: {
+            name: () => $gettext('Self Check'),
+          },
+        }, {
           path: 'about',
           name: 'About',
           component: () => import('@/views/system/About.vue'),

+ 99 - 0
app/src/views/system/SelfCheck/SelfCheck.vue

@@ -0,0 +1,99 @@
+<script setup lang="ts">
+import type { Report } from '@/api/self_check'
+import selfCheck from '@/api/self_check'
+import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons-vue'
+import tasks from './tasks'
+
+const data = ref<Report[]>()
+
+const loading = ref(false)
+const supportWebsocket = ref(false)
+
+function check() {
+  loading.value = true
+  selfCheck.run().then(r => {
+    data.value = r
+  }).finally(() => {
+    loading.value = false
+  })
+  const ws = selfCheck.websocket()
+  ws.onopen = () => {
+    supportWebsocket.value = true
+  }
+}
+
+onMounted(() => {
+  check()
+})
+
+const fixing = reactive({})
+
+function fix(taskName: string) {
+  fixing[taskName] = true
+  selfCheck.fix(taskName).then(() => {
+    check()
+  }).finally(() => {
+    fixing[taskName] = false
+  })
+}
+</script>
+
+<template>
+  <ACard :title="$gettext('Self Check')">
+    <template #extra>
+      <AButton
+        type="link" size="small" :loading @click="check"
+      >
+        {{ $gettext('Recheck') }}
+      </AButton>
+    </template>
+    <AList>
+      <AListItem v-for="(item, index) in data" :key="index">
+        <template v-if="item.err" #actions>
+          <AButton type="link" size="small" :loading="fixing[item.name]" @click="fix(item.name)">
+            {{ $gettext('Attempt to fix') }}
+          </AButton>
+        </template>
+        <AListItemMeta>
+          <template #title>
+            {{ tasks?.[item.name]?.name?.() }}
+          </template>
+          <template #description>
+            {{ tasks?.[item.name]?.description?.() }}
+          </template>
+          <template #avatar>
+            <div class="text-23px">
+              <CheckCircleOutlined v-if="!item.err" class="text-green" />
+              <CloseCircleOutlined v-else class="text-red" />
+            </div>
+          </template>
+        </AListItemMeta>
+      </AListItem>
+      <AListItem>
+        <AListItemMeta>
+          <template #title>
+            WebSocket
+          </template>
+          <template #description>
+            {{ $gettext('Support communication with the backend through the WebSocket protocol. '
+              + 'If your Nginx UI is being used via an Nginx reverse proxy, '
+              + 'please refer to this link to write the corresponding configuration file: '
+              + 'https://nginxui.com/guide/nginx-proxy-example.html') }}
+          </template>
+          <template #avatar>
+            <div class="text-23px">
+              <CheckCircleOutlined v-if="supportWebsocket" class="text-green" />
+              <CloseCircleOutlined v-else class="text-red" />
+            </div>
+          </template>
+        </AListItemMeta>
+      </AListItem>
+    </AList>
+  </ACard>
+</template>
+
+<style scoped lang="less">
+:deep(.ant-list-item-meta) {
+  align-items: center !important;
+}
+</style>

+ 20 - 0
app/src/views/system/SelfCheck/tasks.ts

@@ -0,0 +1,20 @@
+const tasks = {
+  'Directory-Sites': {
+    name: () => $gettext('Sites Directory'),
+    description: () => $gettext('Check if the sites-available and sites-enabled directories are under the nginx configuration directory.'),
+  },
+  'Directory-Streams': {
+    name: () => $gettext('Streams Directory'),
+    description: () => $gettext('Check if the streams-available and streams-enabled directories are under the nginx configuration directory.'),
+  },
+  'NginxConf-Sites-Enabled': {
+    name: () => $gettext('Nginx Conf Include Sites Enabled'),
+    description: () => $gettext('Check if the nginx.conf includes the sites-enabled directory.'),
+  },
+  'NginxConf-Streams-Enabled': {
+    name: () => $gettext('Nginx Conf Include Streams Enabled'),
+    description: () => $gettext('Check if the nginx.conf includes the streams-enabled directory.'),
+  },
+}
+
+export default tasks

+ 1 - 0
docs/guide/about.md

@@ -92,3 +92,4 @@ We welcome translations into any language.
 - [vue3-gettext](https://github.com/jshmrtn/vue3-gettext)
 - [vue3-ace-editor](https://github.com/CarterLi/vue3-ace-editor)
 - [Gonginx](https://github.com/tufanbarisyildirim/gonginx)
+- [lego](https://github.com/go-acme/lego)

+ 1 - 0
docs/zh_CN/guide/about.md

@@ -89,3 +89,4 @@ Nginx UI 可在以下平台中使用:
 - [vue3-gettext](https://github.com/jshmrtn/vue3-gettext)
 - [vue3-ace-editor](https://github.com/CarterLi/vue3-ace-editor)
 - [Gonginx](https://github.com/tufanbarisyildirim/gonginx)
+- [lego](https://github.com/go-acme/lego)

+ 1 - 0
docs/zh_TW/guide/about.md

@@ -89,3 +89,4 @@ Nginx UI 可在以下作業系統中使用:
 - [vue3-gettext](https://github.com/jshmrtn/vue3-gettext)
 - [vue3-ace-editor](https://github.com/CarterLi/vue3-ace-editor)
 - [Gonginx](https://github.com/tufanbarisyildirim/gonginx)
+- [lego](https://github.com/go-acme/lego)

+ 20 - 3
internal/nginx/config_args.go

@@ -39,6 +39,23 @@ func GetConfPath(dir ...string) (confPath string) {
 	return joined
 }
 
+func GetConfEntryPath() (path string) {
+	if settings.NginxSettings.ConfigPath == "" {
+		out := getNginxV()
+		r, _ := regexp.Compile("--conf-path=(.*.conf)")
+		match := r.FindStringSubmatch(out)
+		if len(match) < 1 {
+			logger.Error("nginx.GetConfEntryPath len(match) < 1")
+			return ""
+		}
+		path = match[1]
+	} else {
+		path = settings.NginxSettings.ConfigPath
+	}
+
+	return
+}
+
 func GetPIDPath() (path string) {
 	if settings.NginxSettings.PIDPath == "" {
 		out := getNginxV()
@@ -53,7 +70,7 @@ func GetPIDPath() (path string) {
 		path = settings.NginxSettings.PIDPath
 	}
 
-	return path
+	return
 }
 
 func GetSbinPath() (path string) {
@@ -66,7 +83,7 @@ func GetSbinPath() (path string) {
 	}
 	path = match[1]
 
-	return path
+	return
 }
 
 func GetAccessLogPath() (path string) {
@@ -83,7 +100,7 @@ func GetAccessLogPath() (path string) {
 		path = settings.NginxSettings.AccessLogPath
 	}
 
-	return path
+	return
 }
 
 func GetErrorLogPath() string {

+ 67 - 0
internal/self_check/directory.go

@@ -0,0 +1,67 @@
+package self_check
+
+import (
+	"os"
+
+	"github.com/0xJacky/Nginx-UI/internal/nginx"
+)
+
+// CheckSitesDirectory checks if sites-available/sites-enabled directory exists
+func CheckSitesDirectory() error {
+	// check sites-available directory
+	if _, err := os.Stat(nginx.GetConfPath("sites-available")); os.IsNotExist(err) {
+		return ErrSitesAvailableNotExist
+	}
+
+	// check sites-enabled directory
+	if _, err := os.Stat(nginx.GetConfPath("sites-enabled")); os.IsNotExist(err) {
+		return ErrSitesEnabledNotExist
+	}
+
+	return nil
+}
+
+// CheckStreamDirectory checks if stream-available/stream-enabled directory exists
+func CheckStreamDirectory() error {
+	// check stream-available directory
+	if _, err := os.Stat(nginx.GetConfPath("streams-available")); os.IsNotExist(err) {
+		return ErrStreamAvailableNotExist
+	}
+
+	// check stream-enabled directory
+	if _, err := os.Stat(nginx.GetConfPath("streams-enabled")); os.IsNotExist(err) {
+		return ErrStreamEnabledNotExist
+	}
+
+	return nil
+}
+
+// FixSitesDirectory creates sites-available/sites-enabled directory
+func FixSitesDirectory() error {
+	// create sites-available directory
+	if err := os.MkdirAll(nginx.GetConfPath("sites-available"), 0755); err != nil {
+		return err
+	}
+
+	// create sites-enabled directory
+	if err := os.MkdirAll(nginx.GetConfPath("sites-enabled"), 0755); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// FixStreamDirectory creates stream-available/stream-enabled directory
+func FixStreamDirectory() error {
+	// create stream-available directory
+	if err := os.MkdirAll(nginx.GetConfPath("streams-available"), 0755); err != nil {
+		return err
+	}
+
+	// create stream-enabled directory
+	if err := os.MkdirAll(nginx.GetConfPath("streams-enabled"), 0755); err != nil {
+		return err
+	}
+
+	return nil
+}

+ 19 - 0
internal/self_check/errors.go

@@ -0,0 +1,19 @@
+package self_check
+
+import "github.com/uozi-tech/cosy"
+
+var (
+	e                                   = cosy.NewErrorScope("self_check")
+	ErrTaskNotFound                     = e.New(4040, "Task not found")
+	ErrFailedToReadNginxConf            = e.New(4041, "Failed to read nginx.conf")
+	ErrParseNginxConf                   = e.New(5001, "Failed to parse nginx.conf")
+	ErrNginxConfNoHttpBlock             = e.New(4042, "Nginx conf no http block")
+	ErrNginxConfNotIncludeSitesEnabled  = e.New(4043, "Nginx conf not include sites-enabled")
+	ErrorNginxConfNoStreamBlock         = e.New(4044, "Nginx conf no stream block")
+	ErrNginxConfNotIncludeStreamEnabled = e.New(4045, "Nginx conf not include stream-enabled")
+	ErrFailedToCreateBackup             = e.New(5001, "Failed to create backup")
+	ErrSitesAvailableNotExist           = e.New(4046, "Sites-available directory not exist")
+	ErrSitesEnabledNotExist             = e.New(4047, "Sites-enabled directory not exist")
+	ErrStreamAvailableNotExist          = e.New(4048, "Stream-available directory not exist")
+	ErrStreamEnabledNotExist            = e.New(4049, "Stream-enabled directory not exist")
+)

+ 168 - 0
internal/self_check/nginx_conf.go

@@ -0,0 +1,168 @@
+package self_check
+
+import (
+	"fmt"
+	"os"
+	"time"
+
+	"github.com/0xJacky/Nginx-UI/internal/nginx"
+	"github.com/spf13/cast"
+	"github.com/tufanbarisyildirim/gonginx/config"
+	"github.com/tufanbarisyildirim/gonginx/dumper"
+	"github.com/tufanbarisyildirim/gonginx/parser"
+)
+
+// CheckNginxConfIncludeSites checks if nginx.conf include sites-enabled
+func CheckNginxConfIncludeSites() error {
+	path := nginx.GetConfEntryPath()
+
+	content, err := os.ReadFile(path)
+	if err != nil {
+		return ErrFailedToReadNginxConf
+	}
+
+	// parse nginx.conf
+	p := parser.NewStringParser(string(content), parser.WithSkipValidDirectivesErr())
+	c, err := p.Parse()
+	if err != nil {
+		return ErrParseNginxConf
+	}
+
+	// find http block
+	for _, v := range c.Block.Directives {
+		if v.GetName() == "http" {
+			// find include sites-enabled
+			for _, directive := range v.GetBlock().GetDirectives() {
+				if directive.GetName() == "include" && len(directive.GetParameters()) > 0 &&
+					directive.GetParameters()[0] == nginx.GetConfPath("sites-enabled/*") {
+					return nil
+				}
+			}
+			return ErrNginxConfNotIncludeSitesEnabled
+		}
+	}
+
+	return ErrNginxConfNoHttpBlock
+}
+
+// CheckNginxConfIncludeStreams checks if nginx.conf include streams-enabled
+func CheckNginxConfIncludeStreams() error {
+	path := nginx.GetConfEntryPath()
+
+	content, err := os.ReadFile(path)
+	if err != nil {
+		return ErrFailedToReadNginxConf
+	}
+
+	// parse nginx.conf
+	p := parser.NewStringParser(string(content), parser.WithSkipValidDirectivesErr())
+	c, err := p.Parse()
+	if err != nil {
+		return ErrParseNginxConf
+	}
+
+	// find http block
+	for _, v := range c.Block.Directives {
+		if v.GetName() == "stream" {
+			// find include sites-enabled
+			for _, directive := range v.GetBlock().GetDirectives() {
+				if directive.GetName() == "include" && len(directive.GetParameters()) > 0 &&
+					directive.GetParameters()[0] == nginx.GetConfPath("streams-enabled/*") {
+					return nil
+				}
+			}
+			return ErrNginxConfNotIncludeStreamEnabled
+		}
+	}
+
+	return ErrorNginxConfNoStreamBlock
+}
+
+// FixNginxConfIncludeSites attempts to fix nginx.conf include sites-enabled
+func FixNginxConfIncludeSites() error {
+	path := nginx.GetConfEntryPath()
+
+	content, err := os.ReadFile(path)
+	if err != nil {
+		return ErrFailedToReadNginxConf
+	}
+
+	// create a backup file (+.bak.timestamp)
+	backupPath := path + ".bak." + cast.ToString(time.Now().Unix())
+	err = os.WriteFile(backupPath, content, 0644)
+	if err != nil {
+		return ErrFailedToCreateBackup
+	}
+
+	// parse nginx.conf
+	p := parser.NewStringParser(string(content), parser.WithSkipValidDirectivesErr())
+	c, err := p.Parse()
+	if err != nil {
+		return ErrParseNginxConf
+	}
+
+	// find http block
+	for _, v := range c.Block.Directives {
+		if v.GetName() == "http" {
+			// add include sites-enabled/* to http block
+			includeDirective := &config.Directive{
+				Name:       "include",
+				Parameters: []string{nginx.GetConfPath("sites-enabled/*")},
+			}
+
+			realBlock := v.GetBlock().(*config.HTTP)
+			realBlock.Directives = append(realBlock.Directives, includeDirective)
+
+			// write to file
+			return os.WriteFile(path, []byte(dumper.DumpBlock(c.Block, dumper.IndentedStyle)), 0644)
+		}
+	}
+
+	// if no http block, append http block with include sites-enabled/*
+	content = append(content, []byte(fmt.Sprintf("\nhttp {\n\tinclude %s;\n}\n", nginx.GetConfPath("sites-enabled/*")))...)
+	return os.WriteFile(path, content, 0644)
+}
+
+// FixNginxConfIncludeStreams attempts to fix nginx.conf include streams-enabled
+func FixNginxConfIncludeStreams() error {
+	path := nginx.GetConfEntryPath()
+
+	content, err := os.ReadFile(path)
+	if err != nil {
+		return ErrFailedToReadNginxConf
+	}
+
+	// create a backup file (+.bak.timestamp)
+	backupPath := path + ".bak." + cast.ToString(time.Now().Unix())
+	err = os.WriteFile(backupPath, content, 0644)
+	if err != nil {
+		return ErrFailedToCreateBackup
+	}
+
+	// parse nginx.conf
+	p := parser.NewStringParser(string(content), parser.WithSkipValidDirectivesErr())
+	c, err := p.Parse()
+	if err != nil {
+		return ErrParseNginxConf
+	}
+
+	// find stream block
+	for _, v := range c.Block.Directives {
+		if v.GetName() == "stream" {
+			// add include streams-enabled/* to stream block
+			includeDirective := &config.Directive{
+				Name:       "include",
+				Parameters: []string{nginx.GetConfPath("streams-enabled/*")},
+			}
+			realBlock := v.GetBlock().(*config.Block)
+			realBlock.Directives = append(realBlock.Directives, includeDirective)
+
+			// write to file
+			return os.WriteFile(path, []byte(dumper.DumpBlock(c.Block, dumper.IndentedStyle)), 0644)
+		}
+	}
+
+	// if no stream block, append stream block with include streams-enabled/*
+	content = append(content, []byte(fmt.Sprintf("\nstream {\n\tinclude %s;\n}\n", nginx.GetConfPath("streams-enabled/*")))...)
+	return os.WriteFile(path, content, 0644)
+}

+ 123 - 0
internal/self_check/nginx_conf_test.go

@@ -0,0 +1,123 @@
+package self_check
+
+import (
+	"errors"
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/0xJacky/Nginx-UI/settings"
+	"github.com/stretchr/testify/assert"
+	"github.com/uozi-tech/cosy"
+	"github.com/uozi-tech/cosy/logger"
+)
+
+func TestCheckNginxConfIncludeSites(t *testing.T) {
+	// test ok
+	logger.Init("debug")
+	settings.NginxSettings.ConfigDir = "/etc/nginx"
+	settings.NginxSettings.ConfigPath = "./test_cases/ok.conf"
+	var result *cosy.Error
+	errors.As(CheckNginxConfIncludeSites(), &result)
+	assert.Nil(t, result)
+
+	// test 4041 nginx.conf not found
+	settings.NginxSettings.ConfigDir = "/etc/nginx"
+	settings.NginxSettings.ConfigPath = "./test_cases/4041.conf"
+	errors.As(CheckNginxConfIncludeSites(), &result)
+	assert.Equal(t, int32(4041), result.Code)
+
+	// test 5001 nginx.conf parse error
+	settings.NginxSettings.ConfigDir = "/etc/nginx"
+	settings.NginxSettings.ConfigPath = "./test_cases/5001.conf"
+	errors.As(CheckNginxConfIncludeSites(), &result)
+	assert.Equal(t, int32(5001), result.Code)
+
+	// test 4042 nginx.conf no http block
+	settings.NginxSettings.ConfigDir = "/etc/nginx"
+	settings.NginxSettings.ConfigPath = "./test_cases/no-http-block.conf"
+	errors.As(CheckNginxConfIncludeSites(), &result)
+	assert.Equal(t, int32(4042), result.Code)
+
+	// test 4043 nginx.conf not include sites-enabled
+	settings.NginxSettings.ConfigDir = "/etc/nginx"
+	settings.NginxSettings.ConfigPath = "./test_cases/no-http-sites-enabled.conf"
+	errors.As(CheckNginxConfIncludeSites(), &result)
+	assert.Equal(t, int32(4043), result.Code)
+}
+
+func TestCheckNginxConfIncludeStreams(t *testing.T) {
+	// test ok
+	logger.Init("debug")
+	settings.NginxSettings.ConfigDir = "/etc/nginx"
+	settings.NginxSettings.ConfigPath = "./test_cases/ok.conf"
+	var result *cosy.Error
+	errors.As(CheckNginxConfIncludeStreams(), &result)
+	assert.Nil(t, result)
+
+	// test 4041 nginx.conf not found
+	settings.NginxSettings.ConfigDir = "/etc/nginx"
+	settings.NginxSettings.ConfigPath = "./test_cases/4041.conf"
+	errors.As(CheckNginxConfIncludeStreams(), &result)
+	assert.Equal(t, int32(4041), result.Code)
+
+	// test 5001 nginx.conf parse error
+	settings.NginxSettings.ConfigDir = "/etc/nginx"
+	settings.NginxSettings.ConfigPath = "./test_cases/5001.conf"
+	errors.As(CheckNginxConfIncludeStreams(), &result)
+	assert.Equal(t, int32(5001), result.Code)
+
+	// test 4044 nginx.conf no stream block
+	settings.NginxSettings.ConfigDir = "/etc/nginx"
+	settings.NginxSettings.ConfigPath = "./test_cases/no-http-block.conf"
+	errors.As(CheckNginxConfIncludeStreams(), &result)
+	assert.Equal(t, int32(4044), result.Code)
+
+	// test 4045 nginx.conf not include stream-enabled
+	settings.NginxSettings.ConfigDir = "/etc/nginx"
+	settings.NginxSettings.ConfigPath = "./test_cases/no-http-sites-enabled.conf"
+	errors.As(CheckNginxConfIncludeStreams(), &result)
+	assert.Equal(t, int32(4045), result.Code)
+}
+
+func TestFixNginxConfIncludeSites(t *testing.T) {
+	logger.Init("debug")
+	settings.NginxSettings.ConfigDir = "/etc/nginx"
+
+	// copy file
+	content, err := os.ReadFile("./test_cases/no-http-block.conf")
+	assert.Nil(t, err)
+
+	err = os.WriteFile("./test_cases/no-http-block-fixed.conf", content, 0644)
+	assert.Nil(t, err)
+
+	settings.NginxSettings.ConfigPath = "./test_cases/no-http-block-fixed.conf"
+	var result *cosy.Error
+	errors.As(FixNginxConfIncludeSites(), &result)
+	assert.Nil(t, result)
+
+	// copy file
+	content, err = os.ReadFile("./test_cases/no-http-sites-enabled.conf")
+	assert.Nil(t, err)
+	err = os.WriteFile("./test_cases/no-http-sites-enabled-fixed.conf", content, 0644)
+	assert.Nil(t, err)
+
+	settings.NginxSettings.ConfigPath = "./test_cases/no-http-sites-enabled-fixed.conf"
+	errors.As(FixNginxConfIncludeSites(), &result)
+	assert.Nil(t, result)
+
+	settings.NginxSettings.ConfigPath = "./test_cases/no-http-sites-enabled-fixed.conf"
+	errors.As(FixNginxConfIncludeStreams(), &result)
+	assert.Nil(t, result)
+
+	// remove backup files (./test_cases/*.bak.*)
+	files, err := os.ReadDir("./test_cases")
+	assert.Nil(t, err)
+
+	for _, file := range files {
+		if strings.Contains(file.Name(), ".bak.") {
+			err = os.Remove("./test_cases/" + file.Name())
+			assert.Nil(t, err)
+		}
+	}
+}

+ 74 - 0
internal/self_check/self_check.go

@@ -0,0 +1,74 @@
+package self_check
+
+import (
+	"errors"
+
+	"github.com/uozi-tech/cosy"
+)
+
+type Task struct {
+	Name      string
+	CheckFunc func() error
+	FixFunc   func() error
+}
+
+type Report struct {
+	Name string      `json:"name"`
+	Err  *cosy.Error `json:"err,omitempty"`
+}
+
+type Reports []*Report
+
+var selfCheckTasks = []*Task{
+	{
+		Name:      "Directory-Sites",
+		CheckFunc: CheckSitesDirectory,
+		FixFunc:   FixSitesDirectory,
+	},
+	{
+		Name:      "Directory-Streams",
+		CheckFunc: CheckStreamDirectory,
+		FixFunc:   FixStreamDirectory,
+	},
+	{
+		Name:      "NginxConf-Sites-Enabled",
+		CheckFunc: CheckNginxConfIncludeSites,
+		FixFunc:   FixNginxConfIncludeSites,
+	},
+	{
+		Name:      "NginxConf-Streams-Enabled",
+		CheckFunc: CheckNginxConfIncludeStreams,
+		FixFunc:   FixNginxConfIncludeStreams,
+	},
+}
+
+var selfCheckTaskMap = make(map[string]*Task)
+
+func init() {
+	for _, task := range selfCheckTasks {
+		selfCheckTaskMap[task.Name] = task
+	}
+}
+
+func Run() (reports Reports) {
+	reports = make(Reports, 0)
+	for _, task := range selfCheckTasks {
+		var cErr *cosy.Error
+		if err := task.CheckFunc(); err != nil {
+			errors.As(err, &cErr)
+		}
+		reports = append(reports, &Report{
+			Name: task.Name,
+			Err:  cErr,
+		})
+	}
+	return
+}
+
+func AttemptFix(taskName string) (err error) {
+	task, ok := selfCheckTaskMap[taskName]
+	if !ok {
+		return ErrTaskNotFound
+	}
+	return task.FixFunc()
+}

+ 1 - 0
internal/self_check/test_cases/5001.conf

@@ -0,0 +1 @@
+5001.conf

+ 14 - 0
internal/self_check/test_cases/no-http-block-fixed.conf

@@ -0,0 +1,14 @@
+user  nginx;
+worker_processes  auto;
+
+error_log  /var/log/nginx/error.log notice;
+error_log  /var/log/nginx/error.local.log notice;
+pid        /var/run/nginx.pid;
+
+events {
+    worker_connections  1024;
+}
+
+http {
+	include /etc/nginx/sites-enabled/*;
+}

+ 10 - 0
internal/self_check/test_cases/no-http-block.conf

@@ -0,0 +1,10 @@
+user  nginx;
+worker_processes  auto;
+
+error_log  /var/log/nginx/error.log notice;
+error_log  /var/log/nginx/error.local.log notice;
+pid        /var/run/nginx.pid;
+
+events {
+    worker_connections  1024;
+}

+ 23 - 0
internal/self_check/test_cases/no-http-sites-enabled-fixed.conf

@@ -0,0 +1,23 @@
+user nginx;
+worker_processes auto;
+error_log /var/log/nginx/error.log notice;
+error_log /var/log/nginx/error.local.log notice;
+pid /var/run/nginx.pid;
+events {
+    worker_connections 1024;
+}
+stream {
+    include /etc/nginx/streams-enabled/*;
+}
+http {
+    include /etc/nginx/mime.types;
+    default_type application/octet-stream;
+    log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';
+    access_log /var/log/nginx/access.log main;
+    sendfile on;
+    #tcp_nopush     on;
+    keepalive_timeout 65;
+    gzip on;
+    include /etc/nginx/conf.d/*.conf;
+    include /etc/nginx/sites-enabled/*;
+}

+ 34 - 0
internal/self_check/test_cases/no-http-sites-enabled.conf

@@ -0,0 +1,34 @@
+user  nginx;
+worker_processes  auto;
+
+error_log  /var/log/nginx/error.log notice;
+error_log  /var/log/nginx/error.local.log notice;
+pid        /var/run/nginx.pid;
+
+events {
+    worker_connections  1024;
+}
+
+stream {
+
+}
+
+http {
+    include       /etc/nginx/mime.types;
+    default_type  application/octet-stream;
+
+    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+                      '$status $body_bytes_sent "$http_referer" '
+                      '"$http_user_agent" "$http_x_forwarded_for"';
+
+    access_log  /var/log/nginx/access.log  main;
+
+    sendfile        on;
+    #tcp_nopush     on;
+
+    keepalive_timeout  65;
+
+    gzip  on;
+
+    include /etc/nginx/conf.d/*.conf;
+}

+ 35 - 0
internal/self_check/test_cases/ok.conf

@@ -0,0 +1,35 @@
+user  nginx;
+worker_processes  auto;
+
+error_log  /var/log/nginx/error.log notice;
+error_log  /var/log/nginx/error.local.log notice;
+pid        /var/run/nginx.pid;
+
+events {
+    worker_connections  1024;
+}
+
+stream {
+    include /etc/nginx/streams-enabled/*;
+}
+
+http {
+    include       /etc/nginx/mime.types;
+    default_type  application/octet-stream;
+
+    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+                      '$status $body_bytes_sent "$http_referer" '
+                      '"$http_user_agent" "$http_x_forwarded_for"';
+
+    access_log  /var/log/nginx/access.log  main;
+
+    sendfile        on;
+    #tcp_nopush     on;
+
+    keepalive_timeout  65;
+
+    gzip  on;
+
+    include /etc/nginx/conf.d/*.conf;
+    include /etc/nginx/sites-enabled/*;
+}

+ 1 - 0
internal/self_check/websocket.go

@@ -0,0 +1 @@
+package self_check

+ 1 - 0
settings/nginx.go

@@ -5,6 +5,7 @@ type Nginx struct {
 	ErrorLogPath    string   `json:"error_log_path" protected:"true"`
 	LogDirWhiteList []string `json:"log_dir_white_list" protected:"true"`
 	ConfigDir       string   `json:"config_dir" protected:"true"`
+	ConfigPath      string   `json:"config_path" protected:"true"`
 	PIDPath         string   `json:"pid_path" protected:"true"`
 	TestConfigCmd   string   `json:"test_config_cmd" protected:"true"`
 	ReloadCmd       string   `json:"reload_cmd" protected:"true"`