Browse Source

Merge pull request #977 from 0xJacky/feat/docker-ui-only

feat: manage nginx in another docker container
Jacky 1 week ago
parent
commit
ea90da43f7
90 changed files with 2363 additions and 572 deletions
  1. 23 0
      .devcontainer/docker-compose.yml
  2. 1 1
      .devcontainer/init-nginx.sh
  3. 1 1
      .devcontainer/start.sh
  4. 6 1
      api/config/add.go
  5. 21 4
      api/nginx/control.go
  6. 1 1
      api/nginx/router.go
  7. 6 3
      api/nginx/status.go
  8. 1 0
      api/settings/settings.go
  9. 1 0
      app/src/api/settings.ts
  10. 3 0
      app/src/components/Breadcrumb/index.ts
  11. 62 3
      app/src/components/CodeEditor/CodeCompletion.ts
  12. 3 0
      app/src/components/EnvGroupTabs/index.ts
  13. 3 0
      app/src/components/EnvIndicator/index.ts
  14. 3 0
      app/src/components/ICP/index.ts
  15. 3 0
      app/src/components/Logo/index.ts
  16. 3 0
      app/src/components/NginxControl/index.ts
  17. 3 0
      app/src/components/NodeSelector/index.ts
  18. 3 0
      app/src/components/Notification/index.ts
  19. 3 0
      app/src/components/OTPInput/index.ts
  20. 3 0
      app/src/components/PageHeader/index.ts
  21. 3 0
      app/src/components/ReactiveFromNow/index.ts
  22. 3 0
      app/src/components/SensitiveString/index.ts
  23. 3 0
      app/src/components/SetLanguage/index.ts
  24. 1 1
      app/src/components/SwitchAppearance/SwitchAppearance.vue
  25. 3 0
      app/src/components/SwitchAppearance/index.ts
  26. 3 0
      app/src/components/SystemRestore/index.ts
  27. 8 0
      app/src/components/TwoFA/index.ts
  28. 3 0
      app/src/components/VPSwitch/index.ts
  29. 15 0
      app/src/constants/errors/docker.ts
  30. 1 0
      app/src/constants/errors/nginx.ts
  31. 87 21
      app/src/language/ar/app.po
  32. 88 22
      app/src/language/de_DE/app.po
  33. 87 22
      app/src/language/en/app.po
  34. 87 21
      app/src/language/es/app.po
  35. 86 21
      app/src/language/fr_FR/app.po
  36. 87 22
      app/src/language/ko_KR/app.po
  37. 73 21
      app/src/language/messages.pot
  38. 87 21
      app/src/language/ru_RU/app.po
  39. 87 21
      app/src/language/tr_TR/app.po
  40. 73 21
      app/src/language/uk_UA/app.po
  41. 87 22
      app/src/language/vi_VN/app.po
  42. 87 32
      app/src/language/zh_CN/app.po
  43. 87 21
      app/src/language/zh_TW/app.po
  44. 19 133
      app/src/views/preference/Preference.vue
  45. 0 0
      app/src/views/preference/components/AuthSettings/AddPasskey.vue
  46. 2 2
      app/src/views/preference/components/AuthSettings/Passkey.vue
  47. 1 1
      app/src/views/preference/components/AuthSettings/RecoveryCodes.vue
  48. 2 2
      app/src/views/preference/components/AuthSettings/TOTP.vue
  49. 11 0
      app/src/views/preference/components/AuthSettings/index.ts
  50. 127 0
      app/src/views/preference/store/index.ts
  51. 4 3
      app/src/views/preference/tabs/AppSettings.vue
  52. 6 6
      app/src/views/preference/tabs/AuthSettings.vue
  53. 3 3
      app/src/views/preference/tabs/CertSettings.vue
  54. 2 2
      app/src/views/preference/tabs/ExternalNotify.vue
  55. 3 3
      app/src/views/preference/tabs/HTTPSettings.vue
  56. 3 2
      app/src/views/preference/tabs/LogrotateSettings.vue
  57. 16 2
      app/src/views/preference/tabs/NginxSettings.vue
  58. 4 4
      app/src/views/preference/tabs/NodeSettings.vue
  59. 3 3
      app/src/views/preference/tabs/OpenAISettings.vue
  60. 3 2
      app/src/views/preference/tabs/ServerSettings.vue
  61. 3 2
      app/src/views/preference/tabs/TerminalSettings.vue
  62. 11 0
      app/src/views/preference/tabs/index.ts
  63. 20 6
      docs/guide/config-nginx.md
  64. 18 4
      docs/zh_CN/guide/config-nginx.md
  65. 20 4
      docs/zh_TW/guide/config-nginx.md
  66. 13 0
      go.mod
  67. 37 9
      go.sum
  68. 3 2
      internal/cmd/main.go
  69. 23 0
      internal/cmd/upgrade_docker.go
  70. 5 1
      internal/config/save.go
  71. 22 0
      internal/docker/docker.go
  72. 20 0
      internal/docker/errors.go
  73. 80 0
      internal/docker/exec.go
  74. 329 0
      internal/docker/ota.go
  75. 29 0
      internal/docker/stat_path.go
  76. 58 0
      internal/docker/status.go
  77. 13 0
      internal/kernel/boot.go
  78. 1 0
      internal/nginx/errors.go
  79. 28 0
      internal/nginx/exec.go
  80. 38 51
      internal/nginx/nginx.go
  81. 4 1
      internal/site/disable.go
  82. 8 2
      internal/site/enable.go
  83. 16 4
      internal/site/maintenance.go
  84. 13 6
      internal/site/rename.go
  85. 9 2
      internal/site/save.go
  86. 4 1
      internal/stream/disable.go
  87. 8 2
      internal/stream/enable.go
  88. 8 2
      internal/stream/rename.go
  89. 9 2
      internal/stream/save.go
  90. 5 0
      settings/nginx.go

+ 23 - 0
.devcontainer/docker-compose.yml

@@ -7,6 +7,7 @@ services:
       - ../..:/workspaces:cached
       - ./go-path:/root/go
       - ./data/nginx:/etc/nginx
+      - /var/run/docker.sock:/var/run/docker.sock
     command: sleep infinity
     environment:
       - NGINX_UI_CERT_CA_DIR=https://pebble:14000/dir
@@ -25,6 +26,28 @@ services:
       - nginx-ui
     networks:
       nginxui:
+  nginx-ui-3:
+    image: nginx-ui-dev
+    container_name: nginx-ui-3
+    volumes:
+      - ../..:/workspaces:cached
+      - ./data/nginx-ui-3/nginx:/etc/nginx
+      - ./data/nginx-ui-3/nginx-ui:/etc/nginx-ui
+      - /var/run/docker.sock:/var/run/docker.sock
+    working_dir: /workspaces/nginx-ui
+    command: ./.devcontainer/node-supervisor.sh
+    depends_on:
+      - nginx-ui
+    networks:
+      nginxui:
+  nginx:
+    image: nginx-ui-dev
+    container_name: nginx
+    volumes:
+      - ./data/nginx-ui-3/nginx:/etc/nginx
+    command: sleep infinity
+    networks:
+      nginxui:
   pebble:
     image: ghcr.io/letsencrypt/pebble:latest
     volumes:

+ 1 - 1
.devcontainer/init-nginx.sh

@@ -6,4 +6,4 @@ if [ "$(ls -A /etc/nginx)" = "" ]; then
 fi
 
 # start nginx
-nginx -g "daemon off;"
+nginx

+ 1 - 1
.devcontainer/start.sh

@@ -3,7 +3,7 @@
 # install air
 go install github.com/air-verse/air@latest
 
-# install zsh-autosuggestions
+install zsh-autosuggestions
 git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions
 
 if ! grep -q "zsh-autosuggestions" ~/.zshrc; then

+ 6 - 1
api/config/add.go

@@ -74,7 +74,12 @@ func AddConfig(c *gin.Context) {
 		return
 	}
 
-	output := nginx.Reload()
+	output, err := nginx.Reload()
+	if err != nil {
+		cosy.ErrHandler(c, err)
+		return
+	}
+
 	if nginx.GetLogLevel(output) >= nginx.Warn {
 		cosy.ErrHandler(c, cosy.WrapErrorWithParams(config.ErrNginxReloadFailed, output))
 		return

+ 21 - 4
api/nginx/control.go

@@ -5,24 +5,36 @@ import (
 
 	"github.com/0xJacky/Nginx-UI/internal/nginx"
 	"github.com/gin-gonic/gin"
+	"github.com/uozi-tech/cosy"
 )
 
+// Reload reloads the nginx
 func Reload(c *gin.Context) {
-	output := nginx.Reload()
+	output, err := nginx.Reload()
+	if err != nil {
+		cosy.ErrHandler(c, err)
+		return
+	}
 	c.JSON(http.StatusOK, gin.H{
 		"message": output,
 		"level":   nginx.GetLogLevel(output),
 	})
 }
 
-func Test(c *gin.Context) {
-	output := nginx.TestConf()
+// TestConfig tests the nginx config
+func TestConfig(c *gin.Context) {
+	output, err := nginx.TestConfig()
+	if err != nil {
+		cosy.ErrHandler(c, err)
+		return
+	}
 	c.JSON(http.StatusOK, gin.H{
 		"message": output,
 		"level":   nginx.GetLogLevel(output),
 	})
 }
 
+// Restart restarts the nginx
 func Restart(c *gin.Context) {
 	c.JSON(http.StatusOK, gin.H{
 		"message": "ok",
@@ -30,8 +42,13 @@ func Restart(c *gin.Context) {
 	go nginx.Restart()
 }
 
+// Status returns the status of the nginx
 func Status(c *gin.Context) {
-	lastOutput := nginx.GetLastOutput()
+	lastOutput, err := nginx.GetLastOutput()
+	if err != nil {
+		cosy.ErrHandler(c, err)
+		return
+	}
 
 	running := nginx.IsNginxRunning()
 

+ 1 - 1
api/nginx/router.go

@@ -11,7 +11,7 @@ func InitRouter(r *gin.RouterGroup) {
 	r.POST("ngx/format_code", FormatNginxConfig)
 	r.POST("nginx/reload", Reload)
 	r.POST("nginx/restart", Restart)
-	r.POST("nginx/test", Test)
+	r.POST("nginx/test", TestConfig)
 	r.GET("nginx/status", Status)
 	// Get detailed Nginx status information, including connection count, process information, etc. (Issue #850)
 	r.GET("nginx/detail_status", GetDetailStatus)

+ 6 - 3
api/nginx/status.go

@@ -4,7 +4,6 @@
 package nginx
 
 import (
-	"errors"
 	"net/http"
 	"strings"
 	"time"
@@ -119,10 +118,14 @@ func ToggleStubStatus(c *gin.Context) {
 	}
 
 	// Reload Nginx configuration
-	reloadOutput := nginx.Reload()
+	reloadOutput, err := nginx.Reload()
+	if err != nil {
+		cosy.ErrHandler(c, err)
+		return
+	}
 	if len(reloadOutput) > 0 && (strings.Contains(strings.ToLower(reloadOutput), "error") ||
 		strings.Contains(strings.ToLower(reloadOutput), "failed")) {
-		cosy.ErrHandler(c, errors.New("Reload Nginx failed"))
+		cosy.ErrHandler(c, cosy.WrapErrorWithParams(nginx.ErrReloadFailed, reloadOutput))
 		return
 	}
 

+ 1 - 0
api/settings/settings.go

@@ -25,6 +25,7 @@ func GetSettings(c *gin.Context) {
 	settings.NginxSettings.ErrorLogPath = nginx.GetErrorLogPath()
 	settings.NginxSettings.ConfigDir = nginx.GetConfPath()
 	settings.NginxSettings.PIDPath = nginx.GetPIDPath()
+	settings.NginxSettings.StubStatusPort = settings.NginxSettings.GetStubStatusPort()
 
 	if settings.NginxSettings.ReloadCmd == "" {
 		settings.NginxSettings.ReloadCmd = "nginx -s reload"

+ 1 - 0
app/src/api/settings.ts

@@ -64,6 +64,7 @@ export interface NginxSettings {
   reload_cmd: string
   restart_cmd: string
   stub_status_port: number
+  container_name: string
 }
 
 export interface NodeSettings {

+ 3 - 0
app/src/components/Breadcrumb/index.ts

@@ -0,0 +1,3 @@
+import Breadcrumb from './Breadcrumb.vue'
+
+export default Breadcrumb

+ 62 - 3
app/src/components/CodeEditor/CodeCompletion.ts

@@ -12,12 +12,39 @@ function debug(...args: any[]) {
   }
 }
 
+// Config file patterns and extensions
+const CONFIG_FILE_EXTENSIONS = ['.conf', '.config']
+const SENSITIVE_CONTENT_PATTERNS = [
+  /-----BEGIN [A-Z ]+ PRIVATE KEY-----/,
+  /-----BEGIN CERTIFICATE-----/,
+  /apiKey\s*[:=]\s*["'][a-zA-Z0-9]+["']/,
+  /password\s*[:=]\s*["'][^"']+["']/,
+  /secret\s*[:=]\s*["'][^"']+["']/,
+]
+
 function useCodeCompletion() {
   const editorRef = ref<Editor>()
   const currentGhostText = ref<string>('')
+  const isConfigFile = ref<boolean>(false)
 
   const ws = openai.code_completion()
 
+  // Check if the current file is a configuration file
+  function checkIfConfigFile(filename: string, content: string): boolean {
+    // Check file extension
+    const hasConfigExtension = CONFIG_FILE_EXTENSIONS.some(ext => filename.toLowerCase().endsWith(ext))
+
+    // Check if it's an Nginx configuration file based on common patterns
+    const hasNginxPatterns = /server\s*\{|location\s*\/|http\s*\{|upstream\s*[\w-]+\s*\{/.test(content)
+
+    return hasConfigExtension || hasNginxPatterns
+  }
+
+  // Check if content contains sensitive information that shouldn't be sent
+  function containsSensitiveContent(content: string): boolean {
+    return SENSITIVE_CONTENT_PATTERNS.some(pattern => pattern.test(content))
+  }
+
   function getAISuggestions(code: string, context: string, position: Point, callback: (suggestion: string) => void, language: string = 'nginx', suffix: string = '', requestId: string) {
     if (!ws || ws.readyState !== WebSocket.OPEN) {
       debug('WebSocket is not open')
@@ -29,6 +56,17 @@ function useCodeCompletion() {
       return
     }
 
+    // Skip if not a config file or contains sensitive content
+    if (!isConfigFile.value) {
+      debug('Skipping AI suggestions for non-config file')
+      return
+    }
+
+    if (containsSensitiveContent(context)) {
+      debug('Skipping AI suggestions due to sensitive content')
+      return
+    }
+
     const message = {
       context,
       code,
@@ -57,8 +95,20 @@ function useCodeCompletion() {
       return
     }
 
+    if (!isConfigFile.value) {
+      debug('Skipping ghost text for non-config file')
+      return
+    }
+
     try {
       const currentText = editorRef.value.getValue()
+
+      // Skip if content contains sensitive information
+      if (containsSensitiveContent(currentText)) {
+        debug('Skipping ghost text due to sensitive content')
+        return
+      }
+
       const cursorPosition = editorRef.value.getCursorPosition()
 
       // Get all text before the current cursor position as the code part for the request
@@ -175,7 +225,7 @@ function useCodeCompletion() {
 
   debug('Editor initialized')
 
-  async function init(editor: Editor) {
+  async function init(editor: Editor, filename: string = '') {
     const { enabled } = await openai.get_code_completion_enabled_status()
     if (!enabled) {
       debug('Code completion is not enabled')
@@ -184,6 +234,11 @@ function useCodeCompletion() {
 
     editorRef.value = editor
 
+    // Determine if the current file is a configuration file
+    const content = editor.getValue()
+    isConfigFile.value = checkIfConfigFile(filename, content)
+    debug(`File type check: isConfigFile=${isConfigFile.value}, filename=${filename}`)
+
     // Set up Tab key handler
     setupTabHandler(editor)
 
@@ -195,7 +250,9 @@ function useCodeCompletion() {
 
         if (e.action === 'insert' || e.action === 'remove') {
           // Clear current ghost text
-          debouncedApplyGhostText()
+          if (isConfigFile.value) {
+            debouncedApplyGhostText()
+          }
         }
       })
 
@@ -203,7 +260,9 @@ function useCodeCompletion() {
       editor.selection.on('changeCursor', () => {
         debug('Cursor changed')
         clearGhostText()
-        debouncedApplyGhostText()
+        if (isConfigFile.value) {
+          debouncedApplyGhostText()
+        }
       })
     }, 2000)
   }

+ 3 - 0
app/src/components/EnvGroupTabs/index.ts

@@ -0,0 +1,3 @@
+import EnvGroupTabs from './EnvGroupTabs.vue'
+
+export default EnvGroupTabs

+ 3 - 0
app/src/components/EnvIndicator/index.ts

@@ -0,0 +1,3 @@
+import EnvIndicator from './EnvIndicator.vue'
+
+export default EnvIndicator

+ 3 - 0
app/src/components/ICP/index.ts

@@ -0,0 +1,3 @@
+import ICP from './ICP.vue'
+
+export default ICP

+ 3 - 0
app/src/components/Logo/index.ts

@@ -0,0 +1,3 @@
+import Logo from './Logo.vue'
+
+export default Logo

+ 3 - 0
app/src/components/NginxControl/index.ts

@@ -0,0 +1,3 @@
+import NginxControl from './NginxControl.vue'
+
+export default NginxControl

+ 3 - 0
app/src/components/NodeSelector/index.ts

@@ -0,0 +1,3 @@
+import NodeSelector from './NodeSelector.vue'
+
+export default NodeSelector

+ 3 - 0
app/src/components/Notification/index.ts

@@ -0,0 +1,3 @@
+import Notification from './Notification.vue'
+
+export default Notification

+ 3 - 0
app/src/components/OTPInput/index.ts

@@ -0,0 +1,3 @@
+import OTPInput from './OTPInput.vue'
+
+export default OTPInput

+ 3 - 0
app/src/components/PageHeader/index.ts

@@ -0,0 +1,3 @@
+import PageHeader from './PageHeader.vue'
+
+export default PageHeader

+ 3 - 0
app/src/components/ReactiveFromNow/index.ts

@@ -0,0 +1,3 @@
+import ReactiveFromNow from './ReactiveFromNow.vue'
+
+export default ReactiveFromNow

+ 3 - 0
app/src/components/SensitiveString/index.ts

@@ -0,0 +1,3 @@
+import SensitiveString from './SensitiveString.vue'
+
+export default SensitiveString

+ 3 - 0
app/src/components/SetLanguage/index.ts

@@ -0,0 +1,3 @@
+import SetLanguage from './SetLanguage.vue'
+
+export default SetLanguage

+ 1 - 1
app/src/components/SwitchAppearance/SwitchAppearance.vue

@@ -1,6 +1,6 @@
 <script lang="ts" setup>
 import type { Ref } from 'vue'
-import VPSwitch from '@/components/VPSwitch/VPSwitch.vue'
+import VPSwitch from '@/components/VPSwitch'
 import { useSettingsStore } from '@/pinia'
 import VPIconMoon from './icons/VPIconMoon.vue'
 import VPIconSun from './icons/VPIconSun.vue'

+ 3 - 0
app/src/components/SwitchAppearance/index.ts

@@ -0,0 +1,3 @@
+import SwitchAppearance from './SwitchAppearance.vue'
+
+export default SwitchAppearance

+ 3 - 0
app/src/components/SystemRestore/index.ts

@@ -0,0 +1,3 @@
+import SystemRestoreContainer from './SystemRestoreContent.vue'
+
+export default SystemRestoreContainer

+ 8 - 0
app/src/components/TwoFA/index.ts

@@ -0,0 +1,8 @@
+import Authorization from './Authorization.vue'
+import use2FAModal from './use2FAModal'
+
+export default Authorization
+
+export {
+  use2FAModal,
+}

+ 3 - 0
app/src/components/VPSwitch/index.ts

@@ -0,0 +1,3 @@
+import VPSwitch from './VPSwitch.vue'
+
+export default VPSwitch

+ 15 - 0
app/src/constants/errors/docker.ts

@@ -0,0 +1,15 @@
+export default {
+  500001: () => $gettext('Docker client not initialized'),
+  500002: () => $gettext('Failed to exec command: {0}'),
+  500003: () => $gettext('Failed to attach to exec instance: {0}'),
+  500004: () => $gettext('Failed to read output: {0}'),
+  500005: () => $gettext('Command exited with unexpected exit code: {0}, error: {1}'),
+  500006: () => $gettext('Container status unknown'),
+  500007: () => $gettext('Failed to inspect container: {0}'),
+  500008: () => $gettext('Nginx is not running in another container'),
+  500009: () => $gettext('Failed to get hostname: {0}'),
+  500010: () => $gettext('Failed to pull image: {0}'),
+  500011: () => $gettext('Failed to inspect current container: {0}'),
+  500012: () => $gettext('Failed to create temp container: {0}'),
+  500013: () => $gettext('Failed to start temp container: {0}'),
+}

+ 1 - 0
app/src/constants/errors/nginx.ts

@@ -1,3 +1,4 @@
 export default {
   50001: () => $gettext('Block is nil'),
+  50002: () => $gettext('Reload nginx failed: {0}'),
 }

+ 87 - 21
app/src/language/ar/app.po

@@ -80,7 +80,7 @@ msgid "Add a passkey"
 msgstr "أضف مفتاح مرور"
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 msgid "Add Configuration"
 msgstr "إضافة تكوين"
 
@@ -301,7 +301,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr ""
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -355,7 +355,7 @@ msgstr ""
 msgid "Base information"
 msgstr "المعلومات الأساسية"
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 msgid "Basic"
@@ -594,7 +594,7 @@ msgstr[3] "الشهادات المعدلة"
 msgstr[4] "الشهادات المعدلة"
 msgstr[5] "الشهادات المعدلة"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "Changed Path"
 msgstr "المسار المتغير"
 
@@ -707,6 +707,10 @@ msgstr ""
 msgid "Command"
 msgstr "أمر"
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -787,6 +791,10 @@ msgstr "تم فقدان الاتصال، يرجى تحديث الصفحة."
 msgid "Connection timeout period"
 msgstr ""
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -1018,7 +1026,7 @@ msgstr "تم الحذف بنجاح"
 msgid "Demo"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr "نشر"
 
@@ -1218,6 +1226,10 @@ msgstr "هل تريد إزالة هذا الخادم؟"
 msgid "Do you want to remove this upstream?"
 msgstr "هل تريد إزالة هذا المصدر؟"
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 #, fuzzy
@@ -1281,7 +1293,7 @@ msgstr "تعديل %{n}"
 msgid "Edit %{n}"
 msgstr "تعديل %{n}"
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr "تعديل التكوين"
 
@@ -1495,6 +1507,11 @@ msgstr ""
 msgid "Fail to obtain certificate"
 msgstr "فشل في الحصول على الشهادة"
 
+#: src/constants/errors/docker.ts:4
+#, fuzzy
+msgid "Failed to attach to exec instance: {0}"
+msgstr "فشل في التفعيل %{msg}"
+
 #: src/constants/errors/backup.ts:5
 #, fuzzy
 msgid "Failed to backup Nginx config files: {0}"
@@ -1582,6 +1599,11 @@ msgstr ""
 msgid "Failed to create symbolic link: {0}"
 msgstr "فشل في التفعيل %{msg}"
 
+#: src/constants/errors/docker.ts:13
+#, fuzzy
+msgid "Failed to create temp container: {0}"
+msgstr "فشل في التفعيل %{msg}"
+
 #: src/constants/errors/backup.ts:2
 #, fuzzy
 msgid "Failed to create temporary directory"
@@ -1680,6 +1702,11 @@ msgstr ""
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr "فشل في التفعيل %{msg}"
 
+#: src/constants/errors/docker.ts:3
+#, fuzzy
+msgid "Failed to exec command: {0}"
+msgstr "فشل في التفعيل %{msg}"
+
 #: src/constants/errors/backup.ts:35
 #, fuzzy
 msgid "Failed to extract archive: {0}"
@@ -1699,6 +1726,11 @@ msgstr "فشل في الحصول على معلومات الشهادة"
 msgid "Failed to get certificate information"
 msgstr "فشل في الحصول على معلومات الشهادة"
 
+#: src/constants/errors/docker.ts:10
+#, fuzzy
+msgid "Failed to get hostname: {0}"
+msgstr "فشل في التفعيل %{msg}"
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 #, fuzzy
 msgid "Failed to get Nginx performance settings"
@@ -1709,6 +1741,16 @@ msgstr "فشل في الحصول على معلومات الشهادة"
 msgid "Failed to get performance data"
 msgstr "فشل في الحصول على معلومات الشهادة"
 
+#: src/constants/errors/docker.ts:8
+#, fuzzy
+msgid "Failed to inspect container: {0}"
+msgstr "فشل في التفعيل %{msg}"
+
+#: src/constants/errors/docker.ts:12
+#, fuzzy
+msgid "Failed to inspect current container: {0}"
+msgstr "فشل في التفعيل %{msg}"
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 #, fuzzy
 msgid "Failed to load history records"
@@ -1733,6 +1775,11 @@ msgstr "فشل في التفعيل %{msg}"
 msgid "Failed to parse nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:11
+#, fuzzy
+msgid "Failed to pull image: {0}"
+msgstr "فشل في التفعيل %{msg}"
+
 #: src/constants/errors/backup.ts:53
 #, fuzzy
 msgid "Failed to read encrypted file: {0}"
@@ -1753,6 +1800,11 @@ msgstr "فشل في التفعيل %{msg}"
 msgid "Failed to read nginx.conf"
 msgstr "فشل في التفعيل %{msg}"
 
+#: src/constants/errors/docker.ts:5
+#, fuzzy
+msgid "Failed to read output: {0}"
+msgstr "فشل في التفعيل %{msg}"
+
 #: src/constants/errors/backup.ts:21
 #, fuzzy
 msgid "Failed to read symlink: {0}"
@@ -1777,6 +1829,11 @@ msgstr "فشل في الحصول على الشهادة"
 msgid "Failed to save Nginx performance settings"
 msgstr "فشل في الحصول على معلومات الشهادة"
 
+#: src/constants/errors/docker.ts:14
+#, fuzzy
+msgid "Failed to start temp container: {0}"
+msgstr "فشل في التفعيل %{msg}"
+
 #: src/constants/errors/backup.ts:38
 #, fuzzy
 msgid "Failed to verify hashes: {0}"
@@ -1838,14 +1895,10 @@ msgstr "للمستخدمين الصين: /https://mirror.ghproxy.com"
 msgid "Form parse failed"
 msgstr "فشل التكرار"
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr "تنسيق الكود"
 
-#: src/views/config/ConfigEditor.vue:218
-msgid "Format error %{msg}"
-msgstr "خطأ في التنسيق %{msg}"
-
 #: src/views/config/ConfigEditor.vue:216
 msgid "Format successfully"
 msgstr "تم التنسيق بنجاح"
@@ -1920,7 +1973,7 @@ msgstr "إخفاء"
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 #, fuzzy
@@ -2101,7 +2154,7 @@ msgid "Invalid file path: {0}"
 msgstr "رمز 2FA أو الاسترداد غير صالح"
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 msgid "Invalid filename"
 msgstr "اسم ملف غير صالح"
 
@@ -2525,7 +2578,7 @@ msgstr "توجيه متعدد الأسطر"
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2568,7 +2621,7 @@ msgstr "تثبيت"
 msgid "New name"
 msgstr "اسم جديد"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "New Path"
 msgstr "مسار جديد"
 
@@ -2669,6 +2722,11 @@ msgstr "مسار سجل أخطاء Nginx"
 msgid "Nginx is not running"
 msgstr "Nginx لا يعمل"
 
+#: src/constants/errors/docker.ts:9
+#, fuzzy
+msgid "Nginx is not running in another container"
+msgstr "Nginx لا يعمل"
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 #, fuzzy
 msgid "Nginx is running"
@@ -2999,11 +3057,11 @@ msgstr ""
 msgid "Otp or recovery code empty"
 msgstr "استخدم رمز الاسترداد"
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr "الكتابة فوق"
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr "الكتابة فوق الملف الموجود"
 
@@ -3054,7 +3112,7 @@ msgstr ""
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr "مسار"
@@ -3150,7 +3208,7 @@ msgid ""
 msgstr ""
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 msgid "Please input a filename"
 msgstr "يرجى إدخال اسم الملف"
 
@@ -3386,6 +3444,11 @@ msgstr "إعادة تحميل"
 msgid "Reload Nginx"
 msgstr "إعادة تحميل nginx"
 
+#: src/constants/errors/nginx.ts:3
+#, fuzzy
+msgid "Reload nginx failed: {0}"
+msgstr "فشل في التفعيل %{msg}"
+
 #: src/components/Notification/notifications.ts:10
 #, fuzzy
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
@@ -3675,7 +3738,7 @@ msgstr "يعمل"
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4519,7 +4582,7 @@ msgstr "تم التحديث بنجاح"
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
@@ -4816,6 +4879,9 @@ msgstr ""
 msgid "Your passkeys"
 msgstr "مفاتيح المرور الخاصة بك"
 
+#~ msgid "Format error %{msg}"
+#~ msgstr "خطأ في التنسيق %{msg}"
+
 #~ msgid "Failed to save, syntax error(s) was detected in the configuration."
 #~ msgstr "فشل في الحفظ، تم اكتشاف خطأ(أخطاء) في بناء الجملة في التكوين."
 

+ 88 - 22
app/src/language/de_DE/app.po

@@ -77,7 +77,7 @@ msgid "Add a passkey"
 msgstr "Passkey hinzufügen"
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 #, fuzzy
 msgid "Add Configuration"
 msgstr "Konfiguration bearbeiten"
@@ -315,7 +315,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr ""
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -370,7 +370,7 @@ msgstr ""
 msgid "Base information"
 msgstr "Basisinformationen"
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 #, fuzzy
@@ -610,7 +610,7 @@ msgid_plural "Changed Certificates"
 msgstr[0] "Zertifikat ist gültig"
 msgstr[1] "Zertifikat ist gültig"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 #, fuzzy
 msgid "Changed Path"
 msgstr "Zertifikat ist gültig"
@@ -726,6 +726,10 @@ msgstr ""
 msgid "Command"
 msgstr "Kommando"
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -806,6 +810,10 @@ msgstr "Ver"
 msgid "Connection timeout period"
 msgstr ""
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -1045,7 +1053,7 @@ msgstr "Erfolgreich deaktiviert"
 msgid "Demo"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr "Ausführen"
 
@@ -1255,6 +1263,10 @@ msgstr "Bist du sicher, dass du diese Richtlinie löschen möchtest?"
 msgid "Do you want to remove this upstream?"
 msgstr "Bist du sicher, dass du diese Richtlinie löschen möchtest?"
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 #, fuzzy
@@ -1318,7 +1330,7 @@ msgstr "Bearbeiten %{n}"
 msgid "Edit %{n}"
 msgstr "Bearbeiten %{n}"
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr "Konfiguration bearbeiten"
 
@@ -1542,6 +1554,11 @@ msgstr ""
 msgid "Fail to obtain certificate"
 msgstr "Zertifikat ist gültig"
 
+#: src/constants/errors/docker.ts:4
+#, fuzzy
+msgid "Failed to attach to exec instance: {0}"
+msgstr "Aktiviern von %{msg} fehlgeschlagen"
+
 #: src/constants/errors/backup.ts:5
 msgid "Failed to backup Nginx config files: {0}"
 msgstr ""
@@ -1628,6 +1645,11 @@ msgstr ""
 msgid "Failed to create symbolic link: {0}"
 msgstr "Aktiviern von %{msg} fehlgeschlagen"
 
+#: src/constants/errors/docker.ts:13
+#, fuzzy
+msgid "Failed to create temp container: {0}"
+msgstr "Aktiviern von %{msg} fehlgeschlagen"
+
 #: src/constants/errors/backup.ts:2
 #, fuzzy
 msgid "Failed to create temporary directory"
@@ -1726,6 +1748,11 @@ msgstr ""
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr "Aktiviern von %{msg} fehlgeschlagen"
 
+#: src/constants/errors/docker.ts:3
+#, fuzzy
+msgid "Failed to exec command: {0}"
+msgstr "Aktiviern von %{msg} fehlgeschlagen"
+
 #: src/constants/errors/backup.ts:35
 #, fuzzy
 msgid "Failed to extract archive: {0}"
@@ -1745,6 +1772,11 @@ msgstr "Fehler beim Abrufen von Zertifikatsinformationen"
 msgid "Failed to get certificate information"
 msgstr "Fehler beim Abrufen von Zertifikatsinformationen"
 
+#: src/constants/errors/docker.ts:10
+#, fuzzy
+msgid "Failed to get hostname: {0}"
+msgstr "Aktiviern von %{msg} fehlgeschlagen"
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 #, fuzzy
 msgid "Failed to get Nginx performance settings"
@@ -1755,6 +1787,16 @@ msgstr "Fehler beim Abrufen von Zertifikatsinformationen"
 msgid "Failed to get performance data"
 msgstr "Fehler beim Abrufen von Zertifikatsinformationen"
 
+#: src/constants/errors/docker.ts:8
+#, fuzzy
+msgid "Failed to inspect container: {0}"
+msgstr "Aktiviern von %{msg} fehlgeschlagen"
+
+#: src/constants/errors/docker.ts:12
+#, fuzzy
+msgid "Failed to inspect current container: {0}"
+msgstr "Aktiviern von %{msg} fehlgeschlagen"
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 #, fuzzy
 msgid "Failed to load history records"
@@ -1779,6 +1821,11 @@ msgstr "Aktiviern von %{msg} fehlgeschlagen"
 msgid "Failed to parse nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:11
+#, fuzzy
+msgid "Failed to pull image: {0}"
+msgstr "Aktiviern von %{msg} fehlgeschlagen"
+
 #: src/constants/errors/backup.ts:53
 msgid "Failed to read encrypted file: {0}"
 msgstr ""
@@ -1796,6 +1843,11 @@ msgstr ""
 msgid "Failed to read nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:5
+#, fuzzy
+msgid "Failed to read output: {0}"
+msgstr "Aktiviern von %{msg} fehlgeschlagen"
+
 #: src/constants/errors/backup.ts:21
 #, fuzzy
 msgid "Failed to read symlink: {0}"
@@ -1819,6 +1871,11 @@ msgstr "Zertifikat ist gültig"
 msgid "Failed to save Nginx performance settings"
 msgstr "Fehler beim Abrufen von Zertifikatsinformationen"
 
+#: src/constants/errors/docker.ts:14
+#, fuzzy
+msgid "Failed to start temp container: {0}"
+msgstr "Aktiviern von %{msg} fehlgeschlagen"
+
 #: src/constants/errors/backup.ts:38
 #, fuzzy
 msgid "Failed to verify hashes: {0}"
@@ -1882,15 +1939,10 @@ msgstr "Für chinesische Benutzer: https://mirror.ghproxy.com/"
 msgid "Form parse failed"
 msgstr "Anlegen fehlgeschlagen"
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr "Formatcode"
 
-#: src/views/config/ConfigEditor.vue:218
-#, fuzzy
-msgid "Format error %{msg}"
-msgstr "Fehler beim Speichern %{msg}"
-
 #: src/views/config/ConfigEditor.vue:216
 #, fuzzy
 msgid "Format successfully"
@@ -1968,7 +2020,7 @@ msgstr "Verstecken"
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 msgid "History"
@@ -2150,7 +2202,7 @@ msgid "Invalid file path: {0}"
 msgstr "Ungültige E-Mail!"
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 #, fuzzy
 msgid "Invalid filename"
 msgstr "Ungültige E-Mail!"
@@ -2595,7 +2647,7 @@ msgstr "Einzelne Anweisung"
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2639,7 +2691,7 @@ msgstr "Installieren"
 msgid "New name"
 msgstr "Benutzername"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 #, fuzzy
 msgid "New Path"
 msgstr "Pfad"
@@ -2743,6 +2795,11 @@ msgstr "Nginx Fehlerlog-Pfad"
 msgid "Nginx is not running"
 msgstr "Nginx läuft nicht"
 
+#: src/constants/errors/docker.ts:9
+#, fuzzy
+msgid "Nginx is not running in another container"
+msgstr "Nginx läuft nicht"
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 #, fuzzy
 msgid "Nginx is running"
@@ -3082,11 +3139,11 @@ msgstr ""
 msgid "Otp or recovery code empty"
 msgstr "Benuzte Wiederherstellungscode"
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr "Überschreiben"
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr "Zu überschreibende Datei existiert"
 
@@ -3137,7 +3194,7 @@ msgstr "Passwort darf nicht länger als 20 Zeichen sein"
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr "Pfad"
@@ -3237,7 +3294,7 @@ msgid ""
 msgstr ""
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 #, fuzzy
 msgid "Please input a filename"
 msgstr "Bitte Benutzernamen eingeben!"
@@ -3488,6 +3545,11 @@ msgstr "Neu laden"
 msgid "Reload Nginx"
 msgstr "Lade Nginx neu"
 
+#: src/constants/errors/nginx.ts:3
+#, fuzzy
+msgid "Reload nginx failed: {0}"
+msgstr "Aktiviern von %{msg} fehlgeschlagen"
+
 #: src/components/Notification/notifications.ts:10
 #, fuzzy
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
@@ -3792,7 +3854,7 @@ msgstr "Arbeite"
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4653,7 +4715,7 @@ msgstr "Speichern erfolgreich"
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
@@ -4961,6 +5023,10 @@ msgstr ""
 msgid "Your passkeys"
 msgstr "Deine Passkeys"
 
+#, fuzzy
+#~ msgid "Format error %{msg}"
+#~ msgstr "Fehler beim Speichern %{msg}"
+
 #~ msgid "Failed to save, syntax error(s) was detected in the configuration."
 #~ msgstr ""
 #~ "Fehler beim Speichern, Syntaxfehler wurden in der Konfiguration erkannt."

+ 87 - 22
app/src/language/en/app.po

@@ -78,7 +78,7 @@ msgid "Add a passkey"
 msgstr ""
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 #, fuzzy
 msgid "Add Configuration"
 msgstr "Edit Configuration"
@@ -312,7 +312,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr ""
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -367,7 +367,7 @@ msgstr ""
 msgid "Base information"
 msgstr "Base information"
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 #, fuzzy
@@ -603,7 +603,7 @@ msgid_plural "Changed Certificates"
 msgstr[0] "Certificate is valid"
 msgstr[1] "Certificate is valid"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 #, fuzzy
 msgid "Changed Path"
 msgstr "Certificate is valid"
@@ -718,6 +718,10 @@ msgstr ""
 msgid "Command"
 msgstr "Comments"
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -798,6 +802,10 @@ msgstr ""
 msgid "Connection timeout period"
 msgstr ""
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -1034,7 +1042,7 @@ msgstr "Disabled successfully"
 msgid "Demo"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr ""
 
@@ -1243,6 +1251,10 @@ msgstr "Are you sure you want to remove this directive?"
 msgid "Do you want to remove this upstream?"
 msgstr "Are you sure you want to remove this directive?"
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 #, fuzzy
@@ -1302,7 +1314,7 @@ msgstr "Edit %{n}"
 msgid "Edit %{n}"
 msgstr "Edit %{n}"
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr "Edit Configuration"
 
@@ -1525,6 +1537,11 @@ msgstr ""
 msgid "Fail to obtain certificate"
 msgstr "Certificate is valid"
 
+#: src/constants/errors/docker.ts:4
+#, fuzzy
+msgid "Failed to attach to exec instance: {0}"
+msgstr "Failed to enable %{msg}"
+
 #: src/constants/errors/backup.ts:5
 #, fuzzy
 msgid "Failed to backup Nginx config files: {0}"
@@ -1612,6 +1629,11 @@ msgstr ""
 msgid "Failed to create symbolic link: {0}"
 msgstr "Failed to enable %{msg}"
 
+#: src/constants/errors/docker.ts:13
+#, fuzzy
+msgid "Failed to create temp container: {0}"
+msgstr "Failed to enable %{msg}"
+
 #: src/constants/errors/backup.ts:2
 #, fuzzy
 msgid "Failed to create temporary directory"
@@ -1710,6 +1732,11 @@ msgstr ""
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr "Failed to enable %{msg}"
 
+#: src/constants/errors/docker.ts:3
+#, fuzzy
+msgid "Failed to exec command: {0}"
+msgstr "Failed to enable %{msg}"
+
 #: src/constants/errors/backup.ts:35
 #, fuzzy
 msgid "Failed to extract archive: {0}"
@@ -1730,6 +1757,11 @@ msgstr "Certificate is valid"
 msgid "Failed to get certificate information"
 msgstr "Certificate is valid"
 
+#: src/constants/errors/docker.ts:10
+#, fuzzy
+msgid "Failed to get hostname: {0}"
+msgstr "Failed to enable %{msg}"
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 #, fuzzy
 msgid "Failed to get Nginx performance settings"
@@ -1740,6 +1772,16 @@ msgstr "Certificate is valid"
 msgid "Failed to get performance data"
 msgstr "Certificate is valid"
 
+#: src/constants/errors/docker.ts:8
+#, fuzzy
+msgid "Failed to inspect container: {0}"
+msgstr "Failed to enable %{msg}"
+
+#: src/constants/errors/docker.ts:12
+#, fuzzy
+msgid "Failed to inspect current container: {0}"
+msgstr "Failed to enable %{msg}"
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 #, fuzzy
 msgid "Failed to load history records"
@@ -1764,6 +1806,11 @@ msgstr "Failed to enable %{msg}"
 msgid "Failed to parse nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:11
+#, fuzzy
+msgid "Failed to pull image: {0}"
+msgstr "Failed to enable %{msg}"
+
 #: src/constants/errors/backup.ts:53
 #, fuzzy
 msgid "Failed to read encrypted file: {0}"
@@ -1784,6 +1831,11 @@ msgstr "Failed to enable %{msg}"
 msgid "Failed to read nginx.conf"
 msgstr "Failed to enable %{msg}"
 
+#: src/constants/errors/docker.ts:5
+#, fuzzy
+msgid "Failed to read output: {0}"
+msgstr "Failed to enable %{msg}"
+
 #: src/constants/errors/backup.ts:21
 #, fuzzy
 msgid "Failed to read symlink: {0}"
@@ -1808,6 +1860,11 @@ msgstr "Certificate is valid"
 msgid "Failed to save Nginx performance settings"
 msgstr "Certificate is valid"
 
+#: src/constants/errors/docker.ts:14
+#, fuzzy
+msgid "Failed to start temp container: {0}"
+msgstr "Failed to enable %{msg}"
+
 #: src/constants/errors/backup.ts:38
 #, fuzzy
 msgid "Failed to verify hashes: {0}"
@@ -1869,15 +1926,10 @@ msgstr ""
 msgid "Form parse failed"
 msgstr "Enable failed"
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:218
-#, fuzzy
-msgid "Format error %{msg}"
-msgstr "Save error %{msg}"
-
 #: src/views/config/ConfigEditor.vue:216
 #, fuzzy
 msgid "Format successfully"
@@ -1954,7 +2006,7 @@ msgstr ""
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 #, fuzzy
@@ -2129,7 +2181,7 @@ msgid "Invalid file path: {0}"
 msgstr "Invalid E-mail!"
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 #, fuzzy
 msgid "Invalid filename"
 msgstr "Invalid E-mail!"
@@ -2567,7 +2619,7 @@ msgstr "Single Directive"
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2611,7 +2663,7 @@ msgstr "Install"
 msgid "New name"
 msgstr "Username"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 #, fuzzy
 msgid "New Path"
 msgstr "Path"
@@ -2714,6 +2766,10 @@ msgstr ""
 msgid "Nginx is not running"
 msgstr ""
 
+#: src/constants/errors/docker.ts:9
+msgid "Nginx is not running in another container"
+msgstr ""
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 msgid "Nginx is running"
 msgstr ""
@@ -3045,11 +3101,11 @@ msgstr ""
 msgid "Otp or recovery code empty"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr ""
 
@@ -3097,7 +3153,7 @@ msgstr ""
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr "Path"
@@ -3189,7 +3245,7 @@ msgid ""
 msgstr ""
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 #, fuzzy
 msgid "Please input a filename"
 msgstr "Please input your username!"
@@ -3431,6 +3487,11 @@ msgstr ""
 msgid "Reload Nginx"
 msgstr ""
 
+#: src/constants/errors/nginx.ts:3
+#, fuzzy
+msgid "Reload nginx failed: {0}"
+msgstr "Failed to enable %{msg}"
+
 #: src/components/Notification/notifications.ts:10
 #, fuzzy
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
@@ -3734,7 +3795,7 @@ msgstr ""
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4573,7 +4634,7 @@ msgstr "Saved successfully"
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
@@ -4871,6 +4932,10 @@ msgstr ""
 msgid "Your passkeys"
 msgstr ""
 
+#, fuzzy
+#~ msgid "Format error %{msg}"
+#~ msgstr "Save error %{msg}"
+
 #, fuzzy
 #~ msgid "Access Token"
 #~ msgstr "Sites List"

+ 87 - 21
app/src/language/es/app.po

@@ -83,7 +83,7 @@ msgid "Add a passkey"
 msgstr "Agregar una llave de acceso"
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 msgid "Add Configuration"
 msgstr "Agregar configuración"
 
@@ -306,7 +306,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr ""
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -360,7 +360,7 @@ msgstr ""
 msgid "Base information"
 msgstr "Información general"
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 msgid "Basic"
@@ -593,7 +593,7 @@ msgid_plural "Changed Certificates"
 msgstr[0] "Cambiar Certificado"
 msgstr[1] "Cambiar Certificados"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "Changed Path"
 msgstr "Ruta cambiada"
 
@@ -706,6 +706,10 @@ msgstr ""
 msgid "Command"
 msgstr "Comando"
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -786,6 +790,10 @@ msgstr "Conexión perdida, por favor actualice la página."
 msgid "Connection timeout period"
 msgstr ""
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -1019,7 +1027,7 @@ msgstr "Borrado exitoso"
 msgid "Demo"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr "Desplegar"
 
@@ -1219,6 +1227,10 @@ msgstr "¿Quieres eliminar este servidor?"
 msgid "Do you want to remove this upstream?"
 msgstr "¿Quieres eliminar esta transmisión?"
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 #, fuzzy
@@ -1281,7 +1293,7 @@ msgstr "Editar %{n}"
 msgid "Edit %{n}"
 msgstr "Editar %{n}"
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr "Editar Configuración"
 
@@ -1498,6 +1510,11 @@ msgstr ""
 msgid "Fail to obtain certificate"
 msgstr "Falla al obtener el certificado"
 
+#: src/constants/errors/docker.ts:4
+#, fuzzy
+msgid "Failed to attach to exec instance: {0}"
+msgstr "Error al habilitar %{msg}"
+
 #: src/constants/errors/backup.ts:5
 msgid "Failed to backup Nginx config files: {0}"
 msgstr ""
@@ -1584,6 +1601,11 @@ msgstr ""
 msgid "Failed to create symbolic link: {0}"
 msgstr "Error al habilitar %{msg}"
 
+#: src/constants/errors/docker.ts:13
+#, fuzzy
+msgid "Failed to create temp container: {0}"
+msgstr "Error al habilitar %{msg}"
+
 #: src/constants/errors/backup.ts:2
 #, fuzzy
 msgid "Failed to create temporary directory"
@@ -1682,6 +1704,11 @@ msgstr ""
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr "Error al habilitar %{msg}"
 
+#: src/constants/errors/docker.ts:3
+#, fuzzy
+msgid "Failed to exec command: {0}"
+msgstr "Error al habilitar %{msg}"
+
 #: src/constants/errors/backup.ts:35
 #, fuzzy
 msgid "Failed to extract archive: {0}"
@@ -1701,6 +1728,11 @@ msgstr "No se pudo obtener la información del certificado"
 msgid "Failed to get certificate information"
 msgstr "No se pudo obtener la información del certificado"
 
+#: src/constants/errors/docker.ts:10
+#, fuzzy
+msgid "Failed to get hostname: {0}"
+msgstr "Error al habilitar %{msg}"
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 #, fuzzy
 msgid "Failed to get Nginx performance settings"
@@ -1711,6 +1743,16 @@ msgstr "No se pudo obtener la información del certificado"
 msgid "Failed to get performance data"
 msgstr "No se pudo obtener la información del certificado"
 
+#: src/constants/errors/docker.ts:8
+#, fuzzy
+msgid "Failed to inspect container: {0}"
+msgstr "Error al habilitar %{msg}"
+
+#: src/constants/errors/docker.ts:12
+#, fuzzy
+msgid "Failed to inspect current container: {0}"
+msgstr "Error al habilitar %{msg}"
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 #, fuzzy
 msgid "Failed to load history records"
@@ -1735,6 +1777,11 @@ msgstr "Error al habilitar %{msg}"
 msgid "Failed to parse nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:11
+#, fuzzy
+msgid "Failed to pull image: {0}"
+msgstr "Error al habilitar %{msg}"
+
 #: src/constants/errors/backup.ts:53
 msgid "Failed to read encrypted file: {0}"
 msgstr ""
@@ -1752,6 +1799,11 @@ msgstr ""
 msgid "Failed to read nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:5
+#, fuzzy
+msgid "Failed to read output: {0}"
+msgstr "Error al habilitar %{msg}"
+
 #: src/constants/errors/backup.ts:21
 #, fuzzy
 msgid "Failed to read symlink: {0}"
@@ -1775,6 +1827,11 @@ msgstr "Falla al obtener el certificado"
 msgid "Failed to save Nginx performance settings"
 msgstr "No se pudo obtener la información del certificado"
 
+#: src/constants/errors/docker.ts:14
+#, fuzzy
+msgid "Failed to start temp container: {0}"
+msgstr "Error al habilitar %{msg}"
+
 #: src/constants/errors/backup.ts:38
 #, fuzzy
 msgid "Failed to verify hashes: {0}"
@@ -1838,14 +1895,10 @@ msgstr "Para usuario chino: https://mirror.ghproxy.com/"
 msgid "Form parse failed"
 msgstr "Duplicado fallido"
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr "Código de formato"
 
-#: src/views/config/ConfigEditor.vue:218
-msgid "Format error %{msg}"
-msgstr "Error de formato %{msg}"
-
 #: src/views/config/ConfigEditor.vue:216
 msgid "Format successfully"
 msgstr "Formateado correctamente"
@@ -1920,7 +1973,7 @@ msgstr "Ocultar"
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 #, fuzzy
@@ -2101,7 +2154,7 @@ msgid "Invalid file path: {0}"
 msgstr "Nombre de archivo inválido"
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 msgid "Invalid filename"
 msgstr "Nombre de archivo inválido"
 
@@ -2527,7 +2580,7 @@ msgstr "Directiva multilínea"
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2570,7 +2623,7 @@ msgstr "Instalar"
 msgid "New name"
 msgstr "Nuevo nombre"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "New Path"
 msgstr "Nueva ruta"
 
@@ -2672,6 +2725,11 @@ msgstr "Ruta de registro de errores de Nginx"
 msgid "Nginx is not running"
 msgstr "Nginx no se está ejecutando"
 
+#: src/constants/errors/docker.ts:9
+#, fuzzy
+msgid "Nginx is not running in another container"
+msgstr "Nginx no se está ejecutando"
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 #, fuzzy
 msgid "Nginx is running"
@@ -3006,11 +3064,11 @@ msgstr ""
 msgid "Otp or recovery code empty"
 msgstr "Usar código de recuperación"
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr "Sobrescribir"
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr "Sobrescribir archivo existente"
 
@@ -3062,7 +3120,7 @@ msgstr ""
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr "Ruta"
@@ -3163,7 +3221,7 @@ msgid ""
 msgstr ""
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 msgid "Please input a filename"
 msgstr "Por favor, ingrese un nombre de archivo"
 
@@ -3410,6 +3468,11 @@ msgstr "Recargar"
 msgid "Reload Nginx"
 msgstr "Recargando Nginx"
 
+#: src/constants/errors/nginx.ts:3
+#, fuzzy
+msgid "Reload nginx failed: {0}"
+msgstr "Error al habilitar %{msg}"
+
 #: src/components/Notification/notifications.ts:10
 #, fuzzy
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
@@ -3702,7 +3765,7 @@ msgstr "Corriendo"
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4560,7 +4623,7 @@ msgstr "Actualización exitosa"
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
@@ -4862,6 +4925,9 @@ msgstr ""
 msgid "Your passkeys"
 msgstr "Sus llaves de acceso"
 
+#~ msgid "Format error %{msg}"
+#~ msgstr "Error de formato %{msg}"
+
 #~ msgid "Failed to save, syntax error(s) was detected in the configuration."
 #~ msgstr ""
 #~ "No se pudo guardar, se detectó un error(es) de sintaxis en la "

+ 86 - 21
app/src/language/fr_FR/app.po

@@ -82,7 +82,7 @@ msgid "Add a passkey"
 msgstr "Ajouter une clé d'accès"
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 #, fuzzy
 msgid "Add Configuration"
 msgstr "Modifier la configuration"
@@ -319,7 +319,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr ""
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -373,7 +373,7 @@ msgstr ""
 msgid "Base information"
 msgstr "Information générale"
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 msgid "Basic"
@@ -612,7 +612,7 @@ msgid_plural "Changed Certificates"
 msgstr[0] "Changer de certificat"
 msgstr[1] "Changer de certificat"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 #, fuzzy
 msgid "Changed Path"
 msgstr "Changer de certificat"
@@ -733,6 +733,10 @@ msgstr ""
 msgid "Command"
 msgstr "Commentaires"
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -813,6 +817,10 @@ msgstr "Connexion perdue, merci de recharger la page."
 msgid "Connection timeout period"
 msgstr ""
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -1053,7 +1061,7 @@ msgstr "Désactivé avec succès"
 msgid "Demo"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr "Déployer"
 
@@ -1262,6 +1270,10 @@ msgstr "Voulez-vous supprimer ce serveur ?"
 msgid "Do you want to remove this upstream?"
 msgstr "Voulez-vous supprimer ce serveur ?"
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 #, fuzzy
@@ -1327,7 +1339,7 @@ msgstr "Modifier %{n}"
 msgid "Edit %{n}"
 msgstr "Modifier %{n}"
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr "Modifier la configuration"
 
@@ -1552,6 +1564,11 @@ msgstr ""
 msgid "Fail to obtain certificate"
 msgstr "Obtenir un certificat"
 
+#: src/constants/errors/docker.ts:4
+#, fuzzy
+msgid "Failed to attach to exec instance: {0}"
+msgstr "Impossible d'activer %{msg}"
+
 #: src/constants/errors/backup.ts:5
 #, fuzzy
 msgid "Failed to backup Nginx config files: {0}"
@@ -1639,6 +1656,11 @@ msgstr ""
 msgid "Failed to create symbolic link: {0}"
 msgstr "Impossible d'activer %{msg}"
 
+#: src/constants/errors/docker.ts:13
+#, fuzzy
+msgid "Failed to create temp container: {0}"
+msgstr "Impossible d'activer %{msg}"
+
 #: src/constants/errors/backup.ts:2
 #, fuzzy
 msgid "Failed to create temporary directory"
@@ -1737,6 +1759,11 @@ msgstr ""
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr "Impossible d'activer %{msg}"
 
+#: src/constants/errors/docker.ts:3
+#, fuzzy
+msgid "Failed to exec command: {0}"
+msgstr "Impossible d'activer %{msg}"
+
 #: src/constants/errors/backup.ts:35
 #, fuzzy
 msgid "Failed to extract archive: {0}"
@@ -1756,6 +1783,11 @@ msgstr "Échec de l'obtention des informations sur le certificat"
 msgid "Failed to get certificate information"
 msgstr "Échec de l'obtention des informations sur le certificat"
 
+#: src/constants/errors/docker.ts:10
+#, fuzzy
+msgid "Failed to get hostname: {0}"
+msgstr "Impossible d'activer %{msg}"
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 #, fuzzy
 msgid "Failed to get Nginx performance settings"
@@ -1766,6 +1798,16 @@ msgstr "Échec de l'obtention des informations sur le certificat"
 msgid "Failed to get performance data"
 msgstr "Échec de l'obtention des informations sur le certificat"
 
+#: src/constants/errors/docker.ts:8
+#, fuzzy
+msgid "Failed to inspect container: {0}"
+msgstr "Impossible d'activer %{msg}"
+
+#: src/constants/errors/docker.ts:12
+#, fuzzy
+msgid "Failed to inspect current container: {0}"
+msgstr "Impossible d'activer %{msg}"
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 #, fuzzy
 msgid "Failed to load history records"
@@ -1791,6 +1833,11 @@ msgstr "Impossible d'activer %{msg}"
 msgid "Failed to parse nginx.conf"
 msgstr "Erreur lecture nginx.conf"
 
+#: src/constants/errors/docker.ts:11
+#, fuzzy
+msgid "Failed to pull image: {0}"
+msgstr "Impossible d'activer %{msg}"
+
 #: src/constants/errors/backup.ts:53
 #, fuzzy
 msgid "Failed to read encrypted file: {0}"
@@ -1811,6 +1858,11 @@ msgstr "Impossible d'activer %{msg}"
 msgid "Failed to read nginx.conf"
 msgstr "Impossible d'activer %{msg}"
 
+#: src/constants/errors/docker.ts:5
+#, fuzzy
+msgid "Failed to read output: {0}"
+msgstr "Impossible d'activer %{msg}"
+
 #: src/constants/errors/backup.ts:21
 #, fuzzy
 msgid "Failed to read symlink: {0}"
@@ -1836,6 +1888,11 @@ msgstr "Obtenir un certificat"
 msgid "Failed to save Nginx performance settings"
 msgstr "Échec de l'obtention des informations sur le certificat"
 
+#: src/constants/errors/docker.ts:14
+#, fuzzy
+msgid "Failed to start temp container: {0}"
+msgstr "Impossible d'activer %{msg}"
+
 #: src/constants/errors/backup.ts:38
 #, fuzzy
 msgid "Failed to verify hashes: {0}"
@@ -1901,14 +1958,10 @@ msgstr "Utilisateur chinois : https://mirror.ghproxy.com/"
 msgid "Form parse failed"
 msgstr "Dupliquer"
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr "Code de formatage"
 
-#: src/views/config/ConfigEditor.vue:218
-msgid "Format error %{msg}"
-msgstr "Erreur de format %{msg}"
-
 #: src/views/config/ConfigEditor.vue:216
 msgid "Format successfully"
 msgstr "Formaté avec succès"
@@ -1982,7 +2035,7 @@ msgstr "Cacher"
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 #, fuzzy
@@ -2170,7 +2223,7 @@ msgid "Invalid file path: {0}"
 msgstr "Format de la requête invalide"
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 msgid "Invalid filename"
 msgstr "Nom de fichier invalide"
 
@@ -2608,7 +2661,7 @@ msgstr "Directive multiligne"
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2652,7 +2705,7 @@ msgstr "Installer"
 msgid "New name"
 msgstr "Nom d'utilisateur"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 #, fuzzy
 msgid "New Path"
 msgstr "Chemin"
@@ -2756,6 +2809,10 @@ msgstr "Chemin du journal des erreurs Nginx"
 msgid "Nginx is not running"
 msgstr ""
 
+#: src/constants/errors/docker.ts:9
+msgid "Nginx is not running in another container"
+msgstr ""
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 msgid "Nginx is running"
 msgstr ""
@@ -3086,11 +3143,11 @@ msgstr ""
 msgid "Otp or recovery code empty"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr ""
 
@@ -3138,7 +3195,7 @@ msgstr ""
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr "Chemin"
@@ -3234,7 +3291,7 @@ msgid ""
 msgstr ""
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 msgid "Please input a filename"
 msgstr "Veuillez renseigner un nom de fichier"
 
@@ -3482,6 +3539,11 @@ msgstr "Recharger"
 msgid "Reload Nginx"
 msgstr "Rechargement de nginx"
 
+#: src/constants/errors/nginx.ts:3
+#, fuzzy
+msgid "Reload nginx failed: {0}"
+msgstr "Impossible d'activer %{msg}"
+
 #: src/components/Notification/notifications.ts:10
 #, fuzzy
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
@@ -3786,7 +3848,7 @@ msgstr "En cours d'éxécution"
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4632,7 +4694,7 @@ msgstr "Mis à jour avec succés"
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
@@ -4927,6 +4989,9 @@ msgstr ""
 msgid "Your passkeys"
 msgstr ""
 
+#~ msgid "Format error %{msg}"
+#~ msgstr "Erreur de format %{msg}"
+
 #~ msgid "Failed to save, syntax error(s) was detected in the configuration."
 #~ msgstr ""
 #~ "Échec de l'enregistrement, une ou plusieurs erreurs de syntaxe ont été "

+ 87 - 22
app/src/language/ko_KR/app.po

@@ -81,7 +81,7 @@ msgid "Add a passkey"
 msgstr ""
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 msgid "Add Configuration"
 msgstr "구성 추가"
 
@@ -296,7 +296,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr "사이트 및 스트림 구성에서 자동으로 색인됩니다."
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -350,7 +350,7 @@ msgstr ""
 msgid "Base information"
 msgstr "기본 정보"
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 msgid "Basic"
@@ -582,7 +582,7 @@ msgid_plural "Changed Certificates"
 msgstr[0] "인증서 변경"
 msgstr[1] "인증서 변경"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 #, fuzzy
 msgid "Changed Path"
 msgstr "인증서 변경"
@@ -695,6 +695,10 @@ msgstr ""
 msgid "Command"
 msgstr "명령어"
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -775,6 +779,10 @@ msgstr "연결이 끊어졌습니다. 페이지를 새로 고침하세요."
 msgid "Connection timeout period"
 msgstr ""
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -1009,7 +1017,7 @@ msgstr "성공적으로 삭제됨"
 msgid "Demo"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr "배포"
 
@@ -1211,6 +1219,10 @@ msgstr "이 서버를 제거하시겠습니까?"
 msgid "Do you want to remove this upstream?"
 msgstr "이 업스트림을 제거하시겠습니까?"
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 #, fuzzy
@@ -1269,7 +1281,7 @@ msgstr "%{n} 편집"
 msgid "Edit %{n}"
 msgstr "%{n} 편집"
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr "구성 편집"
 
@@ -1491,6 +1503,11 @@ msgstr ""
 msgid "Fail to obtain certificate"
 msgstr "인증서 획득 실패"
 
+#: src/constants/errors/docker.ts:4
+#, fuzzy
+msgid "Failed to attach to exec instance: {0}"
+msgstr "%{msg} 활성화 실패"
+
 #: src/constants/errors/backup.ts:5
 msgid "Failed to backup Nginx config files: {0}"
 msgstr ""
@@ -1577,6 +1594,11 @@ msgstr ""
 msgid "Failed to create symbolic link: {0}"
 msgstr "%{msg} 활성화 실패"
 
+#: src/constants/errors/docker.ts:13
+#, fuzzy
+msgid "Failed to create temp container: {0}"
+msgstr "%{msg} 활성화 실패"
+
 #: src/constants/errors/backup.ts:2
 #, fuzzy
 msgid "Failed to create temporary directory"
@@ -1675,6 +1697,11 @@ msgstr ""
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr "%{msg} 활성화 실패"
 
+#: src/constants/errors/docker.ts:3
+#, fuzzy
+msgid "Failed to exec command: {0}"
+msgstr "%{msg} 활성화 실패"
+
 #: src/constants/errors/backup.ts:35
 #, fuzzy
 msgid "Failed to extract archive: {0}"
@@ -1694,6 +1721,11 @@ msgstr "인증서 정보 가져오기 실패"
 msgid "Failed to get certificate information"
 msgstr "인증서 정보 가져오기 실패"
 
+#: src/constants/errors/docker.ts:10
+#, fuzzy
+msgid "Failed to get hostname: {0}"
+msgstr "%{msg} 활성화 실패"
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 #, fuzzy
 msgid "Failed to get Nginx performance settings"
@@ -1704,6 +1736,16 @@ msgstr "인증서 정보 가져오기 실패"
 msgid "Failed to get performance data"
 msgstr "인증서 정보 가져오기 실패"
 
+#: src/constants/errors/docker.ts:8
+#, fuzzy
+msgid "Failed to inspect container: {0}"
+msgstr "%{msg} 활성화 실패"
+
+#: src/constants/errors/docker.ts:12
+#, fuzzy
+msgid "Failed to inspect current container: {0}"
+msgstr "%{msg} 활성화 실패"
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 #, fuzzy
 msgid "Failed to load history records"
@@ -1728,6 +1770,11 @@ msgstr "%{msg} 활성화 실패"
 msgid "Failed to parse nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:11
+#, fuzzy
+msgid "Failed to pull image: {0}"
+msgstr "%{msg} 활성화 실패"
+
 #: src/constants/errors/backup.ts:53
 msgid "Failed to read encrypted file: {0}"
 msgstr ""
@@ -1745,6 +1792,11 @@ msgstr ""
 msgid "Failed to read nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:5
+#, fuzzy
+msgid "Failed to read output: {0}"
+msgstr "%{msg} 활성화 실패"
+
 #: src/constants/errors/backup.ts:21
 #, fuzzy
 msgid "Failed to read symlink: {0}"
@@ -1768,6 +1820,11 @@ msgstr "인증서 획득 실패"
 msgid "Failed to save Nginx performance settings"
 msgstr "인증서 정보 가져오기 실패"
 
+#: src/constants/errors/docker.ts:14
+#, fuzzy
+msgid "Failed to start temp container: {0}"
+msgstr "%{msg} 활성화 실패"
+
 #: src/constants/errors/backup.ts:38
 #, fuzzy
 msgid "Failed to verify hashes: {0}"
@@ -1829,15 +1886,10 @@ msgstr "중국 사용자를 위해: https://mirror.ghproxy.com/"
 msgid "Form parse failed"
 msgstr "복제 실패"
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr "코드 형식"
 
-#: src/views/config/ConfigEditor.vue:218
-#, fuzzy
-msgid "Format error %{msg}"
-msgstr "형식 오류 %{msg}"
-
 #: src/views/config/ConfigEditor.vue:216
 #, fuzzy
 msgid "Format successfully"
@@ -1913,7 +1965,7 @@ msgstr ""
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 #, fuzzy
@@ -2088,7 +2140,7 @@ msgid "Invalid file path: {0}"
 msgstr "Invalid E-mail!"
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 #, fuzzy
 msgid "Invalid filename"
 msgstr "Invalid E-mail!"
@@ -2528,7 +2580,7 @@ msgstr "단일 지시문"
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2572,7 +2624,7 @@ msgstr "설치"
 msgid "New name"
 msgstr "이름 변경"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 #, fuzzy
 msgid "New Path"
 msgstr "경로"
@@ -2676,6 +2728,10 @@ msgstr "Nginx 오류 로그 경로"
 msgid "Nginx is not running"
 msgstr ""
 
+#: src/constants/errors/docker.ts:9
+msgid "Nginx is not running in another container"
+msgstr ""
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 msgid "Nginx is running"
 msgstr ""
@@ -3008,11 +3064,11 @@ msgstr ""
 msgid "Otp or recovery code empty"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr "덮어쓰기"
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr "기존 파일 덮어쓰기"
 
@@ -3060,7 +3116,7 @@ msgstr ""
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr "경로"
@@ -3153,7 +3209,7 @@ msgid ""
 msgstr ""
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 #, fuzzy
 msgid "Please input a filename"
 msgstr "사용자 이름을 입력해주세요!"
@@ -3402,6 +3458,11 @@ msgstr "리로드"
 msgid "Reload Nginx"
 msgstr "Nginx 리로딩 중"
 
+#: src/constants/errors/nginx.ts:3
+#, fuzzy
+msgid "Reload nginx failed: {0}"
+msgstr "%{msg} 활성화 실패"
+
 #: src/components/Notification/notifications.ts:10
 #, fuzzy
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
@@ -3707,7 +3768,7 @@ msgstr "실행 중"
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4544,7 +4605,7 @@ msgstr "성공적으로 저장되었습니다"
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
@@ -4844,6 +4905,10 @@ msgstr ""
 msgid "Your passkeys"
 msgstr ""
 
+#, fuzzy
+#~ msgid "Format error %{msg}"
+#~ msgstr "형식 오류 %{msg}"
+
 #~ msgid "Failed to save, syntax error(s) was detected in the configuration."
 #~ msgstr "저장 실패, 구성에서 구문 오류가 감지되었습니다."
 

+ 73 - 21
app/src/language/messages.pot

@@ -71,7 +71,7 @@ msgstr ""
 
 #: src/routes/modules/config.ts:20
 #: src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 msgid "Add Configuration"
 msgstr ""
 
@@ -285,7 +285,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr ""
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268
+#: src/views/config/ConfigEditor.vue:266
 #: src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195
 #: src/views/nginx_log/NginxLog.vue:173
@@ -338,7 +338,7 @@ msgstr ""
 msgid "Base information"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 msgid "Basic"
@@ -553,7 +553,7 @@ msgid_plural "Changed Certificates"
 msgstr[0] ""
 msgstr[1] ""
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "Changed Path"
 msgstr ""
 
@@ -659,6 +659,10 @@ msgstr ""
 msgid "Command"
 msgstr ""
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -734,6 +738,10 @@ msgstr ""
 msgid "Connection timeout period"
 msgstr ""
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -953,7 +961,7 @@ msgstr ""
 msgid "Demo"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr ""
 
@@ -1144,6 +1152,10 @@ msgstr ""
 msgid "Do you want to remove this upstream?"
 msgstr ""
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 msgid "Document"
@@ -1198,7 +1210,7 @@ msgid "Edit %{n}"
 msgstr ""
 
 #: src/routes/modules/config.ts:30
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr ""
 
@@ -1403,6 +1415,10 @@ msgstr ""
 msgid "Fail to obtain certificate"
 msgstr ""
 
+#: src/constants/errors/docker.ts:4
+msgid "Failed to attach to exec instance: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:5
 msgid "Failed to backup Nginx config files: {0}"
 msgstr ""
@@ -1479,6 +1495,10 @@ msgstr ""
 msgid "Failed to create symbolic link: {0}"
 msgstr ""
 
+#: src/constants/errors/docker.ts:13
+msgid "Failed to create temp container: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:2
 msgid "Failed to create temporary directory"
 msgstr ""
@@ -1563,6 +1583,10 @@ msgstr ""
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr ""
 
+#: src/constants/errors/docker.ts:3
+msgid "Failed to exec command: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:35
 msgid "Failed to extract archive: {0}"
 msgstr ""
@@ -1579,6 +1603,10 @@ msgstr ""
 msgid "Failed to get certificate information"
 msgstr ""
 
+#: src/constants/errors/docker.ts:10
+msgid "Failed to get hostname: {0}"
+msgstr ""
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 msgid "Failed to get Nginx performance settings"
 msgstr ""
@@ -1587,6 +1615,14 @@ msgstr ""
 msgid "Failed to get performance data"
 msgstr ""
 
+#: src/constants/errors/docker.ts:8
+msgid "Failed to inspect container: {0}"
+msgstr ""
+
+#: src/constants/errors/docker.ts:12
+msgid "Failed to inspect current container: {0}"
+msgstr ""
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 msgid "Failed to load history records"
 msgstr ""
@@ -1607,6 +1643,10 @@ msgstr ""
 msgid "Failed to parse nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:11
+msgid "Failed to pull image: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:53
 msgid "Failed to read encrypted file: {0}"
 msgstr ""
@@ -1623,6 +1663,10 @@ msgstr ""
 msgid "Failed to read nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:5
+msgid "Failed to read output: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:21
 msgid "Failed to read symlink: {0}"
 msgstr ""
@@ -1643,6 +1687,10 @@ msgstr ""
 msgid "Failed to save Nginx performance settings"
 msgstr ""
 
+#: src/constants/errors/docker.ts:14
+msgid "Failed to start temp container: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:38
 msgid "Failed to verify hashes: {0}"
 msgstr ""
@@ -1701,14 +1749,10 @@ msgstr ""
 msgid "Form parse failed"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:218
-msgid "Format error %{msg}"
-msgstr ""
-
 #: src/views/config/ConfigEditor.vue:216
 msgid "Format successfully"
 msgstr ""
@@ -1779,7 +1823,7 @@ msgstr ""
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 msgid "History"
@@ -1938,7 +1982,7 @@ msgid "Invalid file path: {0}"
 msgstr ""
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 msgid "Invalid filename"
 msgstr ""
 
@@ -2336,7 +2380,7 @@ msgstr ""
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7
-#: src/views/config/ConfigEditor.vue:311
+#: src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2379,7 +2423,7 @@ msgstr ""
 msgid "New name"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "New Path"
 msgstr ""
 
@@ -2476,6 +2520,10 @@ msgstr ""
 msgid "Nginx is not running"
 msgstr ""
 
+#: src/constants/errors/docker.ts:9
+msgid "Nginx is not running in another container"
+msgstr ""
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 msgid "Nginx is running"
 msgstr ""
@@ -2785,11 +2833,11 @@ msgstr ""
 msgid "Otp or recovery code empty"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr ""
 
@@ -2833,7 +2881,7 @@ msgstr ""
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr ""
@@ -2914,7 +2962,7 @@ msgid "Please generate new recovery codes in the preferences immediately to prev
 msgstr ""
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 msgid "Please input a filename"
 msgstr ""
 
@@ -3137,6 +3185,10 @@ msgstr ""
 msgid "Reload Nginx"
 msgstr ""
 
+#: src/constants/errors/nginx.ts:3
+msgid "Reload nginx failed: {0}"
+msgstr ""
+
 #: src/components/Notification/notifications.ts:10
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
 msgstr ""
@@ -3400,7 +3452,7 @@ msgstr ""
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4127,7 +4179,7 @@ msgstr ""
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
 #: src/views/config/configColumns.tsx:36
-#: src/views/config/ConfigEditor.vue:331
+#: src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38

+ 87 - 21
app/src/language/ru_RU/app.po

@@ -83,7 +83,7 @@ msgid "Add a passkey"
 msgstr "Добавить ключ доступа"
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 msgid "Add Configuration"
 msgstr "Добавить конфигурацию"
 
@@ -300,7 +300,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr ""
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -354,7 +354,7 @@ msgstr ""
 msgid "Base information"
 msgstr "Основная информация"
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 msgid "Basic"
@@ -581,7 +581,7 @@ msgid_plural "Changed Certificates"
 msgstr[0] "Сертификат изменен"
 msgstr[1] "Сертификаты изменены"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "Changed Path"
 msgstr "Путь изменён"
 
@@ -694,6 +694,10 @@ msgstr ""
 msgid "Command"
 msgstr "Команда"
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -774,6 +778,10 @@ msgstr "Соединение потеряно, пожалуйста, обнов
 msgid "Connection timeout period"
 msgstr ""
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -1004,7 +1012,7 @@ msgstr "Удалено успешно"
 msgid "Demo"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr "Развернуть"
 
@@ -1207,6 +1215,10 @@ msgstr "Хотите удалить этот сервер?"
 msgid "Do you want to remove this upstream?"
 msgstr "Хотите удалить этот сервер?"
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 #, fuzzy
@@ -1268,7 +1280,7 @@ msgstr "Редактировать %{n}"
 msgid "Edit %{n}"
 msgstr "Редактировать %{n}"
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr "Редактировать Конфигурацию"
 
@@ -1482,6 +1494,11 @@ msgstr ""
 msgid "Fail to obtain certificate"
 msgstr "Не удалось получить сертификат"
 
+#: src/constants/errors/docker.ts:4
+#, fuzzy
+msgid "Failed to attach to exec instance: {0}"
+msgstr "Не удалось включить %{msg}"
+
 #: src/constants/errors/backup.ts:5
 msgid "Failed to backup Nginx config files: {0}"
 msgstr ""
@@ -1568,6 +1585,11 @@ msgstr ""
 msgid "Failed to create symbolic link: {0}"
 msgstr "Не удалось включить %{msg}"
 
+#: src/constants/errors/docker.ts:13
+#, fuzzy
+msgid "Failed to create temp container: {0}"
+msgstr "Не удалось включить %{msg}"
+
 #: src/constants/errors/backup.ts:2
 #, fuzzy
 msgid "Failed to create temporary directory"
@@ -1666,6 +1688,11 @@ msgstr ""
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr "Не удалось включить %{msg}"
 
+#: src/constants/errors/docker.ts:3
+#, fuzzy
+msgid "Failed to exec command: {0}"
+msgstr "Не удалось включить %{msg}"
+
 #: src/constants/errors/backup.ts:35
 #, fuzzy
 msgid "Failed to extract archive: {0}"
@@ -1685,6 +1712,11 @@ msgstr "Не удалось получить информацию о серти
 msgid "Failed to get certificate information"
 msgstr "Не удалось получить информацию о сертификате"
 
+#: src/constants/errors/docker.ts:10
+#, fuzzy
+msgid "Failed to get hostname: {0}"
+msgstr "Не удалось включить %{msg}"
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 #, fuzzy
 msgid "Failed to get Nginx performance settings"
@@ -1695,6 +1727,16 @@ msgstr "Не удалось получить информацию о серти
 msgid "Failed to get performance data"
 msgstr "Не удалось получить информацию о сертификате"
 
+#: src/constants/errors/docker.ts:8
+#, fuzzy
+msgid "Failed to inspect container: {0}"
+msgstr "Не удалось включить %{msg}"
+
+#: src/constants/errors/docker.ts:12
+#, fuzzy
+msgid "Failed to inspect current container: {0}"
+msgstr "Не удалось включить %{msg}"
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 #, fuzzy
 msgid "Failed to load history records"
@@ -1719,6 +1761,11 @@ msgstr "Не удалось включить %{msg}"
 msgid "Failed to parse nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:11
+#, fuzzy
+msgid "Failed to pull image: {0}"
+msgstr "Не удалось включить %{msg}"
+
 #: src/constants/errors/backup.ts:53
 msgid "Failed to read encrypted file: {0}"
 msgstr ""
@@ -1736,6 +1783,11 @@ msgstr ""
 msgid "Failed to read nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:5
+#, fuzzy
+msgid "Failed to read output: {0}"
+msgstr "Не удалось включить %{msg}"
+
 #: src/constants/errors/backup.ts:21
 #, fuzzy
 msgid "Failed to read symlink: {0}"
@@ -1759,6 +1811,11 @@ msgstr "Не удалось получить сертификат"
 msgid "Failed to save Nginx performance settings"
 msgstr "Не удалось получить информацию о сертификате"
 
+#: src/constants/errors/docker.ts:14
+#, fuzzy
+msgid "Failed to start temp container: {0}"
+msgstr "Не удалось включить %{msg}"
+
 #: src/constants/errors/backup.ts:38
 #, fuzzy
 msgid "Failed to verify hashes: {0}"
@@ -1822,14 +1879,10 @@ msgstr "Для китайских пользователей: https://mirror.ghp
 msgid "Form parse failed"
 msgstr "Дублирование не удалось"
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr "Форматировать код"
 
-#: src/views/config/ConfigEditor.vue:218
-msgid "Format error %{msg}"
-msgstr "Ошибка формата %{msg}"
-
 #: src/views/config/ConfigEditor.vue:216
 msgid "Format successfully"
 msgstr "Форматирование успешно"
@@ -1904,7 +1957,7 @@ msgstr "Скрыть"
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 #, fuzzy
@@ -2085,7 +2138,7 @@ msgid "Invalid file path: {0}"
 msgstr "Неверное имя файла"
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 msgid "Invalid filename"
 msgstr "Неверное имя файла"
 
@@ -2508,7 +2561,7 @@ msgstr "Многострочная директива"
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2551,7 +2604,7 @@ msgstr "Установить"
 msgid "New name"
 msgstr "Новое имя"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "New Path"
 msgstr "Новый путь"
 
@@ -2653,6 +2706,11 @@ msgstr "Путь для Nginx Error Log"
 msgid "Nginx is not running"
 msgstr "Nginx не работает"
 
+#: src/constants/errors/docker.ts:9
+#, fuzzy
+msgid "Nginx is not running in another container"
+msgstr "Nginx не работает"
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 #, fuzzy
 msgid "Nginx is running"
@@ -2983,11 +3041,11 @@ msgstr ""
 msgid "Otp or recovery code empty"
 msgstr "Код OTP или восстановления пуст"
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr "Перезаписать"
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr "Перезаписать существующий файл"
 
@@ -3035,7 +3093,7 @@ msgstr ""
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr "Путь"
@@ -3134,7 +3192,7 @@ msgid ""
 msgstr ""
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 msgid "Please input a filename"
 msgstr "Пожалуйста, введите имя файла"
 
@@ -3380,6 +3438,11 @@ msgstr "Перегрузить"
 msgid "Reload Nginx"
 msgstr "Перезагружается nginx"
 
+#: src/constants/errors/nginx.ts:3
+#, fuzzy
+msgid "Reload nginx failed: {0}"
+msgstr "Не удалось включить %{msg}"
+
 #: src/components/Notification/notifications.ts:10
 #, fuzzy
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
@@ -3670,7 +3733,7 @@ msgstr "Выполняется"
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4517,7 +4580,7 @@ msgstr "Успешно обновлено"
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
@@ -4811,6 +4874,9 @@ msgstr "Ваши старые коды больше не будут работа
 msgid "Your passkeys"
 msgstr ""
 
+#~ msgid "Format error %{msg}"
+#~ msgstr "Ошибка формата %{msg}"
+
 #~ msgid "Failed to save, syntax error(s) was detected in the configuration."
 #~ msgstr ""
 #~ "Не удалось сохранить, обнаружены синтаксические ошибки в конфигурации."

+ 87 - 21
app/src/language/tr_TR/app.po

@@ -80,7 +80,7 @@ msgid "Add a passkey"
 msgstr "Geçiş anahtarı ekleme"
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 msgid "Add Configuration"
 msgstr "Yapılandırma Ekle"
 
@@ -297,7 +297,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr ""
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -351,7 +351,7 @@ msgstr ""
 msgid "Base information"
 msgstr "Temel bilgiler"
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 msgid "Basic"
@@ -582,7 +582,7 @@ msgid_plural "Changed Certificates"
 msgstr[0] "Değişen Sertifika"
 msgstr[1] "Değişen Sertifikalar"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "Changed Path"
 msgstr "Değişen Dosya Yolu"
 
@@ -695,6 +695,10 @@ msgstr ""
 msgid "Command"
 msgstr "Komut"
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -775,6 +779,10 @@ msgstr "Bağlantı kesildi, lütfen sayfayı yenileyin."
 msgid "Connection timeout period"
 msgstr ""
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -1011,7 +1019,7 @@ msgstr "Başarıyla silindi"
 msgid "Demo"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr "Yayınla"
 
@@ -1225,6 +1233,10 @@ msgstr "Bu sunucuyu kaldırmak istiyor musunuz?"
 msgid "Do you want to remove this upstream?"
 msgstr "Bu upstream'i kaldırmak istiyor musunuz?"
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 #, fuzzy
@@ -1287,7 +1299,7 @@ msgstr "Düzenle %{n}"
 msgid "Edit %{n}"
 msgstr "Düzenle %{n}"
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr "Yapılandırmayı Düzenle"
 
@@ -1515,6 +1527,11 @@ msgstr ""
 msgid "Fail to obtain certificate"
 msgstr "Sertifika alınamadı"
 
+#: src/constants/errors/docker.ts:4
+#, fuzzy
+msgid "Failed to attach to exec instance: {0}"
+msgstr "Etkinleştirilemedi %{msg}"
+
 #: src/constants/errors/backup.ts:5
 msgid "Failed to backup Nginx config files: {0}"
 msgstr ""
@@ -1601,6 +1618,11 @@ msgstr ""
 msgid "Failed to create symbolic link: {0}"
 msgstr "Etkinleştirilemedi %{msg}"
 
+#: src/constants/errors/docker.ts:13
+#, fuzzy
+msgid "Failed to create temp container: {0}"
+msgstr "Etkinleştirilemedi %{msg}"
+
 #: src/constants/errors/backup.ts:2
 #, fuzzy
 msgid "Failed to create temporary directory"
@@ -1699,6 +1721,11 @@ msgstr ""
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr "Etkinleştirilemedi %{msg}"
 
+#: src/constants/errors/docker.ts:3
+#, fuzzy
+msgid "Failed to exec command: {0}"
+msgstr "Etkinleştirilemedi %{msg}"
+
 #: src/constants/errors/backup.ts:35
 #, fuzzy
 msgid "Failed to extract archive: {0}"
@@ -1718,6 +1745,11 @@ msgstr "Sertifika bilgileri alınamadı"
 msgid "Failed to get certificate information"
 msgstr "Sertifika bilgileri alınamadı"
 
+#: src/constants/errors/docker.ts:10
+#, fuzzy
+msgid "Failed to get hostname: {0}"
+msgstr "Etkinleştirilemedi %{msg}"
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 #, fuzzy
 msgid "Failed to get Nginx performance settings"
@@ -1728,6 +1760,16 @@ msgstr "Sertifika bilgileri alınamadı"
 msgid "Failed to get performance data"
 msgstr "Sertifika bilgileri alınamadı"
 
+#: src/constants/errors/docker.ts:8
+#, fuzzy
+msgid "Failed to inspect container: {0}"
+msgstr "Etkinleştirilemedi %{msg}"
+
+#: src/constants/errors/docker.ts:12
+#, fuzzy
+msgid "Failed to inspect current container: {0}"
+msgstr "Etkinleştirilemedi %{msg}"
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 #, fuzzy
 msgid "Failed to load history records"
@@ -1752,6 +1794,11 @@ msgstr "Etkinleştirilemedi %{msg}"
 msgid "Failed to parse nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:11
+#, fuzzy
+msgid "Failed to pull image: {0}"
+msgstr "Etkinleştirilemedi %{msg}"
+
 #: src/constants/errors/backup.ts:53
 msgid "Failed to read encrypted file: {0}"
 msgstr ""
@@ -1769,6 +1816,11 @@ msgstr ""
 msgid "Failed to read nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:5
+#, fuzzy
+msgid "Failed to read output: {0}"
+msgstr "Etkinleştirilemedi %{msg}"
+
 #: src/constants/errors/backup.ts:21
 #, fuzzy
 msgid "Failed to read symlink: {0}"
@@ -1792,6 +1844,11 @@ msgstr "Sertifika alınamadı"
 msgid "Failed to save Nginx performance settings"
 msgstr "Sertifika bilgileri alınamadı"
 
+#: src/constants/errors/docker.ts:14
+#, fuzzy
+msgid "Failed to start temp container: {0}"
+msgstr "Etkinleştirilemedi %{msg}"
+
 #: src/constants/errors/backup.ts:38
 #, fuzzy
 msgid "Failed to verify hashes: {0}"
@@ -1855,14 +1912,10 @@ msgstr "Çinli kullanıcılar için: https://mirror.ghproxy.com/"
 msgid "Form parse failed"
 msgstr "Kopyalama başarısız oldu"
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr "Kodu Biçimlendir"
 
-#: src/views/config/ConfigEditor.vue:218
-msgid "Format error %{msg}"
-msgstr "Biçimlendirme hatası %{msg}"
-
 #: src/views/config/ConfigEditor.vue:216
 msgid "Format successfully"
 msgstr "Başarıyla biçimlendirildi"
@@ -1937,7 +1990,7 @@ msgstr "Gizle"
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 #, fuzzy
@@ -2119,7 +2172,7 @@ msgid "Invalid file path: {0}"
 msgstr "Geçersiz dosya adı"
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 msgid "Invalid filename"
 msgstr "Geçersiz dosya adı"
 
@@ -2559,7 +2612,7 @@ msgstr "Çok Hatlı Direktif"
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2608,7 +2661,7 @@ msgstr "Yükle"
 msgid "New name"
 msgstr "Yeni Ad"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 #, fuzzy
 msgid "New Path"
 msgstr "Yeni Yol"
@@ -2719,6 +2772,11 @@ msgstr "Nginx Hata Günlüğü Yolu"
 msgid "Nginx is not running"
 msgstr "Nginx çalışmıyor"
 
+#: src/constants/errors/docker.ts:9
+#, fuzzy
+msgid "Nginx is not running in another container"
+msgstr "Nginx çalışmıyor"
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 #, fuzzy
 msgid "Nginx is running"
@@ -3074,12 +3132,12 @@ msgstr ""
 msgid "Otp or recovery code empty"
 msgstr "Kurtarma kodunu kullanın"
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 #, fuzzy
 msgid "Overwrite"
 msgstr "Üzerine yaz"
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 #, fuzzy
 msgid "Overwrite exist file"
 msgstr "Mevcut dosyanın üzerine yaz"
@@ -3136,7 +3194,7 @@ msgstr ""
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 #, fuzzy
 msgid "Path"
@@ -3245,7 +3303,7 @@ msgid ""
 msgstr ""
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 #, fuzzy
 msgid "Please input a filename"
 msgstr "Lütfen bir dosya adı girin"
@@ -3522,6 +3580,11 @@ msgstr "Tekrar yükle"
 msgid "Reload Nginx"
 msgstr "Nginx'i yeniden yükleme"
 
+#: src/constants/errors/nginx.ts:3
+#, fuzzy
+msgid "Reload nginx failed: {0}"
+msgstr "Etkinleştirilemedi %{msg}"
+
 #: src/components/Notification/notifications.ts:10
 #, fuzzy
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
@@ -3843,7 +3906,7 @@ msgstr "Çalışıyor"
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4768,7 +4831,7 @@ msgstr "Güncellendi"
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
@@ -5098,6 +5161,9 @@ msgstr ""
 msgid "Your passkeys"
 msgstr "Geçiş anahtarlarınız"
 
+#~ msgid "Format error %{msg}"
+#~ msgstr "Biçimlendirme hatası %{msg}"
+
 #~ msgid "Failed to save, syntax error(s) was detected in the configuration."
 #~ msgstr "Kaydedilemedi, yapılandırmada sözdizimi hatası(ları) tespit edildi."
 

+ 73 - 21
app/src/language/uk_UA/app.po

@@ -80,7 +80,7 @@ msgid "Add a passkey"
 msgstr ""
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 msgid "Add Configuration"
 msgstr "Додати конфігурацію"
 
@@ -296,7 +296,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr ""
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -347,7 +347,7 @@ msgstr ""
 msgid "Base information"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 msgid "Basic"
@@ -566,7 +566,7 @@ msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "Changed Path"
 msgstr ""
 
@@ -678,6 +678,10 @@ msgstr ""
 msgid "Command"
 msgstr ""
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -753,6 +757,10 @@ msgstr ""
 msgid "Connection timeout period"
 msgstr ""
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -971,7 +979,7 @@ msgstr ""
 msgid "Demo"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr ""
 
@@ -1158,6 +1166,10 @@ msgstr ""
 msgid "Do you want to remove this upstream?"
 msgstr ""
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 msgid "Document"
@@ -1214,7 +1226,7 @@ msgstr ""
 msgid "Edit %{n}"
 msgstr ""
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr ""
 
@@ -1415,6 +1427,10 @@ msgstr ""
 msgid "Fail to obtain certificate"
 msgstr ""
 
+#: src/constants/errors/docker.ts:4
+msgid "Failed to attach to exec instance: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:5
 msgid "Failed to backup Nginx config files: {0}"
 msgstr ""
@@ -1491,6 +1507,10 @@ msgstr ""
 msgid "Failed to create symbolic link: {0}"
 msgstr ""
 
+#: src/constants/errors/docker.ts:13
+msgid "Failed to create temp container: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:2
 msgid "Failed to create temporary directory"
 msgstr ""
@@ -1575,6 +1595,10 @@ msgstr ""
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr ""
 
+#: src/constants/errors/docker.ts:3
+msgid "Failed to exec command: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:35
 msgid "Failed to extract archive: {0}"
 msgstr ""
@@ -1591,6 +1615,10 @@ msgstr ""
 msgid "Failed to get certificate information"
 msgstr ""
 
+#: src/constants/errors/docker.ts:10
+msgid "Failed to get hostname: {0}"
+msgstr ""
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 msgid "Failed to get Nginx performance settings"
 msgstr ""
@@ -1599,6 +1627,14 @@ msgstr ""
 msgid "Failed to get performance data"
 msgstr ""
 
+#: src/constants/errors/docker.ts:8
+msgid "Failed to inspect container: {0}"
+msgstr ""
+
+#: src/constants/errors/docker.ts:12
+msgid "Failed to inspect current container: {0}"
+msgstr ""
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 msgid "Failed to load history records"
 msgstr ""
@@ -1619,6 +1655,10 @@ msgstr ""
 msgid "Failed to parse nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:11
+msgid "Failed to pull image: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:53
 msgid "Failed to read encrypted file: {0}"
 msgstr ""
@@ -1635,6 +1675,10 @@ msgstr ""
 msgid "Failed to read nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:5
+msgid "Failed to read output: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:21
 msgid "Failed to read symlink: {0}"
 msgstr ""
@@ -1655,6 +1699,10 @@ msgstr ""
 msgid "Failed to save Nginx performance settings"
 msgstr ""
 
+#: src/constants/errors/docker.ts:14
+msgid "Failed to start temp container: {0}"
+msgstr ""
+
 #: src/constants/errors/backup.ts:38
 msgid "Failed to verify hashes: {0}"
 msgstr ""
@@ -1714,14 +1762,10 @@ msgstr ""
 msgid "Form parse failed"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:218
-msgid "Format error %{msg}"
-msgstr ""
-
 #: src/views/config/ConfigEditor.vue:216
 msgid "Format successfully"
 msgstr ""
@@ -1792,7 +1836,7 @@ msgstr ""
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 msgid "History"
@@ -1959,7 +2003,7 @@ msgid "Invalid file path: {0}"
 msgstr ""
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 msgid "Invalid filename"
 msgstr ""
 
@@ -2358,7 +2402,7 @@ msgstr ""
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2400,7 +2444,7 @@ msgstr ""
 msgid "New name"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "New Path"
 msgstr ""
 
@@ -2496,6 +2540,10 @@ msgstr ""
 msgid "Nginx is not running"
 msgstr ""
 
+#: src/constants/errors/docker.ts:9
+msgid "Nginx is not running in another container"
+msgstr ""
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 msgid "Nginx is running"
 msgstr ""
@@ -2807,11 +2855,11 @@ msgstr ""
 msgid "Otp or recovery code empty"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr ""
 
@@ -2857,7 +2905,7 @@ msgstr ""
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr ""
@@ -2947,7 +2995,7 @@ msgid ""
 msgstr ""
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 msgid "Please input a filename"
 msgstr ""
 
@@ -3175,6 +3223,10 @@ msgstr ""
 msgid "Reload Nginx"
 msgstr ""
 
+#: src/constants/errors/nginx.ts:3
+msgid "Reload nginx failed: {0}"
+msgstr ""
+
 #: src/components/Notification/notifications.ts:10
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
 msgstr ""
@@ -3441,7 +3493,7 @@ msgstr ""
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4216,7 +4268,7 @@ msgstr ""
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38

+ 87 - 22
app/src/language/vi_VN/app.po

@@ -77,7 +77,7 @@ msgid "Add a passkey"
 msgstr ""
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 #, fuzzy
 msgid "Add Configuration"
 msgstr "Sửa cấu hình"
@@ -312,7 +312,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr ""
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -367,7 +367,7 @@ msgstr ""
 msgid "Base information"
 msgstr "Thông tin"
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 #, fuzzy
@@ -606,7 +606,7 @@ msgid_plural "Changed Certificates"
 msgstr[0] "Thay đổi chứng chỉ"
 msgstr[1] "Thay đổi chứng chỉ"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 #, fuzzy
 msgid "Changed Path"
 msgstr "Thay đổi chứng chỉ"
@@ -721,6 +721,10 @@ msgstr ""
 msgid "Command"
 msgstr "Bình luận"
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -801,6 +805,10 @@ msgstr ""
 msgid "Connection timeout period"
 msgstr ""
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -1038,7 +1046,7 @@ msgstr "Đã xoá thành công"
 msgid "Demo"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr "Triển khai"
 
@@ -1246,6 +1254,10 @@ msgstr "Bạn muốn xóa máy chủ này ?"
 msgid "Do you want to remove this upstream?"
 msgstr "Bạn muốn xóa máy chủ này ?"
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 #, fuzzy
@@ -1306,7 +1318,7 @@ msgstr "Sửa %{n}"
 msgid "Edit %{n}"
 msgstr "Sửa %{n}"
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr "Sửa cấu hình"
 
@@ -1531,6 +1543,11 @@ msgstr ""
 msgid "Fail to obtain certificate"
 msgstr "Nhận chứng chỉ"
 
+#: src/constants/errors/docker.ts:4
+#, fuzzy
+msgid "Failed to attach to exec instance: {0}"
+msgstr "Không thể bật %{msg}"
+
 #: src/constants/errors/backup.ts:5
 msgid "Failed to backup Nginx config files: {0}"
 msgstr ""
@@ -1617,6 +1634,11 @@ msgstr ""
 msgid "Failed to create symbolic link: {0}"
 msgstr "Không thể bật %{msg}"
 
+#: src/constants/errors/docker.ts:13
+#, fuzzy
+msgid "Failed to create temp container: {0}"
+msgstr "Không thể bật %{msg}"
+
 #: src/constants/errors/backup.ts:2
 #, fuzzy
 msgid "Failed to create temporary directory"
@@ -1715,6 +1737,11 @@ msgstr ""
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr "Không thể bật %{msg}"
 
+#: src/constants/errors/docker.ts:3
+#, fuzzy
+msgid "Failed to exec command: {0}"
+msgstr "Không thể bật %{msg}"
+
 #: src/constants/errors/backup.ts:35
 #, fuzzy
 msgid "Failed to extract archive: {0}"
@@ -1734,6 +1761,11 @@ msgstr "Không thể truy xuất thông tin chứng chỉ"
 msgid "Failed to get certificate information"
 msgstr "Không thể truy xuất thông tin chứng chỉ"
 
+#: src/constants/errors/docker.ts:10
+#, fuzzy
+msgid "Failed to get hostname: {0}"
+msgstr "Không thể bật %{msg}"
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 #, fuzzy
 msgid "Failed to get Nginx performance settings"
@@ -1744,6 +1776,16 @@ msgstr "Không thể truy xuất thông tin chứng chỉ"
 msgid "Failed to get performance data"
 msgstr "Không thể truy xuất thông tin chứng chỉ"
 
+#: src/constants/errors/docker.ts:8
+#, fuzzy
+msgid "Failed to inspect container: {0}"
+msgstr "Không thể bật %{msg}"
+
+#: src/constants/errors/docker.ts:12
+#, fuzzy
+msgid "Failed to inspect current container: {0}"
+msgstr "Không thể bật %{msg}"
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 #, fuzzy
 msgid "Failed to load history records"
@@ -1768,6 +1810,11 @@ msgstr "Không thể bật %{msg}"
 msgid "Failed to parse nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:11
+#, fuzzy
+msgid "Failed to pull image: {0}"
+msgstr "Không thể bật %{msg}"
+
 #: src/constants/errors/backup.ts:53
 msgid "Failed to read encrypted file: {0}"
 msgstr ""
@@ -1785,6 +1832,11 @@ msgstr ""
 msgid "Failed to read nginx.conf"
 msgstr ""
 
+#: src/constants/errors/docker.ts:5
+#, fuzzy
+msgid "Failed to read output: {0}"
+msgstr "Không thể bật %{msg}"
+
 #: src/constants/errors/backup.ts:21
 #, fuzzy
 msgid "Failed to read symlink: {0}"
@@ -1808,6 +1860,11 @@ msgstr "Nhận chứng chỉ"
 msgid "Failed to save Nginx performance settings"
 msgstr "Không thể truy xuất thông tin chứng chỉ"
 
+#: src/constants/errors/docker.ts:14
+#, fuzzy
+msgid "Failed to start temp container: {0}"
+msgstr "Không thể bật %{msg}"
+
 #: src/constants/errors/backup.ts:38
 #, fuzzy
 msgid "Failed to verify hashes: {0}"
@@ -1870,15 +1927,10 @@ msgstr "Người dùng Trung Quốc: https://mirror.ghproxy.com/"
 msgid "Form parse failed"
 msgstr "Nhân bản thất bại"
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr "Định dạng code"
 
-#: src/views/config/ConfigEditor.vue:218
-#, fuzzy
-msgid "Format error %{msg}"
-msgstr "Lưu lỗi %{msg}"
-
 #: src/views/config/ConfigEditor.vue:216
 #, fuzzy
 msgid "Format successfully"
@@ -1954,7 +2006,7 @@ msgstr ""
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 #, fuzzy
@@ -2128,7 +2180,7 @@ msgid "Invalid file path: {0}"
 msgstr "E-mail không chính xác!"
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 #, fuzzy
 msgid "Invalid filename"
 msgstr "E-mail không chính xác!"
@@ -2562,7 +2614,7 @@ msgstr "Single Directive"
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2606,7 +2658,7 @@ msgstr "Cài đặt"
 msgid "New name"
 msgstr "Username"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 #, fuzzy
 msgid "New Path"
 msgstr "Đường dẫn"
@@ -2709,6 +2761,10 @@ msgstr "Vị trí lưu log lỗi (Error log) của Nginx"
 msgid "Nginx is not running"
 msgstr ""
 
+#: src/constants/errors/docker.ts:9
+msgid "Nginx is not running in another container"
+msgstr ""
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 msgid "Nginx is running"
 msgstr ""
@@ -3039,11 +3095,11 @@ msgstr ""
 msgid "Otp or recovery code empty"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr "Ghi đè"
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr "Ghi đè tập tin đã tồn tại"
 
@@ -3091,7 +3147,7 @@ msgstr ""
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr "Đường dẫn"
@@ -3185,7 +3241,7 @@ msgid ""
 msgstr ""
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 #, fuzzy
 msgid "Please input a filename"
 msgstr "Vui lòng nhập username!"
@@ -3431,6 +3487,11 @@ msgstr "Tải lại"
 msgid "Reload Nginx"
 msgstr "Tải lại nginx"
 
+#: src/constants/errors/nginx.ts:3
+#, fuzzy
+msgid "Reload nginx failed: {0}"
+msgstr "Không thể bật %{msg}"
+
 #: src/components/Notification/notifications.ts:10
 #, fuzzy
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
@@ -3736,7 +3797,7 @@ msgstr "Running"
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4566,7 +4627,7 @@ msgstr "Cập nhật thành công"
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
@@ -4866,6 +4927,10 @@ msgstr ""
 msgid "Your passkeys"
 msgstr ""
 
+#, fuzzy
+#~ msgid "Format error %{msg}"
+#~ msgstr "Lưu lỗi %{msg}"
+
 #~ msgid "Failed to save, syntax error(s) was detected in the configuration."
 #~ msgstr "Không lưu được, đã phát hiện thấy (các) lỗi cú pháp trong cấu hình."
 

+ 87 - 32
app/src/language/zh_CN/app.po

@@ -3,7 +3,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "POT-Creation-Date: \n"
-"PO-Revision-Date: 2025-04-19 14:14+0800\n"
+"PO-Revision-Date: 2025-04-21 14:19+0800\n"
 "Last-Translator: 0xJacky <me@jackyu.cn>\n"
 "Language-Team: Chinese (Simplified Han script) <https://weblate.nginxui.com/"
 "projects/nginx-ui/frontend/zh_Hans/>\n"
@@ -81,7 +81,7 @@ msgid "Add a passkey"
 msgstr "添加 Passkey"
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 msgid "Add Configuration"
 msgstr "添加配置"
 
@@ -294,7 +294,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr "自动索引站点和 Stream 的配置文件。"
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -345,7 +345,7 @@ msgstr "Bark"
 msgid "Base information"
 msgstr "基本信息"
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 msgid "Basic"
@@ -562,7 +562,7 @@ msgid "Changed Certificate"
 msgid_plural "Changed Certificates"
 msgstr[0] "变更证书"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "Changed Path"
 msgstr "变更后的路径"
 
@@ -677,6 +677,10 @@ msgstr "代码补全模型"
 msgid "Command"
 msgstr "命令"
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr "命令以意外退出代码退出:{0},错误:{1}"
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -752,6 +756,10 @@ msgstr "连接中断,请刷新页面。"
 msgid "Connection timeout period"
 msgstr "连接超时时间"
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr "未知容器状态"
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -971,7 +979,7 @@ msgstr "删除成功"
 msgid "Demo"
 msgstr "Demo"
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr "部署"
 
@@ -1158,6 +1166,10 @@ msgstr "你想删除这个服务器吗?"
 msgid "Do you want to remove this upstream?"
 msgstr "你想删除这个 Upstream 吗?"
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr "Docker 客户端未初始化"
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 msgid "Document"
@@ -1214,7 +1226,7 @@ msgstr "编辑"
 msgid "Edit %{n}"
 msgstr "编辑 %{n}"
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr "编辑配置"
 
@@ -1415,6 +1427,10 @@ msgstr "外部通知"
 msgid "Fail to obtain certificate"
 msgstr "获取证书失败"
 
+#: src/constants/errors/docker.ts:4
+msgid "Failed to attach to exec instance: {0}"
+msgstr "连接执行实例失败:{0}"
+
 #: src/constants/errors/backup.ts:5
 msgid "Failed to backup Nginx config files: {0}"
 msgstr "备份 Nginx 配置文件失败:{0}"
@@ -1491,6 +1507,10 @@ msgstr "创建还原目录失败:{0}"
 msgid "Failed to create symbolic link: {0}"
 msgstr "创建符号链接失败:{0}"
 
+#: src/constants/errors/docker.ts:13
+msgid "Failed to create temp container: {0}"
+msgstr "创建临时容器失败:{0}"
+
 #: src/constants/errors/backup.ts:2
 msgid "Failed to create temporary directory"
 msgstr "创建临时目录失败"
@@ -1575,6 +1595,10 @@ msgstr "加密 Nginx UI 目录失败:{0}"
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr "符号链接解析失败:{0}"
 
+#: src/constants/errors/docker.ts:3
+msgid "Failed to exec command: {0}"
+msgstr "执行命令失败:{0}"
+
 #: src/constants/errors/backup.ts:35
 msgid "Failed to extract archive: {0}"
 msgstr "解压缩失败:{0}"
@@ -1591,6 +1615,10 @@ msgstr "生成初始化向量失败:{0}"
 msgid "Failed to get certificate information"
 msgstr "获取证书信息失败"
 
+#: src/constants/errors/docker.ts:10
+msgid "Failed to get hostname: {0}"
+msgstr "获取主机名失败: {0}"
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 msgid "Failed to get Nginx performance settings"
 msgstr "获取 Nginx 性能参数失败"
@@ -1599,6 +1627,14 @@ msgstr "获取 Nginx 性能参数失败"
 msgid "Failed to get performance data"
 msgstr "获取性能数据失败"
 
+#: src/constants/errors/docker.ts:8
+msgid "Failed to inspect container: {0}"
+msgstr "检查容器失败:{0}"
+
+#: src/constants/errors/docker.ts:12
+msgid "Failed to inspect current container: {0}"
+msgstr "检查当前容器失败:{0}"
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 msgid "Failed to load history records"
 msgstr "加载历史记录失败"
@@ -1619,6 +1655,10 @@ msgstr "打开压缩文件失败:{0}"
 msgid "Failed to parse nginx.conf"
 msgstr "解析 nginx.conf 失败"
 
+#: src/constants/errors/docker.ts:11
+msgid "Failed to pull image: {0}"
+msgstr "拉取镜像失败:{0}"
+
 #: src/constants/errors/backup.ts:53
 msgid "Failed to read encrypted file: {0}"
 msgstr "读取加密文件失败:{0}"
@@ -1635,6 +1675,10 @@ msgstr "读取哈希信息文件失败:{0}"
 msgid "Failed to read nginx.conf"
 msgstr "读取 nginx.conf 失败"
 
+#: src/constants/errors/docker.ts:5
+msgid "Failed to read output: {0}"
+msgstr "读取输出失败:{0}"
+
 #: src/constants/errors/backup.ts:21
 msgid "Failed to read symlink: {0}"
 msgstr "读取符号链接失败:{0}"
@@ -1655,6 +1699,10 @@ msgstr "证书撤销失败"
 msgid "Failed to save Nginx performance settings"
 msgstr "保存 Nginx 性能参数失败"
 
+#: src/constants/errors/docker.ts:14
+msgid "Failed to start temp container: {0}"
+msgstr "启动临时容器失败:{0}"
+
 #: src/constants/errors/backup.ts:38
 msgid "Failed to verify hashes: {0}"
 msgstr "验证哈希值失败:{0}"
@@ -1714,14 +1762,10 @@ msgstr "中国用户:https://mirror.ghproxy.com/"
 msgid "Form parse failed"
 msgstr "表单解析失败"
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr "代码格式化"
 
-#: src/views/config/ConfigEditor.vue:218
-msgid "Format error %{msg}"
-msgstr "保存错误 %{msg}"
-
 #: src/views/config/ConfigEditor.vue:216
 msgid "Format successfully"
 msgstr "格式化成功"
@@ -1792,7 +1836,7 @@ msgstr "隐藏"
 msgid "Higher value means better connection reuse"
 msgstr "更高的值意味着更好的连接再利用"
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 msgid "History"
@@ -1962,7 +2006,7 @@ msgid "Invalid file path: {0}"
 msgstr "文件路径无效:{0}"
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 msgid "Invalid filename"
 msgstr "文件名无效"
 
@@ -2368,7 +2412,7 @@ msgstr "多行指令"
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2410,7 +2454,7 @@ msgstr "新安装"
 msgid "New name"
 msgstr "新名称"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "New Path"
 msgstr "新路径"
 
@@ -2506,6 +2550,10 @@ msgstr "Nginx 错误日志路径"
 msgid "Nginx is not running"
 msgstr "Nginx 未启动"
 
+#: src/constants/errors/docker.ts:9
+msgid "Nginx is not running in another container"
+msgstr "Nginx 未在另一个容器中运行"
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 msgid "Nginx is running"
 msgstr "Nginx 正在运行"
@@ -2817,11 +2865,11 @@ msgstr "其他"
 msgid "Otp or recovery code empty"
 msgstr "OTP 或恢复代码为空"
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr "覆盖"
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr "覆盖现有文件"
 
@@ -2869,7 +2917,7 @@ msgstr "密码长度不能超过 20 个字符"
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr "路径"
@@ -2961,7 +3009,7 @@ msgid ""
 msgstr "请立即在偏好设置中生成新的恢复码,以防止无法访问您的账户。"
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 msgid "Please input a filename"
 msgstr "请输入文件名"
 
@@ -3191,6 +3239,10 @@ msgstr "重载"
 msgid "Reload Nginx"
 msgstr "重载 Nginx"
 
+#: src/constants/errors/nginx.ts:3
+msgid "Reload nginx failed: {0}"
+msgstr "重载 Nginx 失败: {0}"
+
 #: src/components/Notification/notifications.ts:10
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
 msgstr "在 %{node} 上重载 Nginx 失败,响应:%{resp}"
@@ -3459,7 +3511,7 @@ msgstr "运行中"
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -3615,19 +3667,19 @@ msgstr "使用 HTTP01 challenge provider"
 
 #: src/constants/errors/nginx_log.ts:8
 msgid ""
-"Settings.NginxLogSettings.AccessLogPath is empty, refer to https://nginxui."
-"com/guide/config-nginx.html for more information"
+"Settings.NginxLogSettings.AccessLogPath is empty, refer to https://"
+"nginxui.com/guide/config-nginx.html for more information"
 msgstr ""
-"Settings.NginxLogSettings.AccessLogPath 为空,更多信息请参阅 https://nginxui."
-"com/guide/config-nginx.html"
+"Settings.NginxLogSettings.AccessLogPath 为空,更多信息请参阅 https://"
+"nginxui.com/guide/config-nginx.html"
 
 #: src/constants/errors/nginx_log.ts:7
 msgid ""
-"Settings.NginxLogSettings.ErrorLogPath is empty, refer to https://nginxui."
-"com/guide/config-nginx.html for more information"
+"Settings.NginxLogSettings.ErrorLogPath is empty, refer to https://"
+"nginxui.com/guide/config-nginx.html for more information"
 msgstr ""
-"Settings.NginxLogSettings.ErrorLogPath为空,更多信息请参阅 https://nginxui."
-"com/guide/config-nginx.html"
+"Settings.NginxLogSettings.ErrorLogPath为空,更多信息请参阅 https://"
+"nginxui.com/guide/config-nginx.html"
 
 #: src/views/dashboard/components/ParamsOpt/ProxyCacheConfig.vue:147
 msgid "Shared Memory Zone"
@@ -4252,7 +4304,7 @@ msgstr "更新成功"
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
@@ -4545,6 +4597,9 @@ msgstr "您的旧代码将不再有效。"
 msgid "Your passkeys"
 msgstr "你的 Passkeys"
 
+#~ msgid "Format error %{msg}"
+#~ msgstr "保存错误 %{msg}"
+
 #~ msgid "Failed to save, syntax error(s) was detected in the configuration."
 #~ msgstr "保存失败,在配置中检测到语法错误。"
 
@@ -4641,8 +4696,8 @@ msgstr "你的 Passkeys"
 #~ msgstr "请将远程 Nginx UI 升级到最新版本"
 
 #~ msgid ""
-#~ "Rename %{orig_path} to %{new_path} on %{env_name} failed, response: "
-#~ "%{resp}"
+#~ "Rename %{orig_path} to %{new_path} on %{env_name} failed, response: %"
+#~ "{resp}"
 #~ msgstr ""
 #~ "将 %{env_name} 上的 %{orig_path} 重命名为 %{new_path} 失败,响应:%{resp}"
 

+ 87 - 21
app/src/language/zh_TW/app.po

@@ -85,7 +85,7 @@ msgid "Add a passkey"
 msgstr "新增通行金鑰"
 
 #: src/routes/modules/config.ts:20 src/views/config/ConfigEditor.vue:171
-#: src/views/config/ConfigEditor.vue:246
+#: src/views/config/ConfigEditor.vue:244
 msgid "Add Configuration"
 msgstr "新增設定"
 
@@ -298,7 +298,7 @@ msgid "Automatically indexed from site and stream configurations."
 msgstr "自動從網站和串流設定中索引。"
 
 #: src/views/certificate/components/CertificateEditor.vue:259
-#: src/views/config/ConfigEditor.vue:268 src/views/config/ConfigList.vue:112
+#: src/views/config/ConfigEditor.vue:266 src/views/config/ConfigList.vue:112
 #: src/views/config/ConfigList.vue:195 src/views/nginx_log/NginxLog.vue:173
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:150
 #: src/views/stream/components/StreamEditor.vue:106
@@ -349,7 +349,7 @@ msgstr "Bark"
 msgid "Base information"
 msgstr "基本資訊"
 
-#: src/views/config/ConfigEditor.vue:296
+#: src/views/config/ConfigEditor.vue:294
 #: src/views/site/site_edit/components/RightPanel/RightPanel.vue:30
 #: src/views/stream/components/RightPanel/RightPanel.vue:19
 msgid "Basic"
@@ -566,7 +566,7 @@ msgid "Changed Certificate"
 msgid_plural "Changed Certificates"
 msgstr[0] "變更後憑證"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "Changed Path"
 msgstr "變更後路徑"
 
@@ -685,6 +685,10 @@ msgstr ""
 msgid "Command"
 msgstr "命令"
 
+#: src/constants/errors/docker.ts:6
+msgid "Command exited with unexpected exit code: {0}, error: {1}"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:115
 #: src/components/NgxConfigEditor/LocationEditor.vue:104
 #: src/components/NgxConfigEditor/LocationEditor.vue:135
@@ -761,6 +765,10 @@ msgstr "連線中斷,請重新整理此頁面。"
 msgid "Connection timeout period"
 msgstr "連線逾時時間"
 
+#: src/constants/errors/docker.ts:7
+msgid "Container status unknown"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:120
 #: src/components/NgxConfigEditor/LocationEditor.vue:116
 #: src/components/NgxConfigEditor/LocationEditor.vue:144
@@ -980,7 +988,7 @@ msgstr "刪除成功"
 msgid "Demo"
 msgstr "演示"
 
-#: src/views/config/ConfigEditor.vue:340
+#: src/views/config/ConfigEditor.vue:338
 msgid "Deploy"
 msgstr "部署"
 
@@ -1168,6 +1176,10 @@ msgstr "您要移除此伺服器嗎?"
 msgid "Do you want to remove this upstream?"
 msgstr "您要移除這個 Upstream 嗎?"
 
+#: src/constants/errors/docker.ts:2
+msgid "Docker client not initialized"
+msgstr ""
+
 #: src/components/NgxConfigEditor/directive/DirectiveAdd.vue:88
 #: src/components/NgxConfigEditor/directive/DirectiveDocuments.vue:16
 msgid "Document"
@@ -1224,7 +1236,7 @@ msgstr "編輯"
 msgid "Edit %{n}"
 msgstr "編輯 %{n}"
 
-#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:246
+#: src/routes/modules/config.ts:30 src/views/config/ConfigEditor.vue:244
 msgid "Edit Configuration"
 msgstr "編輯設定"
 
@@ -1425,6 +1437,11 @@ msgstr "外部通知"
 msgid "Fail to obtain certificate"
 msgstr "取得憑證失敗"
 
+#: src/constants/errors/docker.ts:4
+#, fuzzy
+msgid "Failed to attach to exec instance: {0}"
+msgstr "無法提取存檔:{0}"
+
 #: src/constants/errors/backup.ts:5
 msgid "Failed to backup Nginx config files: {0}"
 msgstr "備份 Nginx 設定檔案失敗:{0}"
@@ -1501,6 +1518,11 @@ msgstr "無法建立還原目錄:{0}"
 msgid "Failed to create symbolic link: {0}"
 msgstr "建立符號連結失敗:{0}"
 
+#: src/constants/errors/docker.ts:13
+#, fuzzy
+msgid "Failed to create temp container: {0}"
+msgstr "無法建立壓縮項目:{0}"
+
 #: src/constants/errors/backup.ts:2
 msgid "Failed to create temporary directory"
 msgstr "無法建立臨時目錄"
@@ -1585,6 +1607,11 @@ msgstr "加密 Nginx UI 目錄失敗:{0}"
 msgid "Failed to evaluate symbolic links: {0}"
 msgstr "無法評估符號連結:{0}"
 
+#: src/constants/errors/docker.ts:3
+#, fuzzy
+msgid "Failed to exec command: {0}"
+msgstr "讀取檔案失敗:{0}"
+
 #: src/constants/errors/backup.ts:35
 msgid "Failed to extract archive: {0}"
 msgstr "無法提取存檔:{0}"
@@ -1601,6 +1628,11 @@ msgstr "無法生成初始化向量:{0}"
 msgid "Failed to get certificate information"
 msgstr "取得憑證資訊失敗"
 
+#: src/constants/errors/docker.ts:10
+#, fuzzy
+msgid "Failed to get hostname: {0}"
+msgstr "複製檔案內容失敗:{0}"
+
 #: src/views/dashboard/components/ParamsOptimization.vue:61
 msgid "Failed to get Nginx performance settings"
 msgstr "無法取得 Nginx 效能設定"
@@ -1609,6 +1641,16 @@ msgstr "無法取得 Nginx 效能設定"
 msgid "Failed to get performance data"
 msgstr "無法取得效能資料"
 
+#: src/constants/errors/docker.ts:8
+#, fuzzy
+msgid "Failed to inspect container: {0}"
+msgstr "複製檔案內容失敗:{0}"
+
+#: src/constants/errors/docker.ts:12
+#, fuzzy
+msgid "Failed to inspect current container: {0}"
+msgstr "複製檔案內容失敗:{0}"
+
 #: src/components/ConfigHistory/ConfigHistory.vue:77
 msgid "Failed to load history records"
 msgstr "無法載入歷史記錄"
@@ -1629,6 +1671,11 @@ msgstr "無法開啟壓縮檔:{0}"
 msgid "Failed to parse nginx.conf"
 msgstr "解析 nginx.conf 失敗"
 
+#: src/constants/errors/docker.ts:11
+#, fuzzy
+msgid "Failed to pull image: {0}"
+msgstr "讀取檔案失敗:{0}"
+
 #: src/constants/errors/backup.ts:53
 msgid "Failed to read encrypted file: {0}"
 msgstr "無法讀取加密檔案:{0}"
@@ -1645,6 +1692,11 @@ msgstr "無法讀取雜湊資訊檔案:{0}"
 msgid "Failed to read nginx.conf"
 msgstr "讀取 nginx.conf 失敗"
 
+#: src/constants/errors/docker.ts:5
+#, fuzzy
+msgid "Failed to read output: {0}"
+msgstr "讀取檔案失敗:{0}"
+
 #: src/constants/errors/backup.ts:21
 msgid "Failed to read symlink: {0}"
 msgstr "讀取符號連結失敗:{0}"
@@ -1665,6 +1717,11 @@ msgstr "撤銷憑證失敗"
 msgid "Failed to save Nginx performance settings"
 msgstr "儲存 Nginx 效能設定失敗"
 
+#: src/constants/errors/docker.ts:14
+#, fuzzy
+msgid "Failed to start temp container: {0}"
+msgstr "複製檔案內容失敗:{0}"
+
 #: src/constants/errors/backup.ts:38
 msgid "Failed to verify hashes: {0}"
 msgstr "無法驗證雜湊值:{0}"
@@ -1724,14 +1781,10 @@ msgstr "中國使用者:https://mirror.ghproxy.com/"
 msgid "Form parse failed"
 msgstr "表單解析失敗"
 
-#: src/views/config/ConfigEditor.vue:271
+#: src/views/config/ConfigEditor.vue:269
 msgid "Format Code"
 msgstr "格式化代碼"
 
-#: src/views/config/ConfigEditor.vue:218
-msgid "Format error %{msg}"
-msgstr "格式錯誤 %{msg}"
-
 #: src/views/config/ConfigEditor.vue:216
 msgid "Format successfully"
 msgstr "成功格式化"
@@ -1802,7 +1855,7 @@ msgstr "隱藏"
 msgid "Higher value means better connection reuse"
 msgstr ""
 
-#: src/views/config/ConfigEditor.vue:256
+#: src/views/config/ConfigEditor.vue:254
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:81
 #: src/views/stream/components/StreamEditor.vue:43
 msgid "History"
@@ -1972,7 +2025,7 @@ msgid "Invalid file path: {0}"
 msgstr "無效的檔案路徑:{0}"
 
 #: src/views/config/components/Rename.vue:66
-#: src/views/config/ConfigEditor.vue:305
+#: src/views/config/ConfigEditor.vue:303
 msgid "Invalid filename"
 msgstr "無效的檔案名"
 
@@ -2379,7 +2432,7 @@ msgstr "多行指令"
 #: src/views/certificate/components/CertificateEditor.vue:162
 #: src/views/certificate/DNSCredential.vue:11
 #: src/views/config/components/Mkdir.vue:64
-#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:311
+#: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:309
 #: src/views/environments/group/columns.ts:8
 #: src/views/environments/list/envColumns.tsx:9
 #: src/views/nginx_log/NginxLogList.vue:33
@@ -2421,7 +2474,7 @@ msgstr "新安裝"
 msgid "New name"
 msgstr "新名稱"
 
-#: src/views/config/ConfigEditor.vue:324
+#: src/views/config/ConfigEditor.vue:322
 msgid "New Path"
 msgstr "新路徑"
 
@@ -2519,6 +2572,11 @@ msgstr "Nginx 錯誤日誌路徑"
 msgid "Nginx is not running"
 msgstr "Nginx 未執行"
 
+#: src/constants/errors/docker.ts:9
+#, fuzzy
+msgid "Nginx is not running in another container"
+msgstr "Nginx 未執行"
+
 #: src/views/dashboard/NginxDashBoard.vue:112
 msgid "Nginx is running"
 msgstr "Nginx 執行中"
@@ -2832,11 +2890,11 @@ msgstr "其他"
 msgid "Otp or recovery code empty"
 msgstr "OTP 或復原碼為空"
 
-#: src/views/config/ConfigEditor.vue:349
+#: src/views/config/ConfigEditor.vue:347
 msgid "Overwrite"
 msgstr "覆寫"
 
-#: src/views/config/ConfigEditor.vue:353
+#: src/views/config/ConfigEditor.vue:351
 msgid "Overwrite exist file"
 msgstr "覆寫現有檔案"
 
@@ -2884,7 +2942,7 @@ msgstr "密碼長度不能超過 20 個字元"
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:318
+#: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:41
 msgid "Path"
 msgstr "路徑"
@@ -2976,7 +3034,7 @@ msgid ""
 msgstr "請立即在偏好設定中產生新的恢復碼,以免無法存取您的賬戶。"
 
 #: src/views/config/components/Rename.vue:65
-#: src/views/config/ConfigEditor.vue:304
+#: src/views/config/ConfigEditor.vue:302
 msgid "Please input a filename"
 msgstr "請輸入檔案名稱"
 
@@ -3205,6 +3263,11 @@ msgstr "重新載入"
 msgid "Reload Nginx"
 msgstr "重新載入 Nginx"
 
+#: src/constants/errors/nginx.ts:3
+#, fuzzy
+msgid "Reload nginx failed: {0}"
+msgstr "讀取檔案失敗:{0}"
+
 #: src/components/Notification/notifications.ts:10
 msgid "Reload Nginx on %{node} failed, response: %{resp}"
 msgstr "在節點 %{node} 上重新載入 Nginx 失敗,回應:%{resp}"
@@ -3473,7 +3536,7 @@ msgstr "執行中"
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/views/certificate/components/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
-#: src/views/config/ConfigEditor.vue:277
+#: src/views/config/ConfigEditor.vue:275
 #: src/views/preference/components/Passkey.vue:130
 #: src/views/preference/Preference.vue:231
 #: src/views/site/site_edit/components/ConfigName/ConfigName.vue:52
@@ -4266,7 +4329,7 @@ msgstr "更新成功"
 
 #: src/views/certificate/ACMEUser.vue:88
 #: src/views/certificate/DNSCredential.vue:27
-#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:331
+#: src/views/config/configColumns.tsx:36 src/views/config/ConfigEditor.vue:329
 #: src/views/environments/group/columns.ts:37
 #: src/views/environments/list/envColumns.tsx:90
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
@@ -4562,6 +4625,9 @@ msgstr "您的舊代碼將不再有效。"
 msgid "Your passkeys"
 msgstr "您的通行金鑰"
 
+#~ msgid "Format error %{msg}"
+#~ msgstr "格式錯誤 %{msg}"
+
 #~ msgid "Failed to save, syntax error(s) was detected in the configuration."
 #~ msgstr "儲存失敗,在設定中偵測到語法錯誤。"
 

+ 19 - 133
app/src/views/preference/Preference.vue

@@ -1,137 +1,23 @@
 <script setup lang="ts">
-import type { Settings } from '@/api/settings'
-import settings from '@/api/settings'
-import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
-import use2FAModal from '@/components/TwoFA/use2FAModal'
-import { useSettingsStore } from '@/pinia'
-import AppSettings from '@/views/preference/AppSettings.vue'
-import AuthSettings from '@/views/preference/AuthSettings.vue'
-import CertSettings from '@/views/preference/CertSettings.vue'
-import ExternalNotify from '@/views/preference/ExternalNotify.vue'
-import HTTPSettings from '@/views/preference/HTTPSettings.vue'
-import LogrotateSettings from '@/views/preference/LogrotateSettings.vue'
-import NginxSettings from '@/views/preference/NginxSettings.vue'
-import NodeSettings from '@/views/preference/NodeSettings.vue'
-import OpenAISettings from '@/views/preference/OpenAISettings.vue'
-import ServerSettings from '@/views/preference/ServerSettings.vue'
-import TerminalSettings from '@/views/preference/TerminalSettings.vue'
-import { message } from 'ant-design-vue'
-import { storeToRefs } from 'pinia'
+import FooterToolBar from '@/components/FooterToolbar'
+import {
+  AppSettings,
+  AuthSettings,
+  CertSettings,
+  ExternalNotify,
+  HTTPSettings,
+  LogrotateSettings,
+  NginxSettings,
+  NodeSettings,
+  OpenAISettings,
+  ServerSettings,
+  TerminalSettings,
+} from '@/views/preference/tabs'
+import useSystemSettingsStore from './store'
 
-const data = ref<Settings>({
-  app: {
-    page_size: 10,
-    jwt_secret: '',
-  },
-  server: {
-    host: '0.0.0.0',
-    port: 9000,
-    run_mode: 'debug',
-    enable_https: false,
-    ssl_cert: '',
-    ssl_key: '',
-  },
-  database: {
-    name: '',
-  },
-  auth: {
-    ip_white_list: [],
-    ban_threshold_minutes: 10,
-    max_attempts: 10,
-  },
-  casdoor: {
-    endpoint: '',
-    client_id: '',
-    client_secret: '',
-    certificate_path: '',
-    organization: '',
-    application: '',
-    redirect_uri: '',
-  },
-  cert: {
-    email: '',
-    ca_dir: '',
-    renewal_interval: 7,
-    recursive_nameservers: [],
-    http_challenge_port: '9180',
-  },
-  http: {
-    github_proxy: '',
-    insecure_skip_verify: false,
-  },
-  logrotate: {
-    enabled: false,
-    cmd: '',
-    interval: 1440,
-  },
-  nginx: {
-    access_log_path: '',
-    error_log_path: '',
-    config_dir: '',
-    config_path: '',
-    log_dir_white_list: [],
-    pid_path: '',
-    test_config_cmd: '',
-    reload_cmd: '',
-    restart_cmd: '',
-    stub_status_port: 51820,
-  },
-  node: {
-    name: '',
-    secret: '',
-    skip_installation: false,
-    demo: false,
-    icp_number: '',
-    public_security_number: '',
-  },
-  openai: {
-    model: '',
-    base_url: '',
-    proxy: '',
-    token: '',
-    api_type: 'OPEN_AI',
-    enable_code_completion: false,
-    code_completion_model: '',
-  },
-  terminal: {
-    start_cmd: '',
-  },
-  webauthn: {
-    rp_display_name: '',
-    rpid: '',
-    rp_origins: [],
-  },
-})
-
-settings.get().then(r => {
-  data.value = r
-})
-
-const settingsStore = useSettingsStore()
-const { server_name } = storeToRefs(settingsStore)
-const errors = ref<Record<string, Record<string, string>>>({})
-const refAuthSettings = useTemplateRef('refAuthSettings')
-
-async function save() {
-  // fix type
-  data.value.cert.http_challenge_port = data.value.cert.http_challenge_port.toString()
-
-  const otpModal = use2FAModal()
-
-  otpModal.open().then(() => {
-    settings.save(data.value).then(r => {
-      if (!settingsStore.is_remote)
-        server_name.value = r?.server?.name ?? ''
-      data.value = r
-      refAuthSettings.value?.getBannedIPs?.()
-      message.success($gettext('Save successfully'))
-      errors.value = {}
-    })
-  })
-}
+const systemSettingsStore = useSystemSettingsStore()
 
-provide('data', data)
-provide('errors', errors)
+systemSettingsStore.getSettings()
 
 const router = useRouter()
 const route = useRoute()
@@ -195,7 +81,7 @@ onMounted(() => {
           key="auth"
           :tab="$gettext('Auth')"
         >
-          <AuthSettings ref="refAuthSettings" />
+          <AuthSettings />
         </ATabPane>
         <ATabPane
           key="cert"
@@ -226,7 +112,7 @@ onMounted(() => {
     <FooterToolBar v-if="activeKey !== 'external_notify'">
       <AButton
         type="primary"
-        @click="save"
+        @click="systemSettingsStore.save"
       >
         {{ $gettext('Save') }}
       </AButton>

+ 0 - 0
app/src/views/preference/components/AddPasskey.vue → app/src/views/preference/components/AuthSettings/AddPasskey.vue


+ 2 - 2
app/src/views/preference/components/Passkey.vue → app/src/views/preference/components/AuthSettings/Passkey.vue

@@ -1,14 +1,14 @@
 <script setup lang="ts">
 import type { Passkey } from '@/api/passkey'
 import passkey from '@/api/passkey'
-import ReactiveFromNow from '@/components/ReactiveFromNow/ReactiveFromNow.vue'
+import ReactiveFromNow from '@/components/ReactiveFromNow'
 import { formatDateTime } from '@/lib/helper'
 import { useUserStore } from '@/pinia'
-import AddPasskey from '@/views/preference/components/AddPasskey.vue'
 import { DeleteOutlined, EditOutlined, KeyOutlined } from '@ant-design/icons-vue'
 import { message } from 'ant-design-vue'
 import dayjs from 'dayjs'
 import relativeTime from 'dayjs/plugin/relativeTime'
+import AddPasskey from './AddPasskey.vue'
 
 dayjs.extend(relativeTime)
 

+ 1 - 1
app/src/views/preference/components/RecoveryCodes.vue → app/src/views/preference/components/AuthSettings/RecoveryCodes.vue

@@ -2,7 +2,7 @@
 import type { TwoFAStatus } from '@/api/2fa'
 import type { RecoveryCode } from '@/api/recovery'
 import recovery from '@/api/recovery'
-import use2FAModal from '@/components/TwoFA/use2FAModal'
+import { use2FAModal } from '@/components/TwoFA'
 import { CopyOutlined, WarningOutlined } from '@ant-design/icons-vue'
 import { UseClipboard } from '@vueuse/components'
 import { message } from 'ant-design-vue'

+ 2 - 2
app/src/views/preference/components/TOTP.vue → app/src/views/preference/components/AuthSettings/TOTP.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
 import type { RecoveryCode } from '@/api/recovery'
 import otp from '@/api/otp'
-import OTPInput from '@/components/OTPInput/OTPInput.vue'
-import use2FAModal from '@/components/TwoFA/use2FAModal'
+import OTPInput from '@/components/OTPInput'
+import { use2FAModal } from '@/components/TwoFA'
 import { CheckCircleOutlined } from '@ant-design/icons-vue'
 import { UseClipboard } from '@vueuse/components'
 import { message } from 'ant-design-vue'

+ 11 - 0
app/src/views/preference/components/AuthSettings/index.ts

@@ -0,0 +1,11 @@
+import AddPasskey from './AddPasskey.vue'
+import Passkey from './Passkey.vue'
+import RecoveryCodes from './RecoveryCodes.vue'
+import TOTP from './TOTP.vue'
+
+export {
+  AddPasskey,
+  Passkey,
+  RecoveryCodes,
+  TOTP,
+}

+ 127 - 0
app/src/views/preference/store/index.ts

@@ -0,0 +1,127 @@
+import type { Settings } from '@/api/settings'
+import settings from '@/api/settings'
+import { use2FAModal } from '@/components/TwoFA'
+import { useSettingsStore } from '@/pinia'
+import { message } from 'ant-design-vue'
+import { defineStore } from 'pinia'
+
+const useSystemSettingsStore = defineStore('systemSettings', () => {
+  const data = ref<Settings>({
+    app: {
+      page_size: 10,
+      jwt_secret: '',
+    },
+    server: {
+      host: '0.0.0.0',
+      port: 9000,
+      run_mode: 'debug',
+      enable_https: false,
+      ssl_cert: '',
+      ssl_key: '',
+    },
+    database: {
+      name: '',
+    },
+    auth: {
+      ip_white_list: [],
+      ban_threshold_minutes: 10,
+      max_attempts: 10,
+    },
+    casdoor: {
+      endpoint: '',
+      client_id: '',
+      client_secret: '',
+      certificate_path: '',
+      organization: '',
+      application: '',
+      redirect_uri: '',
+    },
+    cert: {
+      email: '',
+      ca_dir: '',
+      renewal_interval: 7,
+      recursive_nameservers: [],
+      http_challenge_port: '9180',
+    },
+    http: {
+      github_proxy: '',
+      insecure_skip_verify: false,
+    },
+    logrotate: {
+      enabled: false,
+      cmd: '',
+      interval: 1440,
+    },
+    nginx: {
+      access_log_path: '',
+      error_log_path: '',
+      config_dir: '',
+      config_path: '',
+      log_dir_white_list: [],
+      pid_path: '',
+      test_config_cmd: '',
+      reload_cmd: '',
+      restart_cmd: '',
+      stub_status_port: 51820,
+      container_name: '',
+    },
+    node: {
+      name: '',
+      secret: '',
+      skip_installation: false,
+      demo: false,
+      icp_number: '',
+      public_security_number: '',
+    },
+    openai: {
+      model: '',
+      base_url: '',
+      proxy: '',
+      token: '',
+      api_type: 'OPEN_AI',
+      enable_code_completion: false,
+      code_completion_model: '',
+    },
+    terminal: {
+      start_cmd: '',
+    },
+    webauthn: {
+      rp_display_name: '',
+      rpid: '',
+      rp_origins: [],
+    },
+  })
+  const errors = ref<Record<string, Record<string, string>>>({})
+
+  function getSettings() {
+    settings.get().then(r => {
+      data.value = r
+    })
+  }
+
+  async function save() {
+    if (!data.value)
+      return
+
+    // fix type
+    data.value.cert.http_challenge_port = data.value.cert.http_challenge_port.toString()
+
+    const otpModal = use2FAModal()
+
+    otpModal.open().then(() => {
+      settings.save(data.value!).then(r => {
+        const settingsStore = useSettingsStore()
+        const { server_name } = storeToRefs(settingsStore)
+        if (!settingsStore.is_remote)
+          server_name.value = r?.server?.name ?? ''
+        data.value = r
+        message.success($gettext('Save successfully'))
+        errors.value = {}
+      })
+    })
+  }
+
+  return { data, errors, getSettings, save }
+})
+
+export default useSystemSettingsStore

+ 4 - 3
app/src/views/preference/AppSettings.vue → app/src/views/preference/tabs/AppSettings.vue

@@ -1,8 +1,9 @@
 <script setup lang="ts">
-import type { Settings } from '@/api/settings'
-import SensitiveString from '@/components/SensitiveString/SensitiveString.vue'
+import SensitiveString from '@/components/SensitiveString'
+import useSystemSettingsStore from '../store'
 
-const data: Ref<Settings> = inject('data') as Ref<Settings>
+const systemSettingsStore = useSystemSettingsStore()
+const { data } = storeToRefs(systemSettingsStore)
 </script>
 
 <template>

+ 6 - 6
app/src/views/preference/AuthSettings.vue → app/src/views/preference/tabs/AuthSettings.vue

@@ -1,18 +1,18 @@
 <script setup lang="tsx">
 import type { TwoFAStatus } from '@/api/2fa'
 import type { RecoveryCode } from '@/api/recovery'
-import type { BannedIP, Settings } from '@/api/settings'
+import type { BannedIP } from '@/api/settings'
 import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import type { Ref } from 'vue'
 import twoFA from '@/api/2fa'
 import setting from '@/api/settings'
-import RecoveryCodes from '@/views/preference/components/RecoveryCodes.vue'
-import TOTP from '@/views/preference/components/TOTP.vue'
 import { message } from 'ant-design-vue'
 import dayjs from 'dayjs'
-import PasskeyRegistration from './components/Passkey.vue'
+import { Passkey, RecoveryCodes, TOTP } from '../components/AuthSettings'
+import useSystemSettingsStore from '../store'
 
-const data: Ref<Settings> = inject('data') as Ref<Settings>
+const systemSettingsStore = useSystemSettingsStore()
+const { data } = storeToRefs(systemSettingsStore)
 
 const bannedIPColumns = [{
   title: $gettext('IP'),
@@ -68,7 +68,7 @@ get2FAStatus()
   <div class="flex justify-center">
     <div>
       <h2>{{ $gettext('2FA Settings') }}</h2>
-      <PasskeyRegistration class="mb-4" />
+      <Passkey class="mb-4" />
 
       <TOTP
         v-model:recovery-codes="recoveryCodes"

+ 3 - 3
app/src/views/preference/CertSettings.vue → app/src/views/preference/tabs/CertSettings.vue

@@ -1,10 +1,10 @@
 <script setup lang="ts">
-import type { Settings } from '@/api/settings'
 import { DeleteOutlined, HolderOutlined } from '@ant-design/icons-vue'
 import Draggable from 'vuedraggable'
+import useSystemSettingsStore from '../store'
 
-const data: Ref<Settings> = inject('data') as Ref<Settings>
-const errors: Record<string, Record<string, string>> = inject('errors') as Record<string, Record<string, string>>
+const systemSettingsStore = useSystemSettingsStore()
+const { data, errors } = storeToRefs(systemSettingsStore)
 </script>
 
 <template>

+ 2 - 2
app/src/views/preference/ExternalNotify.vue → app/src/views/preference/tabs/ExternalNotify.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
 import externalNotify from '@/api/external_notify'
 import { StdCurd } from '@/components/StdDesign/StdDataDisplay'
-import columns from './components/ExternalNotify/columns'
-import ExternalNotifyEditor from './components/ExternalNotify/ExternalNotifyEditor.vue'
+import ExternalNotifyEditor from '../components/ExternalNotify'
+import columns from '../components/ExternalNotify/columns'
 </script>
 
 <template>

+ 3 - 3
app/src/views/preference/HTTPSettings.vue → app/src/views/preference/tabs/HTTPSettings.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
-import type { Settings } from '@/api/settings'
+import useSystemSettingsStore from '../store'
 
-const data: Ref<Settings> = inject('data') as Ref<Settings>
-const errors: Record<string, Record<string, string>> = inject('errors') as Record<string, Record<string, string>>
+const systemSettingsStore = useSystemSettingsStore()
+const { data, errors } = storeToRefs(systemSettingsStore)
 </script>
 
 <template>

+ 3 - 2
app/src/views/preference/LogrotateSettings.vue → app/src/views/preference/tabs/LogrotateSettings.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
-import type { Settings } from '@/api/settings'
+import useSystemSettingsStore from '../store'
 
-const data: Ref<Settings> = inject('data') as Ref<Settings>
+const systemSettingsStore = useSystemSettingsStore()
+const { data } = storeToRefs(systemSettingsStore)
 </script>
 
 <template>

+ 16 - 2
app/src/views/preference/NginxSettings.vue → app/src/views/preference/tabs/NginxSettings.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
-import type { Settings } from '@/api/settings'
+import useSystemSettingsStore from '../store'
 
-const data: Ref<Settings> = inject('data') as Ref<Settings>
+const systemSettingsStore = useSystemSettingsStore()
+const { data } = storeToRefs(systemSettingsStore)
 </script>
 
 <template>
@@ -42,6 +43,19 @@ const data: Ref<Settings> = inject('data') as Ref<Settings>
     <AFormItem :label="$gettext('Nginx Restart Command')">
       {{ data.nginx.restart_cmd }}
     </AFormItem>
+    <AFormItem :label="$gettext('Nginx Control Mode')">
+      <div v-if="data.nginx.container_name">
+        <ATag color="blue" tag>
+          {{ $gettext('External Docker Container') }}
+        </ATag>
+        {{ data.nginx.container_name }}
+      </div>
+      <div v-else>
+        <ATag color="green" tag>
+          {{ $gettext('Local') }}
+        </ATag>
+      </div>
+    </AFormItem>
   </AForm>
 </template>
 

+ 4 - 4
app/src/views/preference/NodeSettings.vue → app/src/views/preference/tabs/NodeSettings.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
-import type { Settings } from '@/api/settings'
-import SensitiveString from '@/components/SensitiveString/SensitiveString.vue'
+import SensitiveString from '@/components/SensitiveString'
+import useSystemSettingsStore from '../store'
 
-const data: Ref<Settings> = inject('data') as Ref<Settings>
-const errors: Record<string, Record<string, string>> = inject('errors') as Record<string, Record<string, string>>
+const systemSettingsStore = useSystemSettingsStore()
+const { data, errors } = storeToRefs(systemSettingsStore)
 </script>
 
 <template>

+ 3 - 3
app/src/views/preference/OpenAISettings.vue → app/src/views/preference/tabs/OpenAISettings.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
-import type { Settings } from '@/api/settings'
 import { LLM_MODELS, LLM_PROVIDERS } from '@/constants/llm'
+import useSystemSettingsStore from '../store'
 
-const data: Ref<Settings> = inject('data') as Ref<Settings>
-const errors: Record<string, Record<string, string>> = inject('errors') as Record<string, Record<string, string>>
+const systemSettingsStore = useSystemSettingsStore()
+const { data, errors } = storeToRefs(systemSettingsStore)
 
 const models = LLM_MODELS.map(model => ({
   value: model,

+ 3 - 2
app/src/views/preference/ServerSettings.vue → app/src/views/preference/tabs/ServerSettings.vue

@@ -1,9 +1,10 @@
 <script setup lang="ts">
 import type { Cert } from '@/api/cert'
-import type { Settings } from '@/api/settings'
 import ChangeCert from '@/views/site/site_edit/components/Cert/ChangeCert.vue'
+import useSystemSettingsStore from '../store'
 
-const data: Ref<Settings> = inject('data') as Ref<Settings>
+const systemSettingsStore = useSystemSettingsStore()
+const { data } = storeToRefs(systemSettingsStore)
 
 function handleCertChange(certs: Cert[]) {
   if (certs.length > 0 && data.value?.server) {

+ 3 - 2
app/src/views/preference/TerminalSettings.vue → app/src/views/preference/tabs/TerminalSettings.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
-import type { Settings } from '@/api/settings'
+import useSystemSettingsStore from '../store'
 
-const data: Ref<Settings> = inject('data') as Ref<Settings>
+const systemSettingsStore = useSystemSettingsStore()
+const { data } = storeToRefs(systemSettingsStore)
 </script>
 
 <template>

+ 11 - 0
app/src/views/preference/tabs/index.ts

@@ -0,0 +1,11 @@
+export { default as AppSettings } from './AppSettings.vue'
+export { default as AuthSettings } from './AuthSettings.vue'
+export { default as CertSettings } from './CertSettings.vue'
+export { default as ExternalNotify } from './ExternalNotify.vue'
+export { default as HTTPSettings } from './HTTPSettings.vue'
+export { default as LogrotateSettings } from './LogrotateSettings.vue'
+export { default as NginxSettings } from './NginxSettings.vue'
+export { default as NodeSettings } from './NodeSettings.vue'
+export { default as OpenAISettings } from './OpenAISettings.vue'
+export { default as ServerSettings } from './ServerSettings.vue'
+export { default as TerminalSettings } from './TerminalSettings.vue'

+ 20 - 6
docs/guide/config-nginx.md

@@ -101,18 +101,12 @@ If the `--sbin-path` path cannot be obtained from `nginx -V`, Nginx UI will use
 nginx
 ```
 
-
-
 If the `--sbin-path` path can be obtained, Nginx UI will use the following command to start the Nginx service:
 
 ```bash
 start-stop-daemon --start --quiet --pidfile $PID --exec $SBIN_PATH
 ```
 
-## Stub Status
-
-In this section, we will introduce configuration options in Nginx UI for the Nginx stub status module.
-
 ### StubStatusPort
 - Type: `uint`
 - Default: `51820`
@@ -123,3 +117,23 @@ This option is used to set the port for the Nginx stub status module. The stub s
 ::: tip Tip
 Make sure the port you set is not being used by other services.
 :::
+
+## Container Control
+
+In this section, we will introduce configuration options in Nginx UI for controlling Nginx services running in another Docker container.
+
+### ContainerName
+- Type: `string`
+- Version: `>= v2.0.0-rc.6`
+
+This option is used to specify the name of the Docker container where Nginx is running.
+
+If this option is empty, Nginx UI will control the Nginx service on the local machine or within the current container.
+
+If this option is not empty, Nginx UI will control the Nginx service running in the specified container.
+
+::: tip Tip
+If you are using the official Nginx UI container and want to control Nginx in another container, you must map the host's docker.sock to the Nginx UI container.
+
+For example: `-v /var/run/docker.sock:/var/run/docker.sock`
+:::

+ 18 - 4
docs/zh_CN/guide/config-nginx.md

@@ -108,10 +108,6 @@ nginx
 start-stop-daemon --start --quiet --pidfile $PID --exec $SBIN_PATH
 ```
 
-## Stub Status
-
-在本节中,我们将会介绍 Nginx UI 中关于 Nginx stub status 模块的配置选项。
-
 ### StubStatusPort
 - 类型:`uint`
 - 默认值:`51820`
@@ -123,4 +119,22 @@ start-stop-daemon --start --quiet --pidfile $PID --exec $SBIN_PATH
 请确保您设置的端口未被其他服务占用。
 :::
 
+## 容器控制
+
+在本节中,我们将会介绍 Nginx UI 中关于控制运行在另一个 Docker 容器中的 Nginx 服务的配置选项。
+
+### ContainerName
+- 类型:`string`
+- 版本:`>= v2.0.0-rc.6`
+
+此选项用于指定运行 Nginx 的 Docker 容器名称。
+
+如果此选项为空,Nginx UI 将控制本机或当前容器内的 Nginx 服务。
+
+如果此选项不为空,Nginx UI 将控制运行在指定容器中的 Nginx 服务。
 
+::: tip 提示
+如果使用 Nginx UI 官方容器,想要控制另外一个容器里的 Nginx,务必将宿主机内的 docker.sock 映射到 Nginx UI 官方容器中。
+
+例如:`-v /var/run/docker.sock:/var/run/docker.sock`
+:::

+ 20 - 4
docs/zh_TW/guide/config-nginx.md

@@ -101,10 +101,6 @@ start-stop-daemon --stop --quiet --oknodo --retry=TERM/30/KILL/5 --pidfile $PID
 start-stop-daemon --start --quiet --pidfile $PID --exec $SBIN_PATH
 ```
 
-## Stub Status
-
-在本節中,我們將會介紹 Nginx UI 中關於 Nginx stub status 模組的設定選項。
-
 ### StubStatusPort
 - 類型:`uint`
 - 預設值:`51820`
@@ -115,3 +111,23 @@ start-stop-daemon --start --quiet --pidfile $PID --exec $SBIN_PATH
 ::: tip 提示
 請確保您設定的連接埠未被其他服務佔用。
 :::
+
+## 容器控制
+
+在本節中,我們將會介紹 Nginx UI 中關於控制運行在另一個 Docker 容器中的 Nginx 服務的設定選項。
+
+### ContainerName
+- 類型:`string`
+- 版本:`>= v2.0.0-rc.6`
+
+此選項用於指定執行 Nginx 的 Docker 容器名稱。
+
+如果此選項為空,Nginx UI 將控制本機或當前容器內的 Nginx 服務。
+
+如果此選項不為空,Nginx UI 將控制執行在指定容器中的 Nginx 服務。
+
+::: tip 提示
+如果使用 Nginx UI 官方容器,想要控制另外一個容器裡的 Nginx,務必將宿主機內的 docker.sock 映射到 Nginx UI 官方容器中。
+
+例如:`-v /var/run/docker.sock:/var/run/docker.sock`
+:::

+ 13 - 0
go.mod

@@ -9,6 +9,7 @@ require (
 	github.com/casdoor/casdoor-go-sdk v1.5.0
 	github.com/creack/pty v1.1.24
 	github.com/dgraph-io/ristretto/v2 v2.2.0
+	github.com/docker/docker v28.1.1+incompatible
 	github.com/dustin/go-humanize v1.0.1
 	github.com/elliotchance/orderedmap/v3 v3.1.0
 	github.com/fsnotify/fsnotify v1.9.0
@@ -75,6 +76,7 @@ require (
 	github.com/Azure/go-autorest/logger v0.2.2 // indirect
 	github.com/Azure/go-autorest/tracing v0.6.1 // indirect
 	github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
+	github.com/Microsoft/go-winio v0.4.14 // indirect
 	github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
 	github.com/StackExchange/wmi v1.2.1 // indirect
 	github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
@@ -106,10 +108,14 @@ require (
 	github.com/civo/civogo v0.3.98 // indirect
 	github.com/cloudflare/cloudflare-go v0.115.0 // indirect
 	github.com/cloudwego/base64x v0.1.5 // indirect
+	github.com/containerd/log v0.1.0 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/dimchansky/utfbom v1.1.1 // indirect
+	github.com/distribution/reference v0.6.0 // indirect
 	github.com/dnsimple/dnsimple-go v1.7.0 // indirect
+	github.com/docker/go-connections v0.5.0 // indirect
+	github.com/docker/go-units v0.5.0 // indirect
 	github.com/ebitengine/purego v0.8.2 // indirect
 	github.com/exoscale/egoscale/v3 v3.1.14 // indirect
 	github.com/fatih/color v1.18.0 // indirect
@@ -184,8 +190,12 @@ require (
 	github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/moby/docker-image-spec v1.3.1 // indirect
+	github.com/moby/sys/atomicwriter v0.1.0 // indirect
+	github.com/moby/term v0.5.2 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/morikuni/aec v1.0.0 // indirect
 	github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
 	github.com/nrdcg/auroradns v1.1.0 // indirect
 	github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea // indirect
@@ -199,6 +209,8 @@ require (
 	github.com/nrdcg/nodion v0.1.0 // indirect
 	github.com/nrdcg/porkbun v0.4.0 // indirect
 	github.com/nzdjb/go-metaname v1.0.0 // indirect
+	github.com/opencontainers/go-digest v1.0.0 // indirect
+	github.com/opencontainers/image-spec v1.1.1 // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
 	github.com/oracle/oci-go-sdk/v65 v65.89.1 // indirect
 	github.com/ovh/go-ovh v1.7.0 // indirect
@@ -257,6 +269,7 @@ require (
 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
 	go.opentelemetry.io/otel v1.35.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
 	go.opentelemetry.io/otel/metric v1.35.0 // indirect
 	go.opentelemetry.io/otel/trace v1.35.0 // indirect
 	go.uber.org/atomic v1.11.0 // indirect

+ 37 - 9
go.sum

@@ -632,6 +632,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourceg
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
+github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
+github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
@@ -675,6 +677,8 @@ github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXY
 github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
+github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
@@ -698,8 +702,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/aliyun/alibaba-cloud-sdk-go v1.63.105 h1:Bj8SsBTGElGniuL83+VqfHnxZIPc7n+rzpwDZIB4rZg=
-github.com/aliyun/alibaba-cloud-sdk-go v1.63.105/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
 github.com/aliyun/alibaba-cloud-sdk-go v1.63.106 h1:+YPfQheppCKOPJxhWDmStF1UMJrxnA1iiwBH12t6Fa4=
 github.com/aliyun/alibaba-cloud-sdk-go v1.63.106/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
 github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
@@ -790,7 +792,6 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
-github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -825,6 +826,8 @@ github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWH
 github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
+github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
+github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -853,8 +856,16 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
 github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
+github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
+github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
 github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY=
 github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8=
+github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
+github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
+github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -912,8 +923,6 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
 github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
 github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
 github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
-github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
-github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
 github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
 github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -1168,9 +1177,12 @@ github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
 github.com/guregu/null/v6 v6.0.0 h1:N14VRS+4di81i1PXRiprbQJ9EM9gqBa0+KVMeS/QSjQ=
 github.com/guregu/null/v6 v6.0.0/go.mod h1:hrMIhIfrOZeLPZhROSn149tpw2gHkidAqxoXNyeX3iQ=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
@@ -1478,6 +1490,14 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
 github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
+github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
+github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
+github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
+github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
+github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
+github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
+github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -1486,6 +1506,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
@@ -1549,6 +1571,10 @@ github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5h
 github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
 github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
+github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
@@ -1770,8 +1796,6 @@ github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSW
 github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
 github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1136/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
-github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1145 h1:DETyir/MtG+GLOD0OatzjrQTTXRguFSJo1ZtPXtbIQw=
-github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1145/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1146 h1:PMhgU4BETyTiikegps6gDtLamNWUiLMEx4fv16UWspY=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1146/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1136 h1:kMIdSU5IvpOROh27ToVQ3hlm6ym3lCRs9tnGCOBoZqk=
@@ -1817,8 +1841,6 @@ github.com/urfave/cli/v3 v3.1.1 h1:bNnl8pFI5dxPOjeONvFCDFoECLQsceDG4ejahs4Jtxk=
 github.com/urfave/cli/v3 v3.1.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
 github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ=
 github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
-github.com/volcengine/volc-sdk-golang v1.0.203 h1:y4zZEjKp6nz3UK8Tb/qdz9tvB915KLO6nc1WNME0Zb8=
-github.com/volcengine/volc-sdk-golang v1.0.203/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ=
 github.com/volcengine/volc-sdk-golang v1.0.204 h1:Njid6coReHV2gWc3bsqWMQf+K8jveauzW8zEX08CTzI=
 github.com/volcengine/volc-sdk-golang v1.0.204/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ=
 github.com/vultr/govultr/v3 v3.19.1 h1:31rOP5Tz40AOc8h6Ws4ryzqAniUBffgRhy9uMG/EFvs=
@@ -1881,6 +1903,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRND
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
 go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
 go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
 go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
 go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
 go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
@@ -1892,6 +1918,8 @@ go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
 go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
+go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
+go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=

+ 3 - 2
internal/cmd/main.go

@@ -26,10 +26,11 @@ func NewAppCmd() *cli.Command {
 				},
 			},
 			{
-				Name:  "reset-password",
-				Usage: "Reset the initial user password",
+				Name:   "reset-password",
+				Usage:  "Reset the initial user password",
 				Action: user.ResetInitUserPassword,
 			},
+			UpgradeDockerStep2Command,
 		},
 		Flags: []cli.Flag{
 			&cli.StringFlag{

+ 23 - 0
internal/cmd/upgrade_docker.go

@@ -0,0 +1,23 @@
+package cmd
+
+import (
+	"context"
+
+	"github.com/0xJacky/Nginx-UI/internal/docker"
+	"github.com/uozi-tech/cosy/logger"
+	"github.com/urfave/cli/v3"
+)
+
+// Command to be executed in the temporary container
+var UpgradeDockerStep2Command = &cli.Command{
+	Name:   "upgrade-docker-step2",
+	Usage:  "Execute the second step of Docker container upgrade (to be run inside the temp container)",
+	Action: UpgradeDockerStep2,
+}
+
+// UpgradeDockerStep2 executes the second step in the temporary container
+func UpgradeDockerStep2(ctx context.Context, command *cli.Command) error {
+	logger.Info("Starting Docker OTA upgrade step 2 from CLI...")
+
+	return docker.UpgradeStepTwo(ctx)
+}

+ 5 - 1
internal/config/save.go

@@ -45,7 +45,11 @@ func Save(absPath string, content string, cfg *model.Config) (err error) {
 		return
 	}
 
-	output := nginx.Reload()
+	output, err := nginx.Reload()
+	if err != nil {
+		return
+	} 
+
 	if nginx.GetLogLevel(output) >= nginx.Warn {
 		return cosy.WrapErrorWithParams(ErrNginxReloadFailed, output)
 	}

+ 22 - 0
internal/docker/docker.go

@@ -0,0 +1,22 @@
+package docker
+
+import (
+	"context"
+
+	"github.com/docker/docker/client"
+)
+
+// Initialize Docker client from environment variables
+func initClient() (cli *client.Client, err error) {
+	cli, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
+	if err != nil {
+		return
+	}
+	// Optionally ping the server to ensure the connection is valid
+	_, err = cli.Ping(context.Background())
+	if err != nil {
+		return
+	}
+
+	return
+}

+ 20 - 0
internal/docker/errors.go

@@ -0,0 +1,20 @@
+package docker
+
+import "github.com/uozi-tech/cosy"
+
+var (
+	e                                    = cosy.NewErrorScope("docker")
+	ErrClientNotInitialized              = e.New(500001, "docker client not initialized")
+	ErrFailedToExec                      = e.New(500002, "failed to exec command: {0}")
+	ErrFailedToAttach                    = e.New(500003, "failed to attach to exec instance: {0}")
+	ErrReadOutput                        = e.New(500004, "failed to read output: {0}")
+	ErrExitUnexpected                    = e.New(500005, "command exited with unexpected exit code: {0}, error: {1}")
+	ErrContainerStatusUnknown            = e.New(500006, "container status unknown")
+	ErrInspectContainer                  = e.New(500007, "failed to inspect container: {0}")
+	ErrNginxNotRunningInAnotherContainer = e.New(500008, "nginx is not running in another container")
+	ErrFailedToGetHostname               = e.New(500009, "failed to get hostname: {0}")
+	ErrFailedToPullImage                 = e.New(500010, "failed to pull image: {0}")
+	ErrFailedToInspectCurrentContainer   = e.New(500011, "failed to inspect current container: {0}")
+	ErrFailedToCreateTempContainer       = e.New(500012, "failed to create temp container: {0}")
+	ErrFailedToStartTempContainer        = e.New(500013, "failed to start temp container: {0}")
+)

+ 80 - 0
internal/docker/exec.go

@@ -0,0 +1,80 @@
+package docker
+
+import (
+	"bytes"
+	"context"
+	"io"
+	"strconv"
+
+	"github.com/0xJacky/Nginx-UI/settings"
+	"github.com/docker/docker/api/types/container"
+	"github.com/docker/docker/pkg/stdcopy"
+	"github.com/uozi-tech/cosy"
+	"github.com/uozi-tech/cosy/logger"
+)
+
+// Exec executes a command in a specific container and returns the output.
+func Exec(ctx context.Context, command []string) (string, error) {
+	if !settings.NginxSettings.RunningInAnotherContainer() {
+		return "", ErrNginxNotRunningInAnotherContainer
+	}
+
+	cli, err := initClient()
+	if err != nil {
+		return "", cosy.WrapErrorWithParams(ErrClientNotInitialized, err.Error())
+	}
+	defer cli.Close()
+
+	execConfig := container.ExecOptions{
+		AttachStdout: true,
+		AttachStderr: true, // Also attach stderr to capture errors from the command
+		Cmd:          command,
+	}
+
+	// Create the exec instance
+	execCreateResp, err := cli.ContainerExecCreate(ctx, settings.NginxSettings.ContainerName, execConfig)
+	if err != nil {
+		return "", cosy.WrapErrorWithParams(ErrFailedToExec, err.Error())
+	}
+	execID := execCreateResp.ID
+
+	// Attach to the exec instance
+	hijackedResp, err := cli.ContainerExecAttach(ctx, execID, container.ExecAttachOptions{})
+	if err != nil {
+		return "", cosy.WrapErrorWithParams(ErrFailedToAttach, err.Error())
+	}
+	defer hijackedResp.Close()
+
+	// Read the output
+	var outBuf, errBuf bytes.Buffer
+	outputDone := make(chan error)
+
+	go func() {
+		// stdcopy.StdCopy demultiplexes the stream into two buffers
+		_, err = stdcopy.StdCopy(&outBuf, &errBuf, hijackedResp.Reader)
+		outputDone <- err
+	}()
+
+	select {
+	case err := <-outputDone:
+		if err != nil && err != io.EOF { // io.EOF is expected when the stream finishes
+			return "", cosy.WrapErrorWithParams(ErrReadOutput, err.Error())
+		}
+	case <-ctx.Done():
+		return "", cosy.WrapErrorWithParams(ErrReadOutput, ctx.Err().Error())
+	}
+
+	// Optionally inspect the exec process to check the exit code
+	execInspectResp, err := cli.ContainerExecInspect(ctx, execID)
+	logger.Debug("docker exec result", outBuf.String(), errBuf.String())
+
+	if err != nil {
+		return "", cosy.WrapErrorWithParams(ErrExitUnexpected, err.Error())
+	} else if execInspectResp.ExitCode != 0 {
+		// Command exited with a non-zero status code. Return stderr as part of the error.
+		return outBuf.String(), cosy.WrapErrorWithParams(ErrExitUnexpected, strconv.Itoa(execInspectResp.ExitCode), errBuf.String())
+	}
+
+	// Return stdout if successful
+	return outBuf.String(), nil
+}

+ 329 - 0
internal/docker/ota.go

@@ -0,0 +1,329 @@
+package docker
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"os"
+	"strings"
+	"time"
+
+	"github.com/0xJacky/Nginx-UI/internal/upgrader"
+	"github.com/docker/docker/api/types/container"
+	"github.com/docker/docker/api/types/image"
+	"github.com/docker/docker/client"
+	"github.com/pkg/errors"
+	"github.com/uozi-tech/cosy"
+	"github.com/uozi-tech/cosy/logger"
+)
+
+const (
+	ImageName  = "uozi/nginx-ui"
+	TempPrefix = "nginx-ui-temp-"
+	OldSuffix  = "_old"
+)
+
+// getTimestampedTempName returns a temporary container name with timestamp
+func getTimestampedTempName() string {
+	return fmt.Sprintf("%s%d", TempPrefix, time.Now().Unix())
+}
+
+// removeAllTempContainers removes all containers with the TempPrefix
+func removeAllTempContainers(ctx context.Context, cli *client.Client) (err error) {
+	containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
+	if err != nil {
+		return
+	}
+
+	for _, c := range containers {
+		for _, name := range c.Names {
+			processedName := strings.TrimPrefix(name, "/")
+			if strings.HasPrefix(processedName, TempPrefix) {
+				err = cli.ContainerRemove(ctx, c.ID, container.RemoveOptions{Force: true})
+				if err != nil {
+					logger.Error("Failed to remove temp container:", err)
+				} else {
+					logger.Info("Successfully removed temp container:", processedName)
+				}
+				break
+			}
+		}
+	}
+
+	return nil
+}
+
+// UpgradeStepOne Trigger in the OTA upgrade
+func UpgradeStepOne(channel string) (err error) {
+	ctx := context.Background()
+
+	// 1. Get the tag of the latest release
+	release, err := upgrader.GetRelease(channel)
+	if err != nil {
+		return err
+	}
+	tag := release.TagName
+
+	// 2. Pull the image
+	cli, err := initClient()
+	if err != nil {
+		return cosy.WrapErrorWithParams(ErrClientNotInitialized, err.Error())
+	}
+	defer cli.Close()
+
+	// Pull the image with the specified tag
+	out, err := cli.ImagePull(ctx, fmt.Sprintf("%s:%s", ImageName, tag), image.PullOptions{})
+	if err != nil {
+		return cosy.WrapErrorWithParams(ErrFailedToPullImage, err.Error())
+	}
+	defer out.Close()
+
+	// Wait for pull to complete by reading the output
+	io.Copy(os.Stdout, out)
+
+	// 3. Create a temp container
+	// Clean up any existing temp containers
+	err = removeAllTempContainers(ctx, cli)
+	if err != nil {
+		logger.Error("Failed to clean up existing temp containers:", err)
+		// Continue execution despite cleanup errors
+	}
+
+	// Generate timestamped temp container name
+	tempContainerName := getTimestampedTempName()
+
+	// Get current container name
+	hostname, err := os.Hostname()
+	if err != nil {
+		return cosy.WrapErrorWithParams(ErrFailedToGetHostname, err.Error())
+	}
+	containerInfo, err := cli.ContainerInspect(ctx, hostname)
+	if err != nil {
+		return cosy.WrapErrorWithParams(ErrFailedToInspectCurrentContainer, err.Error())
+	}
+	currentContainerName := strings.TrimPrefix(containerInfo.Name, "/")
+
+	// Set up the command for the temp container to execute step 2
+	upgradeCmd := []string{"./nginx-ui", "upgrade-docker-step2"}
+
+	// Add old container name as environment variable
+	containerEnv := containerInfo.Config.Env
+	containerEnv = append(containerEnv, fmt.Sprintf("NGINX_UI_CONTAINER_NAME=%s", currentContainerName))
+
+	// Create temp container using new image
+	_, err = cli.ContainerCreate(
+		ctx,
+		&container.Config{
+			Image: fmt.Sprintf("%s:%s", ImageName, tag),
+			Cmd:   upgradeCmd, // Use upgrade command instead of original command
+			Env:   containerEnv,
+		},
+		&container.HostConfig{
+			Binds:         containerInfo.HostConfig.Binds,
+			PortBindings:  containerInfo.HostConfig.PortBindings,
+			RestartPolicy: containerInfo.HostConfig.RestartPolicy,
+		},
+		nil,
+		nil,
+		tempContainerName,
+	)
+	if err != nil {
+		return cosy.WrapErrorWithParams(ErrFailedToCreateTempContainer, err.Error())
+	}
+
+	// Start the temp container to execute step 2
+	err = cli.ContainerStart(ctx, tempContainerName, container.StartOptions{})
+	if err != nil {
+		return cosy.WrapErrorWithParams(ErrFailedToStartTempContainer, err.Error())
+	}
+
+	// Output status information
+	logger.Info("Docker OTA upgrade step 1 completed. Temp container started to execute step 2.")
+
+	return nil
+}
+
+// UpgradeStepTwo Trigger in the temp container
+func UpgradeStepTwo(ctx context.Context) (err error) {
+	// 1. Copy the old config
+	cli, err := initClient()
+	if err != nil {
+		return
+	}
+	defer cli.Close()
+
+	// Get old container name from environment variable, fallback to settings if not available
+	currentContainerName := os.Getenv("NGINX_UI_CONTAINER_NAME")
+	if currentContainerName == "" {
+		return errors.New("could not find old container name")
+	}
+	// Get the current running temp container name
+	// Since we can't directly get our own container name from inside, we'll search all temp containers
+	containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
+	if err != nil {
+		return errors.Wrap(err, "failed to list containers")
+	}
+
+	// Find containers with the temp prefix
+	var tempContainerName string
+	for _, c := range containers {
+		for _, name := range c.Names {
+			processedName := strings.TrimPrefix(name, "/")
+			if strings.HasPrefix(processedName, TempPrefix) {
+				tempContainerName = processedName
+				break
+			}
+		}
+		if tempContainerName != "" {
+			break
+		}
+	}
+
+	if tempContainerName == "" {
+		return errors.New("could not find temp container")
+	}
+
+	// Get temp container info to get the new image
+	tempContainerInfo, err := cli.ContainerInspect(ctx, tempContainerName)
+	if err != nil {
+		return errors.Wrap(err, "failed to inspect temp container")
+	}
+	newImage := tempContainerInfo.Config.Image
+
+	// Get current container info
+	oldContainerInfo, err := cli.ContainerInspect(ctx, currentContainerName)
+	if err != nil {
+		return errors.Wrap(err, "failed to inspect current container")
+	}
+
+	// 2. Stop the old container and rename to _old
+	err = cli.ContainerStop(ctx, currentContainerName, container.StopOptions{})
+	if err != nil {
+		return errors.Wrap(err, "failed to stop current container")
+	}
+
+	// Rename the old container with _old suffix
+	err = cli.ContainerRename(ctx, currentContainerName, currentContainerName+OldSuffix)
+	if err != nil {
+		return errors.Wrap(err, "failed to rename old container")
+	}
+
+	// 3. Use the old config to create and start a new container with the updated image
+	// Create new container with original config but using the new image
+	newContainerEnv := oldContainerInfo.Config.Env
+	// Pass the old container name to the new container
+	newContainerEnv = append(newContainerEnv, fmt.Sprintf("NGINX_UI_CONTAINER_NAME=%s", currentContainerName))
+
+	_, err = cli.ContainerCreate(
+		ctx,
+		&container.Config{
+			Image:        newImage,
+			Cmd:          oldContainerInfo.Config.Cmd,
+			Env:          newContainerEnv,
+			Entrypoint:   oldContainerInfo.Config.Entrypoint,
+			Labels:       oldContainerInfo.Config.Labels,
+			ExposedPorts: oldContainerInfo.Config.ExposedPorts,
+			Volumes:      oldContainerInfo.Config.Volumes,
+			WorkingDir:   oldContainerInfo.Config.WorkingDir,
+		},
+		&container.HostConfig{
+			Binds:         oldContainerInfo.HostConfig.Binds,
+			PortBindings:  oldContainerInfo.HostConfig.PortBindings,
+			RestartPolicy: oldContainerInfo.HostConfig.RestartPolicy,
+			NetworkMode:   oldContainerInfo.HostConfig.NetworkMode,
+			Mounts:        oldContainerInfo.HostConfig.Mounts,
+			Privileged:    oldContainerInfo.HostConfig.Privileged,
+		},
+		nil,
+		nil,
+		currentContainerName,
+	)
+	if err != nil {
+		// If creation fails, try to recover
+		recoverErr := cli.ContainerRename(ctx, currentContainerName+OldSuffix, currentContainerName)
+		if recoverErr == nil {
+			// Start old container
+			recoverErr = cli.ContainerStart(ctx, currentContainerName, container.StartOptions{})
+			if recoverErr == nil {
+				return errors.Wrap(err, "failed to create new container, recovered to old container")
+			}
+		}
+		return errors.Wrap(err, "failed to create new container and failed to recover")
+	}
+
+	// Start the new container
+	err = cli.ContainerStart(ctx, currentContainerName, container.StartOptions{})
+	if err != nil {
+		// If startup fails, try to recover
+		// First remove the failed new container
+		removeErr := cli.ContainerRemove(ctx, currentContainerName, container.RemoveOptions{Force: true})
+		if removeErr != nil {
+			logger.Error("Failed to remove failed new container:", removeErr)
+		}
+
+		// Rename the old container back to original
+		recoverErr := cli.ContainerRename(ctx, currentContainerName+OldSuffix, currentContainerName)
+		if recoverErr == nil {
+			// Start old container
+			recoverErr = cli.ContainerStart(ctx, currentContainerName, container.StartOptions{})
+			if recoverErr == nil {
+				return errors.Wrap(err, "failed to start new container, recovered to old container")
+			}
+		}
+		return errors.Wrap(err, "failed to start new container and failed to recover")
+	}
+
+	logger.Info("Docker OTA upgrade step 2 completed successfully. New container is running.")
+	return nil
+}
+
+// UpgradeStepThree Trigger in the new container
+func UpgradeStepThree() error {
+	ctx := context.Background()
+	// Remove the old container
+	cli, err := initClient()
+	if err != nil {
+		return cosy.WrapErrorWithParams(ErrClientNotInitialized, err.Error())
+	}
+	defer cli.Close()
+
+	// Get old container name from environment variable, fallback to settings if not available
+	currentContainerName := os.Getenv("NGINX_UI_CONTAINER_NAME")
+	if currentContainerName == "" {
+		logger.Warn("Old container name not found in environment, skipping cleanup")
+		return nil
+	}
+	oldContainerName := currentContainerName + OldSuffix
+
+	// Check if old container exists and remove it if it does
+	containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
+	if err != nil {
+		return errors.Wrap(err, "failed to list containers")
+	}
+
+	for _, c := range containers {
+		for _, name := range c.Names {
+			processedName := strings.TrimPrefix(name, "/")
+			// Remove old container
+			if processedName == oldContainerName {
+				err = cli.ContainerRemove(ctx, c.ID, container.RemoveOptions{Force: true})
+				if err != nil {
+					logger.Error("Failed to remove old container:", err)
+					// Continue execution, don't interrupt because of failure to remove old container
+				} else {
+					logger.Info("Successfully removed old container:", oldContainerName)
+				}
+				break
+			}
+		}
+	}
+
+	// Clean up all temp containers
+	err = removeAllTempContainers(ctx, cli)
+	if err != nil {
+		logger.Error("Failed to clean up temp containers:", err)
+		// Continue execution despite cleanup errors
+	}
+
+	return nil
+}

+ 29 - 0
internal/docker/stat_path.go

@@ -0,0 +1,29 @@
+package docker
+
+import (
+	"context"
+
+	"github.com/0xJacky/Nginx-UI/settings"
+	"github.com/uozi-tech/cosy/logger"
+)
+
+// StatPath checks if a path exists in the container
+func StatPath(path string) bool {
+	if !settings.NginxSettings.RunningInAnotherContainer() {
+		return false
+	}
+
+	cli, err := initClient()
+	if err != nil {
+		return false
+	}
+	defer cli.Close()
+
+	_, err = cli.ContainerStatPath(context.Background(), settings.NginxSettings.ContainerName, path)
+	if err != nil {
+		logger.Error("Failed to stat path", "error", err)
+		return false
+	}
+
+	return true
+}

+ 58 - 0
internal/docker/status.go

@@ -0,0 +1,58 @@
+package docker
+
+import (
+	"context"
+
+	"github.com/docker/docker/client"
+	"github.com/uozi-tech/cosy"
+)
+
+type ContainerStatus int
+
+const (
+	ContainerStatusCreated ContainerStatus = iota
+	ContainerStatusRunning
+	ContainerStatusPaused
+	ContainerStatusRestarting
+	ContainerStatusRemoving
+	ContainerStatusExited
+	ContainerStatusDead
+	ContainerStatusUnknown
+	ContainerStatusNotFound
+)
+
+var (
+	containerStatusMap = map[string]ContainerStatus{
+		"created":    ContainerStatusCreated,
+		"running":    ContainerStatusRunning,
+		"paused":     ContainerStatusPaused,
+		"restarting": ContainerStatusRestarting,
+		"removing":   ContainerStatusRemoving,
+		"exited":     ContainerStatusExited,
+		"dead":       ContainerStatusDead,
+	}
+)
+
+// GetContainerStatus checks the status of a given container.
+func GetContainerStatus(ctx context.Context, containerID string) (ContainerStatus, error) {
+	cli, err := initClient()
+	if err != nil {
+		return ContainerStatusUnknown, cosy.WrapErrorWithParams(ErrClientNotInitialized, err.Error())
+	}
+	defer cli.Close()
+
+	containerJSON, err := cli.ContainerInspect(ctx, containerID)
+	if err != nil {
+		if client.IsErrNotFound(err) {
+			return ContainerStatusNotFound, nil // Container doesn't exist
+		}
+		return ContainerStatusUnknown, cosy.WrapErrorWithParams(ErrInspectContainer, err.Error())
+	}
+
+	// Can be one of "created", "running", "paused", "restarting", "removing", "exited", or "dead"
+	status, ok := containerStatusMap[containerJSON.State.Status]
+	if !ok {
+		return ContainerStatusUnknown, ErrContainerStatusUnknown
+	}
+	return status, nil
+}

+ 13 - 0
internal/kernel/boot.go

@@ -12,6 +12,7 @@ import (
 	"github.com/0xJacky/Nginx-UI/internal/cert"
 	"github.com/0xJacky/Nginx-UI/internal/cluster"
 	"github.com/0xJacky/Nginx-UI/internal/cron"
+	"github.com/0xJacky/Nginx-UI/internal/docker"
 	"github.com/0xJacky/Nginx-UI/internal/passkey"
 	"github.com/0xJacky/Nginx-UI/internal/validation"
 	"github.com/0xJacky/Nginx-UI/model"
@@ -35,6 +36,7 @@ func Boot() {
 		InitCryptoSecret,
 		validation.Init,
 		cache.Init,
+		CheckAndCleanupOTAContainers,
 	}
 
 	syncs := []func(){
@@ -129,3 +131,14 @@ func InitJsExtensionType() {
 	// See https://github.com/golang/go/issues/32350
 	_ = mime.AddExtensionType(".js", "text/javascript; charset=utf-8")
 }
+
+// CheckAndCleanupOTAContainers Check and cleanup OTA update temporary containers
+func CheckAndCleanupOTAContainers() {
+	// Execute the third step cleanup operation at startup
+	err := docker.UpgradeStepThree()
+	if err != nil {
+		logger.Error("Failed to cleanup OTA containers:", err)
+	} else {
+		logger.Info("OTA container cleanup completed successfully")
+	}
+}

+ 1 - 0
internal/nginx/errors.go

@@ -5,4 +5,5 @@ import "github.com/uozi-tech/cosy"
 var (
 	e             = cosy.NewErrorScope("nginx")
 	ErrBlockIsNil = e.New(50001, "block is nil")
+	ErrReloadFailed = e.New(50002, "reload nginx failed: {0}")
 )

+ 28 - 0
internal/nginx/exec.go

@@ -0,0 +1,28 @@
+package nginx
+
+import (
+	"context"
+	"os/exec"
+
+	"github.com/0xJacky/Nginx-UI/internal/docker"
+	"github.com/0xJacky/Nginx-UI/settings"
+)
+
+func execShell(cmd string) (stdOut string, stdErr error) {
+	return execCommand("/bin/sh", "-c", cmd)
+}
+
+func execCommand(name string, cmd ...string) (stdOut string, stdErr error) {
+	switch settings.NginxSettings.RunningInAnotherContainer() {
+	case true:
+		cmd = append([]string{name}, cmd...)
+		stdOut, stdErr = docker.Exec(context.Background(), cmd)
+	case false:
+		bytes, err := exec.Command(name, cmd...).CombinedOutput()
+		stdOut = string(bytes)
+		if err != nil {
+			stdErr = err
+		}
+	}
+	return
+}

+ 38 - 51
internal/nginx/nginx.go

@@ -2,46 +2,41 @@ package nginx
 
 import (
 	"os"
-	"os/exec"
 	"strings"
 	"sync"
 	"time"
 
+	"github.com/0xJacky/Nginx-UI/internal/docker"
 	"github.com/0xJacky/Nginx-UI/settings"
 )
 
 var (
 	mutex      sync.Mutex
-	lastOutput string
+	lastStdOut string
+	lastStdErr error
 )
 
-func TestConf() (out string) {
+// TestConfig tests the nginx config
+func TestConfig() (stdOut string, stdErr error) {
 	mutex.Lock()
 	defer mutex.Unlock()
 	if settings.NginxSettings.TestConfigCmd != "" {
-		out = execShell(settings.NginxSettings.TestConfigCmd)
-
-		return
+		return execShell(settings.NginxSettings.TestConfigCmd)
 	}
-
-	out = execCommand("nginx", "-t")
-
-	return
+	return execCommand("nginx", "-t")
 }
 
-func Reload() (out string) {
+// Reload reloads the nginx
+func Reload() (stdOut string, stdErr error) {
 	mutex.Lock()
 	defer mutex.Unlock()
 	if settings.NginxSettings.ReloadCmd != "" {
-		out = execShell(settings.NginxSettings.ReloadCmd)
-		return
+		return execShell(settings.NginxSettings.ReloadCmd)
 	}
-
-	out = execCommand("nginx", "-s", "reload")
-
-	return
+	return execCommand("nginx", "-s", "reload")
 }
 
+// Restart restarts the nginx
 func Restart() {
 	mutex.Lock()
 	defer mutex.Unlock()
@@ -50,41 +45,45 @@ func Restart() {
 	time.Sleep(500 * time.Millisecond)
 
 	if settings.NginxSettings.RestartCmd != "" {
-		lastOutput = execShell(settings.NginxSettings.RestartCmd)
-
+		lastStdOut, lastStdErr = execShell(settings.NginxSettings.RestartCmd)
 		return
 	}
 
 	pidPath := GetPIDPath()
 	daemon := GetSbinPath()
 
-	lastOutput = execCommand("start-stop-daemon", "--stop", "--quiet", "--oknodo", "--retry=TERM/30/KILL/5", "--pidfile", pidPath)
+	lastStdOut, lastStdErr = execCommand("start-stop-daemon", "--stop", "--quiet", "--oknodo", "--retry=TERM/30/KILL/5", "--pidfile", pidPath)
+	if lastStdErr != nil {
+		return
+	}
 
 	if daemon == "" {
-		lastOutput += execCommand("nginx")
-
+		lastStdOut, lastStdErr = execCommand("nginx")
 		return
 	}
 
-	lastOutput += execCommand("start-stop-daemon", "--start", "--quiet", "--pidfile", pidPath, "--exec", daemon)
-
+	lastStdOut, lastStdErr = execCommand("start-stop-daemon", "--start", "--quiet", "--pidfile", pidPath, "--exec", daemon)
 	return
 }
 
-func GetLastOutput() string {
+// GetLastOutput returns the last output of the nginx command
+func GetLastOutput() (stdOut string, stdErr error) {
 	mutex.Lock()
 	defer mutex.Unlock()
-	return lastOutput
+	return lastStdOut, lastStdErr
 }
 
 // GetModulesPath returns the nginx modules path
 func GetModulesPath() string {
 	// First try to get from nginx -V output
-	output := execCommand("nginx", "-V")
-	if output != "" {
+	stdOut, stdErr := execCommand("nginx", "-V")
+	if stdErr != nil {
+		return ""
+	}
+	if stdOut != "" {
 		// Look for --modules-path in the output
-		if strings.Contains(output, "--modules-path=") {
-			parts := strings.Split(output, "--modules-path=")
+		if strings.Contains(stdOut, "--modules-path=") {
+			parts := strings.Split(stdOut, "--modules-path=")
 			if len(parts) > 1 {
 				// Extract the path
 				path := strings.Split(parts[1], " ")[0]
@@ -99,28 +98,16 @@ func GetModulesPath() string {
 	return "/usr/lib/nginx/modules"
 }
 
-func execShell(cmd string) (out string) {
-	bytes, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput()
-	out = string(bytes)
-	if err != nil {
-		out += " " + err.Error()
-	}
-	return
-}
-
-func execCommand(name string, cmd ...string) (out string) {
-	bytes, err := exec.Command(name, cmd...).CombinedOutput()
-	out = string(bytes)
-	if err != nil {
-		out += " " + err.Error()
-	}
-	return
-}
-
 func IsNginxRunning() bool {
 	pidPath := GetPIDPath()
-	if fileInfo, err := os.Stat(pidPath); err != nil || fileInfo.Size() == 0 {
-		return false
+	switch settings.NginxSettings.RunningInAnotherContainer() {
+	case true:
+		return docker.StatPath(pidPath)
+	case false:
+		if fileInfo, err := os.Stat(pidPath); err != nil || fileInfo.Size() == 0 {
+			return false
+		}
+		return true
 	}
-	return true
+	return false
 }

+ 4 - 1
internal/site/disable.go

@@ -35,7 +35,10 @@ func Disable(name string) (err error) {
 		return
 	}
 
-	output := nginx.Reload()
+	output, err := nginx.Reload()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		return cosy.WrapErrorWithParams(ErrNginxReloadFailed, output)
 	}

+ 8 - 2
internal/site/enable.go

@@ -35,13 +35,19 @@ func Enable(name string) (err error) {
 	}
 
 	// Test nginx config, if not pass, then disable the site.
-	output := nginx.TestConf()
+	output, err := nginx.TestConfig()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		_ = os.Remove(enabledConfigFilePath)
 		return cosy.WrapErrorWithParams(ErrNginxTestFailed, output)
 	}
 
-	output = nginx.Reload()
+	output, err = nginx.Reload()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		return cosy.WrapErrorWithParams(ErrNginxReloadFailed, output)
 	}

+ 16 - 4
internal/site/maintenance.go

@@ -76,7 +76,10 @@ func EnableMaintenance(name string) (err error) {
 	}
 
 	// Test nginx config, if not pass, then restore original configuration
-	output := nginx.TestConf()
+	output, err := nginx.TestConfig()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		// Configuration error, cleanup and revert
 		_ = os.Remove(maintenanceConfigPath)
@@ -87,7 +90,10 @@ func EnableMaintenance(name string) (err error) {
 	}
 
 	// Reload nginx
-	output = nginx.Reload()
+	output, err = nginx.Reload()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		return cosy.WrapErrorWithParams(ErrNginxReloadFailed, output)
 	}
@@ -132,7 +138,10 @@ func DisableMaintenance(name string) (err error) {
 	}
 
 	// Test nginx config, if not pass, then revert
-	output := nginx.TestConf()
+	output, err := nginx.TestConfig()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		// Configuration error, cleanup and revert
 		_ = os.Remove(enabledConfigFilePath)
@@ -141,7 +150,10 @@ func DisableMaintenance(name string) (err error) {
 	}
 
 	// Reload nginx
-	output = nginx.Reload()
+	output, err = nginx.Reload()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		return fmt.Errorf("%s", output)
 	}

+ 13 - 6
internal/site/rename.go

@@ -2,16 +2,17 @@ package site
 
 import (
 	"fmt"
+	"net/http"
+	"os"
+	"runtime"
+	"sync"
+
 	"github.com/0xJacky/Nginx-UI/internal/helper"
 	"github.com/0xJacky/Nginx-UI/internal/nginx"
 	"github.com/0xJacky/Nginx-UI/internal/notification"
 	"github.com/0xJacky/Nginx-UI/query"
 	"github.com/go-resty/resty/v2"
 	"github.com/uozi-tech/cosy/logger"
-	"net/http"
-	"os"
-	"runtime"
-	"sync"
 )
 
 func Rename(oldName string, newName string) (err error) {
@@ -47,13 +48,19 @@ func Rename(oldName string, newName string) (err error) {
 	}
 
 	// test nginx configuration
-	output := nginx.TestConf()
+	output, err := nginx.TestConfig()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		return fmt.Errorf("%s", output)
 	}
 
 	// reload nginx
-	output = nginx.Reload()
+	output, err = nginx.Reload()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		return fmt.Errorf("%s", output)
 	}

+ 9 - 2
internal/site/save.go

@@ -38,14 +38,21 @@ func Save(name string, content string, overwrite bool, envGroupId uint64, syncNo
 	enabledConfigFilePath := nginx.GetConfPath("sites-enabled", name)
 	if helper.FileExists(enabledConfigFilePath) {
 		// Test nginx configuration
-		output := nginx.TestConf()
+		var output string
+		output, err = nginx.TestConfig()
+		if err != nil {
+			return
+		}
 
 		if nginx.GetLogLevel(output) > nginx.Warn {
 			return cosy.WrapErrorWithParams(ErrNginxTestFailed, output)
 		}
 
 		if postAction == model.PostSyncActionReloadNginx {
-			output = nginx.Reload()
+			output, err = nginx.Reload()
+			if err != nil {
+				return
+			}
 			if nginx.GetLogLevel(output) > nginx.Warn {
 				return cosy.WrapErrorWithParams(ErrNginxReloadFailed, output)
 			}

+ 4 - 1
internal/stream/disable.go

@@ -35,7 +35,10 @@ func Disable(name string) (err error) {
 		return
 	}
 
-	output := nginx.Reload()
+	output, err := nginx.Reload()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		return cosy.WrapErrorWithParams(ErrNginxReloadFailed, output)
 	}

+ 8 - 2
internal/stream/enable.go

@@ -35,13 +35,19 @@ func Enable(name string) (err error) {
 	}
 
 	// Test nginx config, if not pass, then disable the site.
-	output := nginx.TestConf()
+	output, err := nginx.TestConfig()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		_ = os.Remove(enabledConfigFilePath)
 		return cosy.WrapErrorWithParams(ErrNginxTestFailed, output)
 	}
 
-	output = nginx.Reload()
+	output, err = nginx.Reload()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		return cosy.WrapErrorWithParams(ErrNginxReloadFailed, output)
 	}

+ 8 - 2
internal/stream/rename.go

@@ -49,13 +49,19 @@ func Rename(oldName string, newName string) (err error) {
 	}
 
 	// test nginx configuration
-	output := nginx.TestConf()
+	output, err := nginx.TestConfig()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		return cosy.WrapErrorWithParams(ErrNginxTestFailed, output)
 	}
 
 	// reload nginx
-	output = nginx.Reload()
+	output, err = nginx.Reload()
+	if err != nil {
+		return
+	}
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		return cosy.WrapErrorWithParams(ErrNginxReloadFailed, output)
 	}

+ 9 - 2
internal/stream/save.go

@@ -37,15 +37,22 @@ func Save(name string, content string, overwrite bool, syncNodeIds []uint64, pos
 
 	enabledConfigFilePath := nginx.GetConfPath("streams-enabled", name)
 	if helper.FileExists(enabledConfigFilePath) {
+		var output string
 		// Test nginx configuration
-		output := nginx.TestConf()
+		output, err = nginx.TestConfig()
+		if err != nil {
+			return
+		}
 
 		if nginx.GetLogLevel(output) > nginx.Warn {
 			return cosy.WrapErrorWithParams(ErrNginxTestFailed, output)
 		}
 
 		if postAction == model.PostSyncActionReloadNginx {
-			output = nginx.Reload()
+			output, err = nginx.Reload()
+			if err != nil {
+				return
+			}
 			if nginx.GetLogLevel(output) > nginx.Warn {
 				return cosy.WrapErrorWithParams(ErrNginxReloadFailed, output)
 			}

+ 5 - 0
settings/nginx.go

@@ -11,6 +11,7 @@ type Nginx struct {
 	ReloadCmd       string   `json:"reload_cmd" protected:"true"`
 	RestartCmd      string   `json:"restart_cmd" protected:"true"`
 	StubStatusPort  uint     `json:"stub_status_port" binding:"omitempty,min=1,max=65535"`
+	ContainerName   string   `json:"container_name" protected:"true"`
 }
 
 var NginxSettings = &Nginx{}
@@ -21,3 +22,7 @@ func (n *Nginx) GetStubStatusPort() uint {
 	}
 	return n.StubStatusPort
 }
+
+func (n *Nginx) RunningInAnotherContainer() bool {
+	return n.ContainerName != ""
+}